1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Lai Jiang 05d56fe1a2 Remove SSL initializer from the prober (#378)
The prober now uses the common SSL initializer in the networking
subproject.

Also changed both initializers to take an ImmutableList of certificates
other than an array of those, for better immutability.

I have no idea where these lockfile changes are coming from. They seem
to be pure noise as far as code review is concerned.
2019-11-22 17:46:06 -05:00
sarahcaseybot e318f47fc6 Add a cursor for tracking monthly uploads of ICANN report (#343)
* Add a cursor for tracking monthly uploads of the transaction report to ICANN

* Add cursors to track activity, transaction, and manifest report uploads.

* Address comments

* Add @Nullable annotation to manifestCursor

* Add lock and batch load cursors.

* Add string formatting, autovalue CursorInfo object, and handling for null cursors

* Add some helper functions for loadCursors and restructure to require less round trips to the database

* Switch new cursors to be created with cursorTime at first of next month
2019-11-22 17:40:31 -05:00
Lai Jiang cc5f62587e Make dev project configurable (#371)
* Make dev project configurable

We should not hardcode our dev project in the public config file.

* Remove the use of .ext when using external properties

They are only needed when defining properties.
2019-11-22 16:20:07 -05:00
Lai Jiang 02846bcbdd No-op: Use nicer HCL2 syntax. (#384)
Generated with perl -pi -e 's/\"\$\{([a-zA-Z0-9._-]*)\}\"/$1/g' $(find ./ -name '*.tf')

Copied from cl/282012376.
2019-11-22 16:08:56 -05:00
Ben McIlwain c920f709ef Update the Registries cache to leverage/populate the Registry cache (#382)
* Update the Registries cache to leverage/populate the Registry cache

This is accomplished by also providing a loadAll() method on the Registry cache
that can be used to load an entire batch of Registry objects at once.

This improves efficiency, because now, any operation on Registries that loads
all the Registry entities (getTlds(), getTldsOfType(), and getTldEntities()), is
plumbed through the Registry cache, therefore loading it from that cache if it
exists and only hitting the DB if not. If not, this populates the Registry cache
upon loading, so that subsequent calls to Registry.get() will now hit the cache
instead of the DB.

To give a concrete example, the following code:

    for (String tld : Registries.getTlds()) {
      // ...
      doSomethingWith(Registry.get(tld));
      // ...
    }

is now much more efficient, because the initial call to Registries.getTlds()
populates all the entities in cache, and the subsequent individual calls to
Registry.get(tld) now retrieve them from the cache. Prior to this change,
Registries.getTlds() did not populate the Registry cache, and each subsequent
Registry.get(tld) had the potential to trigger an individual round-trip to the
DB, which is obviously bad for performance.
2019-11-22 14:47:09 -05:00
47 changed files with 865 additions and 971 deletions
@@ -19,6 +19,7 @@ import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Maps.filterValues;
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
@@ -31,10 +32,12 @@ import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.model.registry.Registry.TldType;
import java.util.Map;
import java.util.Optional;
/** Utilities for finding and listing {@link Registry} entities. */
@@ -54,16 +57,21 @@ public final class Registries {
private static Supplier<ImmutableMap<String, TldType>> createFreshCache() {
return memoizeWithShortExpiration(
() ->
tm()
.doTransactionless(
tm().doTransactionless(
() -> {
ImmutableMap.Builder<String, TldType> builder =
new ImmutableMap.Builder<>();
for (Registry registry :
ofy().load().type(Registry.class).ancestor(getCrossTldKey())) {
builder.put(registry.getTldStr(), registry.getTldType());
}
return builder.build();
ImmutableSet<String> tlds =
ofy()
.load()
.type(Registry.class)
.ancestor(getCrossTldKey())
.keys()
.list()
.stream()
.map(Key::getName)
.collect(toImmutableSet());
return Registry.getAll(tlds).stream()
.map(e -> Maps.immutableEntry(e.getTldStr(), e.getTldType()))
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
}));
}
@@ -83,11 +91,7 @@ public final class Registries {
/** Returns the Registry entities themselves of the given type loaded fresh from Datastore. */
public static ImmutableSet<Registry> getTldEntitiesOfType(TldType type) {
ImmutableSet<Key<Registry>> keys =
filterValues(cache.get(), equalTo(type)).keySet().stream()
.map(tld -> Key.create(getCrossTldKey(), Registry.class, tld))
.collect(toImmutableSet());
return ImmutableSet.copyOf(tm().doTransactionless(() -> ofy().load().keys(keys).values()));
return Registry.getAll(filterValues(cache.get(), equalTo(type)).keySet());
}
/** Pass-through check that the specified TLD exists, otherwise throw an IAE. */
@@ -18,6 +18,9 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Maps.toMap;
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
@@ -30,9 +33,11 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.joda.money.CurrencyUnit.USD;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
@@ -58,8 +63,10 @@ import google.registry.model.domain.fee.Fee;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.ReservedList;
import google.registry.util.Idn;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -201,6 +208,25 @@ public class Registry extends ImmutableObject implements Buildable {
return registry;
}
/** Returns the registry entities for the given TLD strings, throwing if any don't exist. */
static ImmutableSet<Registry> getAll(Set<String> tlds) {
try {
ImmutableMap<String, Optional<Registry>> registries = CACHE.getAll(tlds);
ImmutableSet<String> missingRegistries =
registries.entrySet().stream()
.filter(e -> !e.getValue().isPresent())
.map(Map.Entry::getKey)
.collect(toImmutableSet());
if (missingRegistries.isEmpty()) {
return registries.values().stream().map(Optional::get).collect(toImmutableSet());
} else {
throw new RegistryNotFoundException(missingRegistries);
}
} catch (ExecutionException e) {
throw new RuntimeException("Unexpected error retrieving TLDs " + tlds, e);
}
}
/**
* Invalidates the cache entry.
*
@@ -220,15 +246,30 @@ public class Registry extends ImmutableObject implements Buildable {
new CacheLoader<String, Optional<Registry>>() {
@Override
public Optional<Registry> load(final String tld) {
// Enter a transactionless context briefly; we don't want to enroll every TLD in a
// transaction that might be wrapping this call.
// Enter a transaction-less context briefly; we don't want to enroll every TLD in
// a transaction that might be wrapping this call.
return Optional.ofNullable(
tm()
.doTransactionless(
() -> ofy()
.load()
.key(Key.create(getCrossTldKey(), Registry.class, tld))
.now()));
tm().doTransactionless(
() ->
ofy()
.load()
.key(Key.create(getCrossTldKey(), Registry.class, tld))
.now()));
}
@Override
public Map<String, Optional<Registry>> loadAll(Iterable<? extends String> tlds) {
ImmutableMap<String, Key<Registry>> keysMap =
toMap(
ImmutableSet.copyOf(tlds),
tld -> Key.create(getCrossTldKey(), Registry.class, tld));
Map<Key<Registry>, Registry> entities =
tm().doTransactionless(() -> ofy().load().keys(keysMap.values()));
return keysMap.entrySet().stream()
.collect(
toImmutableMap(
Map.Entry::getKey,
e -> Optional.ofNullable(entities.getOrDefault(e.getValue(), null))));
}
});
@@ -883,10 +924,14 @@ public class Registry extends ImmutableObject implements Buildable {
}
}
/** Exception to throw when no Registry is found for a given tld. */
/** Exception to throw when no Registry entity is found for given TLD string(s). */
public static class RegistryNotFoundException extends RuntimeException {
RegistryNotFoundException(ImmutableSet<String> tlds) {
super("No registry object(s) found for " + Joiner.on(", ").join(tlds));
}
RegistryNotFoundException(String tld) {
super("No registry object found for " + tld);
this(ImmutableSet.of(tld));
}
}
}
@@ -15,34 +15,51 @@
package google.registry.reporting.icann;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.transaction.TransactionManagerFactory.tm;
import static google.registry.reporting.icann.IcannReportingModule.MANIFEST_FILE_NAME;
import static google.registry.reporting.icann.IcannReportingModule.PARAM_SUBDIR;
import static google.registry.request.Action.Method.POST;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.ByteStreams;
import com.googlecode.objectify.Key;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.model.common.Cursor;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.registry.Registries;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldType;
import google.registry.request.Action;
import google.registry.request.HttpException.ServiceUnavailableException;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.request.lock.LockHandler;
import google.registry.util.Clock;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* Action that uploads the monthly activity/transactions reports from GCS to ICANN via an HTTP PUT.
@@ -81,44 +98,175 @@ public final class IcannReportingUploadAction implements Runnable {
@Inject @Config("gSuiteOutgoingEmailAddress") InternetAddress sender;
@Inject @Config("alertRecipientEmailAddress") InternetAddress recipient;
@Inject SendEmailService emailService;
@Inject Clock clock;
@Inject LockHandler lockHandler;
@Inject
IcannReportingUploadAction() {}
@Override
public void run() {
String reportBucketname = String.format("%s/%s", reportingBucket, subdir);
ImmutableList<String> manifestedFiles = getManifestedFiles(reportBucketname);
ImmutableMap.Builder<String, Boolean> reportSummaryBuilder = new ImmutableMap.Builder<>();
// Report on all manifested files
for (String reportFilename : manifestedFiles) {
logger.atInfo().log(
"Reading ICANN report %s from bucket %s", reportFilename, reportBucketname);
final GcsFilename gcsFilename = new GcsFilename(reportBucketname, reportFilename);
verifyFileExists(gcsFilename);
boolean success = false;
try {
success =
retrier.callWithRetry(
() -> {
final byte[] payload = readBytesFromGcs(gcsFilename);
return icannReporter.send(payload, reportFilename);
},
IcannReportingUploadAction::isUploadFailureRetryable);
} catch (RuntimeException e) {
logger.atWarning().withCause(e).log("Upload to %s failed.", gcsFilename);
}
reportSummaryBuilder.put(reportFilename, success);
Callable<Void> lockRunner =
() -> {
ImmutableMap.Builder<String, Boolean> reportSummaryBuilder = new ImmutableMap.Builder<>();
ImmutableMap<Cursor, CursorInfo> cursors = loadCursors();
// If cursor time is before now, upload the corresponding report
cursors.entrySet().stream()
.filter(entry -> getCursorTimeOrStartOfTime(entry.getKey()).isBefore(clock.nowUtc()))
.forEach(
entry -> {
DateTime cursorTime = getCursorTimeOrStartOfTime(entry.getKey());
uploadReport(
cursorTime,
entry.getValue().getType(),
entry.getValue().getTld(),
reportSummaryBuilder);
});
// Send email of which reports were uploaded
emailUploadResults(reportSummaryBuilder.build());
response.setStatus(SC_OK);
response.setContentType(PLAIN_TEXT_UTF_8);
return null;
};
String lockname = "IcannReportingUploadAction";
if (!lockHandler.executeWithLocks(lockRunner, null, Duration.standardHours(2), lockname)) {
throw new ServiceUnavailableException("Lock for IcannReportingUploadAction already in use");
}
emailUploadResults(reportSummaryBuilder.build());
response.setStatus(SC_OK);
response.setContentType(PLAIN_TEXT_UTF_8);
response.setPayload(
String.format("OK, attempted uploading %d reports", manifestedFiles.size()));
}
/** Uploads the report and rolls forward the cursor for that report. */
private void uploadReport(
DateTime cursorTime,
CursorType cursorType,
String tldStr,
ImmutableMap.Builder<String, Boolean> reportSummaryBuilder) {
String reportBucketname = String.format("%s/%s", reportingBucket, subdir);
String filename = getFileName(cursorType, cursorTime, tldStr);
final GcsFilename gcsFilename = new GcsFilename(reportBucketname, filename);
logger.atInfo().log("Reading ICANN report %s from bucket %s", filename, reportBucketname);
// Check that the report exists
try {
verifyFileExists(gcsFilename);
} catch (IllegalArgumentException e) {
String logMessage =
String.format(
"Could not upload %s report for %s because file %s did not exist.",
cursorType, tldStr, filename);
if (clock.nowUtc().dayOfMonth().get() == 1) {
logger.atInfo().withCause(e).log(logMessage + " This report may not have been staged yet.");
} else {
logger.atSevere().withCause(e).log(logMessage);
}
reportSummaryBuilder.put(filename, false);
return;
}
// Upload the report
boolean success = false;
try {
success =
retrier.callWithRetry(
() -> {
final byte[] payload = readBytesFromGcs(gcsFilename);
return icannReporter.send(payload, filename);
},
IcannReportingUploadAction::isUploadFailureRetryable);
} catch (RuntimeException e) {
logger.atWarning().withCause(e).log("Upload to %s failed", gcsFilename);
}
reportSummaryBuilder.put(filename, success);
// Set cursor to first day of next month if the upload succeeded
if (success) {
Cursor newCursor;
if (cursorType.equals(CursorType.ICANN_UPLOAD_MANIFEST)) {
newCursor =
Cursor.createGlobal(
cursorType, cursorTime.withTimeAtStartOfDay().withDayOfMonth(1).plusMonths(1));
} else {
newCursor =
Cursor.create(
cursorType,
cursorTime.withTimeAtStartOfDay().withDayOfMonth(1).plusMonths(1),
Registry.get(tldStr));
}
tm().transact(() -> ofy().save().entity(newCursor));
}
}
private String getFileName(CursorType cursorType, DateTime cursorTime, String tld) {
if (cursorType.equals(CursorType.ICANN_UPLOAD_MANIFEST)) {
return MANIFEST_FILE_NAME;
}
return String.format(
"%s%s%d%02d.csv",
tld,
(cursorType.equals(CursorType.ICANN_UPLOAD_ACTIVITY) ? "-activity-" : "-transactions-"),
cursorTime.year().get(),
cursorTime.monthOfYear().get());
}
/** Returns a map of each cursor to the CursorType and tld. */
private ImmutableMap<Cursor, CursorInfo> loadCursors() {
ImmutableSet<Registry> registries = Registries.getTldEntitiesOfType(TldType.REAL);
Map<Key<Cursor>, Registry> activityKeyMap =
loadKeyMap(registries, CursorType.ICANN_UPLOAD_ACTIVITY);
Map<Key<Cursor>, Registry> transactionKeyMap =
loadKeyMap(registries, CursorType.ICANN_UPLOAD_TX);
ImmutableSet.Builder<Key<Cursor>> keys = new ImmutableSet.Builder<>();
keys.addAll(activityKeyMap.keySet());
keys.addAll(transactionKeyMap.keySet());
keys.add(Cursor.createGlobalKey(CursorType.ICANN_UPLOAD_MANIFEST));
Map<Key<Cursor>, Cursor> cursorMap = ofy().load().keys(keys.build());
ImmutableMap.Builder<Cursor, CursorInfo> cursors = new ImmutableMap.Builder<>();
defaultNullCursorsToNextMonthAndAddToMap(
activityKeyMap, CursorType.ICANN_UPLOAD_ACTIVITY, cursorMap, cursors);
defaultNullCursorsToNextMonthAndAddToMap(
transactionKeyMap, CursorType.ICANN_UPLOAD_TX, cursorMap, cursors);
Cursor manifestCursor =
cursorMap.getOrDefault(
Cursor.createGlobalKey(CursorType.ICANN_UPLOAD_MANIFEST),
Cursor.createGlobal(CursorType.ICANN_UPLOAD_MANIFEST, clock.nowUtc().minusDays(1)));
cursors.put(manifestCursor, CursorInfo.create(CursorType.ICANN_UPLOAD_MANIFEST, null));
return cursors.build();
}
private Map<Key<Cursor>, Registry> loadKeyMap(
ImmutableSet<Registry> registries, CursorType type) {
return registries.stream().collect(toImmutableMap(r -> Cursor.createKey(type, r), r -> r));
}
/**
* Populate the cursors map with the Cursor and CursorInfo for each key in the keyMap. If the key
* from the keyMap does not have an existing cursor, create a new cursor with a default cursorTime
* of the first of next month.
*/
private void defaultNullCursorsToNextMonthAndAddToMap(
Map<Key<Cursor>, Registry> keyMap,
CursorType type,
Map<Key<Cursor>, Cursor> cursorMap,
ImmutableMap.Builder<Cursor, CursorInfo> cursors) {
keyMap.forEach(
(key, registry) -> {
// Cursor time is defaulted to the first of next month since a new tld will not yet have a
// report staged for upload.
Cursor cursor =
cursorMap.getOrDefault(
key, Cursor.create(type, clock.nowUtc().minusDays(1), registry));
cursors.put(cursor, CursorInfo.create(type, registry.getTldStr()));
});
}
/** Don't retry when reports are already uploaded or can't be uploaded. */
private static final String ICANN_UPLOAD_PERMANENT_ERROR_MESSAGE =
"A report for that month already exists, the cut-off date already passed.";
"A report for that month already exists, the cut-off date already passed";
/** Don't retry when the IP address isn't whitelisted, as retries go through the same IP. */
private static final Pattern ICANN_UPLOAD_WHITELIST_ERROR =
@@ -146,18 +294,6 @@ public final class IcannReportingUploadAction implements Runnable {
emailService.sendEmail(EmailMessage.create(subject, body, recipient, sender));
}
private ImmutableList<String> getManifestedFiles(String reportBucketname) {
GcsFilename manifestFilename = new GcsFilename(reportBucketname, MANIFEST_FILE_NAME);
verifyFileExists(manifestFilename);
return retrier.callWithRetry(
() ->
ImmutableList.copyOf(
Splitter.on('\n')
.omitEmptyStrings()
.split(new String(readBytesFromGcs(manifestFilename), UTF_8))),
IOException.class);
}
private byte[] readBytesFromGcs(GcsFilename reportFilename) throws IOException {
try (InputStream gcsInput = gcsUtils.openInputStream(reportFilename)) {
return ByteStreams.toByteArray(gcsInput);
@@ -171,4 +307,16 @@ public final class IcannReportingUploadAction implements Runnable {
gcsFilename.getObjectName(),
gcsFilename.getBucketName());
}
@AutoValue
abstract static class CursorInfo {
static CursorInfo create(CursorType type, @Nullable String tld) {
return new AutoValue_IcannReportingUploadAction_CursorInfo(type, tld);
}
public abstract CursorType getType();
@Nullable
abstract String getTld();
}
}
@@ -48,10 +48,7 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ExportReservedTermsActionTest {
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder()
.withDatastore()
.build();
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
private final DriveConnection driveConnection = mock(DriveConnection.class);
private final Response response = mock(Response.class);
@@ -133,6 +130,6 @@ public class ExportReservedTermsActionTest {
assertThat(thrown)
.hasCauseThat()
.hasMessageThat()
.isEqualTo("No registry object found for fakeTld");
.isEqualTo("No registry object(s) found for fakeTld");
}
}
@@ -51,6 +51,7 @@ import org.junit.Test;
/** Unit tests for {@link Registry}. */
public class RegistryTest extends EntityTestCase {
Registry registry;
@Before
@@ -146,6 +147,19 @@ public class RegistryTest extends EntityTestCase {
assertThat(registry.getReservedLists()).isEmpty();
}
@Test
public void testGetAll() {
createTld("foo");
assertThat(Registry.getAll(ImmutableSet.of("foo", "tld")))
.containsExactlyElementsIn(
ofy()
.load()
.keys(
Key.create(getCrossTldKey(), Registry.class, "foo"),
Key.create(getCrossTldKey(), Registry.class, "tld"))
.values());
}
@Test
public void testSetReservedLists() {
ReservedList rl5 = persistReservedList(
@@ -15,8 +15,12 @@
package google.registry.reporting.icann;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.testing.DatastoreHelper.createTlds;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.GcsTestingUtils.writeGcsFile;
import static google.registry.testing.JUnitBackports.assertThrows;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -27,16 +31,25 @@ import static org.mockito.Mockito.when;
import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.appengine.tools.cloudstorage.GcsService;
import com.google.appengine.tools.cloudstorage.GcsServiceFactory;
import com.google.common.testing.TestLogHandler;
import google.registry.gcs.GcsUtils;
import google.registry.model.common.Cursor;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.registry.Registry;
import google.registry.request.HttpException.ServiceUnavailableException;
import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeLockHandler;
import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.util.EmailMessage;
import google.registry.util.Retrier;
import google.registry.util.SendEmailService;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.internet.InternetAddress;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -52,105 +65,175 @@ public class IcannReportingUploadActionTest {
private static final byte[] PAYLOAD_SUCCESS = "test,csv\n13,37".getBytes(UTF_8);
private static final byte[] PAYLOAD_FAIL = "ahah,csv\n12,34".getBytes(UTF_8);
private static final byte[] MANIFEST_PAYLOAD =
"test-transactions-201706.csv\na-activity-201706.csv\n".getBytes(UTF_8);
"tld-transactions-200606.csv\ntld-activity-200606.csv\nfoo-transactions-200606.csv\nfoo-activity-200606.csv\n"
.getBytes(UTF_8);
private final IcannHttpReporter mockReporter = mock(IcannHttpReporter.class);
private final SendEmailService emailService = mock(SendEmailService.class);
private final FakeResponse response = new FakeResponse();
private final GcsService gcsService = GcsServiceFactory.createGcsService();
private final TestLogHandler logHandler = new TestLogHandler();
private final Logger loggerToIntercept =
Logger.getLogger(IcannReportingUploadAction.class.getCanonicalName());
private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ"));
private IcannReportingUploadAction createAction() throws Exception {
IcannReportingUploadAction action = new IcannReportingUploadAction();
action.icannReporter = mockReporter;
action.gcsUtils = new GcsUtils(gcsService, 1024);
action.retrier = new Retrier(new FakeSleeper(new FakeClock()), 3);
action.subdir = "icann/monthly/2017-06";
action.subdir = "icann/monthly/2006-06";
action.reportingBucket = "basin";
action.emailService = emailService;
action.sender = new InternetAddress("sender@example.com");
action.recipient = new InternetAddress("recipient@example.com");
action.response = response;
action.clock = clock;
action.lockHandler = new FakeLockHandler(true);
return action;
}
@Before
public void before() throws Exception {
createTlds("tld", "foo");
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2017-06", "test-transactions-201706.csv"),
new GcsFilename("basin/icann/monthly/2006-06", "tld-transactions-200606.csv"),
PAYLOAD_SUCCESS);
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2017-06", "a-activity-201706.csv"),
new GcsFilename("basin/icann/monthly/2006-06", "tld-activity-200606.csv"),
PAYLOAD_FAIL);
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2017-06", "MANIFEST.txt"),
new GcsFilename("basin/icann/monthly/2006-06", "foo-transactions-200606.csv"),
PAYLOAD_SUCCESS);
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2006-06", "foo-activity-200606.csv"),
PAYLOAD_SUCCESS);
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2006-06", "MANIFEST.txt"),
MANIFEST_PAYLOAD);
when(mockReporter.send(PAYLOAD_SUCCESS, "test-transactions-201706.csv")).thenReturn(true);
when(mockReporter.send(PAYLOAD_FAIL, "a-activity-201706.csv")).thenReturn(false);
when(mockReporter.send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv")).thenReturn(true);
when(mockReporter.send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv")).thenReturn(true);
when(mockReporter.send(PAYLOAD_FAIL, "tld-activity-200606.csv")).thenReturn(false);
when(mockReporter.send(PAYLOAD_SUCCESS, "foo-activity-200606.csv")).thenReturn(true);
when(mockReporter.send(MANIFEST_PAYLOAD, "MANIFEST.txt")).thenReturn(true);
clock.setTo(DateTime.parse("2006-06-06T00:30:00Z"));
persistResource(
Cursor.create(
CursorType.ICANN_UPLOAD_ACTIVITY, DateTime.parse("2006-06-06TZ"), Registry.get("tld")));
persistResource(
Cursor.create(
CursorType.ICANN_UPLOAD_TX, DateTime.parse("2006-06-06TZ"), Registry.get("tld")));
persistResource(
Cursor.createGlobal(CursorType.ICANN_UPLOAD_MANIFEST, DateTime.parse("2006-07-06TZ")));
persistResource(
Cursor.create(
CursorType.ICANN_UPLOAD_ACTIVITY, DateTime.parse("2006-06-06TZ"), Registry.get("foo")));
persistResource(
Cursor.create(
CursorType.ICANN_UPLOAD_TX, DateTime.parse("2006-06-06TZ"), Registry.get("foo")));
loggerToIntercept.addHandler(logHandler);
}
@Test
public void testSuccess() throws Exception {
IcannReportingUploadAction action = createAction();
action.run();
verify(mockReporter).send(PAYLOAD_SUCCESS, "test-transactions-201706.csv");
verify(mockReporter).send(PAYLOAD_FAIL, "a-activity-201706.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_FAIL, "tld-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
assertThat(((FakeResponse) action.response).getPayload())
.isEqualTo("OK, attempted uploading 2 reports");
verify(emailService)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 1/2 succeeded",
"ICANN Monthly report upload summary: 3/4 succeeded",
"Report Filename - Upload status:\n"
+ "test-transactions-201706.csv - SUCCESS\n"
+ "a-activity-201706.csv - FAILURE",
+ "foo-activity-200606.csv - SUCCESS\n"
+ "tld-activity-200606.csv - FAILURE\n"
+ "foo-transactions-200606.csv - SUCCESS\n"
+ "tld-transactions-200606.csv - SUCCESS",
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
}
@Test
public void testSuccess_WithRetry() throws Exception {
public void testSuccess_advancesCursor() throws Exception {
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2006-06", "tld-activity-200606.csv"),
PAYLOAD_SUCCESS);
when(mockReporter.send(PAYLOAD_SUCCESS, "tld-activity-200606.csv")).thenReturn(true);
IcannReportingUploadAction action = createAction();
when(mockReporter.send(PAYLOAD_SUCCESS, "test-transactions-201706.csv"))
action.run();
ofy().clearSessionCache();
Cursor cursor =
ofy()
.load()
.key(Cursor.createKey(CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("tld")))
.now();
assertThat(cursor.getCursorTime()).isEqualTo(DateTime.parse("2006-07-01TZ"));
}
@Test
public void testSuccess_noUploadsNeeded() throws Exception {
clock.setTo(DateTime.parse("2006-5-01T00:30:00Z"));
IcannReportingUploadAction action = createAction();
action.run();
ofy().clearSessionCache();
verifyNoMoreInteractions(mockReporter);
verify(emailService)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 0/0 succeeded",
"Report Filename - Upload status:\n",
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
}
@Test
public void testSuccess_uploadManifest() throws Exception {
persistResource(
Cursor.createGlobal(CursorType.ICANN_UPLOAD_MANIFEST, DateTime.parse("2006-06-06TZ")));
IcannReportingUploadAction action = createAction();
action.run();
ofy().clearSessionCache();
Cursor cursor =
ofy().load().key(Cursor.createGlobalKey(CursorType.ICANN_UPLOAD_MANIFEST)).now();
assertThat(cursor.getCursorTime()).isEqualTo(DateTime.parse("2006-07-01TZ"));
verify(mockReporter).send(PAYLOAD_FAIL, "tld-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verify(mockReporter).send(MANIFEST_PAYLOAD, "MANIFEST.txt");
verifyNoMoreInteractions(mockReporter);
}
@Test
public void testSuccess_withRetry() throws Exception {
IcannReportingUploadAction action = createAction();
when(mockReporter.send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv"))
.thenThrow(new IOException("Expected exception."))
.thenReturn(true);
action.run();
verify(mockReporter, times(2)).send(PAYLOAD_SUCCESS, "test-transactions-201706.csv");
verify(mockReporter).send(PAYLOAD_FAIL, "a-activity-201706.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_FAIL, "tld-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv");
verify(mockReporter, times(2)).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
assertThat(((FakeResponse) action.response).getPayload())
.isEqualTo("OK, attempted uploading 2 reports");
verify(emailService)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 1/2 succeeded",
"ICANN Monthly report upload summary: 3/4 succeeded",
"Report Filename - Upload status:\n"
+ "test-transactions-201706.csv - SUCCESS\n"
+ "a-activity-201706.csv - FAILURE",
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
}
@Test
public void testFailure_firstUnrecoverable_stillAttemptsUploadingBoth() throws Exception {
IcannReportingUploadAction action = createAction();
when(mockReporter.send(PAYLOAD_SUCCESS, "test-transactions-201706.csv"))
.thenThrow(new IOException("Expected exception"));
action.run();
verify(mockReporter, times(3)).send(PAYLOAD_SUCCESS, "test-transactions-201706.csv");
verify(mockReporter).send(PAYLOAD_FAIL, "a-activity-201706.csv");
verifyNoMoreInteractions(mockReporter);
assertThat(((FakeResponse) action.response).getPayload())
.isEqualTo("OK, attempted uploading 2 reports");
verify(emailService)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 0/2 succeeded",
"Report Filename - Upload status:\n"
+ "test-transactions-201706.csv - FAILURE\n"
+ "a-activity-201706.csv - FAILURE",
+ "foo-activity-200606.csv - SUCCESS\n"
+ "tld-activity-200606.csv - FAILURE\n"
+ "foo-transactions-200606.csv - SUCCESS\n"
+ "tld-transactions-200606.csv - SUCCESS",
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
}
@@ -169,38 +252,149 @@ public class IcannReportingUploadActionTest {
new IOException("Your IP address 25.147.130.158 is not allowed to connect"));
}
@Test
public void testFailure_cursorIsNotAdvancedForward() throws Exception {
runTest_nonRetryableException(
new IOException("Your IP address 25.147.130.158 is not allowed to connect"));
ofy().clearSessionCache();
Cursor cursor =
ofy()
.load()
.key(Cursor.createKey(CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("tld")))
.now();
assertThat(cursor.getCursorTime()).isEqualTo(DateTime.parse("2006-06-06TZ"));
}
@Test
public void testNotRunIfCursorDateIsAfterToday() throws Exception {
clock.setTo(DateTime.parse("2006-05-01T00:30:00Z"));
IcannReportingUploadAction action = createAction();
action.run();
ofy().clearSessionCache();
Cursor cursor =
ofy()
.load()
.key(Cursor.createKey(CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("foo")))
.now();
assertThat(cursor.getCursorTime()).isEqualTo(DateTime.parse("2006-06-06TZ"));
verifyNoMoreInteractions(mockReporter);
}
private void runTest_nonRetryableException(Exception nonRetryableException) throws Exception {
IcannReportingUploadAction action = createAction();
when(mockReporter.send(PAYLOAD_FAIL, "a-activity-201706.csv"))
when(mockReporter.send(PAYLOAD_FAIL, "tld-activity-200606.csv"))
.thenThrow(nonRetryableException)
.thenThrow(
new AssertionError(
"This should never be thrown because the previous exception isn't retryable"));
action.run();
verify(mockReporter, times(1)).send(PAYLOAD_FAIL, "a-activity-201706.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "test-transactions-201706.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-activity-200606.csv");
verify(mockReporter, times(1)).send(PAYLOAD_FAIL, "tld-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
assertThat(((FakeResponse) action.response).getPayload())
.isEqualTo("OK, attempted uploading 2 reports");
verify(emailService)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 1/2 succeeded",
"ICANN Monthly report upload summary: 3/4 succeeded",
"Report Filename - Upload status:\n"
+ "test-transactions-201706.csv - SUCCESS\n"
+ "a-activity-201706.csv - FAILURE",
+ "foo-activity-200606.csv - SUCCESS\n"
+ "tld-activity-200606.csv - FAILURE\n"
+ "foo-transactions-200606.csv - SUCCESS\n"
+ "tld-transactions-200606.csv - SUCCESS",
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
}
@Test
public void testFail_FileNotFound() throws Exception {
public void testFail_fileNotFound() throws Exception {
IcannReportingUploadAction action = createAction();
action.subdir = "somewhere/else";
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, action::run);
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.SEVERE,
"Could not upload ICANN_UPLOAD_ACTIVITY report for foo because file"
+ " foo-activity-200606.csv did not exist");
}
@Test
public void testWarning_fileNotStagedYet() throws Exception {
persistResource(
Cursor.create(
CursorType.ICANN_UPLOAD_ACTIVITY, DateTime.parse("2006-07-01TZ"), Registry.get("foo")));
clock.setTo(DateTime.parse("2006-07-01T00:30:00Z"));
IcannReportingUploadAction action = createAction();
action.subdir = "icann/monthly/2006-07";
action.run();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(
Level.INFO,
"Could not upload ICANN_UPLOAD_ACTIVITY report for foo because file"
+ " foo-activity-200607.csv did not exist. This report may not have been staged"
+ " yet.");
}
@Test
public void testFailure_lockIsntAvailable() throws Exception {
IcannReportingUploadAction action = createAction();
action.lockHandler = new FakeLockHandler(false);
ServiceUnavailableException thrown =
assertThrows(ServiceUnavailableException.class, () -> action.run());
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Object MANIFEST.txt in bucket basin/somewhere/else not found");
.contains("Lock for IcannReportingUploadAction already in use");
}
@Test
public void testSuccess_nullCursors() throws Exception {
createTlds("new");
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2006-06", "new-transactions-200606.csv"),
PAYLOAD_SUCCESS);
writeGcsFile(
gcsService,
new GcsFilename("basin/icann/monthly/2006-06", "new-activity-200606.csv"),
PAYLOAD_SUCCESS);
when(mockReporter.send(PAYLOAD_SUCCESS, "new-transactions-200606.csv")).thenReturn(true);
when(mockReporter.send(PAYLOAD_SUCCESS, "new-activity-200606.csv")).thenReturn(true);
IcannReportingUploadAction action = createAction();
action.run();
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_FAIL, "tld-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "new-activity-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "foo-transactions-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "tld-transactions-200606.csv");
verify(mockReporter).send(PAYLOAD_SUCCESS, "new-transactions-200606.csv");
verifyNoMoreInteractions(mockReporter);
verify(emailService)
.sendEmail(
EmailMessage.create(
"ICANN Monthly report upload summary: 5/6 succeeded",
"Report Filename - Upload status:\n"
+ "foo-activity-200606.csv - SUCCESS\n"
+ "new-activity-200606.csv - SUCCESS\n"
+ "tld-activity-200606.csv - FAILURE\n"
+ "foo-transactions-200606.csv - SUCCESS\n"
+ "new-transactions-200606.csv - SUCCESS\n"
+ "tld-transactions-200606.csv - SUCCESS",
new InternetAddress("recipient@example.com"),
new InternetAddress("sender@example.com")));
Cursor newActivityCursor =
ofy()
.load()
.key(Cursor.createKey(CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("new")))
.now();
assertThat(newActivityCursor.getCursorTime()).isEqualTo(DateTime.parse("2006-07-01TZ"));
Cursor newTransactionCursor =
ofy().load().key(Cursor.createKey(CursorType.ICANN_UPLOAD_TX, Registry.get("new"))).now();
assertThat(newTransactionCursor.getCursorTime()).isEqualTo(DateTime.parse("2006-07-01TZ"));
}
}
+2 -2
View File
@@ -73,12 +73,12 @@ ext {
getCloudSqlCredential = { env, role ->
def command =
"""gsutil cp \
gs://domain-registry-dev-deploy/cloudsql-credentials/${env}/${role}_credential.enc - | \
gs://${rootProject.devProject}-deploy/cloudsql-credentials/${env}/${role}_credential.enc - | \
base64 -d | \
gcloud kms decrypt --location global --keyring nomulus-tool-keyring \
--key nomulus-tool-key --plaintext-file=- \
--ciphertext-file=- \
--project=domain-registry-dev"""
--project=${rootProject.devProject}"""
return execInBash(command, '/tmp')
}
+3 -3
View File
@@ -94,8 +94,8 @@ task initCoverageMinimums {
].asImmutable()
rootProject.ext.getMinCoverage = { key ->
if (rootProject.ext.coverageMinimums.containsKey(key)) {
return rootProject.ext.coverageMinimums.get(key)
if (rootProject.coverageMinimums.containsKey(key)) {
return rootProject.coverageMinimums.get(key)
}
return 0.0
}
@@ -117,7 +117,7 @@ jacocoTestCoverageVerification {
// or MISSEDRATIO
// - The 'minimum' threshold, given as a fraction or a percentage (including '%')
limit {
minimum = rootProject.ext.getMinCoverage(project.getName())
minimum = rootProject.getMinCoverage(project.getName())
}
}
}
@@ -17,16 +17,20 @@ package google.registry.networking.handler;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.inject.Singleton;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
@@ -47,14 +51,33 @@ public class SslClientInitializer<C extends Channel> extends ChannelInitializer<
private final Function<Channel, String> hostProvider;
private final Function<Channel, Integer> portProvider;
private final SslProvider sslProvider;
private final X509Certificate[] trustedCertificates;
private final ImmutableList<X509Certificate> trustedCertificates;
// The following two suppliers only need be none-null when client authentication is required.
private final Supplier<PrivateKey> privateKeySupplier;
private final Supplier<ImmutableList<X509Certificate>> certificateChainSupplier;
public SslClientInitializer(
SslProvider sslProvider,
Function<Channel, String> hostProvider,
Function<Channel, Integer> portProvider) {
// null uses the system default trust store.
this(sslProvider, hostProvider, portProvider, null);
public static SslClientInitializer<NioSocketChannel>
createSslClientInitializerWithSystemTrustStore(
SslProvider sslProvider,
Function<Channel, String> hostProvider,
Function<Channel, Integer> portProvider) {
return new SslClientInitializer<>(sslProvider, hostProvider, portProvider, null, null, null);
}
public static SslClientInitializer<NioSocketChannel>
createSslClientInitializerWithSystemTrustStoreAndClientAuthentication(
SslProvider sslProvider,
Function<Channel, String> hostProvider,
Function<Channel, Integer> portProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<ImmutableList<X509Certificate>> certificateChainSupplier) {
return new SslClientInitializer<>(
sslProvider,
hostProvider,
portProvider,
ImmutableList.of(),
privateKeySupplier,
certificateChainSupplier);
}
@VisibleForTesting
@@ -62,22 +85,38 @@ public class SslClientInitializer<C extends Channel> extends ChannelInitializer<
SslProvider sslProvider,
Function<Channel, String> hostProvider,
Function<Channel, Integer> portProvider,
X509Certificate[] trustCertificates) {
ImmutableList<X509Certificate> trustedCertificates,
Supplier<PrivateKey> privateKeySupplier,
Supplier<ImmutableList<X509Certificate>> certificateChainSupplier) {
logger.atInfo().log("Client SSL Provider: %s", sslProvider);
this.sslProvider = sslProvider;
this.hostProvider = hostProvider;
this.portProvider = portProvider;
this.trustedCertificates = trustCertificates;
this.trustedCertificates = trustedCertificates;
this.privateKeySupplier = privateKeySupplier;
this.certificateChainSupplier = certificateChainSupplier;
}
@Override
protected void initChannel(C channel) throws Exception {
checkNotNull(hostProvider.apply(channel), "Cannot obtain SSL host for channel: %s", channel);
checkNotNull(portProvider.apply(channel), "Cannot obtain SSL port for channel: %s", channel);
SslHandler sslHandler =
SslContextBuilder sslContextBuilder =
SslContextBuilder.forClient()
.sslProvider(sslProvider)
.trustManager(trustedCertificates)
.trustManager(
trustedCertificates.isEmpty()
? null
: trustedCertificates.toArray(new X509Certificate[0]));
if (privateKeySupplier != null && certificateChainSupplier != null) {
sslContextBuilder.keyManager(
privateKeySupplier.get(), certificateChainSupplier.get().toArray(new X509Certificate[0]));
}
SslHandler sslHandler =
sslContextBuilder
.build()
.newHandler(channel.alloc(), hostProvider.apply(channel), portProvider.apply(channel));
@@ -14,6 +14,7 @@
package google.registry.networking.handler;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
@@ -39,7 +40,7 @@ import java.util.function.Supplier;
* come before this handler. The type parameter {@code C} is needed so that unit tests can construct
* this handler that works with {@link EmbeddedChannel};
*
* <p>The ssl handler added requires client authentication, but it uses an {@link
* <p>The ssl handler added can require client authentication, but it uses an {@link
* InsecureTrustManagerFactory}, which accepts any ssl certificate presented by the client, as long
* as the client uses the corresponding private key to establish SSL handshake. The client
* certificate hash will be passed along to GAE as an HTTP header for verification (not handled by
@@ -58,14 +59,16 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final boolean requireClientCert;
private final SslProvider sslProvider;
// We use suppliers for the key/cert pair because they are fetched and cached from GCS, and can
// change when the artifacts on GCS changes.
private final Supplier<PrivateKey> privateKeySupplier;
private final Supplier<X509Certificate[]> certificatesSupplier;
private final Supplier<ImmutableList<X509Certificate>> certificatesSupplier;
public SslServerInitializer(
boolean requireClientCert,
SslProvider sslProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<X509Certificate[]> certificatesSupplier) {
Supplier<ImmutableList<X509Certificate>> certificatesSupplier) {
logger.atInfo().log("Server SSL Provider: %s", sslProvider);
this.requireClientCert = requireClientCert;
this.sslProvider = sslProvider;
@@ -76,7 +79,9 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
@Override
protected void initChannel(C channel) throws Exception {
SslHandler sslHandler =
SslContextBuilder.forServer(privateKeySupplier.get(), certificatesSupplier.get())
SslContextBuilder.forServer(
privateKeySupplier.get(),
certificatesSupplier.get().toArray(new X509Certificate[0]))
.sslProvider(sslProvider)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.clientAuth(requireClientCert ? ClientAuth.REQUIRE : ClientAuth.NONE)
@@ -48,7 +48,7 @@ import org.junit.rules.ExternalResource;
*
* <p>Used in {@link SslClientInitializerTest} and {@link SslServerInitializerTest}.
*/
final class NettyRule extends ExternalResource {
public final class NettyRule extends ExternalResource {
// All I/O operations are done inside the single thread within this event loop group, which is
// different from the main test thread. Therefore synchronizations are required to make sure that
@@ -63,8 +63,12 @@ final class NettyRule extends ExternalResource {
private Channel channel;
public EventLoopGroup getEventLoopGroup() {
return eventLoopGroup;
}
/** Sets up a server channel bound to the given local address. */
void setUpServer(LocalAddress localAddress, ChannelHandler handler) {
public void setUpServer(LocalAddress localAddress, ChannelHandler... handlers) {
checkState(echoHandler == null, "Can't call setUpServer twice");
echoHandler = new EchoHandler();
ChannelInitializer<LocalChannel> serverInitializer =
@@ -72,7 +76,7 @@ final class NettyRule extends ExternalResource {
@Override
protected void initChannel(LocalChannel ch) {
// Add the given handler
ch.pipeline().addLast(handler);
ch.pipeline().addLast(handlers);
// Add the "echoHandler" last to log the incoming message and send it back
ch.pipeline().addLast(echoHandler);
}
@@ -147,6 +151,11 @@ final class NettyRule extends ExternalResource {
assertThrows(ExecutionException.class, () -> dumpHandler.getResponseFuture().get())));
}
// TODO(jianglai): find a way to remove this helper method.
public void assertReceivedMessage(String message) throws Exception {
assertThat(echoHandler.getRequestFuture().get()).isEqualTo(message);
}
/**
* A handler that echoes back its inbound message. The message is also saved in a promise for
* inspection later.
@@ -19,18 +19,21 @@ import static google.registry.networking.handler.SslInitializerTestUtils.getKeyP
import static google.registry.networking.handler.SslInitializerTestUtils.setUpSslChannel;
import static google.registry.networking.handler.SslInitializerTestUtils.signKeyPair;
import com.google.common.collect.ImmutableList;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.SniHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.security.KeyPair;
import java.security.PrivateKey;
@@ -39,6 +42,7 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.function.Function;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -86,9 +90,14 @@ public class SslClientInitializerTest {
/** Saves the SNI hostname received by the server, if sent by the client. */
private String sniHostReceived;
private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate certificate)
private ChannelHandler getServerHandler(
boolean requireClientCert, PrivateKey privateKey, X509Certificate certificate)
throws Exception {
SslContext sslContext = SslContextBuilder.forServer(privateKey, certificate).build();
SslContext sslContext =
SslContextBuilder.forServer(privateKey, certificate)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.clientAuth(requireClientCert ? ClientAuth.REQUIRE : ClientAuth.NONE)
.build();
return new SniHandler(
hostname -> {
sniHostReceived = hostname;
@@ -99,7 +108,8 @@ public class SslClientInitializerTest {
@Test
public void testSuccess_swappedInitializerWithSslHandler() throws Exception {
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, hostProvider, portProvider);
new SslClientInitializer<>(
sslProvider, hostProvider, portProvider, ImmutableList.of(), null, null);
EmbeddedChannel channel = new EmbeddedChannel();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslClientInitializer);
@@ -114,7 +124,8 @@ public class SslClientInitializerTest {
@Test
public void testSuccess_nullHost() {
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, channel -> null, portProvider);
new SslClientInitializer<>(
sslProvider, channel -> null, portProvider, ImmutableList.of(), null, null);
EmbeddedChannel channel = new EmbeddedChannel();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslClientInitializer);
@@ -125,7 +136,8 @@ public class SslClientInitializerTest {
@Test
public void testSuccess_nullPort() {
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, hostProvider, channel -> null);
new SslClientInitializer<>(
sslProvider, hostProvider, channel -> null, ImmutableList.of(), null, null);
EmbeddedChannel channel = new EmbeddedChannel();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslClientInitializer);
@@ -138,9 +150,10 @@ public class SslClientInitializerTest {
SelfSignedCertificate ssc = new SelfSignedCertificate(SSL_HOST);
LocalAddress localAddress =
new LocalAddress("DEFAULT_TRUST_MANAGER_REJECT_SELF_SIGNED_CERT_" + sslProvider);
nettyRule.setUpServer(localAddress, getServerHandler(ssc.key(), ssc.cert()));
nettyRule.setUpServer(localAddress, getServerHandler(false, ssc.key(), ssc.cert()));
SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, hostProvider, portProvider);
new SslClientInitializer<>(
sslProvider, hostProvider, portProvider, ImmutableList.of(), null, null);
nettyRule.setUpClient(localAddress, sslClientInitializer);
// The connection is now terminated, both the client side and the server side should get
// exceptions.
@@ -163,12 +176,12 @@ public class SslClientInitializerTest {
// Set up the server to use the signed cert and private key to perform handshake;
PrivateKey privateKey = keyPair.getPrivate();
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
nettyRule.setUpServer(localAddress, getServerHandler(false, privateKey, cert));
// Set up the client to trust the self signed cert used to sign the cert that server provides.
SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(
sslProvider, hostProvider, portProvider, new X509Certificate[] {ssc.cert()});
sslProvider, hostProvider, portProvider, ImmutableList.of(ssc.cert()), null, null);
nettyRule.setUpClient(localAddress, sslClientInitializer);
setUpSslChannel(nettyRule.getChannel(), cert);
@@ -178,6 +191,43 @@ public class SslClientInitializerTest {
assertThat(sniHostReceived).isEqualTo(SSL_HOST);
}
@Test
public void testSuccess_customTrustManager_acceptSelfSignedCert_clientCertRequired()
throws Exception {
LocalAddress localAddress =
new LocalAddress(
"CUSTOM_TRUST_MANAGER_ACCEPT_SELF_SIGNED_CERT_CLIENT_CERT_REQUIRED_" + sslProvider);
SelfSignedCertificate serverSsc = new SelfSignedCertificate(SSL_HOST);
SelfSignedCertificate clientSsc = new SelfSignedCertificate();
// Set up the server to require client certificate.
nettyRule.setUpServer(localAddress, getServerHandler(true, serverSsc.key(), serverSsc.cert()));
// Set up the client to trust the server certificate and use the client certificate.
SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(
sslProvider,
hostProvider,
portProvider,
ImmutableList.of(serverSsc.cert()),
() -> clientSsc.key(),
() -> ImmutableList.of(clientSsc.cert()));
nettyRule.setUpClient(localAddress, sslClientInitializer);
SSLSession sslSession = setUpSslChannel(nettyRule.getChannel(), serverSsc.cert());
nettyRule.assertThatMessagesWork();
// Verify that the SNI extension is sent during handshake.
assertThat(sniHostReceived).isEqualTo(SSL_HOST);
// Verify that the SSL session gets the client cert. Note that this SslSession is for the client
// channel, therefore its local certificates are the remote certificates of the SslSession for
// the server channel, and vice versa.
assertThat(sslSession.getLocalCertificates()).asList().containsExactly(clientSsc.cert());
assertThat(sslSession.getPeerCertificates()).asList().containsExactly(serverSsc.cert());
}
@Test
public void testFailure_customTrustManager_wrongHostnameInCertificate() throws Exception {
LocalAddress localAddress =
@@ -192,12 +242,12 @@ public class SslClientInitializerTest {
// Set up the server to use the signed cert and private key to perform handshake;
PrivateKey privateKey = keyPair.getPrivate();
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
nettyRule.setUpServer(localAddress, getServerHandler(false, privateKey, cert));
// Set up the client to trust the self signed cert used to sign the cert that server provides.
SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(
sslProvider, hostProvider, portProvider, new X509Certificate[] {ssc.cert()});
sslProvider, hostProvider, portProvider, ImmutableList.of(ssc.cert()), null, null);
nettyRule.setUpClient(localAddress, sslClientInitializer);
// When the client rejects the server cert due to wrong hostname, both the client and server
@@ -29,15 +29,23 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import javax.net.ssl.SSLSession;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
/**
* Utility class that provides methods used by {@link SslClientInitializerTest} and {@link
* SslServerInitializerTest}.
*/
@SuppressWarnings("deprecation")
public final class SslInitializerTestUtils {
static {
@@ -59,16 +67,26 @@ public final class SslInitializerTestUtils {
*/
public static X509Certificate signKeyPair(
SelfSignedCertificate ssc, KeyPair keyPair, String hostname) throws Exception {
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
X500Principal dnName = new X500Principal("CN=" + hostname);
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setSubjectDN(dnName);
certGen.setIssuerDN(ssc.cert().getSubjectX500Principal());
certGen.setNotBefore(Date.from(Instant.now().minus(Duration.ofDays(1))));
certGen.setNotAfter(Date.from(Instant.now().plus(Duration.ofDays(1))));
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
return certGen.generate(ssc.key(), "BC");
X500Name subjectDnName = new X500Name("CN=" + hostname);
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
X500Name issuerDnName = new X500Name(ssc.cert().getIssuerDN().getName());
Date from = Date.from(Instant.now().minus(Duration.ofDays(1)));
Date to = Date.from(Instant.now().plus(Duration.ofDays(1)));
SubjectPublicKeyInfo subPubKeyInfo =
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
AlgorithmIdentifier sigAlgId =
new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
ContentSigner sigGen =
new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
.build(PrivateKeyFactory.createKey(ssc.key().getEncoded()));
X509v3CertificateBuilder v3CertGen =
new X509v3CertificateBuilder(
issuerDnName, serialNumber, from, to, subjectDnName, subPubKeyInfo);
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
}
/**
@@ -20,6 +20,7 @@ import static google.registry.networking.handler.SslInitializerTestUtils.setUpSs
import static google.registry.networking.handler.SslInitializerTestUtils.signKeyPair;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
@@ -86,7 +87,7 @@ public class SslServerInitializerTest {
requireClientCert,
sslProvider,
Suppliers.ofInstance(privateKey),
Suppliers.ofInstance(certificates));
Suppliers.ofInstance(ImmutableList.copyOf(certificates)));
}
private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate... certificates) {
@@ -125,7 +126,7 @@ public class SslServerInitializerTest {
true,
sslProvider,
Suppliers.ofInstance(ssc.key()),
Suppliers.ofInstance(new X509Certificate[] {ssc.cert()}));
Suppliers.ofInstance(ImmutableList.of(ssc.cert())));
EmbeddedChannel channel = new EmbeddedChannel();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslServerInitializer);
+2
View File
@@ -40,6 +40,7 @@ dependencies {
compile deps['xpp3:xpp3']
compile project(':common')
compile project(':util')
compile project(':networking')
runtime deps['com.google.flogger:flogger-system-backend']
runtime deps['com.google.auto.value:auto-value']
@@ -51,6 +52,7 @@ dependencies {
testCompile deps['org.mockito:mockito-core']
testCompile project(':third_party')
testCompile project(path: ':common', configuration: 'testing')
testCompile project(path: ':networking', configuration: 'testRuntime')
// Include auto-value in compile until nebula-lint understands
// annotationProcessor
@@ -13,6 +13,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.5
com.google.dagger:dagger:2.21
com.google.errorprone:error_prone_annotations:2.3.2
com.google.flogger:flogger-system-backend:0.1
com.google.flogger:flogger:0.1
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:28.1-jre
@@ -33,6 +34,7 @@ io.netty:netty-codec:4.1.31.Final
io.netty:netty-common:4.1.31.Final
io.netty:netty-handler:4.1.31.Final
io.netty:netty-resolver:4.1.31.Final
io.netty:netty-tcnative-boringssl-static:2.0.22.Final
io.netty:netty-transport:4.1.31.Final
io.opencensus:opencensus-api:0.21.0
io.opencensus:opencensus-contrib-http-util:0.21.0
@@ -13,6 +13,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.5
com.google.dagger:dagger:2.21
com.google.errorprone:error_prone_annotations:2.3.2
com.google.flogger:flogger-system-backend:0.1
com.google.flogger:flogger:0.1
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:28.1-jre
@@ -33,6 +34,7 @@ io.netty:netty-codec:4.1.31.Final
io.netty:netty-common:4.1.31.Final
io.netty:netty-handler:4.1.31.Final
io.netty:netty-resolver:4.1.31.Final
io.netty:netty-tcnative-boringssl-static:2.0.22.Final
io.netty:netty-transport:4.1.31.Final
io.opencensus:opencensus-api:0.21.0
io.opencensus:opencensus-contrib-http-util:0.21.0
@@ -13,6 +13,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.5
com.google.dagger:dagger:2.21
com.google.errorprone:error_prone_annotations:2.3.2
com.google.flogger:flogger-system-backend:0.1
com.google.flogger:flogger:0.1
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:28.1-jre
@@ -36,6 +37,7 @@ io.netty:netty-codec:4.1.31.Final
io.netty:netty-common:4.1.31.Final
io.netty:netty-handler:4.1.31.Final
io.netty:netty-resolver:4.1.31.Final
io.netty:netty-tcnative-boringssl-static:2.0.22.Final
io.netty:netty-transport:4.1.31.Final
io.opencensus:opencensus-api:0.21.0
io.opencensus:opencensus-contrib-http-util:0.21.0
@@ -13,6 +13,7 @@ com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.8.5
com.google.dagger:dagger:2.21
com.google.errorprone:error_prone_annotations:2.3.2
com.google.flogger:flogger-system-backend:0.1
com.google.flogger:flogger:0.1
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:28.1-jre
@@ -36,6 +37,7 @@ io.netty:netty-codec:4.1.31.Final
io.netty:netty-common:4.1.31.Final
io.netty:netty-handler:4.1.31.Final
io.netty:netty-resolver:4.1.31.Final
io.netty:netty-tcnative-boringssl-static:2.0.22.Final
io.netty:netty-transport:4.1.31.Final
io.opencensus:opencensus-api:0.21.0
io.opencensus:opencensus-contrib-http-util:0.21.0
@@ -21,6 +21,7 @@ import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.module.CertificateModule;
import google.registry.monitoring.blackbox.module.EppModule;
import google.registry.monitoring.blackbox.module.WebWhoisModule;
import google.registry.networking.handler.SslClientInitializer;
import google.registry.util.Clock;
import google.registry.util.SystemClock;
import io.netty.bootstrap.Bootstrap;
@@ -45,10 +46,7 @@ public class ProberModule {
/** Default {@link Duration} chosen to be time between each {@link ProbingAction} call. */
private static final Duration DEFAULT_PROBER_INTERVAL = Duration.standardSeconds(4);
/**
* {@link Provides} the {@link SslProvider} used by instances of {@link
* google.registry.monitoring.blackbox.handler.SslClientInitializer}
*/
/** {@link Provides} the {@link SslProvider} used by instances of {@link SslClientInitializer} */
@Provides
@Singleton
static SslProvider provideSslProvider() {
@@ -1,116 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.handler;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.connection.Protocol;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.function.Supplier;
import javax.inject.Singleton;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
/**
* Adds a client side SSL handler to the channel pipeline.
*
* <p>Code is close to unchanged from {@link SslClientInitializer} in proxy, but is modified for
* revised overall structure of connections, and to accomdate EPP connections
*
* <p>This <b>must</b> be the first handler provided for any handler provider list, if it is
* provided. The type parameter {@code C} is needed so that unit tests can construct this handler
* that works with {@link EmbeddedChannel};
*/
@Singleton
@Sharable
public class SslClientInitializer<C extends Channel> extends ChannelInitializer<C> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final SslProvider sslProvider;
private final X509Certificate[] trustedCertificates;
private final Supplier<PrivateKey> privateKeySupplier;
private final Supplier<X509Certificate[]> certificateSupplier;
public SslClientInitializer(SslProvider sslProvider) {
// null uses the system default trust store.
// Used for WebWhois, so we don't care about privateKey and certificates, setting them to null
this(sslProvider, null, null, null);
}
public SslClientInitializer(
SslProvider sslProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<X509Certificate[]> certificateSupplier) {
// We use the default trust store here as well, setting trustCertificates to null
this(sslProvider, null, privateKeySupplier, certificateSupplier);
}
@VisibleForTesting
SslClientInitializer(SslProvider sslProvider, X509Certificate[] trustCertificates) {
this(sslProvider, trustCertificates, null, null);
}
private SslClientInitializer(
SslProvider sslProvider,
X509Certificate[] trustCertificates,
Supplier<PrivateKey> privateKeySupplier,
Supplier<X509Certificate[]> certificateSupplier) {
logger.atInfo().log("Client SSL Provider: %s", sslProvider);
this.sslProvider = sslProvider;
this.trustedCertificates = trustCertificates;
this.privateKeySupplier = privateKeySupplier;
this.certificateSupplier = certificateSupplier;
}
@Override
protected void initChannel(C channel) throws Exception {
Protocol protocol = channel.attr(PROTOCOL_KEY).get();
String host = channel.attr(REMOTE_ADDRESS_KEY).get();
// Builds SslHandler from Protocol, and based on if we require a privateKey and certificate
checkNotNull(protocol, "Protocol is not set for channel: %s", channel);
SslContextBuilder sslContextBuilder =
SslContextBuilder.forClient().sslProvider(sslProvider).trustManager(trustedCertificates);
if (privateKeySupplier != null && certificateSupplier != null) {
sslContextBuilder =
sslContextBuilder.keyManager(privateKeySupplier.get(), certificateSupplier.get());
}
SslHandler sslHandler =
sslContextBuilder.build().newHandler(channel.alloc(), host, protocol.port());
// Enable hostname verification.
SSLEngine sslEngine = sslHandler.engine();
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParameters);
channel.pipeline().addLast(sslHandler);
}
}
@@ -18,6 +18,7 @@ import static com.google.common.base.Suppliers.memoizeWithExpiration;
import static google.registry.util.ResourceUtils.readResourceBytes;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
import java.io.IOException;
@@ -90,7 +91,8 @@ public class CertificateModule {
@Provides
@LocalSecrets
static X509Certificate[] provideCertificates(@LocalSecrets Provider<String> passwordProvider) {
static ImmutableList<X509Certificate> provideCertificates(
@LocalSecrets Provider<String> passwordProvider) {
try {
InputStream inStream = readResource("secrets/prober-client-tls-sandbox.p12");
@@ -98,7 +100,7 @@ public class CertificateModule {
ks.load(inStream, passwordProvider.get().toCharArray());
String alias = ks.aliases().nextElement();
return new X509Certificate[] {(X509Certificate) ks.getCertificate(alias)};
return ImmutableList.of((X509Certificate) ks.getCertificate(alias));
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -116,8 +118,8 @@ public class CertificateModule {
@Singleton
@Provides
@LocalSecrets
static Supplier<X509Certificate[]> provideCertificatesSupplier(
@LocalSecrets Provider<X509Certificate[]> certificatesProvider,
static Supplier<ImmutableList<X509Certificate>> provideCertificatesSupplier(
@LocalSecrets Provider<ImmutableList<X509Certificate>> certificatesProvider,
@LocalSecrets Duration duration) {
return memoizeWithExpiration(
certificatesProvider::get, duration.getStandardSeconds(), TimeUnit.SECONDS);
@@ -14,6 +14,8 @@
package google.registry.monitoring.blackbox.module;
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.monitoring.blackbox.message.EppRequestMessage.CLIENT_ID_KEY;
import static google.registry.monitoring.blackbox.message.EppRequestMessage.CLIENT_PASSWORD_KEY;
import static google.registry.monitoring.blackbox.message.EppRequestMessage.CLIENT_TRID_KEY;
@@ -30,13 +32,13 @@ import google.registry.monitoring.blackbox.ProbingStep;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.handler.EppActionHandler;
import google.registry.monitoring.blackbox.handler.EppMessageHandler;
import google.registry.monitoring.blackbox.handler.SslClientInitializer;
import google.registry.monitoring.blackbox.message.EppMessage;
import google.registry.monitoring.blackbox.message.EppRequestMessage;
import google.registry.monitoring.blackbox.message.EppResponseMessage;
import google.registry.monitoring.blackbox.metric.MetricsCollector;
import google.registry.monitoring.blackbox.module.CertificateModule.LocalSecrets;
import google.registry.monitoring.blackbox.token.EppToken;
import google.registry.networking.handler.SslClientInitializer;
import google.registry.util.Clock;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelHandler;
@@ -556,9 +558,15 @@ public class EppModule {
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
SslProvider sslProvider,
@LocalSecrets Supplier<PrivateKey> privateKeySupplier,
@LocalSecrets Supplier<X509Certificate[]> certificatesSupplier) {
@LocalSecrets Supplier<ImmutableList<X509Certificate>> certificatesSupplier) {
return new SslClientInitializer<>(sslProvider, privateKeySupplier, certificatesSupplier);
return SslClientInitializer
.createSslClientInitializerWithSystemTrustStoreAndClientAuthentication(
sslProvider,
channel -> channel.attr(REMOTE_ADDRESS_KEY).get(),
channel -> channel.attr(PROTOCOL_KEY).get().port(),
privateKeySupplier,
certificatesSupplier);
}
@Provides
@@ -14,6 +14,10 @@
package google.registry.monitoring.blackbox.module;
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.networking.handler.SslClientInitializer.createSslClientInitializerWithSystemTrustStore;
import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
@@ -21,12 +25,12 @@ import dagger.multibindings.IntoSet;
import google.registry.monitoring.blackbox.ProbingSequence;
import google.registry.monitoring.blackbox.ProbingStep;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.handler.SslClientInitializer;
import google.registry.monitoring.blackbox.handler.WebWhoisActionHandler;
import google.registry.monitoring.blackbox.handler.WebWhoisMessageHandler;
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
import google.registry.monitoring.blackbox.metric.MetricsCollector;
import google.registry.monitoring.blackbox.token.WebWhoisToken;
import google.registry.networking.handler.SslClientInitializer;
import google.registry.util.Clock;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
@@ -156,7 +160,10 @@ public class WebWhoisModule {
@HttpsWhoisProtocol
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
SslProvider sslProvider) {
return new SslClientInitializer<>(sslProvider);
return createSslClientInitializerWithSystemTrustStore(
sslProvider,
channel -> channel.attr(REMOTE_ADDRESS_KEY).get(),
channel -> channel.attr(PROTOCOL_KEY).get().port());
}
/** {@link Provides} the {@link Bootstrap} used by the WebWhois sequence. */
@@ -26,11 +26,11 @@ import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
import google.registry.monitoring.blackbox.handler.ActionHandler;
import google.registry.monitoring.blackbox.handler.ConversionHandler;
import google.registry.monitoring.blackbox.handler.NettyRule;
import google.registry.monitoring.blackbox.handler.TestActionHandler;
import google.registry.monitoring.blackbox.message.OutboundMessageType;
import google.registry.monitoring.blackbox.message.TestMessage;
import google.registry.monitoring.blackbox.token.Token;
import google.registry.networking.handler.NettyRule;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
@@ -62,7 +62,7 @@ public class ProbingStepTest {
private final Bootstrap bootstrap =
new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class);
/** Used for testing how well probing step can create connection to blackbox server */
@Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup);
@Rule public NettyRule nettyRule = new NettyRule();
/**
* The two main handlers we need in any test pipeline used that connects to {@link NettyRule's
@@ -22,19 +22,17 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.handler.ActionHandler;
import google.registry.monitoring.blackbox.handler.ConversionHandler;
import google.registry.monitoring.blackbox.handler.NettyRule;
import google.registry.monitoring.blackbox.handler.TestActionHandler;
import google.registry.monitoring.blackbox.message.TestMessage;
import google.registry.networking.handler.NettyRule;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import org.joda.time.Duration;
import org.junit.Ignore;
import org.junit.Rule;
@@ -57,10 +55,7 @@ public class ProbingActionTest {
private static final String ADDRESS_NAME = "TEST_ADDRESS";
private static final int TEST_PORT = 0;
private static final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
/** Used for testing how well probing step can create connection to blackbox server */
@Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup);
@Rule public NettyRule nettyRule = new NettyRule();
/**
* We use custom Test {@link ActionHandler} and {@link ConversionHandler} so test depends only on
@@ -127,7 +122,8 @@ public class ProbingActionTest {
// setup
LocalAddress address = new LocalAddress(ADDRESS_NAME);
Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class);
Bootstrap bootstrap =
new Bootstrap().group(nettyRule.getEventLoopGroup()).channel(LocalChannel.class);
// Sets up a Protocol corresponding to when a new connection is created.
Protocol protocol =
@@ -1,229 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.handler;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.testing.JUnitBackports.assertThrows;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.truth.ThrowableSubject;
import google.registry.monitoring.blackbox.ProbingStepTest;
import google.registry.monitoring.blackbox.connection.ProbingActionTest;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.testserver.TestServer;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.ReferenceCountUtil;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.junit.rules.ExternalResource;
/**
* Helper for setting up and testing client / server connection with netty.
*
* <p>Code based on and almost identical to {@code NettyRule} in the proxy. Used in {@link
* SslClientInitializerTest}, {@link ProbingActionTest}, and {@link ProbingStepTest}
*/
public final class NettyRule extends ExternalResource {
private final EventLoopGroup eventLoopGroup;
// Handler attached to server's channel to record the request received.
private EchoHandler echoHandler;
// Handler attached to client's channel to record the response received.
private DumpHandler dumpHandler;
private Channel channel;
// All I/O operations are done inside the single thread within this event loop group, which is
// different from the main test thread. Therefore synchronizations are required to make sure that
// certain I/O activities are finished when assertions are performed.
public NettyRule() {
eventLoopGroup = new NioEventLoopGroup(1);
}
public NettyRule(EventLoopGroup e) {
eventLoopGroup = e;
}
private static void writeToChannelAndFlush(Channel channel, String data) {
ChannelFuture unusedFuture =
channel.writeAndFlush(Unpooled.wrappedBuffer(data.getBytes(US_ASCII)));
}
/** Sets up a server channel bound to the given local address. */
public void setUpServer(LocalAddress localAddress, ChannelHandler... handlers) {
checkState(echoHandler == null, "Can't call setUpServer twice");
echoHandler = new EchoHandler();
new TestServer(
eventLoopGroup,
localAddress,
ImmutableList.<ChannelHandler>builder().add(handlers).add(echoHandler).build());
}
/** Sets up a client channel connecting to the give local address. */
void setUpClient(
LocalAddress localAddress, Protocol protocol, String host, ChannelHandler handler) {
checkState(echoHandler != null, "Must call setUpServer before setUpClient");
checkState(dumpHandler == null, "Can't call setUpClient twice");
dumpHandler = new DumpHandler();
ChannelInitializer<LocalChannel> clientInitializer =
new ChannelInitializer<LocalChannel>() {
@Override
protected void initChannel(LocalChannel ch) throws Exception {
// Add the given handler
ch.pipeline().addLast(handler);
// Add the "dumpHandler" last to log the incoming message
ch.pipeline().addLast(dumpHandler);
}
};
Bootstrap b =
new Bootstrap()
.group(eventLoopGroup)
.channel(LocalChannel.class)
.handler(clientInitializer)
.attr(PROTOCOL_KEY, protocol)
.attr(REMOTE_ADDRESS_KEY, host);
channel = b.connect(localAddress).syncUninterruptibly().channel();
}
private void checkReady() {
checkState(channel != null, "Must call setUpClient to finish NettyRule setup");
}
/** Test that custom setup to send message to current server sends right message */
public void assertReceivedMessage(String message) throws Exception {
assertThat(echoHandler.getRequestFuture().get()).isEqualTo(message);
}
/**
* Test that a message can go through, both inbound and outbound.
*
* <p>The client writes the message to the server, which echos it back and saves the string in its
* promise. The client receives the echo and saves it in its promise. All these activities happens
* in the I/O thread, and this call itself returns immediately.
*/
void assertThatMessagesWork() throws Exception {
checkReady();
assertThat(channel.isActive()).isTrue();
writeToChannelAndFlush(channel, "Hello, world!");
assertThat(echoHandler.getRequestFuture().get()).isEqualTo("Hello, world!");
assertThat(dumpHandler.getResponseFuture().get()).isEqualTo("Hello, world!");
}
Channel getChannel() {
checkReady();
return channel;
}
ThrowableSubject assertThatServerRootCause() {
checkReady();
return assertThat(
Throwables.getRootCause(
assertThrows(ExecutionException.class, () -> echoHandler.getRequestFuture().get())));
}
ThrowableSubject assertThatClientRootCause() {
checkReady();
return assertThat(
Throwables.getRootCause(
assertThrows(ExecutionException.class, () -> dumpHandler.getResponseFuture().get())));
}
@Override
protected void after() {
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully();
}
/**
* A handler that echoes back its inbound message. The message is also saved in a promise for
* inspection later.
*/
public static class EchoHandler extends ChannelInboundHandlerAdapter {
private final CompletableFuture<String> requestFuture = new CompletableFuture<>();
public Future<String> getRequestFuture() {
return requestFuture;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// In the test we only send messages of type ByteBuf.
assertThat(msg).isInstanceOf(ByteBuf.class);
String request = ((ByteBuf) msg).toString(UTF_8);
// After the message is written back to the client, fulfill the promise.
ChannelFuture unusedFuture =
ctx.writeAndFlush(msg).addListener(f -> requestFuture.complete(request));
}
/** Saves any inbound error as the cause of the promise failure. */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ChannelFuture unusedFuture =
ctx.channel().closeFuture().addListener(f -> requestFuture.completeExceptionally(cause));
}
}
/** A handler that dumps its inbound message to a promise that can be inspected later. */
private static class DumpHandler extends ChannelInboundHandlerAdapter {
private final CompletableFuture<String> responseFuture = new CompletableFuture<>();
Future<String> getResponseFuture() {
return responseFuture;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// In the test we only send messages of type ByteBuf.
assertThat(msg).isInstanceOf(ByteBuf.class);
String response = ((ByteBuf) msg).toString(UTF_8);
// There is no more use of this message, we should release its reference count so that it
// can be more effectively garbage collected by Netty.
ReferenceCountUtil.release(msg);
// Save the string in the promise and make it as complete.
responseFuture.complete(response);
}
/** Saves any inbound error into the failure cause of the promise. */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().closeFuture().addListener(f -> responseFuture.completeExceptionally(cause));
}
}
}
@@ -1,207 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.handler;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.monitoring.blackbox.handler.SslInitializerTestUtils.getKeyPair;
import static google.registry.monitoring.blackbox.handler.SslInitializerTestUtils.setUpSslChannel;
import static google.registry.monitoring.blackbox.handler.SslInitializerTestUtils.signKeyPair;
import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.connection.Protocol;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.SniHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
/**
* Unit tests for {@link SslClientInitializer}.
*
* <p>To validate that the handler accepts & rejects connections as expected, a test server and a
* test client are spun up, and both connect to the {@link LocalAddress} within the JVM. This avoids
* the overhead of routing traffic through the network layer, even if it were to go through
* loopback. It also alleviates the need to pick a free port to use.
*
* <p>The local addresses used in each test method must to be different, otherwise tests run in
* parallel may interfere with each other.
*/
@RunWith(Parameterized.class)
public class SslClientInitializerTest {
/** Fake host to test if the SSL engine gets the correct peer host. */
private static final String SSL_HOST = "www.example.tld";
/** Fake port to test if the SSL engine gets the correct peer port. */
private static final int SSL_PORT = 12345;
/** Fake protocol saved in channel attribute. */
private static final Protocol PROTOCOL =
Protocol.builder()
.setName("ssl")
.setPort(SSL_PORT)
.setHandlerProviders(ImmutableList.of())
.setPersistentConnection(false)
.build();
@Rule public NettyRule nettyRule = new NettyRule();
@Parameter(0)
public SslProvider sslProvider;
/** Saves the SNI hostname received by the server, if sent by the client. */
private String sniHostReceived;
// We do our best effort to test all available SSL providers.
@Parameters(name = "{0}")
public static SslProvider[] data() {
return OpenSsl.isAvailable()
? new SslProvider[] {SslProvider.JDK, SslProvider.OPENSSL}
: new SslProvider[] {SslProvider.JDK};
}
private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate certificate)
throws Exception {
SslContext sslContext = SslContextBuilder.forServer(privateKey, certificate).build();
return new SniHandler(
hostname -> {
sniHostReceived = hostname;
return sslContext;
});
}
@Test
public void testSuccess_swappedInitializerWithSslHandler() throws Exception {
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider);
EmbeddedChannel channel = new EmbeddedChannel();
channel.attr(PROTOCOL_KEY).set(PROTOCOL);
channel.attr(REMOTE_ADDRESS_KEY).set(SSL_HOST);
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslClientInitializer);
ChannelHandler firstHandler = pipeline.first();
assertThat(firstHandler.getClass()).isEqualTo(SslHandler.class);
SslHandler sslHandler = (SslHandler) firstHandler;
assertThat(sslHandler.engine().getPeerHost()).isEqualTo(SSL_HOST);
assertThat(sslHandler.engine().getPeerPort()).isEqualTo(SSL_PORT);
assertThat(channel.isActive()).isTrue();
}
@Test
public void testSuccess_protocolAttributeNotSet() {
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider);
EmbeddedChannel channel = new EmbeddedChannel();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslClientInitializer);
// Channel initializer swallows error thrown, and closes the connection.
assertThat(channel.isActive()).isFalse();
}
@Test
public void testFailure_defaultTrustManager_rejectSelfSignedCert() throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate(SSL_HOST);
LocalAddress localAddress =
new LocalAddress("DEFAULT_TRUST_MANAGER_REJECT_SELF_SIGNED_CERT_" + sslProvider);
nettyRule.setUpServer(localAddress, getServerHandler(ssc.key(), ssc.cert()));
SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider);
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
// The connection is now terminated, both the client side and the server side should get
// exceptions.
nettyRule.assertThatClientRootCause().isInstanceOf(CertPathBuilderException.class);
nettyRule.assertThatServerRootCause().isInstanceOf(SSLException.class);
assertThat(nettyRule.getChannel().isActive()).isFalse();
}
@Test
public void testSuccess_customTrustManager_acceptCertSignedByTrustedCa() throws Exception {
LocalAddress localAddress =
new LocalAddress("CUSTOM_TRUST_MANAGER_ACCEPT_CERT_SIGNED_BY_TRUSTED_CA_" + sslProvider);
// Generate a new key pair.
KeyPair keyPair = getKeyPair();
// Generate a self signed certificate, and use it to sign the key pair.
SelfSignedCertificate ssc = new SelfSignedCertificate();
X509Certificate cert = signKeyPair(ssc, keyPair, SSL_HOST);
// Set up the server to use the signed cert and private key to perform handshake;
PrivateKey privateKey = keyPair.getPrivate();
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
// Set up the client to trust the self signed cert used to sign the cert that server provides.
SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()});
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
setUpSslChannel(nettyRule.getChannel(), cert);
nettyRule.assertThatMessagesWork();
// Verify that the SNI extension is sent during handshake.
assertThat(sniHostReceived).isEqualTo(SSL_HOST);
}
@Test
public void testFailure_customTrustManager_wrongHostnameInCertificate() throws Exception {
LocalAddress localAddress =
new LocalAddress("CUSTOM_TRUST_MANAGER_WRONG_HOSTNAME_" + sslProvider);
// Generate a new key pair.
KeyPair keyPair = getKeyPair();
// Generate a self signed certificate, and use it to sign the key pair.
SelfSignedCertificate ssc = new SelfSignedCertificate();
X509Certificate cert = signKeyPair(ssc, keyPair, "wrong.com");
// Set up the server to use the signed cert and private key to perform handshake;
PrivateKey privateKey = keyPair.getPrivate();
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
// Set up the client to trust the self signed cert used to sign the cert that server provides.
SslClientInitializer<LocalChannel> sslClientInitializer =
new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()});
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
// When the client rejects the server cert due to wrong hostname, both the client and server
// should throw exceptions.
nettyRule.assertThatClientRootCause().isInstanceOf(CertificateException.class);
nettyRule.assertThatClientRootCause().hasMessageThat().contains(SSL_HOST);
nettyRule.assertThatServerRootCause().isInstanceOf(SSLException.class);
assertThat(nettyRule.getChannel().isActive()).isFalse();
}
}
@@ -1,108 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.handler;
import static com.google.common.truth.Truth.assertThat;
import io.netty.channel.Channel;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import javax.net.ssl.SSLSession;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
/** Utility class that provides methods used by {@link SslClientInitializerTest} */
public class SslInitializerTestUtils {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048, new SecureRandom());
return keyPairGenerator.generateKeyPair();
}
/**
* Signs the given key pair with the given self signed certificate.
*
* @return signed public key (of the key pair) certificate
*/
public static X509Certificate signKeyPair(
SelfSignedCertificate ssc, KeyPair keyPair, String hostname) throws Exception {
X500Name subjectDnName = new X500Name("CN=" + hostname);
BigInteger serialNumber = (BigInteger.valueOf(System.currentTimeMillis()));
X500Name issuerDnName = new X500Name(ssc.cert().getIssuerDN().getName());
Date from = Date.from(Instant.now().minus(Duration.ofDays(1)));
Date to = Date.from(Instant.now().plus(Duration.ofDays(1)));
SubjectPublicKeyInfo subPubKeyInfo =
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
AlgorithmIdentifier sigAlgId =
new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
ContentSigner sigGen =
new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
.build(PrivateKeyFactory.createKey(ssc.key().getEncoded()));
X509v3CertificateBuilder v3CertGen =
new X509v3CertificateBuilder(
issuerDnName, serialNumber, from, to, subjectDnName, subPubKeyInfo);
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
}
/**
* Verifies tha the SSL channel is established as expected, and also sends a message to the server
* and verifies if it is echoed back correctly.
*
* @param certs The certificate that the server should provide.
* @return The SSL session in current channel, can be used for further validation.
*/
static SSLSession setUpSslChannel(Channel channel, X509Certificate... certs) throws Exception {
SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
// Wait till the handshake is complete.
sslHandler.handshakeFuture().get();
assertThat(channel.isActive()).isTrue();
assertThat(sslHandler.handshakeFuture().isSuccess()).isTrue();
assertThat(sslHandler.engine().getSession().isValid()).isTrue();
assertThat(sslHandler.engine().getSession().getPeerCertificates())
.asList()
.containsExactlyElementsIn(certs);
// Returns the SSL session for further assertion.
return sslHandler.engine().getSession();
}
}
+5
View File
@@ -15,7 +15,12 @@
// Mapping from environment names to GCP projects.
// Replace the values with the names of your deployment environments.
// The projects to run your deployment Nomulus application.
rootProject.ext.projects = ['production': 'your-production-project',
'sandbox' : 'your-sandbox-project',
'alpha' : 'your-alpha-project',
'crash' : 'your-crash-project']
// The project to host your development/deployment infrastructure. It hosts
// things like release artifacts, CI/CD system, etc.
rootProject.ext.devProject = 'your-dev-project'
@@ -62,7 +62,9 @@ import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
* @see <a href="https://cloud.google.com/kms/">Cloud Key Management Service</a>
*/
@Module
public class CertificateModule {
public final class CertificateModule {
private CertificateModule() {}
/** Dagger qualifier to provide bindings related to the certificates that the server provides. */
@Qualifier
@@ -94,8 +96,7 @@ public class CertificateModule {
*/
private static <T, E> ImmutableList<E> filterAndConvert(
ImmutableList<Object> objects, Class<T> clazz, Function<T, E> converter) {
return objects
.stream()
return objects.stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.map(converter)
@@ -112,19 +113,20 @@ public class CertificateModule {
@Singleton
@Provides
static Supplier<X509Certificate[]> provideCertificatesSupplier(
@ServerCertificates Provider<X509Certificate[]> certificatesProvider, ProxyConfig config) {
static Supplier<ImmutableList<X509Certificate>> provideCertificatesSupplier(
@ServerCertificates Provider<ImmutableList<X509Certificate>> certificatesProvider,
ProxyConfig config) {
return memoizeWithExpiration(
certificatesProvider::get, config.serverCertificateCacheSeconds, SECONDS);
}
@Provides
@ServerCertificates
static X509Certificate[] provideCertificates(
static ImmutableList<X509Certificate> provideCertificates(
Environment env,
@Local Lazy<X509Certificate[]> localCertificates,
@Prod Lazy<X509Certificate[]> prodCertificates) {
return (env == Environment.LOCAL) ? localCertificates.get() : prodCertificates.get();
@Local Lazy<ImmutableList<X509Certificate>> localCertificates,
@Prod Lazy<ImmutableList<X509Certificate>> prodCertificates) {
return env == Environment.LOCAL ? localCertificates.get() : prodCertificates.get();
}
@Provides
@@ -133,7 +135,7 @@ public class CertificateModule {
Environment env,
@Local Lazy<PrivateKey> localPrivateKey,
@Prod Lazy<PrivateKey> prodPrivateKey) {
return (env == Environment.LOCAL) ? localPrivateKey.get() : prodPrivateKey.get();
return env == Environment.LOCAL ? localPrivateKey.get() : prodPrivateKey.get();
}
@Singleton
@@ -156,8 +158,8 @@ public class CertificateModule {
@Singleton
@Provides
@Local
static X509Certificate[] provideLocalCertificates(SelfSignedCertificate ssc) {
return new X509Certificate[] {ssc.cert()};
static ImmutableList<X509Certificate> provideLocalCertificates(SelfSignedCertificate ssc) {
return ImmutableList.of(ssc.cert());
}
@Provides
@@ -210,7 +212,7 @@ public class CertificateModule {
// This binding should not be used directly. Use the supplier binding instead.
@Provides
@Prod
static X509Certificate[] provideProdCertificates(
static ImmutableList<X509Certificate> provideProdCertificates(
@Named("pemObjects") ImmutableList<Object> pemObject) {
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");
Function<X509CertificateHolder, X509Certificate> certificateConverter =
@@ -224,7 +226,7 @@ public class CertificateModule {
};
ImmutableList<X509Certificate> certificates =
filterAndConvert(pemObject, X509CertificateHolder.class, certificateConverter);
checkState(certificates.size() != 0, "No certificates found in the pem file");
checkState(!certificates.isEmpty(), "No certificates found in the pem file");
X509Certificate lastCert = null;
for (X509Certificate cert : certificates) {
if (lastCert != null) {
@@ -236,8 +238,6 @@ public class CertificateModule {
}
lastCert = cert;
}
X509Certificate[] certificateArray = new X509Certificate[certificates.size()];
certificates.toArray(certificateArray);
return certificateArray;
return certificates;
}
}
@@ -53,7 +53,9 @@ import javax.inject.Singleton;
/** A module that provides the {@link FrontendProtocol} used for epp protocol. */
@Module
public class EppProtocolModule {
public final class EppProtocolModule {
private EppProtocolModule() {}
/** Dagger qualifier to provide epp protocol related handlers and other bindings. */
@Qualifier
@@ -159,7 +161,7 @@ public class EppProtocolModule {
static SslServerInitializer<NioSocketChannel> provideSslServerInitializer(
SslProvider sslProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<X509Certificate[]> certificatesSupplier) {
Supplier<ImmutableList<X509Certificate>> certificatesSupplier) {
return new SslServerInitializer<>(true, sslProvider, privateKeySupplier, certificatesSupplier);
}
@@ -14,6 +14,8 @@
package google.registry.proxy;
import static google.registry.networking.handler.SslClientInitializer.createSslClientInitializerWithSystemTrustStore;
import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
@@ -63,7 +65,7 @@ public class HttpsRelayProtocolModule {
@HttpsRelayProtocol
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
SslProvider sslProvider) {
return new SslClientInitializer<>(
return createSslClientInitializerWithSystemTrustStore(
sslProvider,
channel -> ((BackendProtocol) channel.attr(Protocol.PROTOCOL_KEY).get()).host(),
channel -> channel.attr(Protocol.PROTOCOL_KEY).get().port());
@@ -217,8 +217,7 @@ public class ProxyModule {
@Singleton
@Provides
@Named("accessToken")
static Supplier<String> provideAccessTokenSupplier(
GoogleCredentialsBundle credentialsBundle, ProxyConfig config) {
static Supplier<String> provideAccessTokenSupplier(GoogleCredentialsBundle credentialsBundle) {
return () -> {
GoogleCredentials credentials = credentialsBundle.getGoogleCredentials();
try {
@@ -329,14 +328,14 @@ public class ProxyModule {
@Singleton
@Component(
modules = {
ProxyModule.class,
CertificateModule.class,
HttpsRelayProtocolModule.class,
WhoisProtocolModule.class,
WebWhoisProtocolsModule.class,
EppProtocolModule.class,
HealthCheckProtocolModule.class,
MetricsModule.class
ProxyModule.class,
CertificateModule.class,
HttpsRelayProtocolModule.class,
WhoisProtocolModule.class,
WebWhoisProtocolsModule.class,
EppProtocolModule.class,
HealthCheckProtocolModule.class,
MetricsModule.class
})
interface ProxyComponent {
@@ -35,7 +35,9 @@ import javax.inject.Singleton;
/** A module that provides the {@link FrontendProtocol}s to redirect HTTP(S) web WHOIS requests. */
@Module
public class WebWhoisProtocolsModule {
public final class WebWhoisProtocolsModule {
private WebWhoisProtocolsModule() {}
/** Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. */
@Qualifier
@@ -54,7 +56,7 @@ public class WebWhoisProtocolsModule {
static FrontendProtocol provideHttpWhoisProtocol(
@HttpWhoisProtocol int httpWhoisPort,
@HttpWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
return google.registry.proxy.Protocol.frontendBuilder()
return Protocol.frontendBuilder()
.name(HTTP_PROTOCOL_NAME)
.port(httpWhoisPort)
.hasBackend(false)
@@ -68,7 +70,7 @@ public class WebWhoisProtocolsModule {
static FrontendProtocol provideHttpsWhoisProtocol(
@HttpsWhoisProtocol int httpsWhoisPort,
@HttpsWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
return google.registry.proxy.Protocol.frontendBuilder()
return Protocol.frontendBuilder()
.name(HTTPS_PROTOCOL_NAME)
.port(httpsWhoisPort)
.hasBackend(false)
@@ -110,15 +112,13 @@ public class WebWhoisProtocolsModule {
@Provides
@HttpWhoisProtocol
static WebWhoisRedirectHandler provideHttpRedirectHandler(
google.registry.proxy.ProxyConfig config) {
static WebWhoisRedirectHandler provideHttpRedirectHandler(ProxyConfig config) {
return new WebWhoisRedirectHandler(false, config.webWhois.redirectHost);
}
@Provides
@HttpsWhoisProtocol
static WebWhoisRedirectHandler provideHttpsRedirectHandler(
google.registry.proxy.ProxyConfig config) {
static WebWhoisRedirectHandler provideHttpsRedirectHandler(ProxyConfig config) {
return new WebWhoisRedirectHandler(true, config.webWhois.redirectHost);
}
@@ -133,7 +133,7 @@ public class WebWhoisProtocolsModule {
static SslServerInitializer<NioSocketChannel> provideSslServerInitializer(
SslProvider sslProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<X509Certificate[]> certificatesSupplier) {
Supplier<ImmutableList<X509Certificate>> certificatesSupplier) {
return new SslServerInitializer<>(false, sslProvider, privateKeySupplier, certificatesSupplier);
}
}
@@ -20,6 +20,7 @@ import static google.registry.networking.handler.SslInitializerTestUtils.signKey
import static google.registry.testing.JUnitBackports.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import dagger.Component;
import dagger.Module;
import dagger.Provides;
@@ -79,7 +80,7 @@ public class CertificateModuleTest {
byte[] pemBytes = getPemBytes(cert, ssc.cert(), key);
component = createComponent(pemBytes);
assertThat(component.privateKey()).isEqualTo(key);
assertThat(component.certificates()).asList().containsExactly(cert, ssc.cert()).inOrder();
assertThat(component.certificates()).containsExactly(cert, ssc.cert()).inOrder();
}
@Test
@@ -87,7 +88,7 @@ public class CertificateModuleTest {
byte[] pemBytes = getPemBytes(cert, key, ssc.cert());
component = createComponent(pemBytes);
assertThat(component.privateKey()).isEqualTo(key);
assertThat(component.certificates()).asList().containsExactly(cert, ssc.cert()).inOrder();
assertThat(component.certificates()).containsExactly(cert, ssc.cert()).inOrder();
}
@Test
@@ -131,13 +132,13 @@ public class CertificateModuleTest {
private final byte[] pemBytes;
PemBytesModule(byte[] pemBytes) {
this.pemBytes = pemBytes;
this.pemBytes = pemBytes.clone();
}
@Provides
@Named("pemBytes")
byte[] providePemBytes() {
return pemBytes;
return pemBytes.clone();
}
}
@@ -156,6 +157,6 @@ public class CertificateModuleTest {
PrivateKey privateKey();
@Prod
X509Certificate[] certificates();
ImmutableList<X509Certificate> certificates();
}
}
+4 -4
View File
@@ -15,17 +15,17 @@ module "proxy" {
}
output "proxy_service_account" {
value = "${module.proxy.proxy_service_account}"
value = module.proxy.proxy_service_account
}
output "proxy_name_servers" {
value = "${module.proxy.proxy_name_servers}"
value = module.proxy.proxy_name_servers
}
output "proxy_instance_groups" {
value = "${module.proxy.proxy_instance_groups}"
value = module.proxy.proxy_instance_groups
}
output "proxy_ip_addresses" {
value = "${module.proxy.proxy_ip_addresses}"
value = module.proxy.proxy_ip_addresses
}
+1 -1
View File
@@ -1,4 +1,4 @@
provider "google" {
version = ">= 1.13.0"
project = "${var.proxy_project_name}"
project = var.proxy_project_name
}
+2 -2
View File
@@ -1,10 +1,10 @@
resource "google_storage_bucket" "proxy_certificate" {
name = "${var.proxy_certificate_bucket}"
name = var.proxy_certificate_bucket
storage_class = "MULTI_REGIONAL"
}
resource "google_storage_bucket_iam_member" "certificate_viewer" {
bucket = "${google_storage_bucket.proxy_certificate.name}"
bucket = google_storage_bucket.proxy_certificate.name
role = "roles/storage.objectViewer"
member = "serviceAccount:${google_service_account.proxy_service_account.email}"
}
+6 -6
View File
@@ -1,25 +1,25 @@
module "proxy_gke_americas" {
source = "./gke"
proxy_cluster_region = "americas"
proxy_service_account_email = "${google_service_account.proxy_service_account.email}"
proxy_service_account_email = google_service_account.proxy_service_account.email
}
module "proxy_gke_emea" {
source = "./gke"
proxy_cluster_region = "emea"
proxy_service_account_email = "${google_service_account.proxy_service_account.email}"
proxy_service_account_email = google_service_account.proxy_service_account.email
}
module "proxy_gke_apac" {
source = "./gke"
proxy_cluster_region = "apac"
proxy_service_account_email = "${google_service_account.proxy_service_account.email}"
proxy_service_account_email = google_service_account.proxy_service_account.email
}
locals {
"proxy_instance_groups" = {
americas = "${module.proxy_gke_americas.proxy_instance_group}"
emea = "${module.proxy_gke_emea.proxy_instance_group}"
apac = "${module.proxy_gke_apac.proxy_instance_group}"
americas = module.proxy_gke_americas.proxy_instance_group
emea = module.proxy_gke_emea.proxy_instance_group
apac = module.proxy_gke_apac.proxy_instance_group
}
}
+2 -2
View File
@@ -4,7 +4,7 @@ locals {
resource "google_container_cluster" "proxy_cluster" {
name = "proxy-cluster-${var.proxy_cluster_region}"
zone = "${local.proxy_cluster_zone}"
zone = local.proxy_cluster_zone
timeouts {
update = "30m"
@@ -19,7 +19,7 @@ resource "google_container_cluster" "proxy_cluster" {
"proxy-cluster",
]
service_account = "${var.proxy_service_account_email}"
service_account = var.proxy_service_account_email
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform",
+2 -2
View File
@@ -1,10 +1,10 @@
resource "google_kms_key_ring" "proxy_key_ring" {
name = "${var.proxy_key_ring}"
name = var.proxy_key_ring
location = "global"
}
resource "google_kms_crypto_key" "proxy_key" {
name = "${var.proxy_key}"
name = var.proxy_key
key_ring = google_kms_key_ring.proxy_key_ring.self_link
lifecycle {
+8 -8
View File
@@ -5,17 +5,17 @@ resource "google_dns_managed_zone" "proxy_domain" {
module "proxy_networking" {
source = "./networking"
proxy_instance_groups = "${local.proxy_instance_groups}"
proxy_ports = "${var.proxy_ports}"
proxy_domain = "${google_dns_managed_zone.proxy_domain.name}"
proxy_domain_name = "${google_dns_managed_zone.proxy_domain.dns_name}"
proxy_instance_groups = local.proxy_instance_groups
proxy_ports = var.proxy_ports
proxy_domain = google_dns_managed_zone.proxy_domain.name
proxy_domain_name = google_dns_managed_zone.proxy_domain.dns_name
}
module "proxy_networking_canary" {
source = "./networking"
proxy_instance_groups = "${local.proxy_instance_groups}"
proxy_instance_groups = local.proxy_instance_groups
suffix = "-canary"
proxy_ports = "${var.proxy_ports_canary}"
proxy_domain = "${google_dns_managed_zone.proxy_domain.name}"
proxy_domain_name = "${google_dns_managed_zone.proxy_domain.dns_name}"
proxy_ports = var.proxy_ports_canary
proxy_domain = google_dns_managed_zone.proxy_domain.name
proxy_domain_name = google_dns_managed_zone.proxy_domain.dns_name
}
+8 -8
View File
@@ -2,30 +2,30 @@ resource "google_dns_record_set" "proxy_epp_a_record" {
name = "epp${var.suffix}.${var.proxy_domain_name}"
type = "A"
ttl = 300
managed_zone = "${var.proxy_domain}"
rrdatas = ["${google_compute_global_address.proxy_ipv4_address.address}"]
managed_zone = var.proxy_domain
rrdatas = [google_compute_global_address.proxy_ipv4_address.address]
}
resource "google_dns_record_set" "proxy_epp_aaaa_record" {
name = "epp${var.suffix}.${var.proxy_domain_name}"
type = "AAAA"
ttl = 300
managed_zone = "${var.proxy_domain}"
rrdatas = ["${google_compute_global_address.proxy_ipv6_address.address}"]
managed_zone = var.proxy_domain
rrdatas = [google_compute_global_address.proxy_ipv6_address.address]
}
resource "google_dns_record_set" "proxy_whois_a_record" {
name = "whois${var.suffix}.${var.proxy_domain_name}"
type = "A"
ttl = 300
managed_zone = "${var.proxy_domain}"
rrdatas = ["${google_compute_global_address.proxy_ipv4_address.address}"]
managed_zone = var.proxy_domain
rrdatas = [google_compute_global_address.proxy_ipv4_address.address]
}
resource "google_dns_record_set" "proxy_whois_aaaa_record" {
name = "whois${var.suffix}.${var.proxy_domain_name}"
type = "AAAA"
ttl = 300
managed_zone = "${var.proxy_domain}"
rrdatas = ["${google_compute_global_address.proxy_ipv6_address.address}"]
managed_zone = var.proxy_domain
rrdatas = [google_compute_global_address.proxy_ipv6_address.address]
}
@@ -56,7 +56,7 @@ resource "google_compute_health_check" "proxy_http_health_check" {
resource "google_compute_url_map" "proxy_url_map" {
name = "proxy-url-map${var.suffix}"
default_service = "${google_compute_backend_service.http_whois_backend_service.self_link}"
default_service = google_compute_backend_service.http_whois_backend_service.self_link
}
resource "google_compute_backend_service" "epp_backend_service" {
@@ -78,7 +78,7 @@ resource "google_compute_backend_service" "epp_backend_service" {
}
health_checks = [
"${google_compute_health_check.proxy_health_check.self_link}",
google_compute_health_check.proxy_health_check.self_link,
]
}
@@ -101,7 +101,7 @@ resource "google_compute_backend_service" "whois_backend_service" {
}
health_checks = [
"${google_compute_health_check.proxy_health_check.self_link}",
google_compute_health_check.proxy_health_check.self_link,
]
}
@@ -124,7 +124,7 @@ resource "google_compute_backend_service" "https_whois_backend_service" {
}
health_checks = [
"${google_compute_health_check.proxy_health_check.self_link}",
google_compute_health_check.proxy_health_check.self_link,
]
}
@@ -147,84 +147,84 @@ resource "google_compute_backend_service" "http_whois_backend_service" {
}
health_checks = [
"${google_compute_health_check.proxy_http_health_check.self_link}",
google_compute_health_check.proxy_http_health_check.self_link,
]
}
resource "google_compute_target_tcp_proxy" "epp_tcp_proxy" {
name = "epp-tcp-proxy${var.suffix}"
proxy_header = "PROXY_V1"
backend_service = "${google_compute_backend_service.epp_backend_service.self_link}"
backend_service = google_compute_backend_service.epp_backend_service.self_link
}
resource "google_compute_target_tcp_proxy" "whois_tcp_proxy" {
name = "whois-tcp-proxy${var.suffix}"
proxy_header = "PROXY_V1"
backend_service = "${google_compute_backend_service.whois_backend_service.self_link}"
backend_service = google_compute_backend_service.whois_backend_service.self_link
}
resource "google_compute_target_tcp_proxy" "https_whois_tcp_proxy" {
name = "https-whois-tcp-proxy${var.suffix}"
backend_service = "${google_compute_backend_service.https_whois_backend_service.self_link}"
backend_service = google_compute_backend_service.https_whois_backend_service.self_link
}
resource "google_compute_target_http_proxy" "http_whois_http_proxy" {
name = "http-whois-tcp-proxy${var.suffix}"
url_map = "${google_compute_url_map.proxy_url_map.self_link}"
url_map = google_compute_url_map.proxy_url_map.self_link
}
resource "google_compute_global_forwarding_rule" "epp_ipv4_forwarding_rule" {
name = "epp-ipv4-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv4_address.address}"
target = "${google_compute_target_tcp_proxy.epp_tcp_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv4_address.address
target = google_compute_target_tcp_proxy.epp_tcp_proxy.self_link
port_range = "700"
}
resource "google_compute_global_forwarding_rule" "epp_ipv6_forwarding_rule" {
name = "epp-ipv6-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv6_address.address}"
target = "${google_compute_target_tcp_proxy.epp_tcp_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv6_address.address
target = google_compute_target_tcp_proxy.epp_tcp_proxy.self_link
port_range = "700"
}
resource "google_compute_global_forwarding_rule" "whois_ipv4_forwarding_rule" {
name = "whois-ipv4-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv4_address.address}"
target = "${google_compute_target_tcp_proxy.whois_tcp_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv4_address.address
target = google_compute_target_tcp_proxy.whois_tcp_proxy.self_link
port_range = "43"
}
resource "google_compute_global_forwarding_rule" "whois_ipv6_forwarding_rule" {
name = "whois-ipv6-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv6_address.address}"
target = "${google_compute_target_tcp_proxy.whois_tcp_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv6_address.address
target = google_compute_target_tcp_proxy.whois_tcp_proxy.self_link
port_range = "43"
}
resource "google_compute_global_forwarding_rule" "https_whois_ipv4_forwarding_rule" {
name = "https-whois-ipv4-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv4_address.address}"
target = "${google_compute_target_tcp_proxy.https_whois_tcp_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv4_address.address
target = google_compute_target_tcp_proxy.https_whois_tcp_proxy.self_link
port_range = "443"
}
resource "google_compute_global_forwarding_rule" "https_whois_ipv6_forwarding_rule" {
name = "https-whois-ipv6-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv6_address.address}"
target = "${google_compute_target_tcp_proxy.https_whois_tcp_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv6_address.address
target = google_compute_target_tcp_proxy.https_whois_tcp_proxy.self_link
port_range = "443"
}
resource "google_compute_global_forwarding_rule" "http_whois_ipv4_forwarding_rule" {
name = "http-whois-ipv4-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv4_address.address}"
target = "${google_compute_target_http_proxy.http_whois_http_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv4_address.address
target = google_compute_target_http_proxy.http_whois_http_proxy.self_link
port_range = "80"
}
resource "google_compute_global_forwarding_rule" "http_whois_ipv6_forwarding_rule" {
name = "http-whois-ipv6-forwarding-rule${var.suffix}"
ip_address = "${google_compute_global_address.proxy_ipv6_address.address}"
target = "${google_compute_target_http_proxy.http_whois_http_proxy.self_link}"
ip_address = google_compute_global_address.proxy_ipv6_address.address
target = google_compute_target_http_proxy.http_whois_http_proxy.self_link
port_range = "80"
}
+2 -2
View File
@@ -1,7 +1,7 @@
output "proxy_ipv4_address" {
value = "${google_compute_global_address.proxy_ipv4_address.address}"
value = google_compute_global_address.proxy_ipv4_address.address
}
output "proxy_ipv6_address" {
value = "${google_compute_global_address.proxy_ipv6_address.address}"
value = google_compute_global_address.proxy_ipv6_address.address
}
+8 -8
View File
@@ -1,23 +1,23 @@
output "proxy_name_servers" {
value = "${google_dns_managed_zone.proxy_domain.name_servers}"
value = google_dns_managed_zone.proxy_domain.name_servers
}
output "proxy_instance_groups" {
value = "${local.proxy_instance_groups}"
value = local.proxy_instance_groups
}
output "proxy_service_account" {
value = {
email = "${google_service_account.proxy_service_account.email}"
client_id = "${google_service_account.proxy_service_account.unique_id}"
email = google_service_account.proxy_service_account.email
client_id = google_service_account.proxy_service_account.unique_id
}
}
output "proxy_ip_addresses" {
value = {
ipv4 = "${module.proxy_networking.proxy_ipv4_address}"
ipv6 = "${module.proxy_networking.proxy_ipv6_address}"
ipv4_canary = "${module.proxy_networking_canary.proxy_ipv4_address}"
ipv6_canary = "${module.proxy_networking_canary.proxy_ipv6_address}"
ipv4 = module.proxy_networking.proxy_ipv4_address
ipv6 = module.proxy_networking.proxy_ipv6_address
ipv4_canary = module.proxy_networking_canary.proxy_ipv4_address
ipv6_canary = module.proxy_networking_canary.proxy_ipv6_address
}
}