Make keyring use SecretManager as sole storage (#1185)

* Make keyring use SecretManager as sole storage

The Keyring will only use the SecretManager as storage. Accesses to the
Datastore are removed.

Also consolidated KmsKeyringTest into KmsKeyingUpdaterTest. The latter
is left with its original name to facilitate code reviews. It will be
renamed in planned cleanups.

Additional cleanup is left for a future PR. These include:

- Remove KmsConnection and its associated injection modules

- Remove KmsSecretRevision from SQL schema and code

- Rename relevant files to more appropriate names.
This commit is contained in:
Weimin Yu 2021-06-01 15:28:22 -04:00 committed by GitHub
parent 92f5f8989b
commit 3fa56dec45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 118 additions and 493 deletions

View file

@ -16,24 +16,12 @@ package google.registry.keyring.kms;
import static com.google.common.base.CaseFormat.LOWER_HYPHEN; import static com.google.common.base.CaseFormat.LOWER_HYPHEN;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.KeySerializer; import google.registry.keyring.api.KeySerializer;
import google.registry.keyring.api.Keyring; import google.registry.keyring.api.Keyring;
import google.registry.keyring.api.KeyringException; import google.registry.keyring.api.KeyringException;
import google.registry.model.server.KmsSecret;
import google.registry.model.server.KmsSecretRevision;
import google.registry.model.server.KmsSecretRevisionSqlDao;
import google.registry.privileges.secretmanager.KeyringSecretStore; import google.registry.privileges.secretmanager.KeyringSecretStore;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyPair;
@ -47,11 +35,9 @@ import org.bouncycastle.openpgp.PGPPublicKey;
* @see <a href="https://cloud.google.com/kms/docs/">Google Cloud Key Management Service * @see <a href="https://cloud.google.com/kms/docs/">Google Cloud Key Management Service
* Documentation</a> * Documentation</a>
*/ */
// TODO(2021-06-01): rename this class to SecretManagerKeyring and delete KmsSecretRevision // TODO(2021-07-01): rename this class to SecretManagerKeyring and delete KmsSecretRevision
public class KmsKeyring implements Keyring { public class KmsKeyring implements Keyring {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Key labels for private key secrets. */ /** Key labels for private key secrets. */
enum PrivateKeyLabel { enum PrivateKeyLabel {
BRDA_SIGNING_PRIVATE, BRDA_SIGNING_PRIVATE,
@ -92,13 +78,10 @@ public class KmsKeyring implements Keyring {
} }
} }
private final KmsConnection kmsConnection;
private final KeyringSecretStore secretStore; private final KeyringSecretStore secretStore;
@Inject @Inject
KmsKeyring( KmsKeyring(KeyringSecretStore secretStore) {
@Config("defaultKmsConnection") KmsConnection kmsConnection, KeyringSecretStore secretStore) {
this.kmsConnection = kmsConnection;
this.secretStore = secretStore; this.secretStore = secretStore;
} }
@ -201,44 +184,11 @@ public class KmsKeyring implements Keyring {
return getKeyPair(keyLabel).getPrivateKey(); return getKeyPair(keyLabel).getPrivateKey();
} }
private byte[] getDecryptedDataFromDatastore(String keyName) { private byte[] getDecryptedData(String keyName) {
String encryptedData;
if (tm().isOfy()) {
KmsSecret secret =
auditedOfy().load().key(Key.create(getCrossTldKey(), KmsSecret.class, keyName)).now();
checkState(secret != null, "Requested secret '%s' does not exist.", keyName);
encryptedData = auditedOfy().load().key(secret.getLatestRevision()).now().getEncryptedValue();
} else {
Optional<KmsSecretRevision> revision =
tm().transact(() -> KmsSecretRevisionSqlDao.getLatestRevision(keyName));
checkState(revision.isPresent(), "Requested secret '%s' does not exist.", keyName);
encryptedData = revision.get().getEncryptedValue();
}
try {
return kmsConnection.decrypt(keyName, encryptedData);
} catch (Exception e) {
return new byte[0];
}
}
private byte[] getDataFromSecretStore(String keyName) {
try { try {
return secretStore.getSecret(keyName); return secretStore.getSecret(keyName);
} catch (Exception e) { } catch (Exception e) {
throw new KeyringException("Failed to retrieve secret for " + keyName, e); throw new KeyringException("Failed to retrieve secret for " + keyName, e);
} }
} }
private byte[] getDecryptedData(String keyName) {
byte[] dsData = getDecryptedDataFromDatastore(keyName);
byte[] secretStoreData = getDataFromSecretStore(keyName);
if (Arrays.equals(dsData, secretStoreData)) {
logger.atInfo().log("Values for %s in Datastore and Secret Manager match.", keyName);
return secretStoreData;
}
logger.atWarning().log("Values for %s in Datastore and Secret Manager do not match.", keyName);
return secretStoreData;
}
} }

