diff --git a/java/google/registry/env/common/tools/WEB-INF/web.xml b/java/google/registry/env/common/tools/WEB-INF/web.xml index eb7dde515..829c5ddcf 100644 --- a/java/google/registry/env/common/tools/WEB-INF/web.xml +++ b/java/google/registry/env/common/tools/WEB-INF/web.xml @@ -48,6 +48,11 @@ /_dr/loadtest + + tools-servlet + /_dr/admin/downloadCredential + + Remote API Servlet diff --git a/java/google/registry/keyring/kms/KmsKeyring.java b/java/google/registry/keyring/kms/KmsKeyring.java index 75a2f97db..06dbaf55e 100644 --- a/java/google/registry/keyring/kms/KmsKeyring.java +++ b/java/google/registry/keyring/kms/KmsKeyring.java @@ -41,29 +41,32 @@ import org.bouncycastle.openpgp.PGPPublicKey; */ public class KmsKeyring implements Keyring { - enum PrivateKeyLabel { + /** Key labels for private key secrets. */ + public enum PrivateKeyLabel { BRDA_SIGNING_PRIVATE, RDE_SIGNING_PRIVATE, RDE_STAGING_PRIVATE; - String getLabel() { + public String getLabel() { return UPPER_UNDERSCORE.to(LOWER_HYPHEN, name()); } } - enum PublicKeyLabel { + /** Key labels for public key secrets. */ + public enum PublicKeyLabel { BRDA_RECEIVER_PUBLIC, BRDA_SIGNING_PUBLIC, RDE_RECEIVER_PUBLIC, RDE_SIGNING_PUBLIC, RDE_STAGING_PUBLIC; - String getLabel() { + public String getLabel() { return UPPER_UNDERSCORE.to(LOWER_HYPHEN, name()); } } - enum StringKeyLabel { + /** Key labels for string secrets. */ + public enum StringKeyLabel { SAFE_BROWSING_API_KEY, ICANN_REPORTING_PASSWORD_STRING, JSON_CREDENTIAL_STRING, @@ -73,7 +76,7 @@ public class KmsKeyring implements Keyring { RDE_SSH_CLIENT_PRIVATE_STRING, RDE_SSH_CLIENT_PUBLIC_STRING; - String getLabel() { + public String getLabel() { return UPPER_UNDERSCORE.to(LOWER_HYPHEN, name()); } } diff --git a/java/google/registry/module/tools/ToolsRequestComponent.java b/java/google/registry/module/tools/ToolsRequestComponent.java index 633a38709..b0ae7d38c 100644 --- a/java/google/registry/module/tools/ToolsRequestComponent.java +++ b/java/google/registry/module/tools/ToolsRequestComponent.java @@ -32,6 +32,7 @@ import google.registry.request.RequestScope; import google.registry.tools.server.CreateGroupsAction; import google.registry.tools.server.CreatePremiumListAction; import google.registry.tools.server.DeleteEntityAction; +import google.registry.tools.server.DownloadServiceAccountCredentialAction; import google.registry.tools.server.GenerateZoneFilesAction; import google.registry.tools.server.KillAllCommitLogsAction; import google.registry.tools.server.KillAllEppResourcesAction; @@ -64,6 +65,7 @@ import google.registry.tools.server.VerifyOteAction; interface ToolsRequestComponent { CreateGroupsAction createGroupsAction(); CreatePremiumListAction createPremiumListAction(); + DownloadServiceAccountCredentialAction downloadServiceAccountCredentialAction(); DeleteEntityAction deleteEntityAction(); EppToolAction eppToolAction(); FlowComponent.Builder flowComponentBuilder(); diff --git a/java/google/registry/tools/server/BUILD b/java/google/registry/tools/server/BUILD index 0c2fa9cda..ba98ef8e4 100644 --- a/java/google/registry/tools/server/BUILD +++ b/java/google/registry/tools/server/BUILD @@ -14,6 +14,7 @@ java_library( "//java/google/registry/flows", "//java/google/registry/gcs", "//java/google/registry/groups", + "//java/google/registry/keyring/kms", "//java/google/registry/mapreduce", "//java/google/registry/mapreduce/inputs", "//java/google/registry/model", diff --git a/java/google/registry/tools/server/DownloadServiceAccountCredentialAction.java b/java/google/registry/tools/server/DownloadServiceAccountCredentialAction.java new file mode 100644 index 000000000..9ac7e11a6 --- /dev/null +++ b/java/google/registry/tools/server/DownloadServiceAccountCredentialAction.java @@ -0,0 +1,70 @@ +// Copyright 2018 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.tools.server; + +import static google.registry.request.Action.Method.GET; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + +import com.google.common.flogger.FluentLogger; +import com.google.common.io.BaseEncoding; +import com.google.common.net.MediaType; +import google.registry.keyring.kms.KmsKeyring.StringKeyLabel; +import google.registry.request.Action; +import google.registry.request.Response; +import google.registry.request.auth.Auth; +import java.util.function.Function; +import javax.inject.Inject; +import javax.inject.Named; + +/** + * An action that returns KMS encrypted service account credential in its payload. + * + *

This credential can be stored locally, and a {@code RemoteApiOptions} can use it to initialize + * an {@code AppEngineConnection}. + */ +@Action( + path = DownloadServiceAccountCredentialAction.PATH, + method = {GET}, + auth = Auth.AUTH_INTERNAL_OR_ADMIN) +public class DownloadServiceAccountCredentialAction implements Runnable { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + public static final String PATH = "/_dr/admin/downloadCredential"; + + @Inject + @Named("encryptedDataRetriever") + Function encryptedDataRetriever; + + @Inject Response response; + + @Inject + DownloadServiceAccountCredentialAction() {} + + @Override + public void run() { + try { + String encryptedJsonCredential = + encryptedDataRetriever.apply(StringKeyLabel.JSON_CREDENTIAL_STRING.getLabel()); + response.setContentType(MediaType.APPLICATION_BINARY); + response.setPayload(BaseEncoding.base64().encode(encryptedJsonCredential.getBytes(UTF_8))); + } catch (Exception e) { + logger.atSevere().withCause(e).log("Cannot retrieve encrypted service account credential."); + response.setPayload(e.getMessage()); + response.setStatus(SC_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/javatests/google/registry/module/tools/testdata/tools_routing.txt b/javatests/google/registry/module/tools/testdata/tools_routing.txt index 40756e6f4..e36d76689 100644 --- a/javatests/google/registry/module/tools/testdata/tools_routing.txt +++ b/javatests/google/registry/module/tools/testdata/tools_routing.txt @@ -1,21 +1,22 @@ -PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY -/_dr/admin/createGroups CreateGroupsAction POST n INTERNAL,API APP ADMIN -/_dr/admin/createPremiumList CreatePremiumListAction POST n INTERNAL,API APP ADMIN -/_dr/admin/deleteEntity DeleteEntityAction GET n INTERNAL,API APP ADMIN -/_dr/admin/list/domains ListDomainsAction GET,POST n INTERNAL,API APP ADMIN -/_dr/admin/list/hosts ListHostsAction GET,POST n INTERNAL,API APP ADMIN -/_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n INTERNAL,API APP ADMIN -/_dr/admin/list/registrars ListRegistrarsAction GET,POST n INTERNAL,API APP ADMIN -/_dr/admin/list/reservedLists ListReservedListsAction GET,POST n INTERNAL,API APP ADMIN -/_dr/admin/list/tlds ListTldsAction GET,POST n INTERNAL,API APP ADMIN -/_dr/admin/updatePremiumList UpdatePremiumListAction POST n INTERNAL,API APP ADMIN -/_dr/admin/verifyOte VerifyOteAction POST n INTERNAL,API APP ADMIN -/_dr/epptool EppToolAction POST n INTERNAL,API APP ADMIN -/_dr/loadtest LoadTestAction POST y INTERNAL,API APP ADMIN -/_dr/task/generateZoneFiles GenerateZoneFilesAction POST n INTERNAL,API APP ADMIN -/_dr/task/killAllCommitLogs KillAllCommitLogsAction POST n INTERNAL APP IGNORED -/_dr/task/killAllEppResources KillAllEppResourcesAction POST n INTERNAL APP IGNORED -/_dr/task/pollMapreduce PollMapreduceAction POST n INTERNAL APP IGNORED -/_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n INTERNAL,API APP ADMIN -/_dr/task/resaveAllHistoryEntries ResaveAllHistoryEntriesAction GET n INTERNAL,API APP ADMIN -/_dr/task/restoreCommitLogs RestoreCommitLogsAction POST y INTERNAL,API APP ADMIN +PATH CLASS METHODS OK AUTH_METHODS MIN USER_POLICY +/_dr/admin/createGroups CreateGroupsAction POST n INTERNAL,API APP ADMIN +/_dr/admin/createPremiumList CreatePremiumListAction POST n INTERNAL,API APP ADMIN +/_dr/admin/deleteEntity DeleteEntityAction GET n INTERNAL,API APP ADMIN +/_dr/admin/downloadCredential DownloadServiceAccountCredentialAction GET n INTERNAL,API APP ADMIN +/_dr/admin/list/domains ListDomainsAction GET,POST n INTERNAL,API APP ADMIN +/_dr/admin/list/hosts ListHostsAction GET,POST n INTERNAL,API APP ADMIN +/_dr/admin/list/premiumLists ListPremiumListsAction GET,POST n INTERNAL,API APP ADMIN +/_dr/admin/list/registrars ListRegistrarsAction GET,POST n INTERNAL,API APP ADMIN +/_dr/admin/list/reservedLists ListReservedListsAction GET,POST n INTERNAL,API APP ADMIN +/_dr/admin/list/tlds ListTldsAction GET,POST n INTERNAL,API APP ADMIN +/_dr/admin/updatePremiumList UpdatePremiumListAction POST n INTERNAL,API APP ADMIN +/_dr/admin/verifyOte VerifyOteAction POST n INTERNAL,API APP ADMIN +/_dr/epptool EppToolAction POST n INTERNAL,API APP ADMIN +/_dr/loadtest LoadTestAction POST y INTERNAL,API APP ADMIN +/_dr/task/generateZoneFiles GenerateZoneFilesAction POST n INTERNAL,API APP ADMIN +/_dr/task/killAllCommitLogs KillAllCommitLogsAction POST n INTERNAL APP IGNORED +/_dr/task/killAllEppResources KillAllEppResourcesAction POST n INTERNAL APP IGNORED +/_dr/task/pollMapreduce PollMapreduceAction POST n INTERNAL APP IGNORED +/_dr/task/refreshDnsForAllDomains RefreshDnsForAllDomainsAction GET n INTERNAL,API APP ADMIN +/_dr/task/resaveAllHistoryEntries ResaveAllHistoryEntriesAction GET n INTERNAL,API APP ADMIN +/_dr/task/restoreCommitLogs RestoreCommitLogsAction POST y INTERNAL,API APP ADMIN diff --git a/javatests/google/registry/tools/server/DownloadServiceAccountCredentialActionTest.java b/javatests/google/registry/tools/server/DownloadServiceAccountCredentialActionTest.java new file mode 100644 index 000000000..ffbbef651 --- /dev/null +++ b/javatests/google/registry/tools/server/DownloadServiceAccountCredentialActionTest.java @@ -0,0 +1,66 @@ +// Copyright 2018 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.tools.server; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_OK; + +import com.google.common.io.BaseEncoding; +import com.google.common.net.MediaType; +import google.registry.testing.FakeResponse; +import java.util.function.Function; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link google.registry.tools.server.DownloadServiceAccountCredentialAction}. */ +@RunWith(JUnit4.class) +public class DownloadServiceAccountCredentialActionTest { + + private final DownloadServiceAccountCredentialAction action = + new DownloadServiceAccountCredentialAction(); + private final FakeResponse response = new FakeResponse(); + private final Function encryptedDataRetriever = input -> input + "_mohaha"; + + @Before + public void setUp() { + action.response = response; + action.encryptedDataRetriever = encryptedDataRetriever; + } + + @Test + public void testSuccess_returnServiceAccountCredential() { + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_OK); + assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_BINARY); + assertThat(new String(BaseEncoding.base64().decode(response.getPayload()), UTF_8)) + .isEqualTo("json-credential-string_mohaha"); + } + + @Test + public void testFailure_cannotGetEncryptedCredential() { + action.encryptedDataRetriever = + input -> { + throw new RuntimeException("Something went wrong."); + }; + action.run(); + assertThat(response.getStatus()).isEqualTo(SC_INTERNAL_SERVER_ERROR); + assertThat(response.getContentType()).isEqualTo(MediaType.HTML_UTF_8); + assertThat(response.getPayload()).isEqualTo("Something went wrong."); + } +}