1
0
mirror of https://github.com/google/nomulus synced 2026-05-24 16:51:49 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Weimin Yu
16758879f0 Allow rotation when updating registrar cert (#2012)
* Allow rotation when updating registrar cert

When updating a registrar's primary cert, add a flag to activate
rotation of previous primary cert to failover.

This functionality is part of the prober ssl cert renewal automation.
2023-04-27 14:42:11 -04:00
Lai Jiang
2021247ab4 Update README on how to manually push schema (#2009) 2023-04-26 16:32:15 -04:00
Lai Jiang
4fc7038690 Make a few minor changes to make the linter happy (#2010)
<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/2010)
<!-- Reviewable:end -->
2023-04-26 15:49:32 -04:00
Weimin Yu
9272e7fd14 Add a test of failover certificate (#2008)
Verifies that client can log in with correct failover certificate.
2023-04-26 15:47:47 -04:00
6 changed files with 134 additions and 9 deletions

View File

@@ -173,7 +173,7 @@ public class CloudDnsWriter extends BaseDnsWriter {
domainRecords.add(
new ResourceRecordSet()
.setName(absoluteDomainName)
.setTtl((int) (tld.getDnsNsTtl().orElse(defaultNsTtl).getStandardSeconds()))
.setTtl((int) tld.getDnsNsTtl().orElse(defaultNsTtl).getStandardSeconds())
.setType("NS")
.setKind("dns#resourceRecordSet")
.setRrdatas(ImmutableList.copyOf(nsRrData)));
@@ -279,11 +279,12 @@ public class CloudDnsWriter extends BaseDnsWriter {
}
/** Returns the glue records for in-bailiwick nameservers for the given domain+records. */
private Stream<String> filterGlueRecords(String domainName, Stream<ResourceRecordSet> records) {
private static Stream<String> filterGlueRecords(
String domainName, Stream<ResourceRecordSet> records) {
return records
.filter(record -> record.getType().equals("NS"))
.filter(record -> "NS".equals(record.getType()))
.flatMap(record -> record.getRrdatas().stream())
.filter(hostName -> hostName.endsWith("." + domainName) && !hostName.equals(domainName));
.filter(hostName -> hostName.endsWith('.' + domainName) && !hostName.equals(domainName));
}
/** Mutate the zone with the provided map of hostnames to desired DNS records. */
@@ -366,8 +367,8 @@ public class CloudDnsWriter extends BaseDnsWriter {
* <p>This call should be used in conjunction with {@link #getResourceRecordsForDomains} in a
* get-and-set retry loop.
*
* <p>See {@link "https://cloud.google.com/dns/troubleshooting"} for a list of errors produced by
* the Google Cloud DNS API.
* <p>See {@link "<a href="https://cloud.google.com/dns/troubleshooting">Troubleshoot Cloud
* DNS</a>"} for a list of errors produced by the Google Cloud DNS API.
*
* @throws ZoneStateException if the operation could not be completely successfully because the
* records to delete do not exist, already exist or have been modified with different
@@ -420,12 +421,12 @@ public class CloudDnsWriter extends BaseDnsWriter {
* @param hostName the fully qualified hostname
*/
private static String getAbsoluteHostName(String hostName) {
return hostName.endsWith(".") ? hostName : hostName + ".";
return hostName.endsWith(".") ? hostName : hostName + '.';
}
/** Zone state on Cloud DNS does not match the expected state. */
static class ZoneStateException extends RuntimeException {
public ZoneStateException(String reason) {
ZoneStateException(String reason) {
super("Zone state on Cloud DNS does not match the expected state: " + reason);
}
}

View File

@@ -17,6 +17,7 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Predicates.isNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
@@ -130,6 +131,13 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
validateWith = PathParameter.InputFile.class)
Path clientCertificateFilename;
@Parameter(
names = "--rotate_primary_cert",
description =
"Used together with --cert_file when updating an registrar. "
+ "If set, current cert is saved as failover.")
private Boolean rotatePrimaryCert = Boolean.FALSE;
@Nullable
@Parameter(
names = "--failover_cert_file",
@@ -341,8 +349,20 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
if (!asciiCert.equals("")) {
certificateChecker.validateCertificate(asciiCert);
}
if (rotatePrimaryCert) {
verify(
oldRegistrar != null,
"--rotate_primary_cert cannot be set by create_registrar command.");
verify(
oldRegistrar.getClientCertificate().isPresent(),
"Primary cert is absent. Rotation may remove a failover certificate still in use.");
builder.setFailoverClientCertificate(oldRegistrar.getClientCertificate().get(), now);
}
builder.setClientCertificate(asciiCert, now);
}
if (rotatePrimaryCert && clientCertificateFilename == null) {
throw new IllegalArgumentException("--rotate_primary_cert must be used with --cert_file.");
}
if (failoverClientCertificateFilename != null) {
String asciiCert =

View File

@@ -17,6 +17,7 @@ package google.registry.flows;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT;
import static google.registry.testing.CertificateSamples.SAMPLE_CERT_HASH;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -128,4 +129,18 @@ final class TlsCredentialsTest {
// This would throw a RegistrarCertificateNotConfiguredException if cert hashes wren't bypassed.
tls.validateCertificateHash(Registrar.loadByRegistrarId("TheRegistrar").get());
}
@Test
void test_validateCertificateHash_passWithFailOverCerticate() throws Exception {
TlsCredentials tls =
new TlsCredentials(
false, Optional.of(SAMPLE_CERT_HASH), Optional.of("192.168.1.1"), certificateChecker);
persistResource(
loadRegistrar("TheRegistrar")
.asBuilder()
.setClientCertificate(null, clock.nowUtc())
.setFailoverClientCertificate(SAMPLE_CERT, clock.nowUtc())
.build());
tls.validateCertificateHash(Registrar.loadByRegistrarId("TheRegistrar").get());
}
}

View File

@@ -32,6 +32,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.beust.jcommander.ParameterException;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
@@ -1785,4 +1786,31 @@ class CreateRegistrarCommandTest extends CommandTestCase<CreateRegistrarCommand>
.hasMessageThat()
.isEqualTo("Provided email lolcat is not a valid email address");
}
@Test
void testRotatePrimaryCertFlag_throwException() {
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
VerifyException thrown =
assertThrows(
VerifyException.class,
() ->
runCommandForced(
"--name=blobio",
"--password=some_password",
"--registrar_type=REAL",
"--iana_id=8",
"--cert_file=" + getCertFilename(SAMPLE_CERT3),
"--rotate_primary_cert",
"--passcode=01234",
"--icann_referral_email=foo@bar.test",
"--street=\"123 Fake St\"",
"--city Fakington",
"--state MA",
"--zip 00351",
"--cc US",
"clientz"));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("--rotate_primary_cert cannot be set by create_registrar command.");
}
}

View File

@@ -30,6 +30,7 @@ import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -248,6 +249,64 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
// converting the result from a hex string to non-padded base64 encoded string.
assertThat(registrar.getClientCertificate()).hasValue(SAMPLE_CERT3);
assertThat(registrar.getClientCertificateHash()).hasValue(SAMPLE_CERT3_HASH);
assertThat(registrar.getFailoverClientCertificate()).isEmpty();
assertThat(registrar.getFailoverClientCertificateHash()).isEmpty();
}
@Test
void testSuccess_rotatePrimaryCert() throws Exception {
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
persistResource(
loadRegistrar("NewRegistrar")
.asBuilder()
.setClientCertificate(SAMPLE_CERT3, fakeClock.nowUtc())
.setFailoverClientCertificate(null, fakeClock.nowUtc())
.build());
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getFailoverClientCertificate()).isEmpty();
assertThat(registrar.getFailoverClientCertificateHash()).isEmpty();
runCommand(
"--cert_file=" + getCertFilename(SAMPLE_CERT3),
"--rotate_primary_cert",
"--force",
"NewRegistrar");
registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getFailoverClientCertificate()).hasValue(SAMPLE_CERT3);
assertThat(registrar.getFailoverClientCertificateHash()).hasValue(SAMPLE_CERT3_HASH);
}
@Test
void test_rotatePrimaryCert_noPrimaryCert() throws Exception {
fakeClock.setTo(DateTime.parse("2020-11-01T00:00:00Z"));
Registrar registrar = loadRegistrar("NewRegistrar");
assertThat(registrar.getClientCertificate()).isEmpty();
assertThat(registrar.getClientCertificateHash()).isEmpty();
VerifyException thrown =
assertThrows(
VerifyException.class,
() ->
runCommand(
"--cert_file=" + getCertFilename(SAMPLE_CERT3),
"--rotate_primary_cert",
"--force",
"NewRegistrar"));
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Primary cert is absent. Rotation may remove a failover certificate still in use.");
}
@Test
public void test_rotatePrimaryCert_withoutNewCertFile_throws() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> runCommand("--rotate_primary_cert", "--force", "NewRegistrar"));
assertThat(thrown)
.hasMessageThat()
.isEqualTo("--rotate_primary_cert must be used with --cert_file.");
}
@Test

View File

@@ -243,6 +243,7 @@ From the root of the repository:
```
$ TARGET_ENV=[alpha|crash]
$ BUILDER_PROJECT=<project-where-the-builder-image-is-stored>
$ ./nom_build :db:schema
$ mkdir -p release/schema-deployer/flyway/jars release/schema-deployer/secrets
$ gcloud secrets versions access latest \
@@ -255,7 +256,8 @@ $ nomulus -e ${TARGET_ENV} \
--output release/schema-deployer/secrets/schema_deployer_credential.dec
$ cp db/build/libs/schema.jar release/schema-deployer/flyway/jars
$ cd release/schema-deployer
$ docker build -t schema_deployer .
$ docker build -t --build-arg PROJECT_ID=${BUILDER_PROJECT} \
--build-arg TAG_NAME=latest schema_deployer .
$ docker run -v `pwd`/secrets:/secrets \
-v `pwd`/flyway/jars:/flyway/jars -w `pwd` \
schema_deployer:latest \