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()