From f615e88ff6adf3d970977f7e9cc3427cc75e788e Mon Sep 17 00:00:00 2001 From: Pavlo Tkach <3469726+ptkach@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:40:56 -0500 Subject: [PATCH] Move App Engine cron jobs to cloud scheduler (#1939) --- .../registry/config/RegistryConfig.java | 6 + .../config/RegistryConfigSettings.java | 1 + .../registry/config/files/default-config.yaml | 2 + .../default/WEB-INF/cloud-scheduler-tasks.xml | 140 ++++++++ .../env/alpha/default/WEB-INF/cron.xml | 147 +-------- .../default/WEB-INF/cloud-scheduler-tasks.xml | 163 +++++++++ .../env/crash/default/WEB-INF/cron.xml | 162 +-------- .../default/WEB-INF/cloud-scheduler-tasks.xml | 288 ++++++++++++++++ .../env/production/default/WEB-INF/cron.xml | 312 +----------------- .../default/WEB-INF/cloud-scheduler-tasks.xml | 71 ++++ .../registry/env/qa/default/WEB-INF/cron.xml | 67 +--- .../default/WEB-INF/cloud-scheduler-tasks.xml | 156 +++++++++ .../env/sandbox/default/WEB-INF/cron.xml | 174 +--------- .../registry/request/auth/AuthModule.java | 24 +- .../IapHeaderAuthenticationMechanism.java | 47 +-- .../auth/IdTokenAuthenticationBase.java | 70 ++++ ...ServiceAccountAuthenticationMechanism.java | 62 ++++ ...iceAccountAuthenticationMechanismTest.java | 75 +++++ release/builder/Dockerfile | 9 + release/builder/cloudSchedulerDeployer.go | 176 ++++++++++ release/cloudbuild-deploy.yaml | 18 +- 21 files changed, 1272 insertions(+), 898 deletions(-) create mode 100644 core/src/main/java/google/registry/env/alpha/default/WEB-INF/cloud-scheduler-tasks.xml create mode 100644 core/src/main/java/google/registry/env/crash/default/WEB-INF/cloud-scheduler-tasks.xml create mode 100644 core/src/main/java/google/registry/env/production/default/WEB-INF/cloud-scheduler-tasks.xml create mode 100644 core/src/main/java/google/registry/env/qa/default/WEB-INF/cloud-scheduler-tasks.xml create mode 100644 core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cloud-scheduler-tasks.xml create mode 100644 core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java create mode 100644 core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java create mode 100644 core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java create mode 100644 release/builder/cloudSchedulerDeployer.go diff --git a/core/src/main/java/google/registry/config/RegistryConfig.java b/core/src/main/java/google/registry/config/RegistryConfig.java index cc1c7e671..6f9634caa 100644 --- a/core/src/main/java/google/registry/config/RegistryConfig.java +++ b/core/src/main/java/google/registry/config/RegistryConfig.java @@ -106,6 +106,12 @@ public final class RegistryConfig { return config.gcpProject.projectId; } + @Provides + @Config("cloudSchedulerServiceAccountEmail") + public static String provideCloudSchedulerServiceAccountEmail(RegistryConfigSettings config) { + return config.gcpProject.cloudSchedulerServiceAccountEmail; + } + @Provides @Config("projectIdNumber") public static long provideProjectIdNumber(RegistryConfigSettings config) { diff --git a/core/src/main/java/google/registry/config/RegistryConfigSettings.java b/core/src/main/java/google/registry/config/RegistryConfigSettings.java index 4e6e0a536..878206c83 100644 --- a/core/src/main/java/google/registry/config/RegistryConfigSettings.java +++ b/core/src/main/java/google/registry/config/RegistryConfigSettings.java @@ -54,6 +54,7 @@ public class RegistryConfigSettings { public String backendServiceUrl; public String toolsServiceUrl; public String pubapiServiceUrl; + public String cloudSchedulerServiceAccountEmail; } /** Configuration options for OAuth settings for authenticating users. */ diff --git a/core/src/main/java/google/registry/config/files/default-config.yaml b/core/src/main/java/google/registry/config/files/default-config.yaml index cb6951fa1..d9111c271 100644 --- a/core/src/main/java/google/registry/config/files/default-config.yaml +++ b/core/src/main/java/google/registry/config/files/default-config.yaml @@ -22,6 +22,8 @@ gcpProject: backendServiceUrl: https://localhost toolsServiceUrl: https://localhost pubapiServiceUrl: https://localhost + # Service account used by Cloud Scheduler to send authenticated requests. + cloudSchedulerServiceAccountEmail: cloud-scheduler-email@email.com gSuite: # Publicly accessible domain name of the running G Suite instance. diff --git a/core/src/main/java/google/registry/env/alpha/default/WEB-INF/cloud-scheduler-tasks.xml b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/cloud-scheduler-tasks.xml new file mode 100644 index 000000000..6fdee14c2 --- /dev/null +++ b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/cloud-scheduler-tasks.xml @@ -0,0 +1,140 @@ + + + + /_dr/task/rdeStaging + rdeStaging + + This job generates a full RDE escrow deposit as a single gigantic XML document + and streams it to cloud storage. When this job has finished successfully, it'll + launch a separate task that uploads the deposit file to Iron Mountain via SFTP. + + 7 0 * * * + + + + + rdeUpload + + This job is a no-op unless RdeUploadCursor falls behind for some reason. + + 0 */4 * * * + + + + + tmchDnl + + This job downloads the latest DNL from MarksDB and inserts it into the database. + (See: TmchDnlAction, ClaimsList) + + 0 */12 * * * + + + + + tmchSmdrl + + This job downloads the latest SMDRL from MarksDB and inserts it into the database. + (See: TmchSmdrlAction, SignedMarkRevocationList) + + 15 */12 * * * + + + + + tmchCrl + + This job downloads the latest CRL from MarksDB and inserts it into the database. + (See: TmchCrlAction) + + 0 */12 * * * + + + + + syncGroupMembers + + Syncs RegistrarContact changes in the past hour to Google Groups. + + 0 */1 * * * + + + + + syncRegistrarsSheet + + Synchronize Registrar entities to Google Spreadsheets. + + 0 */1 * * * + + + + + resaveAllEppResourcesPipeline + + This job resaves all our resources, projected in time to "now". + + + 0 9 1 * * + + + + + expandRecurringBillingEvents + + This job runs an action that creates synthetic OneTime billing events from Recurring billing + events. Events are created for all instances of Recurring billing events that should exist + between the RECURRING_BILLING cursor's time and the execution time of the action. + + 0 3 * * * + + + + + deleteExpiredDomains + + This job runs an action that deletes domains that are past their + autorenew end date. + + 7 3 * * * + + + + + deleteProberData + + This job clears out data from probers and runs once a week. + + 0 14 * * 1 + + + + + + exportReservedTerms + + Reserved terms export to Google Drive job for creating once-daily exports. + + 30 5 * * * + + + + + exportPremiumTerms + + Premium terms export to Google Drive job for creating once-daily exports. + + 0 5 * * * + + + + + readDnsQueue + + Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each + group. + + */1 * * * * + + diff --git a/core/src/main/java/google/registry/env/alpha/default/WEB-INF/cron.xml b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/cron.xml index 5ea49ad76..9ab88efce 100644 --- a/core/src/main/java/google/registry/env/alpha/default/WEB-INF/cron.xml +++ b/core/src/main/java/google/registry/env/alpha/default/WEB-INF/cron.xml @@ -1,150 +1,5 @@ + - - - - /_dr/task/rdeStaging - - This job generates a full RDE escrow deposit as a single gigantic XML document - and streams it to cloud storage. When this job has finished successfully, it'll - launch a separate task that uploads the deposit file to Iron Mountain via SFTP. - - every day 00:07 - backend - - - - - - This job is a no-op unless RdeUploadCursor falls behind for some reason. - - every 4 hours synchronized - backend - - - - - - This job downloads the latest DNL from MarksDB and inserts it into the database. - (See: TmchDnlAction, ClaimsList) - - every 12 hours synchronized - backend - - - - - - This job downloads the latest SMDRL from MarksDB and inserts it into the database. - (See: TmchSmdrlAction, SignedMarkRevocationList) - - every 12 hours from 00:15 to 12:15 - backend - - - - - - This job downloads the latest CRL from MarksDB and inserts it into the database. - (See: TmchCrlAction) - - every 12 hours synchronized - backend - - - - - - Syncs RegistrarContact changes in the past hour to Google Groups. - - every 1 hours synchronized - backend - - - - - - Synchronize Registrar entities to Google Spreadsheets. - - every 1 hours synchronized - backend - - - - - - This job resaves all our resources, projected in time to "now". - - 1st monday of month 09:00 - backend - - - - - - This job runs an action that creates synthetic OneTime billing events from Recurring billing - events. Events are created for all instances of Recurring billing events that should exist - between the RECURRING_BILLING cursor's time and the execution time of the action. - - every day 03:00 - backend - - - - - - This job runs an action that deletes domains that are past their - autorenew end date. - - every day 03:07 - backend - - - - - - This job clears out data from probers and runs once a week. - - every monday 14:00 - UTC - backend - - - - - - - Reserved terms export to Google Drive job for creating once-daily exports. - - every day 05:30 - backend - - - - - - Premium terms export to Google Drive job for creating once-daily exports. - - every day 05:00 - backend - - - - - - Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each - group. - - every 1 minutes synchronized - backend - diff --git a/core/src/main/java/google/registry/env/crash/default/WEB-INF/cloud-scheduler-tasks.xml b/core/src/main/java/google/registry/env/crash/default/WEB-INF/cloud-scheduler-tasks.xml new file mode 100644 index 000000000..6b0d28187 --- /dev/null +++ b/core/src/main/java/google/registry/env/crash/default/WEB-INF/cloud-scheduler-tasks.xml @@ -0,0 +1,163 @@ + + + + + + + /_dr/task/rdeStaging + rdeStaging + + This job generates a full RDE escrow deposit as a single gigantic XML document + and streams it to cloud storage. When this job has finished successfully, it'll + launch a separate task that uploads the deposit file to Iron Mountain via SFTP. + + + 7 */4 * * * + + + + + rdeUpload + + This job is a no-op unless RdeUploadCursor falls behind for some reason. + + 0 */4 * * * + + + + + rdeReport + + This job is a no-op unless RdeReportCursor falls behind for some reason. + + 0 */4 * * * + + + + + tmchDnl + + This job downloads the latest DNL from MarksDB and inserts it into the database. + (See: TmchDnlAction, ClaimsList) + + 0 */12 * * * + + + + + tmchSmdrl + + This job downloads the latest SMDRL from MarksDB and inserts it into the database. + (See: TmchSmdrlAction, SignedMarkRevocationList) + + 15 */12 * * * + + + + + tmchCrl + + This job downloads the latest CRL from MarksDB and inserts it into the database. + (See: TmchCrlAction) + + 0 */12 * * * + + + + + syncGroupMembers + + Syncs RegistrarContact changes in the past hour to Google Groups. + + 0 */1 * * * + + + + + syncRegistrarsSheet + + Synchronize Registrar entities to Google Spreadsheets. + + 0 */1 * * * + + + + + deleteProberData + + This job clears out data from probers and runs once a week. + + 0 14 * * 1 + + + + + + exportReservedTerms + + Reserved terms export to Google Drive job for creating once-daily exports. + + 30 5 * * * + + + + + exportPremiumTerms + + Exports premium price lists to the Google Drive folders for each TLD once per day. + + 0 5 * * * + + + + + readDnsQueue + + Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each + group. + + */1 * * * * + + + + + deleteExpiredDomains + + This job runs an action that deletes domains that are past their + autorenew end date. + + 7 3 * * * + + + + + + wipeOutCloudSql + + This job runs an action that deletes all data in Cloud SQL. + + 7 3 * * 6 + + diff --git a/core/src/main/java/google/registry/env/crash/default/WEB-INF/cron.xml b/core/src/main/java/google/registry/env/crash/default/WEB-INF/cron.xml index 2c0479719..9ab88efce 100644 --- a/core/src/main/java/google/registry/env/crash/default/WEB-INF/cron.xml +++ b/core/src/main/java/google/registry/env/crash/default/WEB-INF/cron.xml @@ -1,165 +1,5 @@ + - - - - /_dr/task/rdeStaging - - This job generates a full RDE escrow deposit as a single gigantic XML document - and streams it to cloud storage. When this job has finished successfully, it'll - launch a separate task that uploads the deposit file to Iron Mountain via SFTP. - - - every 4 hours from 00:07 to 20:00 - backend - - - - - - This job is a no-op unless RdeUploadCursor falls behind for some reason. - - every 4 hours synchronized - backend - - - - - - This job is a no-op unless RdeReportCursor falls behind for some reason. - - every 4 hours synchronized - backend - - - - - - This job downloads the latest DNL from MarksDB and inserts it into the database. - (See: TmchDnlAction, ClaimsList) - - every 12 hours synchronized - backend - - - - - - This job downloads the latest SMDRL from MarksDB and inserts it into the database. - (See: TmchSmdrlAction, SignedMarkRevocationList) - - every 12 hours from 00:15 to 12:15 - backend - - - - - - This job downloads the latest CRL from MarksDB and inserts it into the database. - (See: TmchCrlAction) - - every 12 hours synchronized - backend - - - - - - Syncs RegistrarContact changes in the past hour to Google Groups. - - every 1 hours synchronized - backend - - - - - - Synchronize Registrar entities to Google Spreadsheets. - - every 1 hours synchronized - backend - - - - - - This job clears out data from probers and runs once a week. - - every monday 14:00 - UTC - backend - - - - - - - Reserved terms export to Google Drive job for creating once-daily exports. - - every day 05:30 - backend - - - - - - Exports premium price lists to the Google Drive folders for each TLD once per day. - - every day 05:00 - backend - - - - - - Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each - group. - - every 1 minutes synchronized - backend - - - - - - This job runs an action that deletes domains that are past their - autorenew end date. - - every day 03:07 - backend - - - - - - - This job runs an action that deletes all data in Cloud SQL. - - every saturday 03:07 - backend - diff --git a/core/src/main/java/google/registry/env/production/default/WEB-INF/cloud-scheduler-tasks.xml b/core/src/main/java/google/registry/env/production/default/WEB-INF/cloud-scheduler-tasks.xml new file mode 100644 index 000000000..6966328b3 --- /dev/null +++ b/core/src/main/java/google/registry/env/production/default/WEB-INF/cloud-scheduler-tasks.xml @@ -0,0 +1,288 @@ + + + + /_dr/task/rdeStaging + rdeStaging + + This job generates a full RDE escrow deposit as a single gigantic XML document + and streams it to cloud storage. When this job has finished successfully, it'll + launch a separate task that uploads the deposit file to Iron Mountain via SFTP. + + + 7 */8 * * * + + + + + rdeUpload + + This job is a no-op unless RdeUploadCursor falls behind for some reason. + + 0 */4 * * * + + + + + rdeReport + + This job is a no-op unless RdeReportCursor falls behind for some reason. + + 0 */4 * * * + + + + + tmchDnl + + This job downloads the latest DNL from MarksDB and inserts it into the database. + (See: TmchDnlAction, ClaimsList) + + 0 0,12 * * * + + + + + tmchSmdrl + + This job downloads the latest SMDRL from MarksDB and inserts it into the database. + (See: TmchSmdrlAction, SignedMarkRevocationList) + + 15 */12 * * * + + + + + tmchCrl + + This job downloads the latest CRL from MarksDB and inserts it into the database. + (See: TmchCrlAction) + + 0 */12 * * * + + + + + syncGroupMembers + + Syncs RegistrarContact changes in the past hour to Google Groups. + + 0 */1 * * * + + + + + syncRegistrarsSheet + + Synchronize Registrar entities to Google Spreadsheets. + + 0 */1 * * * + + + + + + + updateRegistrarRdapBaseUrls + + This job reloads all registrar RDAP base URLs from ICANN. + + 34 2 * * * + + + + + exportDomainLists + + This job exports lists of all active domain names to Google Drive and Google Cloud Storage. + + 0 */12 * * * + + + + + expandRecurringBillingEvents + + This job runs an action that creates synthetic OneTime billing events from Recurring billing + events. Events are created for all instances of Recurring billing events that should exist + between the RECURRING_BILLING cursor's time and the execution time of the action. + + 0 3 * * * + + + + + deleteExpiredDomains + + This job runs an action that deletes domains that are past their + autorenew end date. + + 7 3 * * * + + + + + sendExpiringCertificateNotificationEmail + + This job runs an action that sends emails to partners if their certificates are expiring soon. + + 30 4 * * * + + + + + nordnUploadSunrise + + This job uploads LORDN Sunrise CSV files for each TLD to MarksDB. It should be + run at most every three hours, or at absolute minimum every 26 hours. + + + 0 */12 * * * + + + + + nordnUploadClaims + + This job uploads LORDN Claims CSV files for each TLD to MarksDB. It should be + run at most every three hours, or at absolute minimum every 26 hours. + + + 0 */12 * * * + + + + + nordnUploadSunrisePullQueue + + This job uploads LORDN Sunrise CSV files for each TLD to MarksDB using + pull queue. It should be run at most every three hours, or at absolute + minimum every 26 hours. + + + 0 */12 * * * + + + + + nordnUploadClaimsPullQueue + + This job uploads LORDN Claims CSV files for each TLD to MarksDB using pull + queue. It should be run at most every three hours, or at absolute minimum + every 26 hours. + + + 0 */12 * * * + + + + + deleteProberData + + This job clears out data from probers and runs once a week. + + 0 14 * * 1 + + + + + exportReservedTerms + + Reserved terms export to Google Drive job for creating once-daily exports. + + 30 5 * * * + + + + + exportPremiumTerms + + Exports premium price lists to the Google Drive folders for each TLD once per day. + + 0 5 * * * + + + + + readDnsQueue + + Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each + group. + + */1 * * * * + + + + + icannReportingStaging + + Create ICANN activity and transaction reports for last month, storing them in + gs://[PROJECT-ID]-reporting/icann/monthly/yyyy-MM + Upon success, enqueues the icannReportingUpload task to POST these files to ICANN. + + 0 9 2 * * + + + + + icannReportingUpload + + Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again. + Most of the time, this job should not do anything since the uploads are triggered when the reports are staged. + However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP allow list issues), + this cron job will continue to retry uploads daily until they succeed. + + 0 15 * * * + + + + + generateInvoices + + Starts the beam/billing/InvoicingPipeline Dataflow template, which creates the overall invoice and + detail report CSVs for last month, storing them in gs://[PROJECT-ID]-billing/invoices/yyyy-MM. + Upon success, sends an e-mail copy of the invoice to billing personnel, and copies detail + reports to the associated registrars' drive folders. + See GenerateInvoicesAction for more details. + + + 0 19 1 * * + + + + + generateSpec11 + + Starts the beam/spec11/Spec11Pipeline Dataflow template, which creates today's Spec11 + report. This report is stored in gs://[PROJECT-ID]-reporting/icann/spec11/yyyy-MM/. + This job will only send email notifications on the second of every month. + See GenerateSpec11ReportAction for more details. + + 0 15 * * * + + + + + wipeOutContactHistoryPii + + This job runs weekly to wipe out PII fields of ContactHistory entities + that have been in the database for a certain period of time. + + 0 15 * * 1 + + diff --git a/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml b/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml index 00a91a2a8..288444fa4 100644 --- a/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml +++ b/core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml @@ -1,314 +1,4 @@ + - - - - - /_dr/task/rdeStaging - - This job generates a full RDE escrow deposit as a single gigantic XML document - and streams it to cloud storage. When this job has finished successfully, it'll - launch a separate task that uploads the deposit file to Iron Mountain via SFTP. - - - every 8 hours from 00:07 to 20:00 - backend - - - - - - This job is a no-op unless RdeUploadCursor falls behind for some reason. - - every 4 hours synchronized - backend - - - - - - This job is a no-op unless RdeReportCursor falls behind for some reason. - - every 4 hours synchronized - backend - - - - - - This job downloads the latest DNL from MarksDB and inserts it into the database. - (See: TmchDnlAction, ClaimsList) - - every 12 hours synchronized - backend - - - - - - This job downloads the latest SMDRL from MarksDB and inserts it into the database. - (See: TmchSmdrlAction, SignedMarkRevocationList) - - every 12 hours from 00:15 to 12:15 - backend - - - - - - This job downloads the latest CRL from MarksDB and inserts it into the database. - (See: TmchCrlAction) - - every 12 hours synchronized - backend - - - - - - Syncs RegistrarContact changes in the past hour to Google Groups. - - every 1 hours synchronized - backend - - - - - - Synchronize Registrar entities to Google Spreadsheets. - - every 1 hours synchronized - backend - - - - - - - - This job reloads all registrar RDAP base URLs from ICANN. - - every day 02:34 - backend - - - - - - This job exports lists of all active domain names to Google Drive and Google Cloud Storage. - - every 12 hours synchronized - backend - - - - - - This job runs an action that creates synthetic OneTime billing events from Recurring billing - events. Events are created for all instances of Recurring billing events that should exist - between the RECURRING_BILLING cursor's time and the execution time of the action. - - every day 03:00 - backend - - - - - - This job runs an action that deletes domains that are past their - autorenew end date. - - every day 03:07 - backend - - - - - - This job runs an action that sends emails to partners if their certificates are expiring soon. - - every day 04:30 - backend - - - - - - This job uploads LORDN Sunrise CSV files for each TLD to MarksDB. It should be - run at most every three hours, or at absolute minimum every 26 hours. - - - every 12 hours synchronized - UTC - backend - - - - - - This job uploads LORDN Claims CSV files for each TLD to MarksDB. It should be - run at most every three hours, or at absolute minimum every 26 hours. - - - every 12 hours synchronized - UTC - backend - - - - - - This job uploads LORDN Sunrise CSV files for each TLD to MarksDB using - pull queue. It should be run at most every three hours, or at absolute - minimum every 26 hours. - - - every 12 hours synchronized - UTC - backend - - - - - - This job uploads LORDN Claims CSV files for each TLD to MarksDB using pull - queue. It should be run at most every three hours, or at absolute minimum - every 26 hours. - - - every 12 hours synchronized - UTC - backend - - - - - - This job clears out data from probers and runs once a week. - - every monday 14:00 - UTC - backend - - - - - - Reserved terms export to Google Drive job for creating once-daily exports. - - every day 05:30 - backend - - - - - - Exports premium price lists to the Google Drive folders for each TLD once per day. - - every day 05:00 - backend - - - - - - Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each - group. - - every 1 minutes synchronized - backend - - - - - - Create ICANN activity and transaction reports for last month, storing them in - gs://[PROJECT-ID]-reporting/icann/monthly/yyyy-MM - Upon success, enqueues the icannReportingUpload task to POST these files to ICANN. - - 2 of month 09:00 - backend - - - - - - Checks if the monthly ICANN reports have been successfully uploaded. If they have not, attempts to upload them again. - Most of the time, this job should not do anything since the uploads are triggered when the reports are staged. - However, in the event that an upload failed for any reason (e.g. ICANN server is down, IP allow list issues), - this cron job will continue to retry uploads daily until they succeed. - - every day 15:00 - backend - - - - - - Starts the beam/billing/InvoicingPipeline Dataflow template, which creates the overall invoice and - detail report CSVs for last month, storing them in gs://[PROJECT-ID]-billing/invoices/yyyy-MM. - Upon success, sends an e-mail copy of the invoice to billing personnel, and copies detail - reports to the associated registrars' drive folders. - See GenerateInvoicesAction for more details. - - - 1 of month 19:00 - backend - - - - - - Starts the beam/spec11/Spec11Pipeline Dataflow template, which creates today's Spec11 - report. This report is stored in gs://[PROJECT-ID]-reporting/icann/spec11/yyyy-MM/. - This job will only send email notifications on the second of every month. - See GenerateSpec11ReportAction for more details. - - every day 15:00 - backend - - - - - - This job runs weekly to wipe out PII fields of ContactHistory entities - that have been in the database for a certain period of time. - - every monday 15:00 - backend - diff --git a/core/src/main/java/google/registry/env/qa/default/WEB-INF/cloud-scheduler-tasks.xml b/core/src/main/java/google/registry/env/qa/default/WEB-INF/cloud-scheduler-tasks.xml new file mode 100644 index 000000000..39db7da50 --- /dev/null +++ b/core/src/main/java/google/registry/env/qa/default/WEB-INF/cloud-scheduler-tasks.xml @@ -0,0 +1,71 @@ + + + + /_dr/task/rdeStaging + rdeStaging + + This job generates a full RDE escrow deposit as a single gigantic XML document + and streams it to cloud storage. When this job has finished successfully, it'll + launch a separate task that uploads the deposit file to Iron Mountain via SFTP. + + 7 0 * * * + + + + + readDnsQueue + + Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each + group. + + */1 * * * * + + + + + syncRegistrarsSheet + + Synchronize Registrar entities to Google Spreadsheets. + + 0 */1 * * * + + + + + resaveAllEppResourcesPipeline + + This job resaves all our resources, projected in time to "now". + + + 0 9 1 * * + + + + + syncGroupMembers + + Syncs RegistrarContact changes in the past hour to Google Groups. + + 0 */1 * * * + + + + + deleteExpiredDomains + + This job runs an action that deletes domains that are past their + autorenew end date. + + 7 3 * * * + + + + + wipeOutCloudSql + + This job runs an action that deletes all data in Cloud SQL. + + 7 3 * * 6 + + diff --git a/core/src/main/java/google/registry/env/qa/default/WEB-INF/cron.xml b/core/src/main/java/google/registry/env/qa/default/WEB-INF/cron.xml index 3f8b56ccf..9ab88efce 100644 --- a/core/src/main/java/google/registry/env/qa/default/WEB-INF/cron.xml +++ b/core/src/main/java/google/registry/env/qa/default/WEB-INF/cron.xml @@ -1,70 +1,5 @@ + - - /_dr/task/rdeStaging - - This job generates a full RDE escrow deposit as a single gigantic XML document - and streams it to cloud storage. When this job has finished successfully, it'll - launch a separate task that uploads the deposit file to Iron Mountain via SFTP. - - every day 00:07 - backend - - - - - - Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each - group. - - every 1 minutes synchronized - backend - - - - - - Synchronize Registrar entities to Google Spreadsheets. - - every 1 hours synchronized - backend - - - - - - This job resaves all our resources, projected in time to "now". - - 1st monday of month 09:00 - backend - - - - - - Syncs RegistrarContact changes in the past hour to Google Groups. - - every 1 hours synchronized - backend - - - - - - This job runs an action that deletes domains that are past their - autorenew end date. - - every day 03:07 - backend - - - - - - This job runs an action that deletes all data in Cloud SQL. - - every saturday 03:07 - backend - diff --git a/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cloud-scheduler-tasks.xml b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cloud-scheduler-tasks.xml new file mode 100644 index 000000000..29615c879 --- /dev/null +++ b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cloud-scheduler-tasks.xml @@ -0,0 +1,156 @@ + + + + /_dr/task/rdeStaging + rdeStaging + + This job generates a full RDE escrow deposit as a single gigantic XML document + and streams it to cloud storage. When this job has finished successfully, it'll + launch a separate task that uploads the deposit file to Iron Mountain via SFTP. + + + 7 */12 * * * + + + + + rdeUpload + + This job is a no-op unless RdeUploadCursor falls behind for some reason. + + 0 */4 * * * + + + + + tmchDnl + + This job downloads the latest DNL from MarksDB and inserts it into the database. + (See: TmchDnlAction, ClaimsList) + + 0 */12 * * * + + + + + tmchSmdrl + + This job downloads the latest SMDRL from MarksDB and inserts it into the database. + (See: TmchSmdrlAction, SignedMarkRevocationList) + + 15 */12 * * * + + + + + tmchCrl + + This job downloads the latest CRL from MarksDB and inserts it into the database. + (See: TmchCrlAction) + + 0 */12 * * * + + + + + syncGroupMembers + + Syncs RegistrarContact changes in the past hour to Google Groups. + + 0 */1 * * * + + + + + syncRegistrarsSheet + + Synchronize Registrar entities to Google Spreadsheets. + + 0 */1 * * * + + + + + exportDomainLists + + This job exports lists of all active domain names to Google Drive and Google Cloud Storage. + + 0 */12 * * * + + + + + expandRecurringBillingEvents + + This job runs an action that creates synthetic OneTime billing events from Recurring billing + events. Events are created for all instances of Recurring billing events that should exist + between the RECURRING_BILLING cursor's time and the execution time of the action. + + 0 3 * * * + + + + + deleteExpiredDomains + + This job runs an action that deletes domains that are past their + autorenew end date. + + 7 3 * * * + + + + + deleteProberData + + This job clears out data from probers and runs once a week. + + 0 14 * * 1 + + + + + exportReservedTerms + + Reserved terms export to Google Drive job for creating once-daily exports. + + 30 5 * * * + + + + + exportPremiumTerms + + Exports premium price lists to the Google Drive folders for each TLD once per day. + + 0 5 * * * + + + + + + + readDnsQueue + + Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each + group. + + */1 * * * * + + + + + wipeOutContactHistoryPii + + This job runs weekly to wipe out PII fields of ContactHistory entities + that have been in the database for a certain period of time. + + 0 15 * * 1 + + diff --git a/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cron.xml b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cron.xml index 40cff0086..9ab88efce 100644 --- a/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cron.xml +++ b/core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cron.xml @@ -1,177 +1,5 @@ + - - - - /_dr/task/rdeStaging - - This job generates a full RDE escrow deposit as a single gigantic XML document - and streams it to cloud storage. When this job has finished successfully, it'll - launch a separate task that uploads the deposit file to Iron Mountain via SFTP. - - - every 12 hours from 00:07 to 12:07 - backend - - - - - - This job is a no-op unless RdeUploadCursor falls behind for some reason. - - every 4 hours synchronized - backend - - - - - - This job downloads the latest DNL from MarksDB and inserts it into the database. - (See: TmchDnlAction, ClaimsList) - - every 12 hours synchronized - backend - - - - - - This job downloads the latest SMDRL from MarksDB and inserts it into the database. - (See: TmchSmdrlAction, SignedMarkRevocationList) - - every 12 hours from 00:15 to 12:15 - backend - - - - - - This job downloads the latest CRL from MarksDB and inserts it into the database. - (See: TmchCrlAction) - - every 12 hours synchronized - backend - - - - - - Syncs RegistrarContact changes in the past hour to Google Groups. - - every 1 hours synchronized - backend - - - - - - Synchronize Registrar entities to Google Spreadsheets. - - every 1 hours synchronized - backend - - - - - - - - This job exports lists of all active domain names to Google Drive and Google Cloud Storage. - - every 12 hours synchronized - backend - - - - - - This job runs an action that creates synthetic OneTime billing events from Recurring billing - events. Events are created for all instances of Recurring billing events that should exist - between the RECURRING_BILLING cursor's time and the execution time of the action. - - every day 03:00 - backend - - - - - - This job runs an action that deletes domains that are past their - autorenew end date. - - every day 03:07 - backend - - - - - - This job clears out data from probers and runs once a week. - - every monday 14:00 - UTC - backend - - - - - - Reserved terms export to Google Drive job for creating once-daily exports. - - every day 05:30 - backend - - - - - - Exports premium price lists to the Google Drive folders for each TLD once per day. - - every day 05:00 - backend - - - - - - Lease all tasks from the dns-pull queue, group by TLD, and invoke PublishDnsUpdates for each - group. - - every 1 minutes synchronized - backend - - - - - - This job runs weekly to wipe out PII fields of ContactHistory entities - that have been in the database for a certain period of time. - - every monday 15:00 - backend - diff --git a/core/src/main/java/google/registry/request/auth/AuthModule.java b/core/src/main/java/google/registry/request/auth/AuthModule.java index cb54f850f..45f20a037 100644 --- a/core/src/main/java/google/registry/request/auth/AuthModule.java +++ b/core/src/main/java/google/registry/request/auth/AuthModule.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import dagger.Module; import dagger.Provides; import google.registry.config.RegistryConfig.Config; +import javax.inject.Qualifier; import javax.inject.Singleton; /** @@ -30,15 +31,26 @@ import javax.inject.Singleton; public class AuthModule { private static final String IAP_ISSUER_URL = "https://cloud.google.com/iap"; + private static final String SA_ISSUER_URL = "https://accounts.google.com"; /** Provides the custom authentication mechanisms (including OAuth). */ @Provides ImmutableList provideApiAuthenticationMechanisms( OAuthAuthenticationMechanism oauthAuthenticationMechanism, - IapHeaderAuthenticationMechanism iapHeaderAuthenticationMechanism) { - return ImmutableList.of(oauthAuthenticationMechanism, iapHeaderAuthenticationMechanism); + IapHeaderAuthenticationMechanism iapHeaderAuthenticationMechanism, + ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism) { + return ImmutableList.of( + oauthAuthenticationMechanism, + iapHeaderAuthenticationMechanism, + serviceAccountAuthenticationMechanism); } + @Qualifier + @interface IAP {} + + @Qualifier + @interface ServiceAccount {} + /** Provides the OAuthService instance. */ @Provides OAuthService provideOauthService() { @@ -46,10 +58,18 @@ public class AuthModule { } @Provides + @IAP @Singleton TokenVerifier provideTokenVerifier( @Config("projectId") String projectId, @Config("projectIdNumber") long projectIdNumber) { String audience = String.format("/projects/%d/apps/%s", projectIdNumber, projectId); return TokenVerifier.newBuilder().setAudience(audience).setIssuer(IAP_ISSUER_URL).build(); } + + @Provides + @ServiceAccount + @Singleton + TokenVerifier provideServiceAccountTokenVerifier(@Config("projectId") String projectId) { + return TokenVerifier.newBuilder().setAudience(projectId).setIssuer(SA_ISSUER_URL).build(); + } } diff --git a/core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java index 3ec697f93..76d95408b 100644 --- a/core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java +++ b/core/src/main/java/google/registry/request/auth/IapHeaderAuthenticationMechanism.java @@ -14,15 +14,11 @@ package google.registry.request.auth; -import com.google.api.client.json.webtoken.JsonWebSignature; import com.google.auth.oauth2.TokenVerifier; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.flogger.FluentLogger; -import google.registry.config.RegistryEnvironment; import google.registry.model.console.User; import google.registry.model.console.UserDao; +import google.registry.request.auth.AuthModule.IAP; import java.util.Optional; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -37,41 +33,22 @@ import javax.servlet.http.HttpServletRequest; * @see the documentation on GCP * IAP's signed headers for more information. */ -public class IapHeaderAuthenticationMechanism implements AuthenticationMechanism { +public class IapHeaderAuthenticationMechanism extends IdTokenAuthenticationBase { private static final String ID_TOKEN_HEADER_NAME = "X-Goog-IAP-JWT-Assertion"; - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - - // A workaround that allows "use" of the IAP-based authenticator when running local testing, i.e. - // the RegistryTestServer - private static Optional userForTesting = Optional.empty(); - - private final TokenVerifier tokenVerifier; - @Inject - public IapHeaderAuthenticationMechanism(TokenVerifier tokenVerifier) { - this.tokenVerifier = tokenVerifier; + public IapHeaderAuthenticationMechanism(@IAP TokenVerifier tokenVerifier) { + super(tokenVerifier); } @Override - public AuthResult authenticate(HttpServletRequest request) { - if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST) - && userForTesting.isPresent()) { - return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userForTesting.get())); - } - String rawIdToken = request.getHeader(ID_TOKEN_HEADER_NAME); - if (rawIdToken == null) { - return AuthResult.NOT_AUTHENTICATED; - } - JsonWebSignature token; - try { - token = tokenVerifier.verify(rawIdToken); - } catch (TokenVerifier.VerificationException e) { - logger.atInfo().withCause(e).log("Error when verifying access token"); - return AuthResult.NOT_AUTHENTICATED; - } - String emailAddress = (String) token.getPayload().get("email"); + String rawTokenFromRequest(HttpServletRequest request) { + return request.getHeader(ID_TOKEN_HEADER_NAME); + } + + @Override + AuthResult authResultFromEmail(String emailAddress) { Optional maybeUser = UserDao.loadUser(emailAddress); if (!maybeUser.isPresent()) { logger.atInfo().log("No user found for email address %s", emailAddress); @@ -80,8 +57,4 @@ public class IapHeaderAuthenticationMechanism implements AuthenticationMechanism return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(maybeUser.get())); } - @VisibleForTesting - public static void setUserAuthInfoForTestServer(@Nullable User user) { - userForTesting = Optional.ofNullable(user); - } } diff --git a/core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java b/core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java new file mode 100644 index 000000000..556f2e63c --- /dev/null +++ b/core/src/main/java/google/registry/request/auth/IdTokenAuthenticationBase.java @@ -0,0 +1,70 @@ +// 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.request.auth; + +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.auth.oauth2.TokenVerifier; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.flogger.FluentLogger; +import google.registry.config.RegistryEnvironment; +import google.registry.model.console.User; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +public abstract class IdTokenAuthenticationBase implements AuthenticationMechanism { + + public static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + // A workaround that allows "use" of the IAP-based authenticator when running local testing, i.e. + // the RegistryTestServer + private static Optional userForTesting = Optional.empty(); + + private final TokenVerifier tokenVerifier; + + public IdTokenAuthenticationBase(TokenVerifier tokenVerifier) { + this.tokenVerifier = tokenVerifier; + } + + abstract String rawTokenFromRequest(HttpServletRequest request); + + abstract AuthResult authResultFromEmail(String email); + + @Override + public AuthResult authenticate(HttpServletRequest request) { + if (RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST) + && userForTesting.isPresent()) { + return AuthResult.create(AuthLevel.USER, UserAuthInfo.create(userForTesting.get())); + } + String rawIdToken = rawTokenFromRequest(request); + if (rawIdToken == null) { + return AuthResult.NOT_AUTHENTICATED; + } + JsonWebSignature token; + try { + token = tokenVerifier.verify(rawIdToken); + } catch (TokenVerifier.VerificationException e) { + logger.atInfo().withCause(e).log("Error when verifying access token"); + return AuthResult.NOT_AUTHENTICATED; + } + String emailAddress = (String) token.getPayload().get("email"); + return authResultFromEmail(emailAddress); + } + + @VisibleForTesting + public static void setUserAuthInfoForTestServer(@Nullable User user) { + userForTesting = Optional.ofNullable(user); + } +} diff --git a/core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java b/core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java new file mode 100644 index 000000000..cd0963656 --- /dev/null +++ b/core/src/main/java/google/registry/request/auth/ServiceAccountAuthenticationMechanism.java @@ -0,0 +1,62 @@ +// 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.request.auth; + +import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import static google.registry.request.auth.AuthLevel.APP; + +import com.google.auth.oauth2.TokenVerifier; +import google.registry.config.RegistryConfig.Config; +import google.registry.request.auth.AuthModule.ServiceAccount; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; + +/** + * A way to authenticate HTTP requests signed by Service Account + * + *

Currently used by cloud scheduler service account + */ +public class ServiceAccountAuthenticationMechanism extends IdTokenAuthenticationBase { + + private final String cloudSchedulerEmailPrefix; + private static final String BEARER_PREFIX = "Bearer "; + + @Inject + public ServiceAccountAuthenticationMechanism( + @ServiceAccount TokenVerifier tokenVerifier, + @Config("cloudSchedulerServiceAccountEmail") String cloudSchedulerEmailPrefix) { + + super(tokenVerifier); + this.cloudSchedulerEmailPrefix = cloudSchedulerEmailPrefix; + } + + @Override + String rawTokenFromRequest(HttpServletRequest request) { + String rawToken = request.getHeader(AUTHORIZATION); + if (rawToken != null && rawToken.startsWith(BEARER_PREFIX)) { + return rawToken.substring(BEARER_PREFIX.length()); + } + return null; + } + + @Override + AuthResult authResultFromEmail(String emailAddress) { + if (emailAddress.equals(cloudSchedulerEmailPrefix)) { + return AuthResult.create(APP); + } else { + return AuthResult.NOT_AUTHENTICATED; + } + } +} diff --git a/core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java b/core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java new file mode 100644 index 000000000..db6a34c32 --- /dev/null +++ b/core/src/test/java/google/registry/request/auth/ServiceAccountAuthenticationMechanismTest.java @@ -0,0 +1,75 @@ +// 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.request.auth; + +import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.api.client.json.webtoken.JsonWebSignature.Header; +import com.google.auth.oauth2.TokenVerifier; +import javax.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class ServiceAccountAuthenticationMechanismTest { + + @Mock private TokenVerifier tokenVerifier; + @Mock private HttpServletRequest request; + + private JsonWebSignature token; + private ServiceAccountAuthenticationMechanism serviceAccountAuthenticationMechanism; + + @BeforeEach + void beforeEach() throws Exception { + serviceAccountAuthenticationMechanism = + new ServiceAccountAuthenticationMechanism(tokenVerifier, "sa-prefix@email.com"); + when(request.getHeader(AUTHORIZATION)).thenReturn("Bearer jwtValue"); + Payload payload = new Payload(); + payload.setEmail("sa-prefix@email.com"); + token = new JsonWebSignature(new Header(), payload, new byte[0], new byte[0]); + when(tokenVerifier.verify("jwtValue")).thenReturn(token); + } + + @Test + void testSuccess_authenticates() throws Exception { + AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request); + assertThat(authResult.isAuthenticated()).isTrue(); + assertThat(authResult.authLevel()).isEqualTo(AuthLevel.APP); + } + + @Test + void testFails_authenticateWrongEmail() throws Exception { + token.getPayload().set("email", "not-service-account-email@email.com"); + AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request); + assertThat(authResult.isAuthenticated()).isFalse(); + } + + @Test + void testFails_authenticateWrongHeader() throws Exception { + when(request.getHeader(AUTHORIZATION)).thenReturn("BEARER asd"); + AuthResult authResult = serviceAccountAuthenticationMechanism.authenticate(request); + assertThat(authResult.isAuthenticated()).isFalse(); + } +} diff --git a/release/builder/Dockerfile b/release/builder/Dockerfile index 7f708956c..2fd7bb764 100644 --- a/release/builder/Dockerfile +++ b/release/builder/Dockerfile @@ -19,7 +19,16 @@ # 3. Google Cloud SDK for generating the WARs. # 4. Git to manipulate the private and the merged repos. # 5. Docker to build and push images. +# 6. cloudSchedulerDeployer for deploying cloud scheduler tasks based on config + +FROM golang:1.19 as cloudSchedulerBuilder +WORKDIR /usr/src/cloudSchedulerDeployer +RUN go mod init cloudSchedulerDeployer +COPY *.go ./ +RUN go build -o /cloudSchedulerDeployer + FROM marketplace.gcr.io/google/debian10 ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8 +COPY --from=cloudSchedulerBuilder /cloudSchedulerDeployer /usr/local/bin/cloudSchedulerDeployer ADD ./build.sh . RUN ["bash", "./build.sh"] diff --git a/release/builder/cloudSchedulerDeployer.go b/release/builder/cloudSchedulerDeployer.go new file mode 100644 index 000000000..ef229d6f5 --- /dev/null +++ b/release/builder/cloudSchedulerDeployer.go @@ -0,0 +1,176 @@ +// 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. + +// The cloudScheduler tool allows creating, updating and deleting cloud +// scheduler jobs from xml config file + +package main + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io" + "log" + "os" + "os/exec" + "strings" +) + +func main() { + if len(os.Args) < 3 || os.Args[1] == "" || os.Args[2] == "" { + panic("Error - invalid parameters:\nFirst parameter required - config file path;\nSecond parameter required - project name") + } + + // Config file path + configFileLocation := os.Args[1] + // Project name where to submit the tasks + projectName := os.Args[2] + + log.Default().Println("Filepath " + configFileLocation) + + xmlFile, err := os.Open(configFileLocation) + if err != nil { + panic(err) + } + defer xmlFile.Close() + + type Task struct { + XMLName xml.Name `xml:"task"` + URL string `xml:"url"` + Description string `xml:"description"` + Schedule string `xml:"schedule"` + Name string `xml:"name"` + } + + type Taskentries struct { + XMLName xml.Name `xml:"taskentries"` + Task []Task `xml:"task"` + } + + type ServiceAccount struct { + DisplayName string `json:"displayName"` + Email string `json:"email"` + } + + type ExistingJob struct { + Name string `json:"name"` + State string `json:"state"` + } + + byteValue, _ := io.ReadAll(xmlFile) + + var taskEntries Taskentries + + if err := xml.Unmarshal(byteValue, &taskEntries); err != nil { + panic("Failed to unmarshal taskentries: " + err.Error()) + } + + getArgs := func(taskRecord Task, operationType string, serviceAccountEmail string) []string { + // Cloud Schedule doesn't allow description of more than 499 chars and \n + var description string + if len(taskRecord.Description) > 499 { + description = taskRecord.Description[:499] + } else { + description = taskRecord.Description + } + description = strings.ReplaceAll(description, "\n", " ") + + return []string{ + "--project", projectName, + "scheduler", "jobs", operationType, + "http", taskRecord.Name, + "--location", "us-central1", + "--schedule", taskRecord.Schedule, + "--uri", fmt.Sprintf("https://backend-dot-%s.appspot.com%s", projectName, taskRecord.URL), + "--description", description, + "--http-method", "get", + "--oidc-service-account-email", serviceAccountEmail, + "--oidc-token-audience", projectName, + } + } + + // Get existing jobs from Cloud Scheduler + var allExistingJobs []ExistingJob + cmdGetExistingList := exec.Command("gcloud", "scheduler", "jobs", "list", "--project="+projectName, "--location=us-central1", "--format=json") + cmdGetExistingListOutput, cmdGetExistingListError := cmdGetExistingList.CombinedOutput() + if cmdGetExistingListError != nil { + panic("Can't obtain existing cloud scheduler jobs for " + projectName) + } + err = json.Unmarshal(cmdGetExistingListOutput, &allExistingJobs) + if err != nil { + panic("Failed to parse existing jobs from cloud schedule: " + err.Error()) + } + + // Sync deleted jobs + enabledExistingJobs := map[string]bool{} + for i := 0; i < len(allExistingJobs); i++ { + jobName := strings.Split(allExistingJobs[i].Name, "jobs/")[1] + toBeDeleted := true + if allExistingJobs[i].State == "ENABLED" { + enabledExistingJobs[jobName] = true + } + for i := 0; i < len(taskEntries.Task); i++ { + if taskEntries.Task[i].Name == jobName { + toBeDeleted = false + break + } + } + if toBeDeleted { + cmdDelete := exec.Command("gcloud", "scheduler", "jobs", "delete", jobName, "--project="+projectName, "--quiet") + cmdDeleteOutput, cmdDeleteError := cmdDelete.CombinedOutput() + log.Default().Println("Deleting cloud scheduler job " + jobName) + if cmdDeleteError != nil { + panic(string(cmdDeleteOutput)) + } + } + } + + // Find service account email + var serviceAccounts []ServiceAccount + var serviceAccountEmail string + cmdGetServiceAccounts := exec.Command("gcloud", "iam", "service-accounts", "list", "--project="+projectName, "--format=json") + cmdGetServiceAccountsOutput, cmdGetServiceAccountsError := cmdGetServiceAccounts.CombinedOutput() + if cmdGetServiceAccountsError != nil { + panic(cmdGetServiceAccountsError) + } + err = json.Unmarshal(cmdGetServiceAccountsOutput, &serviceAccounts) + if err != nil { + panic(err.Error()) + } + for i := 0; i < len(serviceAccounts); i++ { + if serviceAccounts[i].DisplayName == "cloud-scheduler" { + serviceAccountEmail = serviceAccounts[i].Email + break + } + } + if serviceAccountEmail == "" { + panic("Service account for cloud scheduler is not created for " + projectName) + } + + // Sync created and updated jobs + for i := 0; i < len(taskEntries.Task); i++ { + cmdType := "update" + if enabledExistingJobs[taskEntries.Task[i].Name] != true { + cmdType = "create" + } + + syncCommand := exec.Command("gcloud", getArgs(taskEntries.Task[i], cmdType, serviceAccountEmail)...) + syncCommandOutput, syncCommandError := syncCommand.CombinedOutput() + log.Default().Println(cmdType + " cloud scheduler job " + taskEntries.Task[i].Name) + if syncCommandError != nil { + panic(string(syncCommandOutput)) + } + } +} diff --git a/release/cloudbuild-deploy.yaml b/release/cloudbuild-deploy.yaml index 7f989565a..2f84aa878 100644 --- a/release/cloudbuild-deploy.yaml +++ b/release/cloudbuild-deploy.yaml @@ -28,6 +28,22 @@ steps: set -e gcloud secrets versions access latest \ --secret nomulus-tool-cloudbuild-credential > tool-credential.json +# Create/Update cloud scheduler jobs based on a cloud-scheduler-tasks.xml +- name: 'gcr.io/$PROJECT_ID/builder:latest' + entrypoint: /bin/bash + args: + - -c + - | + set -e + gcloud auth activate-service-account --key-file=tool-credential.json + if [ ${_ENV} == production ]; then + project_id="domain-registry" + else + project_id="domain-registry-${_ENV}" + fi + gsutil cp gs://$PROJECT_ID-deploy/${TAG_NAME}/${_ENV}.tar . + tar -xvf ${_ENV}.tar + cloudSchedulerDeployer default/WEB-INF/cloud-scheduler-tasks.xml $project_id # Deploy the GAE config files. # First authorize the gcloud tool to use the credential json file, then # download and unzip the tarball that contains the relevant config files @@ -43,8 +59,6 @@ steps: else project_id="domain-registry-${_ENV}" fi - gsutil cp gs://$PROJECT_ID-deploy/${TAG_NAME}/${_ENV}.tar . - tar -xvf ${_ENV}.tar for filename in cron dispatch queue; do gcloud -q --project $project_id app deploy \ default/WEB-INF/appengine-generated/$filename.yaml