mirror of
https://github.com/google/nomulus
synced 2026-04-26 11:10:48 +00:00
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:
@@ -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.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.Keyring;
|
||||
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 java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
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
|
||||
* 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 {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** Key labels for private key secrets. */
|
||||
enum PrivateKeyLabel {
|
||||
BRDA_SIGNING_PRIVATE,
|
||||
@@ -92,13 +78,10 @@ public class KmsKeyring implements Keyring {
|
||||
}
|
||||
}
|
||||
|
||||
private final KmsConnection kmsConnection;
|
||||
private final KeyringSecretStore secretStore;
|
||||
|
||||
@Inject
|
||||
KmsKeyring(
|
||||
@Config("defaultKmsConnection") KmsConnection kmsConnection, KeyringSecretStore secretStore) {
|
||||
this.kmsConnection = kmsConnection;
|
||||
KmsKeyring(KeyringSecretStore secretStore) {
|
||||
this.secretStore = secretStore;
|
||||
}
|
||||
|
||||
@@ -201,44 +184,11 @@ public class KmsKeyring implements Keyring {
|
||||
return getKeyPair(keyLabel).getPrivateKey();
|
||||
}
|
||||
|
||||
private byte[] getDecryptedDataFromDatastore(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) {
|
||||
private byte[] getDecryptedData(String keyName) {
|
||||
try {
|
||||
return secretStore.getSecret(keyName);
|
||||
} catch (Exception 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_PUBLIC_STRING;
|
||||
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 com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.api.KeySerializer;
|
||||
import google.registry.keyring.kms.KmsKeyring.PrivateKeyLabel;
|
||||
import google.registry.keyring.kms.KmsKeyring.PublicKeyLabel;
|
||||
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 java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
@@ -62,14 +57,11 @@ import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
public final class KmsUpdater {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final KmsConnection kmsConnection;
|
||||
private final KeyringSecretStore secretStore;
|
||||
private final HashMap<String, byte[]> secretValues;
|
||||
|
||||
@Inject
|
||||
public KmsUpdater(
|
||||
@Config("defaultKmsConnection") KmsConnection kmsConnection, KeyringSecretStore secretStore) {
|
||||
this.kmsConnection = kmsConnection;
|
||||
public KmsUpdater(KeyringSecretStore secretStore) {
|
||||
this.secretStore = secretStore;
|
||||
|
||||
// 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.",
|
||||
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) {
|
||||
@@ -195,32 +169,6 @@ public final class KmsUpdater {
|
||||
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) {
|
||||
checkArgument(!secretValues.containsKey(secretName), "Attempted to set %s twice", secretName);
|
||||
secretValues.put(secretName, value);
|
||||
|
||||
Reference in New Issue
Block a user