diff --git a/core/src/main/java/google/registry/beam/initsql/BeamJpaModule.java b/core/src/main/java/google/registry/beam/initsql/BeamJpaModule.java index dde47b695..9b0be691b 100644 --- a/core/src/main/java/google/registry/beam/initsql/BeamJpaModule.java +++ b/core/src/main/java/google/registry/beam/initsql/BeamJpaModule.java @@ -31,6 +31,7 @@ import google.registry.persistence.PersistenceModule; import google.registry.persistence.PersistenceModule.JdbcJpaTm; import google.registry.persistence.PersistenceModule.SocketFactoryJpaTm; import google.registry.persistence.transaction.JpaTransactionManager; +import google.registry.privileges.secretmanager.SecretManagerModule; import google.registry.util.UtilsModule; import java.io.BufferedReader; import java.io.IOException; @@ -168,6 +169,7 @@ public class BeamJpaModule { BeamJpaModule.class, KmsModule.class, PersistenceModule.class, + SecretManagerModule.class, UtilsModule.class }) public interface JpaTransactionManagerComponent { diff --git a/core/src/main/java/google/registry/persistence/PersistenceComponent.java b/core/src/main/java/google/registry/persistence/PersistenceComponent.java index 5dd6d29c0..cbe6cea9a 100644 --- a/core/src/main/java/google/registry/persistence/PersistenceComponent.java +++ b/core/src/main/java/google/registry/persistence/PersistenceComponent.java @@ -20,6 +20,7 @@ import google.registry.config.RegistryConfig.ConfigModule; import google.registry.keyring.kms.KmsModule; import google.registry.persistence.PersistenceModule.AppEngineJpaTm; import google.registry.persistence.transaction.JpaTransactionManager; +import google.registry.privileges.secretmanager.SecretManagerModule; import google.registry.util.UtilsModule; import javax.inject.Singleton; import javax.persistence.EntityManagerFactory; @@ -32,6 +33,7 @@ import javax.persistence.EntityManagerFactory; CredentialModule.class, KmsModule.class, PersistenceModule.class, + SecretManagerModule.class, UtilsModule.class }) public interface PersistenceComponent { diff --git a/core/src/main/java/google/registry/persistence/PersistenceModule.java b/core/src/main/java/google/registry/persistence/PersistenceModule.java index 3fbfd5f01..cd52e10a9 100644 --- a/core/src/main/java/google/registry/persistence/PersistenceModule.java +++ b/core/src/main/java/google/registry/persistence/PersistenceModule.java @@ -26,6 +26,7 @@ import com.google.api.client.auth.oauth2.Credential; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import com.google.common.flogger.FluentLogger; import dagger.Module; import dagger.Provides; import google.registry.config.RegistryConfig.Config; @@ -33,6 +34,11 @@ import google.registry.keyring.kms.KmsKeyring; import google.registry.persistence.transaction.CloudSqlCredentialSupplier; import google.registry.persistence.transaction.JpaTransactionManager; import google.registry.persistence.transaction.JpaTransactionManagerImpl; +import google.registry.privileges.secretmanager.SqlCredential; +import google.registry.privileges.secretmanager.SqlCredentialStore; +import google.registry.privileges.secretmanager.SqlUser; +import google.registry.privileges.secretmanager.SqlUser.RobotId; +import google.registry.privileges.secretmanager.SqlUser.RobotUser; import google.registry.tools.AuthModule.CloudSqlClientCredential; import google.registry.util.Clock; import java.lang.annotation.Documented; @@ -47,6 +53,8 @@ import org.hibernate.cfg.Environment; /** Dagger module class for the persistence layer. */ @Module public class PersistenceModule { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + // This name must be the same as the one defined in persistence.xml. public static final String PERSISTENCE_UNIT_NAME = "nomulus"; public static final String HIKARI_CONNECTION_TIMEOUT = "hibernate.hikari.connectionTimeout"; @@ -122,11 +130,17 @@ public class PersistenceModule { static JpaTransactionManager provideAppEngineJpaTm( @Config("cloudSqlUsername") String username, KmsKeyring kmsKeyring, + SqlCredentialStore credentialStore, @PartialCloudSqlConfigs ImmutableMap cloudSqlConfigs, Clock clock) { HashMap overrides = Maps.newHashMap(cloudSqlConfigs); overrides.put(Environment.USER, username); overrides.put(Environment.PASS, kmsKeyring.getCloudSqlPassword()); + validateCredentialStore( + credentialStore, + new RobotUser(RobotId.NOMULUS), + overrides.get(Environment.USER), + overrides.get(Environment.PASS)); return new JpaTransactionManagerImpl(create(overrides), clock); } @@ -136,6 +150,7 @@ public class PersistenceModule { static JpaTransactionManager provideNomulusToolJpaTm( @Config("toolsCloudSqlUsername") String username, KmsKeyring kmsKeyring, + SqlCredentialStore credentialStore, @PartialCloudSqlConfigs ImmutableMap cloudSqlConfigs, @CloudSqlClientCredential Credential credential, Clock clock) { @@ -143,6 +158,11 @@ public class PersistenceModule { HashMap overrides = Maps.newHashMap(cloudSqlConfigs); overrides.put(Environment.USER, username); overrides.put(Environment.PASS, kmsKeyring.getToolsCloudSqlPassword()); + validateCredentialStore( + credentialStore, + new RobotUser(RobotId.TOOL), + overrides.get(Environment.USER), + overrides.get(Environment.PASS)); return new JpaTransactionManagerImpl(create(overrides), clock); } @@ -150,6 +170,7 @@ public class PersistenceModule { @Singleton @SocketFactoryJpaTm static JpaTransactionManager provideSocketFactoryJpaTm( + SqlCredentialStore credentialStore, @Config("beamCloudSqlUsername") String username, @Config("beamCloudSqlPassword") String password, @Config("beamHibernateHikariMaximumPoolSize") int hikariMaximumPoolSize, @@ -159,6 +180,12 @@ public class PersistenceModule { overrides.put(Environment.USER, username); overrides.put(Environment.PASS, password); overrides.put(HIKARI_MAXIMUM_POOL_SIZE, String.valueOf(hikariMaximumPoolSize)); + // TODO(b/175700623): consider assigning different logins to pipelines + validateCredentialStore( + credentialStore, + new RobotUser(RobotId.NOMULUS), + overrides.get(Environment.USER), + overrides.get(Environment.PASS)); return new JpaTransactionManagerImpl(create(overrides), clock); } @@ -203,6 +230,30 @@ public class PersistenceModule { return emf; } + /** Verifies that the credential from the Secret Manager matches the one currently in use. + * + *

This is a helper for the transition to the Secret Manager, and will be removed once data + * and permissions are properly set up for all projects. + **/ + private static void validateCredentialStore( + SqlCredentialStore credentialStore, SqlUser sqlUser, String login, String password) { + try { + SqlCredential credential = credentialStore.getCredential(sqlUser); + if (!credential.login().equals(login)) { + logger.atWarning().log( + "Wrong login for %s. Expecting %s, found %s.", + sqlUser.geUserName(), login, credential.login()); + return; + } + if (!credential.password().equals(password)) { + logger.atWarning().log("Wrong password for %s.", sqlUser.geUserName()); + } + logger.atWarning().log("Credentials in the kerying and the secret manager match."); + } catch (Throwable e) { + logger.atWarning().log(e.getMessage()); + } + } + /** Dagger qualifier for {@link JpaTransactionManager} used for App Engine application. */ @Qualifier @Documented diff --git a/core/src/main/java/google/registry/privileges/secretmanager/SecretManagerModule.java b/core/src/main/java/google/registry/privileges/secretmanager/SecretManagerModule.java index f829d50ff..7d48b9e3e 100644 --- a/core/src/main/java/google/registry/privileges/secretmanager/SecretManagerModule.java +++ b/core/src/main/java/google/registry/privileges/secretmanager/SecretManagerModule.java @@ -16,13 +16,13 @@ package google.registry.privileges.secretmanager; import com.google.cloud.secretmanager.v1.SecretManagerServiceClient; -import dagger.Component; +import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; import dagger.Module; import dagger.Provides; +import google.registry.config.CredentialModule.DefaultCredential; import google.registry.config.RegistryConfig.Config; -import google.registry.config.RegistryConfig.ConfigModule; +import google.registry.util.GoogleCredentialsBundle; import google.registry.util.Retrier; -import google.registry.util.UtilsModule; import java.io.IOException; import javax.inject.Singleton; @@ -32,20 +32,29 @@ public abstract class SecretManagerModule { @Provides @Singleton - static SecretManagerClient provideSecretManagerClient( - @Config("projectId") String project, Retrier retrier) { + static SecretManagerServiceSettings provideSecretManagerSetting( + @DefaultCredential GoogleCredentialsBundle credentialsBundle) { try { - SecretManagerServiceClient stub = SecretManagerServiceClient.create(); + return SecretManagerServiceSettings.newBuilder() + .setCredentialsProvider(() -> credentialsBundle.getGoogleCredentials()) + .build(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Provides + @Singleton + static SecretManagerClient provideSecretManagerClient( + SecretManagerServiceSettings serviceSettings, + @Config("projectId") String project, + Retrier retrier) { + try { + SecretManagerServiceClient stub = SecretManagerServiceClient.create(serviceSettings); Runtime.getRuntime().addShutdownHook(new Thread(stub::close)); return new SecretManagerClientImpl(project, stub, retrier); } catch (IOException e) { throw new RuntimeException(e); } } - - @Singleton - @Component(modules = {ConfigModule.class, SecretManagerModule.class, UtilsModule.class}) - public interface SecretManagerComponent { - SecretManagerClient secretManagerClient(); - } } diff --git a/core/src/main/java/google/registry/privileges/secretmanager/SqlUser.java b/core/src/main/java/google/registry/privileges/secretmanager/SqlUser.java index 394570824..7b168e21f 100644 --- a/core/src/main/java/google/registry/privileges/secretmanager/SqlUser.java +++ b/core/src/main/java/google/registry/privileges/secretmanager/SqlUser.java @@ -48,7 +48,12 @@ public abstract class SqlUser { /** Enumerates the {@link RobotUser RobotUsers} in the system. */ public enum RobotId { - NOMULUS; + NOMULUS, + /** + * Credential for RegistryTool. This is temporary, and will be removed when tool users are + * assigned their personal credentials. + */ + TOOL; } /** Information of a RobotUser for privilege management purposes. */ diff --git a/core/src/test/java/google/registry/privileges/secretmanager/SecretManagerClientTest.java b/core/src/test/java/google/registry/privileges/secretmanager/SecretManagerClientTest.java index a626a80e1..94ea98f0d 100644 --- a/core/src/test/java/google/registry/privileges/secretmanager/SecretManagerClientTest.java +++ b/core/src/test/java/google/registry/privileges/secretmanager/SecretManagerClientTest.java @@ -17,6 +17,8 @@ package google.registry.privileges.secretmanager; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings; import com.google.cloud.secretmanager.v1.SecretVersion.State; import google.registry.privileges.secretmanager.SecretManagerClient.NoSuchSecretResourceException; import google.registry.privileges.secretmanager.SecretManagerClient.SecretAlreadyExistsException; @@ -54,11 +56,14 @@ public class SecretManagerClientTest { private String secretId; @BeforeAll - static void beforeAll() { + static void beforeAll() throws IOException { String environmentName = System.getProperty("test.gcp_integration.env"); if (environmentName != null) { secretManagerClient = SecretManagerModule.provideSecretManagerClient( + SecretManagerServiceSettings.newBuilder() + .setCredentialsProvider(() -> GoogleCredentials.getApplicationDefault()) + .build(), String.format("domain-registry-%s", environmentName), new Retrier(new SystemSleeper(), 1)); isUnitTest = false; diff --git a/java_common.gradle b/java_common.gradle index 7aacbed35..23697ef93 100644 --- a/java_common.gradle +++ b/java_common.gradle @@ -74,10 +74,12 @@ test { useJUnitPlatform() } -// Sets up integration test with a registry environment. The target environment is -// passed by the 'test.gcp_integration.env' property. Test runner must have been -// authorized to access the corresponding GCP project, e.g., by running 'gcloud auth' -// or placing a credential file at a well known place. +// Sets up integration test with a registry environment. The target environment +// is passed by the 'test.gcp_integration.env' property. Test runner must have +// been authorized to access the corresponding GCP project, e.g., by running +// 'gcloud auth application-default login' or by downloading a credential file +// and assign the path to it to the GOOGLE_APPLICATION_CREDENTIALS environment +// variable. // // A typical use case is to run tests from desktop that accesses Cloud resources. See // core/src/test/java/google/registry/beam/initsql/BeamJpaModuleTest.java for an example.