1
0
mirror of https://github.com/google/nomulus synced 2026-02-17 02:19:13 +00:00

Compare commits

..

3 Commits

Author SHA1 Message Date
Juan Celhay
bca05f3982 Set heap size flags in nomulus start script (#2956)
* Set heap size flags in nomulus start script

* Add comment for flags
2026-02-13 21:16:57 +00:00
Ben McIlwain
763630bca5 Fix bug in updating registrar display name canonicalization (#2957)
We have a restriction in our system that registrar display names be unique (as
the display name is how registrars are queried through RDAP). And, the
uniqueness constraint is enforced on the canonicalized version of the display
name (with spaces and non alphanumeric characters removed). However, in the
check enforcing this uniqueness, we were incorrectly checking against the
existing saved entity of the same registrar, meaning that you couldn't update
the display name of a single registrar to a new value that canonicalized the
same (you would instead have to rename it to something else first that doesn't
canonicalize the same, and then afterwards to the new desired value).

That didn't make sense, so now we exclude the existing registrar entity from
consideration when checking if there are conflicts.
2026-02-13 19:20:34 +00:00
Weimin Yu
140b19e919 Simplify SQL credential store (#2955)
The current SQL credential store was designed to support automatic
password rotation without any disruption to the applications. For that
goal, the credentials are stored with one level of indirection, and the
secret name of the actual credential data may change automatically.

The automatic password rotation feature has been dropped. In the
meantime, the need arises that we use sidecar SQL proxy to get around
the Enterprise Plus edition's post-maintenance reconnection failures
by the socket factory library. This is hampered by the indirection in
storage.

This PR removes the indirection. This change is transparent to the rest
of the code base. We will manually populate the secret manager with the
new secrets in all environments after submissiion of this PR.
2026-02-12 20:01:08 +00:00
5 changed files with 36 additions and 88 deletions

View File

@@ -14,28 +14,12 @@
package google.registry.privileges.secretmanager;
import com.google.cloud.secretmanager.v1.SecretVersionName;
import google.registry.config.RegistryConfig.Config;
import google.registry.privileges.secretmanager.SecretManagerClient.NoSuchSecretResourceException;
import jakarta.inject.Inject;
import java.util.Optional;
/**
* Storage of SQL users' login credentials, backed by Cloud Secret Manager.
*
* <p>A user's credential is stored with one level of indirection using two secret IDs: Each version
* of the <em>credential data</em> is stored as follows: its secret ID is determined by {@link
* #getCredentialDataSecretId(SqlUser, String dbInstance)}, and the value of each version is a
* {@link SqlCredential}, serialized using {@link SqlCredential#toFormattedString}. The 'live'
* version of the credential is saved under the 'live pointer' secret explained below.
*
* <p>The pointer to the 'live' version of the credential data is stored as follows: its secret ID
* is determined by {@link #getLiveLabelSecretId(SqlUser, String dbInstance)}; and the value of each
* version is a {@link SecretVersionName} in String form, pointing to a version of the credential
* data. Only the 'latest' version of this secret should be used. It is guaranteed to be valid.
*
* <p>The indirection in credential storage makes it easy to handle failures in the credential
* change process.
*/
public class SqlCredentialStore {
private final SecretManagerClient csmClient;
@@ -49,61 +33,19 @@ public class SqlCredentialStore {
}
public SqlCredential getCredential(SqlUser user) {
SecretVersionName credentialName = getLiveCredentialSecretVersion(user);
return SqlCredential.fromFormattedString(
csmClient.getSecretData(
credentialName.getSecret(), Optional.of(credentialName.getSecretVersion())));
var secretId = getSecretIdForUserPassword(user);
var secretData = csmClient.getSecretData(secretId, Optional.empty());
return SqlCredential.fromFormattedString(secretData);
}
public void createOrUpdateCredential(SqlUser user, String password) {
SecretVersionName dataName = saveCredentialData(user, password);
saveLiveLabel(user, dataName);
var secretId = getSecretIdForUserPassword(user);
csmClient.createSecretIfAbsent(secretId);
csmClient.addSecretVersion(
secretId, SqlCredential.create(user.geUserName(), password).toFormattedString());
}
public void deleteCredential(SqlUser user) {
try {
csmClient.deleteSecret(getCredentialDataSecretId(user, dbInstance));
} catch (NoSuchSecretResourceException e) {
// ok
}
try {
csmClient.deleteSecret(getLiveLabelSecretId(user, dbInstance));
} catch (NoSuchSecretResourceException e) {
// ok.
}
}
private SecretVersionName saveCredentialData(SqlUser user, String password) {
String credentialDataSecretId = getCredentialDataSecretId(user, dbInstance);
csmClient.createSecretIfAbsent(credentialDataSecretId);
String credentialVersion =
csmClient.addSecretVersion(
credentialDataSecretId,
SqlCredential.create(createDatabaseLoginName(user), password).toFormattedString());
return SecretVersionName.of(csmClient.getProject(), credentialDataSecretId, credentialVersion);
}
private void saveLiveLabel(SqlUser user, SecretVersionName dataVersionName) {
String liveLabelSecretId = getLiveLabelSecretId(user, dbInstance);
csmClient.createSecretIfAbsent(liveLabelSecretId);
csmClient.addSecretVersion(liveLabelSecretId, dataVersionName.toString());
}
private SecretVersionName getLiveCredentialSecretVersion(SqlUser user) {
return SecretVersionName.parse(
csmClient.getSecretData(getLiveLabelSecretId(user, dbInstance), Optional.empty()));
}
private static String getLiveLabelSecretId(SqlUser user, String dbInstance) {
return String.format("sql-cred-live-label-%s-%s", user.geUserName(), dbInstance);
}
private static String getCredentialDataSecretId(SqlUser user, String dbInstance) {
return String.format("sql-cred-data-%s-%s", user.geUserName(), dbInstance);
}
// WIP: when b/170230882 is complete, login will be versioned.
private static String createDatabaseLoginName(SqlUser user) {
return user.geUserName();
private String getSecretIdForUserPassword(SqlUser user) {
return String.format("sql-password-for-%s-on-%s", user.geUserName(), this.dbInstance);
}
}

View File

@@ -400,7 +400,10 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
if (registrarName != null && !registrarName.equals(oldRegistrarName)) {
String normalizedName = normalizeRegistrarName(registrarName);
for (Registrar registrar : Registrar.loadAll()) {
if (registrar.getRegistrarName() != null) {
// Only check against other registrars (i.e. not the existing version of this one), and
// which also have a display name set.
if (!registrar.getRegistrarId().equals(clientId)
&& registrar.getRegistrarName() != null) {
checkArgument(
!normalizedName.equals(normalizeRegistrarName(registrar.getRegistrarName())),
"The registrar name %s normalizes identically to existing registrar name %s",

View File

@@ -16,7 +16,6 @@ package google.registry.privileges.secretmanager;
import static com.google.common.truth.Truth.assertThat;
import com.google.cloud.secretmanager.v1.SecretVersionName;
import google.registry.privileges.secretmanager.SqlUser.RobotId;
import google.registry.privileges.secretmanager.SqlUser.RobotUser;
import java.util.Optional;
@@ -32,17 +31,20 @@ public class SqlCredentialStoreTest {
@Test
void createSecret() {
credentialStore.createOrUpdateCredential(user, "password");
assertThat(client.secretExists("sql-cred-live-label-nomulus-db")).isTrue();
assertThat(
SecretVersionName.parse(
client.getSecretData("sql-cred-live-label-nomulus-db", Optional.empty()))
.getSecret())
.isEqualTo("sql-cred-data-nomulus-db");
assertThat(client.secretExists("sql-cred-data-nomulus-db")).isTrue();
assertThat(client.getSecretData("sql-cred-data-nomulus-db", Optional.empty()))
assertThat(client.secretExists("sql-password-for-nomulus-on-db")).isTrue();
assertThat(client.getSecretData("sql-password-for-nomulus-on-db", Optional.empty()))
.isEqualTo("nomulus password");
}
@Test
void updateSecret() {
credentialStore.createOrUpdateCredential(user, "password");
credentialStore.createOrUpdateCredential(user, "new-password");
assertThat(client.secretExists("sql-password-for-nomulus-on-db")).isTrue();
assertThat(client.getSecretData("sql-password-for-nomulus-on-db", Optional.empty()))
.isEqualTo("nomulus new-password");
}
@Test
void getCredential() {
credentialStore.createOrUpdateCredential(user, "password");
@@ -50,14 +52,4 @@ public class SqlCredentialStoreTest {
assertThat(credential.login()).isEqualTo("nomulus");
assertThat(credential.password()).isEqualTo("password");
}
@Test
void deleteCredential() {
credentialStore.createOrUpdateCredential(user, "password");
assertThat(client.secretExists("sql-cred-live-label-nomulus-db")).isTrue();
assertThat(client.secretExists("sql-cred-data-nomulus-db")).isTrue();
credentialStore.deleteCredential(user);
assertThat(client.secretExists("sql-cred-live-label-nomulus-db")).isFalse();
assertThat(client.secretExists("sql-cred-data-nomulus-db")).isFalse();
}
}

View File

@@ -968,6 +968,14 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
() -> runCommand("--name tHeRe GiStRaR", "--force", "NewRegistrar"));
}
@Test
void testSuccess_updateSameRegistrar_registrarNameSimilarToExisting() throws Exception {
// Note that "The -- registrar" normalizes identically to "The Registrar", which is created by
// JpaTransactionManagerExtension.
runCommand("--name The -- registrar", "--force", "TheRegistrar");
assertThat(loadRegistrar("TheRegistrar").getRegistrarName()).isEqualTo("The -- registrar");
}
@Test
void testSuccess_poNumberNotSpecified_doesntWipeOutExisting() throws Exception {
Registrar registrar =

View File

@@ -22,12 +22,15 @@ find . -maxdepth 1 -type d -name "console-*" -exec rm -rf {} +
cd /jetty-base
echo "Running ${env}"
PROFILER_ARGS=""
# # Use the CONTAINER_NAME variable from Kubernetes YAML to set Cloud profiler args, enable it only in frontend and console.
# Use the CONTAINER_NAME variable from Kubernetes YAML to set Cloud profiler args, enable it only in frontend and console.
case "${CONTAINER_NAME}" in
"frontend"|"console")
PROFILER_ARGS="-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=${CONTAINER_NAME},-cprof_enable_heap_sampling=true"
esac
java $PROFILER_ARGS \
# Allocate bigger than default fraction of available memory to the application, as it's running in a (single-purposed) container.
-XX:InitialRAMPercentage=50.0 \
-XX:MaxRAMPercentage=50.0 \
-Dgoogle.registry.environment=${env} \
-Djava.util.logging.config.file=/logging.properties \
-jar /usr/local/jetty/start.jar