Add certificate checks for create and update registrar commands (#837)

* Add certificatechecks for create and update registrar commands

* Add CertificateCheckerModule

* Remove commented out code

* Still tring to get dependency injection to work

* Get this actually working

* Add tests for multiple violations

* Small formatting fixes

* Rename configs and fix collectors

* Add checks for failover client certificate

* Fix formatting
This commit is contained in:
sarahcaseybot
2020-10-22 11:43:22 -04:00
committed by GitHub
parent 0b73e9032c
commit 93d922af6f
11 changed files with 463 additions and 19 deletions
@@ -0,0 +1,44 @@
// Copyright 2020 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.config;
import com.google.common.collect.ImmutableSortedMap;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
import google.registry.util.CertificateChecker;
import google.registry.util.Clock;
import javax.inject.Singleton;
import org.joda.time.DateTime;
/** Dagger module that provides the {@link CertificateChecker} used in the application. */
// TODO(sarahbot@): Move this module to a better location. Possibly flows/. If we decide to move
// CertificateChecker.java to core/ delete this file and inject the CertificateChecker constructor
// instead.
@Module
public abstract class CertificateCheckerModule {
@Provides
@Singleton
static CertificateChecker provideCertificateChecker(
@Config("maxValidityDaysSchedule") ImmutableSortedMap<DateTime, Integer> validityDaysMap,
@Config("expirationWarningDays") int daysToExpiration,
@Config("minimumRsaKeyLength") int minimumRsaKeyLength,
Clock clock) {
return new CertificateChecker(validityDaysMap, daysToExpiration, minimumRsaKeyLength, clock);
}
private CertificateCheckerModule() {}
}
@@ -16,9 +16,12 @@ package google.registry.config;
import static com.google.common.base.Suppliers.memoize;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
import static google.registry.config.ConfigUtils.makeUrl;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.Comparator.naturalOrder;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
@@ -27,6 +30,7 @@ import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import dagger.Module;
import dagger.Provides;
import google.registry.util.TaskQueueUtils;
@@ -46,6 +50,7 @@ import javax.inject.Qualifier;
import javax.inject.Singleton;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.Duration;
@@ -1345,6 +1350,33 @@ public final class RegistryConfig {
public static String provideRdapTosStaticUrl(RegistryConfigSettings config) {
return config.registryPolicy.rdapTosStaticUrl;
}
@Provides
@Config("maxValidityDaysSchedule")
public static ImmutableSortedMap<DateTime, Integer> provideValidityDaysMap(
RegistryConfigSettings config) {
return config.sslCertificateValidation.maxValidityDaysSchedule.entrySet().stream()
.collect(
toImmutableSortedMap(
naturalOrder(),
e ->
e.getKey().equals("START_OF_TIME")
? START_OF_TIME
: DateTime.parse(e.getKey()),
e -> e.getValue()));
}
@Provides
@Config("expirationWarningDays")
public static int provideDaysToExpiration(RegistryConfigSettings config) {
return config.sslCertificateValidation.expirationWarningDays;
}
@Provides
@Config("minimumRsaKeyLength")
public static int provideMinimumRsaKeyLength(RegistryConfigSettings config) {
return config.sslCertificateValidation.minimumRsaKeyLength;
}
}
/** Returns the App Engine project ID, which is based off the environment name. */
@@ -15,6 +15,7 @@
package google.registry.config;
import java.util.List;
import java.util.Map;
/** The POJO that YAML config files are deserialized into. */
public class RegistryConfigSettings {
@@ -38,6 +39,7 @@ public class RegistryConfigSettings {
public Beam beam;
public Keyring keyring;
public RegistryTool registryTool;
public SslCertificateValidation sslCertificateValidation;
/** Configuration options that apply to the entire App Engine project. */
public static class AppEngine {
@@ -218,4 +220,11 @@ public class RegistryConfigSettings {
public String clientSecret;
public String username;
}
/** Configuration for the certificate checker. */
public static class SslCertificateValidation {
public Map<String, Integer> maxValidityDaysSchedule;
public int expirationWarningDays;
public int minimumRsaKeyLength;
}
}
@@ -446,3 +446,17 @@ registryTool:
# OAuth client secret used by the tool.
clientSecret: YOUR_CLIENT_SECRET
username: toolusername
# Configuration options for checking SSL certificates.
sslCertificateValidation:
# A map specifying the maximum amount of days the certificate can be valid.
# The entry key is the date closest before the date the certificate was issued
# and the entry value is the applicable maximum validity days for that certificate.
maxValidityDaysSchedule:
"START_OF_TIME": 825
"2020-09-01T00:00:00Z": 398
# The number of days before a certificate expires that indicates the
# certificate is nearing expiration and warnings should be sent.
expirationWarningDays: 30
# The minimum number of bits an RSA key must contain
minimumRsaKeyLength: 2048
@@ -22,6 +22,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.RegistrarUtils.normalizeRegistrarName;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.Parameter;
@@ -38,9 +39,15 @@ import google.registry.tools.params.OptionalLongParameter;
import google.registry.tools.params.OptionalPhoneNumberParameter;
import google.registry.tools.params.OptionalStringParameter;
import google.registry.tools.params.PathParameter;
import google.registry.util.CertificateChecker;
import google.registry.util.CertificateChecker.CertificateViolation;
import google.registry.util.CidrAddressBlock;
import java.io.ByteArrayInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
@@ -48,7 +55,9 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.joda.money.CurrencyUnit;
import org.joda.time.DateTime;
@@ -57,9 +66,9 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Parameter(
description = "Client identifier of the registrar account",
required = true)
@Inject CertificateChecker certificateChecker;
@Parameter(description = "Client identifier of the registrar account", required = true)
List<String> mainParameters;
@Parameter(
@@ -356,11 +365,21 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
}
if (clientCertificateFilename != null) {
String asciiCert = new String(Files.readAllBytes(clientCertificateFilename), US_ASCII);
// An empty certificate file is allowed in order to provide a functionality for removing an
// existing certificate without providing a replacement. An uploaded empty certificate file
// will prevent the registrar from being able to establish EPP connections.
if (!asciiCert.equals("")) {
verifyCertificate(asciiCert);
}
builder.setClientCertificate(asciiCert, now);
}
if (failoverClientCertificateFilename != null) {
String asciiCert =
new String(Files.readAllBytes(failoverClientCertificateFilename), US_ASCII);
if (!asciiCert.equals("")) {
verifyCertificate(asciiCert);
}
builder.setFailoverClientCertificate(asciiCert, now);
}
if (!isNullOrEmpty(clientCertificateHash)) {
@@ -463,6 +482,22 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
}
}
private void verifyCertificate(String certificateString) throws CertificateException {
X509Certificate certificate =
(X509Certificate)
CertificateFactory.getInstance("X509")
.generateCertificate(new ByteArrayInputStream(certificateString.getBytes(UTF_8)));
ImmutableSet<CertificateViolation> violations =
certificateChecker.checkCertificate(certificate);
if (!violations.isEmpty()) {
String displayMessages =
violations.stream()
.map(violation -> violation.getDisplayMessage(certificateChecker))
.collect(Collectors.joining("\n"));
throw new CertificateException(displayMessages);
}
}
@Override
protected String execute() throws Exception {
// Save registrar to Datastore and output its response
@@ -20,6 +20,7 @@ import dagger.Lazy;
import google.registry.batch.BatchModule;
import google.registry.beam.initsql.BeamJpaModule;
import google.registry.bigquery.BigqueryModule;
import google.registry.config.CertificateCheckerModule;
import google.registry.config.CredentialModule.LocalCredentialJson;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule;
@@ -60,6 +61,7 @@ import javax.inject.Singleton;
BatchModule.class,
BeamJpaModule.class,
BigqueryModule.class,
CertificateCheckerModule.class,
ConfigModule.class,
CloudDnsWriterModule.class,
DatastoreAdminModule.class,
@@ -83,42 +85,83 @@ import javax.inject.Singleton;
})
interface RegistryToolComponent {
void inject(AckPollMessagesCommand command);
void inject(CheckDomainClaimsCommand command);
void inject(CheckDomainCommand command);
void inject(CountDomainsCommand command);
void inject(CreateAnchorTenantCommand command);
void inject(CreateCdnsTld command);
void inject(CreateContactCommand command);
void inject(CreateDomainCommand command);
void inject(CreateRegistrarCommand command);
void inject(CreateTldCommand command);
void inject(DeployInvoicingPipelineCommand command);
void inject(DeploySpec11PipelineCommand command);
void inject(EncryptEscrowDepositCommand command);
void inject(GenerateAllocationTokensCommand command);
void inject(GenerateDnsReportCommand command);
void inject(GenerateEscrowDepositCommand command);
void inject(GetKeyringSecretCommand command);
void inject(GetOperationStatusCommand command);
void inject(GhostrydeCommand command);
void inject(ImportDatastoreCommand command);
void inject(ListCursorsCommand command);
void inject(ListDatastoreOperationsCommand command);
void inject(LoadSnapshotCommand command);
void inject(LockDomainCommand command);
void inject(LoginCommand command);
void inject(LogoutCommand command);
void inject(PendingEscrowCommand command);
void inject(RenewDomainCommand command);
void inject(SendEscrowReportToIcannCommand command);
void inject(SetNumInstancesCommand command);
void inject(SetupOteCommand command);
void inject(UnlockDomainCommand command);
void inject(UnrenewDomainCommand command);
void inject(UpdateCursorsCommand command);
void inject(UpdateDomainCommand command);
void inject(UpdateKmsKeyringCommand command);
void inject(UpdateRegistrarCommand command);
void inject(UpdateTldCommand command);
void inject(ValidateEscrowDepositCommand command);
void inject(WhoisQueryCommand command);
AppEngineConnection appEngineConnection();