View file

@ -32,18 +32,13 @@ import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.MARKSDB_SMDR
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PRIVATE_STRING; import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PRIVATE_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PUBLIC_STRING; import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.RDE_SSH_CLIENT_PUBLIC_STRING;
import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.SAFE_BROWSING_API_KEY; import static google.registry.keyring.kms.KmsKeyring.StringKeyLabel.SAFE_BROWSING_API_KEY;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.keyring.api.KeySerializer; import google.registry.keyring.api.KeySerializer;
import google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel; import google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel;
import google.registry.keyring.kms.KmsKeyring.PublicKeyLabel; import google.registry.keyring.kms.KmsKeyring.PublicKeyLabel;
import google.registry.keyring.kms.KmsKeyring.StringKeyLabel; import google.registry.keyring.kms.KmsKeyring.StringKeyLabel;
import google.registry.model.server.KmsSecret;
import google.registry.model.server.KmsSecretRevision;
import google.registry.privileges.secretmanager.KeyringSecretStore; import google.registry.privileges.secretmanager.KeyringSecretStore;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
@ -62,14 +57,11 @@ import org.bouncycastle.openpgp.PGPPublicKey;
public final class KmsUpdater { public final class KmsUpdater {
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final KmsConnection kmsConnection;
private final KeyringSecretStore secretStore; private final KeyringSecretStore secretStore;
private final HashMap<String, byte[]> secretValues; private final HashMap<String, byte[]> secretValues;
@Inject @Inject
public KmsUpdater( public KmsUpdater(KeyringSecretStore secretStore) {
@Config("defaultKmsConnection") KmsConnection kmsConnection, KeyringSecretStore secretStore) {
this.kmsConnection = kmsConnection;
this.secretStore = secretStore; this.secretStore = secretStore;
// Use LinkedHashMap to preserve insertion order on update() to simplify testing and debugging // Use LinkedHashMap to preserve insertion order on update() to simplify testing and debugging
@ -150,24 +142,6 @@ public final class KmsUpdater {
+ "Please check the status of Secret Manager and re-run the command.", + "Please check the status of Secret Manager and re-run the command.",
e); e);
} }
// TODO(2021-06-01): remove the writes to Datastore
persistEncryptedValues(encryptValues(secretValues));
}
/**
* Encrypts updated secrets using KMS. If the configured {@code KeyRing} or {@code CryptoKey}
* associated with a secret doesn't exist, they will first be created.
*
* @see google.registry.config.RegistryConfigSettings.Kms
*/
private ImmutableMap<String, EncryptResponse> encryptValues(Map<String, byte[]> keyValues) {
ImmutableMap.Builder<String, EncryptResponse> encryptedValues = new ImmutableMap.Builder<>();
for (Map.Entry<String, byte[]> entry : keyValues.entrySet()) {
String secretName = entry.getKey();
encryptedValues.put(secretName, kmsConnection.encrypt(secretName, entry.getValue()));
}
return encryptedValues.build();
} }
private KmsUpdater setString(String key, StringKeyLabel stringKeyLabel) { private KmsUpdater setString(String key, StringKeyLabel stringKeyLabel) {
@ -195,32 +169,6 @@ public final class KmsUpdater {
return this; return this;
} }
/**
* Persists encrypted secrets to Datastore as {@link KmsSecretRevision} entities and makes them
* primary. {@link KmsSecret} entities point to the latest {@link KmsSecretRevision}.
*
* <p>The changes are committed transactionally; if an error occurs, all existing {@link
* KmsSecretRevision} entities will remain primary.
*/
private static void persistEncryptedValues(
final ImmutableMap<String, EncryptResponse> encryptedValues) {
tm().transact(
() -> {
for (Map.Entry<String, EncryptResponse> entry : encryptedValues.entrySet()) {
String secretName = entry.getKey();
EncryptResponse revisionData = entry.getValue();
KmsSecretRevision secretRevision =
new KmsSecretRevision.Builder()
.setEncryptedValue(revisionData.ciphertext())
.setKmsCryptoKeyVersionName(revisionData.cryptoKeyVersionName())
.setParent(secretName)
.build();
tm().putAll(secretRevision, KmsSecret.create(secretName, secretRevision));
}
});
}
private void setSecret(String secretName, byte[] value) { private void setSecret(String secretName, byte[] value) {
checkArgument(!secretValues.containsKey(secretName), "Attempted to set %s twice", secretName); checkArgument(!secretValues.containsKey(secretName), "Attempted to set %s twice", secretName);
secretValues.put(secretName, value); secretValues.put(secretName, value);

View file

@ -1,45 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.keyring.kms;
import com.google.common.io.BaseEncoding;
import org.bouncycastle.util.Arrays;
class FakeKmsConnection implements KmsConnection {
FakeKmsConnection() {}
/**
* Returns a dummy {@link EncryptResponse}.
*
* <p>The "encrypted value" in the response is the provided value reversed and then base64-encoded
* and the name of the cryptoKeyVersion is {@code cryptoKeyName + "/foo"}.
*/
@Override
public EncryptResponse encrypt(String cryptoKeyName, byte[] plaintext) {
return EncryptResponse.create(
BaseEncoding.base64().encode(Arrays.reverse(plaintext)), cryptoKeyName + "/foo");
}
/**
* Returns a "decrypted" plaintext.
*
* <p>The plaintext is the encodedCiphertext base64-decoded and then reversed.
*/
@Override
public byte[] decrypt(String cryptoKeyName, String encodedCiphertext) {
return Arrays.reverse(BaseEncoding.base64().decode(encodedCiphertext));
}
}

View file

@ -1,187 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.keyring.kms;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import google.registry.keyring.api.KeySerializer;
import google.registry.model.server.KmsSecret;
import google.registry.model.server.KmsSecretRevision;
import google.registry.privileges.secretmanager.FakeSecretManagerClient;
import google.registry.privileges.secretmanager.KeyringSecretStore;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.BouncyCastleProviderExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link KmsKeyring}. */
@DualDatabaseTest
class KmsKeyringTest {
@RegisterExtension
final BouncyCastleProviderExtension bouncy = new BouncyCastleProviderExtension();
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
private KmsKeyring keyring;
private KeyringSecretStore fakeSecretStore =
new KeyringSecretStore(new FakeSecretManagerClient());
@BeforeEach
void beforeEach() {
keyring = new KmsKeyring(new FakeKmsConnection(), fakeSecretStore);
}
@TestOfyAndSql
void test_getRdeSigningKey() throws Exception {
saveKeyPairSecret("rde-signing-public", "rde-signing-private");
PGPKeyPair rdeSigningKey = keyring.getRdeSigningKey();
assertThat(KeySerializer.serializeKeyPair(rdeSigningKey))
.isEqualTo(KeySerializer.serializeKeyPair(KmsTestHelper.getKeyPair()));
}
@TestOfyAndSql
void test_getRdeStagingEncryptionKey() throws Exception {
savePublicKeySecret("rde-staging-public");
PGPPublicKey rdeStagingEncryptionKey = keyring.getRdeStagingEncryptionKey();
assertThat(rdeStagingEncryptionKey.getFingerprint())
.isEqualTo(KmsTestHelper.getPublicKey().getFingerprint());
}
@TestOfyAndSql
void test_getRdeStagingDecryptionKey() throws Exception {
savePrivateKeySecret("rde-staging-private");
savePublicKeySecret("rde-staging-public");
PGPPrivateKey rdeStagingDecryptionKey = keyring.getRdeStagingDecryptionKey();
PGPPublicKey rdeStagingEncryptionKey = keyring.getRdeStagingEncryptionKey();
PGPKeyPair keyPair = new PGPKeyPair(rdeStagingEncryptionKey, rdeStagingDecryptionKey);
assertThat(KeySerializer.serializeKeyPair(keyPair))
.isEqualTo(KeySerializer.serializeKeyPair(KmsTestHelper.getKeyPair()));
}
@TestOfyAndSql
void test_getRdeReceiverKey() throws Exception {
savePublicKeySecret("rde-receiver-public");
PGPPublicKey rdeReceiverKey = keyring.getRdeReceiverKey();
assertThat(rdeReceiverKey.getFingerprint())
.isEqualTo(KmsTestHelper.getPublicKey().getFingerprint());
}
@TestOfyAndSql
void test_getBrdaSigningKey() throws Exception {
saveKeyPairSecret("brda-signing-public", "brda-signing-private");
PGPKeyPair brdaSigningKey = keyring.getBrdaSigningKey();
assertThat(KeySerializer.serializeKeyPair(brdaSigningKey))
.isEqualTo(KeySerializer.serializeKeyPair(KmsTestHelper.getKeyPair()));
}
@TestOfyAndSql
void test_getBrdaReceiverKey() throws Exception {
savePublicKeySecret("brda-receiver-public");
PGPPublicKey brdaReceiverKey = keyring.getBrdaReceiverKey();
assertThat(brdaReceiverKey.getFingerprint())
.isEqualTo(KmsTestHelper.getPublicKey().getFingerprint());
}
@TestOfyAndSql
void test_getRdeSshClientPublicKey() {
saveCleartextSecret("rde-ssh-client-public-string");
String rdeSshClientPublicKey = keyring.getRdeSshClientPublicKey();
assertThat(rdeSshClientPublicKey).isEqualTo("rde-ssh-client-public-stringmoo");
}
@TestOfyAndSql
void test_getRdeSshClientPrivateKey() {
saveCleartextSecret("rde-ssh-client-private-string");
String rdeSshClientPrivateKey = keyring.getRdeSshClientPrivateKey();
assertThat(rdeSshClientPrivateKey).isEqualTo("rde-ssh-client-private-stringmoo");
}
@TestOfyAndSql
void test_getIcannReportingPassword() {
saveCleartextSecret("icann-reporting-password-string");
String icannReportingPassword = keyring.getIcannReportingPassword();
assertThat(icannReportingPassword).isEqualTo("icann-reporting-password-stringmoo");
}
@TestOfyAndSql
void test_getMarksdbDnlLoginAndPassword() {
saveCleartextSecret("marksdb-dnl-login-string");
String marksdbDnlLoginAndPassword = keyring.getMarksdbDnlLoginAndPassword();
assertThat(marksdbDnlLoginAndPassword).isEqualTo("marksdb-dnl-login-stringmoo");
}
@TestOfyAndSql
void test_getMarksdbLordnPassword() {
saveCleartextSecret("marksdb-lordn-password-string");
String marksdbLordnPassword = keyring.getMarksdbLordnPassword();
assertThat(marksdbLordnPassword).isEqualTo("marksdb-lordn-password-stringmoo");
}
@TestOfyAndSql
void test_getMarksdbSmdrlLoginAndPassword() {
saveCleartextSecret("marksdb-smdrl-login-string");
String marksdbSmdrlLoginAndPassword = keyring.getMarksdbSmdrlLoginAndPassword();
assertThat(marksdbSmdrlLoginAndPassword).isEqualTo("marksdb-smdrl-login-stringmoo");
}
@TestOfyAndSql
void test_getJsonCredential() {
saveCleartextSecret("json-credential-string");
String jsonCredential = keyring.getJsonCredential();
assertThat(jsonCredential).isEqualTo("json-credential-stringmoo");
}
private void persistSecret(String secretName, byte[] secretValue) {
KmsConnection kmsConnection = new FakeKmsConnection();
KmsSecretRevision secretRevision =
new KmsSecretRevision.Builder()
.setEncryptedValue(kmsConnection.encrypt(secretName, secretValue).ciphertext())
.setKmsCryptoKeyVersionName(KmsTestHelper.DUMMY_CRYPTO_KEY_VERSION)
.setParent(secretName)
.build();
KmsSecret secret = KmsSecret.create(secretName, secretRevision);
tm().transact(() -> tm().putAll(secretRevision, secret));
fakeSecretStore.createOrUpdateSecret(secretName, secretValue);
}
private void saveCleartextSecret(String secretName) {
persistSecret(secretName, KeySerializer.serializeString(secretName + "moo"));
}
private void savePublicKeySecret(String publicKeyName) throws Exception {
persistSecret(publicKeyName, KeySerializer.serializePublicKey(KmsTestHelper.getPublicKey()));
}
private void savePrivateKeySecret(String privateKeyName) throws Exception {
persistSecret(privateKeyName, KeySerializer.serializeKeyPair(KmsTestHelper.getKeyPair()));
}
private void saveKeyPairSecret(String publicKeyName, String privateKeyName) throws Exception {
savePublicKeySecret(publicKeyName);
savePrivateKeySecret(privateKeyName);
}
}

View file

@ -26,7 +26,7 @@ import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
/** Stores dummy values for test use in {@link KmsUpdaterTest} and {@link KmsKeyringTest}. */ /** Stores dummy values for test use in {@link KmsUpdaterTest}. */
final class KmsTestHelper { final class KmsTestHelper {
static final String DUMMY_CRYPTO_KEY_VERSION = "cheeseburger"; static final String DUMMY_CRYPTO_KEY_VERSION = "cheeseburger";

View file

@ -15,226 +15,185 @@
package google.registry.keyring.kms; package google.registry.keyring.kms;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.googlecode.objectify.Key;
import google.registry.keyring.api.KeySerializer; import google.registry.keyring.api.KeySerializer;
import google.registry.model.server.KmsSecret;
import google.registry.model.server.KmsSecretRevision;
import google.registry.model.server.KmsSecretRevisionSqlDao;
import google.registry.persistence.VKey;
import google.registry.privileges.secretmanager.FakeSecretManagerClient; import google.registry.privileges.secretmanager.FakeSecretManagerClient;
import google.registry.privileges.secretmanager.KeyringSecretStore; import google.registry.privileges.secretmanager.KeyringSecretStore;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.BouncyCastleProviderExtension; import google.registry.testing.BouncyCastleProviderExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link KmsUpdater} */ /** Unit tests for {@link KmsKeyring} and {@link KmsUpdater} */
@DualDatabaseTest // TODO(2021-07-01): Rename this class along with KmsKeyring
public class KmsUpdaterTest { public class KmsUpdaterTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
@RegisterExtension @RegisterExtension
public final BouncyCastleProviderExtension bouncy = new BouncyCastleProviderExtension(); public final BouncyCastleProviderExtension bouncy = new BouncyCastleProviderExtension();
private KeyringSecretStore secretStore;
private KmsUpdater updater; private KmsUpdater updater;
private KmsKeyring keyring;
@BeforeEach @BeforeEach
void beforeEach() { void beforeEach() {
updater = secretStore = new KeyringSecretStore(new FakeSecretManagerClient());
new KmsUpdater( updater = new KmsUpdater(secretStore);
new FakeKmsConnection(), new KeyringSecretStore(new FakeSecretManagerClient())); keyring = new KmsKeyring(secretStore);
} }
@TestOfyAndSql @Test
void test_setMultipleSecrets() { void setAndReadMultiple() {
String secretPrefix = "setAndReadMultiple_";
updater updater
.setMarksdbDnlLoginAndPassword("value1") .setMarksdbDnlLoginAndPassword(secretPrefix + "marksdb")
.setIcannReportingPassword("value2") .setIcannReportingPassword(secretPrefix + "icann")
.setJsonCredential("value3") .setJsonCredential(secretPrefix + "json")
.update(); .update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getMarksdbDnlLoginAndPassword()).isEqualTo(secretPrefix + "marksdb");
"marksdb-dnl-login-string", assertThat(keyring.getIcannReportingPassword()).isEqualTo(secretPrefix + "icann");
"marksdb-dnl-login-string/foo", assertThat(keyring.getJsonCredential()).isEqualTo(secretPrefix + "json");
getCiphertext("value1"));
verifySecretAndSecretRevisionWritten( verifyPersistedSecret("marksdb-dnl-login-string", secretPrefix + "marksdb");
"icann-reporting-password-string", verifyPersistedSecret("icann-reporting-password-string", secretPrefix + "icann");
"icann-reporting-password-string/foo", verifyPersistedSecret("json-credential-string", secretPrefix + "json");
getCiphertext("value2"));
verifySecretAndSecretRevisionWritten(
"json-credential-string", "json-credential-string/foo", getCiphertext("value3"));
} }
@TestOfyAndSql @Test
void test_setBrdaReceiverKey() throws Exception { void brdaReceiverKey() throws Exception {
updater.setBrdaReceiverPublicKey(KmsTestHelper.getPublicKey()).update(); PGPPublicKey publicKey = KmsTestHelper.getPublicKey();
updater.setBrdaReceiverPublicKey(publicKey).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getBrdaReceiverKey().getFingerprint()).isEqualTo(publicKey.getFingerprint());
"brda-receiver-public", verifyPersistedSecret("brda-receiver-public", serializePublicKey(publicKey));
"brda-receiver-public/foo",
getCiphertext(KmsTestHelper.getPublicKey()));
} }
@TestOfyAndSql @Test
void test_setBrdaSigningKey() throws Exception { void brdaSigningKey() throws Exception {
updater.setBrdaSigningKey(KmsTestHelper.getKeyPair()).update(); PGPKeyPair keyPair = KmsTestHelper.getKeyPair();
updater.setBrdaSigningKey(keyPair).update();
verifySecretAndSecretRevisionWritten( assertThat(serializeKeyPair(keyring.getBrdaSigningKey())).isEqualTo(serializeKeyPair(keyPair));
"brda-signing-private", verifyPersistedSecret("brda-signing-private", serializeKeyPair(KmsTestHelper.getKeyPair()));
"brda-signing-private/foo", verifyPersistedSecret("brda-signing-public", serializePublicKey(KmsTestHelper.getPublicKey()));
getCiphertext(KmsTestHelper.getKeyPair()));
verifySecretAndSecretRevisionWritten(
"brda-signing-public",
"brda-signing-public/foo",
getCiphertext(KmsTestHelper.getPublicKey()));
} }
@TestOfyAndSql @Test
void test_setIcannReportingPassword() { void icannReportingPassword() {
updater.setIcannReportingPassword("value1").update(); String secret = "icannReportingPassword";
updater.setIcannReportingPassword(secret).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getIcannReportingPassword()).isEqualTo(secret);
"icann-reporting-password-string", verifyPersistedSecret("icann-reporting-password-string", secret);
"icann-reporting-password-string/foo",
getCiphertext("value1"));
} }
@TestOfyAndSql @Test
void test_setJsonCredential() { void jsonCredential() {
updater.setJsonCredential("value1").update(); String secret = "jsonCredential";
updater.setJsonCredential(secret).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getJsonCredential()).isEqualTo(secret);
"json-credential-string", "json-credential-string/foo", getCiphertext("value1")); verifyPersistedSecret("json-credential-string", secret);
} }
@TestOfyAndSql @Test
void test_setMarksdbDnlLoginAndPassword() { void marksdbDnlLoginAndPassword() {
updater.setMarksdbDnlLoginAndPassword("value1").update(); String secret = "marksdbDnlLoginAndPassword";
updater.setMarksdbDnlLoginAndPassword(secret).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getMarksdbDnlLoginAndPassword()).isEqualTo(secret);
"marksdb-dnl-login-string", "marksdb-dnl-login-string/foo", getCiphertext("value1")); verifyPersistedSecret("marksdb-dnl-login-string", secret);
} }
@TestOfyAndSql @Test
void test_setMarksdbLordnPassword() { void marksdbLordnPassword() {
updater.setMarksdbLordnPassword("value1").update(); String secret = "marksdbLordnPassword";
updater.setMarksdbLordnPassword(secret).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getMarksdbLordnPassword()).isEqualTo(secret);
"marksdb-lordn-password-string", verifyPersistedSecret("marksdb-lordn-password-string", secret);
"marksdb-lordn-password-string/foo",
getCiphertext("value1"));
} }
@TestOfyAndSql @Test
void test_setMarksdbSmdrlLoginAndPassword() { void marksdbSmdrlLoginAndPassword() {
updater.setMarksdbSmdrlLoginAndPassword("value1").update(); String secret = "marksdbSmdrlLoginAndPassword";
updater.setMarksdbSmdrlLoginAndPassword(secret).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getMarksdbSmdrlLoginAndPassword()).isEqualTo(secret);
"marksdb-smdrl-login-string", "marksdb-smdrl-login-string/foo", getCiphertext("value1")); verifyPersistedSecret("marksdb-smdrl-login-string", secret);
} }
@TestOfyAndSql @Test
void test_setRdeReceiverKey() throws Exception { void rdeReceiverKey() throws Exception {
updater.setRdeReceiverPublicKey(KmsTestHelper.getPublicKey()).update(); PGPPublicKey publicKey = KmsTestHelper.getPublicKey();
updater.setRdeReceiverPublicKey(publicKey).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getRdeReceiverKey().getFingerprint()).isEqualTo(publicKey.getFingerprint());
"rde-receiver-public", verifyPersistedSecret("rde-receiver-public", serializePublicKey(KmsTestHelper.getPublicKey()));
"rde-receiver-public/foo",
getCiphertext(
KeySerializer.serializePublicKey(KmsTestHelper.getPublicKey())));
} }
@TestOfyAndSql @Test
void test_setRdeSigningKey() throws Exception { void rdeSigningKey() throws Exception {
updater.setRdeSigningKey(KmsTestHelper.getKeyPair()).update(); PGPKeyPair keyPair = KmsTestHelper.getKeyPair();
updater.setRdeSigningKey(keyPair).update();
verifySecretAndSecretRevisionWritten( assertThat(serializeKeyPair(keyring.getRdeSigningKey())).isEqualTo(serializeKeyPair(keyPair));
"rde-signing-private",
"rde-signing-private/foo", verifyPersistedSecret("rde-signing-private", serializeKeyPair(keyPair));
getCiphertext(KmsTestHelper.getKeyPair())); verifyPersistedSecret("rde-signing-public", serializePublicKey(keyPair.getPublicKey()));
verifySecretAndSecretRevisionWritten(
"rde-signing-public",
"rde-signing-public/foo",
getCiphertext(KmsTestHelper.getPublicKey()));
} }
@TestOfyAndSql @Test
void test_setRdeSshClientPrivateKey() { void rdeSshClientPrivateKey() {
updater.setRdeSshClientPrivateKey("value1").update(); String secret = "rdeSshClientPrivateKey";
updater.setRdeSshClientPrivateKey(secret).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getRdeSshClientPrivateKey()).isEqualTo(secret);
"rde-ssh-client-private-string", verifyPersistedSecret("rde-ssh-client-private-string", secret);
"rde-ssh-client-private-string/foo",
getCiphertext("value1"));
} }
@TestOfyAndSql @Test
void test_setRdeSshClientPublicKey() { void rdeSshClientPublicKey() {
updater.setRdeSshClientPublicKey("value1").update(); String secret = "rdeSshClientPublicKey";
updater.setRdeSshClientPublicKey(secret).update();
verifySecretAndSecretRevisionWritten( assertThat(keyring.getRdeSshClientPublicKey()).isEqualTo(secret);
"rde-ssh-client-public-string", verifyPersistedSecret("rde-ssh-client-public-string", secret);
"rde-ssh-client-public-string/foo",
getCiphertext("value1"));
} }
@TestOfyAndSql @Test
void test_setRdeStagingKey() throws Exception { void rdeStagingKey() throws Exception {
updater.setRdeStagingKey(KmsTestHelper.getKeyPair()).update(); PGPKeyPair keyPair = KmsTestHelper.getKeyPair();
updater.setRdeStagingKey(keyPair).update();
verifySecretAndSecretRevisionWritten( assertThat(serializePublicKey(keyring.getRdeStagingEncryptionKey()))
"rde-staging-private", .isEqualTo(serializePublicKey(keyPair.getPublicKey()));
"rde-staging-private/foo", // Since we do not have dedicated tools to compare private keys, we leverage key-pair
getCiphertext(KmsTestHelper.getKeyPair())); // serialization util to compare private keys.
verifySecretAndSecretRevisionWritten( assertThat(
"rde-staging-public", serializeKeyPair(
"rde-staging-public/foo", new PGPKeyPair(
getCiphertext(KmsTestHelper.getPublicKey())); keyring.getRdeStagingEncryptionKey(), keyring.getRdeStagingDecryptionKey())))
.isEqualTo(serializeKeyPair(keyPair));
verifyPersistedSecret("rde-staging-private", serializeKeyPair(keyPair));
verifyPersistedSecret("rde-staging-public", serializePublicKey(KmsTestHelper.getPublicKey()));
} }
private static void verifySecretAndSecretRevisionWritten( private void verifyPersistedSecret(String secretName, String expectedPlainTextValue) {
String secretName, String expectedCryptoKeyVersionName, String expectedEncryptedValue) { assertThat(new String(secretStore.getSecret(secretName), StandardCharsets.UTF_8))
KmsSecretRevision secretRevision; .isEqualTo(expectedPlainTextValue);
if (tm().isOfy()) {
KmsSecret secret =
tm().loadByKey(
VKey.createOfy(
KmsSecret.class, Key.create(getCrossTldKey(), KmsSecret.class, secretName)));
assertThat(secret).isNotNull();
secretRevision =
tm().loadByKey(VKey.createOfy(KmsSecretRevision.class, secret.getLatestRevision()));
} else {
secretRevision =
tm().transact(() -> KmsSecretRevisionSqlDao.getLatestRevision(secretName).get());
}
assertThat(secretRevision.getKmsCryptoKeyVersionName()).isEqualTo(expectedCryptoKeyVersionName);
assertThat(secretRevision.getEncryptedValue()).isEqualTo(expectedEncryptedValue);
} }
private static String getCiphertext(byte[] plaintext) { private static String serializePublicKey(PGPPublicKey publicKey) throws IOException {
return new FakeKmsConnection().encrypt("blah", plaintext).ciphertext(); return new String(KeySerializer.serializePublicKey(publicKey), StandardCharsets.UTF_8);
} }
private static String getCiphertext(String plaintext) { private static String serializeKeyPair(PGPKeyPair keyPair) throws Exception {
return getCiphertext(KeySerializer.serializeString(plaintext)); return new String(KeySerializer.serializeKeyPair(keyPair), StandardCharsets.UTF_8);
}
private static String getCiphertext(PGPPublicKey publicKey) throws IOException {
return getCiphertext(KeySerializer.serializePublicKey(publicKey));
}
private static String getCiphertext(PGPKeyPair keyPair) throws Exception {
return getCiphertext(KeySerializer.serializeKeyPair(keyPair));
} }
} }