mirror of
https://github.com/google/nomulus
synced 2026-06-09 16:33:02 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05d56fe1a2 | |||
| e318f47fc6 | |||
| cc5f62587e | |||
| 02846bcbdd | |||
| c920f709ef |
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+191
-43
@@ -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(
|
||||
|
||||
+250
-56
@@ -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
@@ -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
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+50
-11
@@ -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));
|
||||
|
||||
|
||||
+9
-4
@@ -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.
|
||||
|
||||
+61
-11
@@ -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
|
||||
|
||||
+31
-13
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
-116
@@ -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);
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -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
|
||||
|
||||
+4
-8
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
-207
@@ -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();
|
||||
}
|
||||
}
|
||||
-108
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
provider "google" {
|
||||
version = ">= 1.13.0"
|
||||
project = "${var.proxy_project_name}"
|
||||
project = var.proxy_project_name
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user