From 15a15a4667b58911c2b8c49b5d3c94fbf024b711 Mon Sep 17 00:00:00 2001 From: Weimin Yu Date: Mon, 1 May 2023 11:23:19 -0400 Subject: [PATCH] Prepare switch of credential annotation (#2014) * Prepare switch of credential annotation Prepare the switch from DefaultCredential to ApplicationCredential. In nomulus tools, start using the new annotation. This is tested by successfully using the nomulus curl command, which actually needs a valid credential to work. For remaining use cases of the old annotation in Nomulus server, add some code that relies on the new credential to work. Once these code are tested in sandbox and production, we will switch the annotations. --- .../google/registry/batch/BatchModule.java | 8 - .../batch/CannedScriptExecutionAction.java | 27 +-- .../batch/cannedscript/CannedScripts.java | 199 ++++++++++++++++++ .../batch/cannedscript/GroupsApiChecker.java | 122 ----------- .../registry/tools/RequestFactoryModule.java | 4 +- 5 files changed, 208 insertions(+), 152 deletions(-) create mode 100644 core/src/main/java/google/registry/batch/cannedscript/CannedScripts.java delete mode 100644 core/src/main/java/google/registry/batch/cannedscript/GroupsApiChecker.java diff --git a/core/src/main/java/google/registry/batch/BatchModule.java b/core/src/main/java/google/registry/batch/BatchModule.java index 95b64a88a..ece51133d 100644 --- a/core/src/main/java/google/registry/batch/BatchModule.java +++ b/core/src/main/java/google/registry/batch/BatchModule.java @@ -17,7 +17,6 @@ package google.registry.batch; import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME; import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESAVE_TIMES; import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY; -import static google.registry.batch.CannedScriptExecutionAction.SCRIPT_PARAM; import static google.registry.request.RequestParameters.extractBooleanParameter; import static google.registry.request.RequestParameters.extractIntParameter; import static google.registry.request.RequestParameters.extractLongParameter; @@ -139,11 +138,4 @@ public class BatchModule { static boolean provideIsDryRun(HttpServletRequest req) { return extractBooleanParameter(req, PARAM_DRY_RUN); } - - // TODO(b/234424397): remove method after credential changes are rolled out. - @Provides - @Parameter(SCRIPT_PARAM) - static String provideScriptName(HttpServletRequest req) { - return extractRequiredParameter(req, SCRIPT_PARAM); - } } diff --git a/core/src/main/java/google/registry/batch/CannedScriptExecutionAction.java b/core/src/main/java/google/registry/batch/CannedScriptExecutionAction.java index f62098bed..1cd41d2ec 100644 --- a/core/src/main/java/google/registry/batch/CannedScriptExecutionAction.java +++ b/core/src/main/java/google/registry/batch/CannedScriptExecutionAction.java @@ -16,11 +16,9 @@ package google.registry.batch; import static google.registry.request.Action.Method.POST; -import com.google.common.collect.ImmutableMap; import com.google.common.flogger.FluentLogger; -import google.registry.batch.cannedscript.GroupsApiChecker; +import google.registry.batch.cannedscript.CannedScripts; import google.registry.request.Action; -import google.registry.request.Parameter; import google.registry.request.auth.Auth; import javax.inject.Inject; @@ -35,7 +33,7 @@ import javax.inject.Inject; *

This action can be invoked using the Nomulus CLI command: {@code nomulus -e ${env} curl * --service BACKEND -X POST -u '/_dr/task/executeCannedScript?script=${script_name}'} */ -// TODO(b/234424397): remove class after credential changes are rolled out. +// TODO(b/277239043): remove class after credential changes are rolled out. @Action( service = Action.Service.BACKEND, path = "/_dr/task/executeCannedScript", @@ -45,29 +43,18 @@ import javax.inject.Inject; public class CannedScriptExecutionAction implements Runnable { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - static final String SCRIPT_PARAM = "script"; - - static final ImmutableMap SCRIPTS = - ImmutableMap.of("runGroupsApiChecks", GroupsApiChecker::runGroupsApiChecks); - - private final String scriptName; - @Inject - CannedScriptExecutionAction(@Parameter(SCRIPT_PARAM) String scriptName) { - logger.atInfo().log("Received request to run script %s", scriptName); - this.scriptName = scriptName; + CannedScriptExecutionAction() { + logger.atInfo().log("Received request to run scripts."); } @Override public void run() { - if (!SCRIPTS.containsKey(scriptName)) { - throw new IllegalArgumentException("Script not found:" + scriptName); - } try { - SCRIPTS.get(scriptName).run(); - logger.atInfo().log("Finished running %s.", scriptName); + CannedScripts.runAllChecks(); + logger.atInfo().log("Finished running scripts."); } catch (Throwable t) { - logger.atWarning().withCause(t).log("Error executing %s", scriptName); + logger.atWarning().withCause(t).log("Error executing scripts."); throw new RuntimeException("Execution failed."); } } diff --git a/core/src/main/java/google/registry/batch/cannedscript/CannedScripts.java b/core/src/main/java/google/registry/batch/cannedscript/CannedScripts.java new file mode 100644 index 000000000..f99d81e2d --- /dev/null +++ b/core/src/main/java/google/registry/batch/cannedscript/CannedScripts.java @@ -0,0 +1,199 @@ +// Copyright 2023 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.batch.cannedscript; + +import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.services.bigquery.Bigquery; +import com.google.api.services.dataflow.Dataflow; +import com.google.api.services.dns.Dns; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.cloud.tasks.v2.CloudTasksClient; +import com.google.cloud.tasks.v2.CloudTasksSettings; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.flogger.FluentLogger; +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import google.registry.config.CredentialModule; +import google.registry.config.CredentialModule.ApplicationDefaultCredential; +import google.registry.config.RegistryConfig.Config; +import google.registry.config.RegistryConfig.ConfigModule; +import google.registry.util.GoogleCredentialsBundle; +import google.registry.util.UtilsModule; +import java.io.IOException; +import java.util.Optional; +import javax.inject.Singleton; + +/** Canned actions invoked from {@link google.registry.batch.CannedScriptExecutionAction}. */ +// TODO(b/277239043): remove class after credential changes are rolled out. +public class CannedScripts { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private static final Supplier COMPONENT_SUPPLIER = + Suppliers.memoize(DaggerCannedScripts_CannedScriptsComponent::create); + + public static void runAllChecks() { + CannedScriptsComponent component = COMPONENT_SUPPLIER.get(); + String projectId = component.projectId(); + Bigquery bigquery = component.bigQuery(); + try { + bigquery.datasets().list(projectId).execute().getDatasets().stream() + .findAny() + .ifPresent( + datasets -> + logger.atInfo().log("Found a BQ dataset [%s]", datasets.getFriendlyName())); + logger.atInfo().log("Finished accessing BQ."); + } catch (IOException ioe) { + logger.atSevere().withCause(ioe).log("Failed to access bigquery."); + } + try { + Dataflow dataflow = component.dataflow(); + dataflow.projects().jobs().list(projectId).execute().getJobs().stream() + .findAny() + .ifPresent(job -> logger.atInfo().log("Found a job [%s]", job.getName())); + logger.atInfo().log("Finished accessing Dataflow."); + } catch (IOException ioe) { + logger.atSevere().withCause(ioe).log("Failed to access dataflow."); + } + try { + Storage gcs = component.gcs(); + gcs.listAcls(projectId + "-beam"); + logger.atInfo().log("Finished accessing gcs."); + } catch (RuntimeException e) { + logger.atSevere().withCause(e).log("Failed to access gcs."); + } + try { + Dns dns = component.dns(); + dns.managedZones().list(projectId).execute().getManagedZones().stream() + .findAny() + .ifPresent(zone -> logger.atInfo().log("Found one zone [%s].", zone.getName())); + logger.atInfo().log("Finished accessing dns."); + } catch (IOException ioe) { + logger.atSevere().withCause(ioe).log("Failed to access dns."); + } + try { + CloudTasksClient client = component.cloudtasksClient(); + com.google.cloud.tasks.v2.Queue queue = + client.getQueue( + String.format( + "projects/%s/locations/%s/queues/async-actions", + projectId, component.locationId())); + logger.atInfo().log("Got async queue state [%s]", queue.getState().name()); + logger.atInfo().log("Finished accessing cloudtasks."); + } catch (RuntimeException e) { + logger.atSevere().withCause(e).log("Failed to access cloudtasks."); + } + } + + @Singleton + @Component( + modules = { + ConfigModule.class, + CredentialModule.class, + CannedScriptsModule.class, + UtilsModule.class + }) + interface CannedScriptsComponent { + Bigquery bigQuery(); + + CloudTasksClient cloudtasksClient(); + + Dataflow dataflow(); + + Dns dns(); + + Storage gcs(); + + @Config("projectId") + String projectId(); + + @Config("locationId") + String locationId(); + } + + @Module + static class CannedScriptsModule { + @Provides + static Bigquery provideBigquery( + @ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle, + @Config("projectId") String projectId) { + return new Bigquery.Builder( + credentialsBundle.getHttpTransport(), + credentialsBundle.getJsonFactory(), + credentialsBundle.getHttpRequestInitializer()) + .setApplicationName(projectId) + .build(); + } + + @Provides + static Dataflow provideDataflow( + @ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle, + @Config("projectId") String projectId) { + return new Dataflow.Builder( + credentialsBundle.getHttpTransport(), + credentialsBundle.getJsonFactory(), + credentialsBundle.getHttpRequestInitializer()) + .setApplicationName(String.format("%s billing", projectId)) + .build(); + } + + @Provides + static Storage provideGcs( + @ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle) { + return StorageOptions.newBuilder() + .setCredentials(credentialsBundle.getGoogleCredentials()) + .build() + .getService(); + } + + @Provides + static Dns provideDns( + @ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle, + @Config("projectId") String projectId, + @Config("cloudDnsRootUrl") Optional rootUrl, + @Config("cloudDnsServicePath") Optional servicePath) { + Dns.Builder builder = + new Dns.Builder( + credentialsBundle.getHttpTransport(), + credentialsBundle.getJsonFactory(), + credentialsBundle.getHttpRequestInitializer()) + .setApplicationName(projectId); + + rootUrl.ifPresent(builder::setRootUrl); + servicePath.ifPresent(builder::setServicePath); + + return builder.build(); + } + + @Provides + public static CloudTasksClient provideCloudTasksClient( + @ApplicationDefaultCredential GoogleCredentialsBundle credentials) { + CloudTasksClient client; + try { + client = + CloudTasksClient.create( + CloudTasksSettings.newBuilder() + .setCredentialsProvider( + FixedCredentialsProvider.create(credentials.getGoogleCredentials())) + .build()); + } catch (IOException e) { + throw new RuntimeException(e); + } + return client; + } + } +} diff --git a/core/src/main/java/google/registry/batch/cannedscript/GroupsApiChecker.java b/core/src/main/java/google/registry/batch/cannedscript/GroupsApiChecker.java deleted file mode 100644 index 26937b96d..000000000 --- a/core/src/main/java/google/registry/batch/cannedscript/GroupsApiChecker.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2022 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.batch.cannedscript; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static google.registry.util.RegistrarUtils.normalizeRegistrarId; - -import com.google.api.services.admin.directory.Directory; -import com.google.api.services.groupssettings.Groupssettings; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.google.common.base.Throwables; -import com.google.common.collect.Streams; -import com.google.common.flogger.FluentLogger; -import dagger.Component; -import dagger.Module; -import dagger.Provides; -import google.registry.config.CredentialModule; -import google.registry.config.CredentialModule.AdcDelegatedCredential; -import google.registry.config.RegistryConfig.Config; -import google.registry.config.RegistryConfig.ConfigModule; -import google.registry.groups.DirectoryGroupsConnection; -import google.registry.model.registrar.Registrar; -import google.registry.model.registrar.RegistrarPoc; -import google.registry.util.GoogleCredentialsBundle; -import google.registry.util.UtilsModule; -import java.util.List; -import java.util.Set; -import javax.inject.Singleton; - -/** - * Verifies that the credential with the {@link AdcDelegatedCredential} annotation can be used to - * access the Google Workspace Groups API. - */ -// TODO(b/234424397): remove class after credential changes are rolled out. -public class GroupsApiChecker { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - private static final Supplier COMPONENT_SUPPLIER = - Suppliers.memoize(DaggerGroupsApiChecker_GroupsConnectionComponent::create); - - public static void runGroupsApiChecks() { - GroupsConnectionComponent component = COMPONENT_SUPPLIER.get(); - DirectoryGroupsConnection groupsConnection = component.groupsConnection(); - - List registrars = - Streams.stream(Registrar.loadAllCached()) - .filter(registrar -> registrar.isLive() && registrar.getType() == Registrar.Type.REAL) - .collect(toImmutableList()); - for (Registrar registrar : registrars) { - for (final RegistrarPoc.Type type : RegistrarPoc.Type.values()) { - String groupKey = - String.format( - "%s-%s-contacts@%s", - normalizeRegistrarId(registrar.getRegistrarId()), - type.getDisplayName(), - component.gSuiteDomainName()); - try { - Set currentMembers = groupsConnection.getMembersOfGroup(groupKey); - logger.atInfo().log("Found %s members for %s.", currentMembers.size(), groupKey); - } catch (Exception e) { - Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); - } - } - } - } - - @Singleton - @Component( - modules = { - ConfigModule.class, - CredentialModule.class, - GroupsApiModule.class, - UtilsModule.class - }) - interface GroupsConnectionComponent { - DirectoryGroupsConnection groupsConnection(); - - @Config("gSuiteDomainName") - String gSuiteDomainName(); - } - - @Module - static class GroupsApiModule { - @Provides - static Directory provideDirectory( - @AdcDelegatedCredential GoogleCredentialsBundle credentialsBundle, - @Config("projectId") String projectId) { - return new Directory.Builder( - credentialsBundle.getHttpTransport(), - credentialsBundle.getJsonFactory(), - credentialsBundle.getHttpRequestInitializer()) - .setApplicationName(projectId) - .build(); - } - - @Provides - static Groupssettings provideGroupsSettings( - @AdcDelegatedCredential GoogleCredentialsBundle credentialsBundle, - @Config("projectId") String projectId) { - return new Groupssettings.Builder( - credentialsBundle.getHttpTransport(), - credentialsBundle.getJsonFactory(), - credentialsBundle.getHttpRequestInitializer()) - .setApplicationName(projectId) - .build(); - } - } -} diff --git a/core/src/main/java/google/registry/tools/RequestFactoryModule.java b/core/src/main/java/google/registry/tools/RequestFactoryModule.java index 906a3f93d..2c8099228 100644 --- a/core/src/main/java/google/registry/tools/RequestFactoryModule.java +++ b/core/src/main/java/google/registry/tools/RequestFactoryModule.java @@ -24,7 +24,7 @@ import com.google.api.client.util.GenericData; import com.google.auth.oauth2.UserCredentials; import dagger.Module; import dagger.Provides; -import google.registry.config.CredentialModule.DefaultCredential; +import google.registry.config.CredentialModule.ApplicationDefaultCredential; import google.registry.config.RegistryConfig; import google.registry.config.RegistryConfig.Config; import google.registry.util.GoogleCredentialsBundle; @@ -56,7 +56,7 @@ class RequestFactoryModule { @Provides static HttpRequestFactory provideHttpRequestFactory( - @DefaultCredential GoogleCredentialsBundle credentialsBundle, + @ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle, @Config("iapClientId") Optional iapClientId) { if (RegistryConfig.areServersLocal()) { return new NetHttpTransport()