From b6d33d8da12ae1ce74c7c8d7f0464f4b69418120 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Mon, 22 Apr 2024 20:03:35 +0200 Subject: [PATCH 01/10] Principle of Least Privilege. Solve deprecation in logging client. --- Readme.md | 375 ++++++++++++------ .../src/components/BackupCreateDialog.vue | 7 +- go.mod | 1 + go.sum | 2 + pkg/http/mock/fixtures.go | 45 ++- pkg/processor/creating_processor_factory.go | 26 +- pkg/processor/schedule_processor_test.go | 10 + pkg/service/gcs/gcs_client.go | 65 ++- pkg/service/gcs/gcs_mock.go | 10 + pkg/service/logging/logging_client.go | 4 +- 10 files changed, 413 insertions(+), 132 deletions(-) diff --git a/Readme.md b/Readme.md index 2983709..51e18a9 100644 --- a/Readme.md +++ b/Readme.md @@ -1,38 +1,42 @@ -# Penelope - GCP Backup Solution +# Penelope - GCP Backup Solution - [Penelope - GCP Backup Solution](#penelope---gcp-backup-solution) - [Introduction](#introduction) - [Requirements](#requirements) - [Getting Started](#getting-started) - - [Migration](#migration) - - [Configuration](#configuration) + - [Migration](#migration) + - [Configuration](#configuration) - [Deploy Basic Setup](#deploy-basic-setup) - - [1. Step: Migration with Flyway](#1-step-migration-with-flyway) - - [2. Step: Configuration of App Engine](#2-step-configuration-of-app-engine) - - [3. Step: Penelope Deployment](#3-step-penelope-deployment) - - [4. Step: Configuration of Cron-Jobs](#4-step-configuration-of-cron-jobs) - - [5. Step: Cron-Jobs Scheduling](#5-step-cron-jobs-scheduling) + - [1. Step: Migration with Flyway](#1-step-migration-with-flyway) + - [2. Step: Configuration of App Engine](#2-step-configuration-of-app-engine) + - [3. Step: Penelope Deployment](#3-step-penelope-deployment) + - [4. Step: Configuration of Cron-Jobs](#4-step-configuration-of-cron-jobs) + - [5. Step: Cron-Jobs Scheduling](#5-step-cron-jobs-scheduling) - [Providers](#providers) - - [The Secret Provider](#the-secret-provider) - - [Default](#default) - - [Backup Provider](#backup-provider) - - [Default](#default-1) - - [Target Principal Provider](#target-principal-provider) - - [Default](#default-2) - - [Principal Provider](#principal-provider) - - [Default](#default-3) - - [Source Project Provider](#source-project-provider) - - [Default](#default-4) + - [The Secret Provider](#the-secret-provider) + - [Default](#default) + - [Backup Provider](#backup-provider) + - [Default](#default-1) + - [Target Principal Provider](#target-principal-provider) + - [Default](#default-2) + - [Principal Provider](#principal-provider) + - [Default](#default-3) + - [Source Project Provider](#source-project-provider) + - [Default](#default-4) - [Internal Data Model and Backup Mechanics](#internal-data-model-and-backup-mechanics) - +- [Role and rights concept](#role-and-rights-concept) + # Introduction -Penelope is a tool, which allows you to back up data stored in GCP automatically. You can create backups from BigQuery -datasets and tables as well as from Cloud Storage buckets within Google Cloud Storage. For authentication against -GCP services Penelope uses Google service accounts for performing backups and it assumes that it is behind an authentication provider like [Google Identity Aware Proxy](https://cloud.google.com/iap). -Penelope consists of three main components: +Penelope is a tool, which allows you to back up data stored in GCP automatically. You can create backups from BigQuery +datasets and tables as well as from Cloud Storage buckets within Google Cloud Storage. For authentication against +GCP services Penelope uses Google service accounts for performing backups and it assumes that it is behind an +authentication provider like [Google Identity Aware Proxy](https://cloud.google.com/iap). + +Penelope consists of three main components: + * A Docker image for a server written in GO providing an API with different methods to create, start, etc. backups -* A web frontend allowing users to easily create and manage backup jobs +* A web frontend allowing users to easily create and manage backup jobs * A PostgreSQL database storing different pieces of information about backup jobs **Bellow:** Screenshot from Penelope using the form to create a new backup @@ -46,25 +50,30 @@ Penelope consists of three main components: * Google Service Account Because Penelope uses the Google Cloud SDK, you first have to set up your local environment to access GCP. You need -to create a Google service account to authenticate Penelope. See [Creating and managing service accounts](https://cloud.google.com/iam/docs/creating-managing-service-accounts) +to create a Google service account to authenticate Penelope. +See [Creating and managing service accounts](https://cloud.google.com/iam/docs/creating-managing-service-accounts) documentation for more details. # Getting Started -This repository provides a starter kit to set up Penelope on your own. Penelope uses providers for different purposes, -for example penelope needs a credential to connect with the configured database (see environment variables). You can -use Penelopes basic secret provider, which uses a specific environment variable to provide a secret credential, or +This repository provides a starter kit to set up Penelope on your own. Penelope uses providers for different purposes, +for example penelope needs a credential to connect with the configured database (see environment variables). You can +use Penelopes basic secret provider, which uses a specific environment variable to provide a secret credential, or you can define a more advance provider, which for example fetches the credentials during runtime. Penelope needs four specific providers: * `SecretProvider` - containing the method *GetSecret*, which provides the database password for given user. -* `SinkGCPProjectProvider` - containing the method *GetSinkGCPProjectID*, which provides for a given GCP project id a specific cloud storage backup sink. -* `TargetPrincipalForProjectProvider` - contains the method *GetTargetPrincipalForProject*, which provides a target service account to be impersonated for a given project. -* `PrincipalProvider` - contains the method *GetPrincipalForEmail*, which provides the users principal (containing the user and role bindings) for a given email address. +* `SinkGCPProjectProvider` - containing the method *GetSinkGCPProjectID*, which provides for a given GCP project id a + specific cloud storage backup sink. +* `TargetPrincipalForProjectProvider` - contains the method *GetTargetPrincipalForProject*, which provides a target + service account to be impersonated for a given project. +* `PrincipalProvider` - contains the method *GetPrincipalForEmail*, which provides the users principal (containing the + user and role bindings) for a given email address. ## Migration -Penelope uses a PostgreSQL database to store the backup state. You can find the migrations under the folder `resources/migrations/`. +Penelope uses a PostgreSQL database to store the backup state. You can find the migrations under the +folder `resources/migrations/`. You can use [Flyway](https://flywaydb.org/) to run the migrations against your own PostgreSQL database. ## Configuration @@ -79,6 +88,8 @@ settings. If you not provide required settings, penelope will not run. | `DEFAULT_PROVIDER_BUCKET` | required | Set the bucket for all providers | | `DEFAULT_BACKUP_SINK_PROVIDER_FOR_PROJECT_FILE_PATH` | required | Set the path to the `.yaml` file which contains the target backup project for `SinkGCPProjectProvider`. | | `DEFAULT_USER_PRINCIPAL_PROVIDER_FILE_PATH` | required | Set the path to the `.yaml` file which contains the user principal for `PrincipalProvider`. | +| `DEFAULT_GCP_SOURCE_PROJECT_PROVIDER_FILE_PATH` | required | Set the path to the `.yaml` file which contains the user principal for `PrincSourceGCPProjectProvideripalProvider`. | +| `DEFAULT_PROVIDER_CACHE_TTL` | required | Set time to life (TTL) for data stored in cache by defualt providers | | `DEFAULT_PROVIDER_IMPERSONATE_GOOGLE_SERVICE_ACCOUNT` | required | Set default impersonated google service account for `TargetPrincipalForProjectProvider`. | | `DEV_MODE` | required | Set Penelope to run locally in dev mode any skipping user authentification. | | `APP_JWT_AUDIENCE` | required | Set the expected audience value of the jwt token. | @@ -105,27 +116,28 @@ settings. If you not provide required settings, penelope will not run. # Deploy Basic Setup -This step-by-step guide will walk you through how to set up Penelope in your own Google App Engine instance. Let us start with the database migration. +This step-by-step guide will walk you through how to set up Penelope in your own Google App Engine instance. Let us +start with the database migration. ## 1. Step: Migration with Flyway -In the following you will learn, how you can use Flyway for migration. However, feel free to use any other tool which +In the following you will learn, how you can use Flyway for migration. However, feel free to use any other tool which fits best for your use case. The migration files are in the folder `resource/migrations` as already mentioned above. ```shell script flyway migrate -url=jdbc:postgresql://:/ -user= -password= -locations=filesystem:./resources/migrations ``` -Because we are going to deploy Penelope to App Engine, it maybe useful to take +Because we are going to deploy Penelope to App Engine, it maybe useful to take CloudSQL into consideration. You can use Cloud SQL Proxy to connect with your instance via a secure connection. In order to find out more about the proxy client see the -[About the Cloud SQL Proxy](https://cloud.google.com/sql/docs/mysql/sql-proxy) documentation. +[About the Cloud SQL Proxy](https://cloud.google.com/sql/docs/mysql/sql-proxy) documentation. ## 2. Step: Configuration of App Engine You are going to need a `app.yaml` file to deploy and configure your App Engine service. -In this file you specify the go runtime version, url handlers and all environment variables to configure Penelope. -This repository provides a default configuration template for your own App Engine. Replace the brackets and feel +In this file you specify the go runtime version, url handlers and all environment variables to configure Penelope. +This repository provides a default configuration template for your own App Engine. Replace the brackets and feel free to change the values, but be carefully with the handlers. ```yaml @@ -133,9 +145,9 @@ free to change the values, but be carefully with the handlers. runtime: go119 service: default handlers: - - url: / - static_files: static/ui/index.html - upload: static/ui/index.html + - url: / + static_files: static/ui/index.html + upload: static/ui/index.html # ... env_variables: @@ -150,8 +162,8 @@ env_variables: ## 3. Step: Penelope Deployment -Now that you have specified the configuration for Penelope, you are able to deploy the local application and -configuration settings with Cloud SDK. For more details on how to install or manage your GCP resources and +Now that you have specified the configuration for Penelope, you are able to deploy the local application and +configuration settings with Cloud SDK. For more details on how to install or manage your GCP resources and applications see [Google Cloud SDK Documentation](https://cloud.google.com/sdk/docs/quickstart). Since we are going to deploy the application to app engine, we will use `gcloud app deploy` for deployment. @@ -162,25 +174,28 @@ gcloud app deploy app.yaml ## 4. Step: Configuration of Cron-Jobs Congratulations. If you configured your application correctly, you successfully deployed Penelope to -App Engine. But you're not done yet. There are still tasks, which need to be triggered. These Penelope tasks are responsible for -making backups, cleanups of expired sinks and so on. This repository provides a basic cron job configuration as well for all +App Engine. But you're not done yet. There are still tasks, which need to be triggered. These Penelope tasks are +responsible for +making backups, cleanups of expired sinks and so on. This repository provides a basic cron job configuration as well for +all tasks. There are no changes required. ```yaml # cron.yaml cron: - - description: "prepare backup jobs" - url: /api/tasks/prepare_backup_jobs - schedule: every 60 minutes from 00:00 to 23:00 - - description: "schedule new jobs" - url: /api/tasks/run_new_jobs - schedule: every 10 minutes from 00:05 to 23:55 - # ... + - description: "prepare backup jobs" + url: /api/tasks/prepare_backup_jobs + schedule: every 60 minutes from 00:00 to 23:00 + - description: "schedule new jobs" + url: /api/tasks/run_new_jobs + schedule: every 10 minutes from 00:05 to 23:55 + # ... ``` ## 5. Step: Cron-Jobs Scheduling -Deploying the `cron.yaml` configuration file to App Engine is straight forward. You just need to run the following command and you are finished. +Deploying the `cron.yaml` configuration file to App Engine is straight forward. You just need to run the following +command and you are finished. ```shell script gcloud app deploy cron.yaml @@ -188,67 +203,74 @@ gcloud app deploy cron.yaml # Providers -This section is specifically tell you about the special Penelope providers. As mentioned before, there are four -providers which provide Penelope with information like where to store the backup, which role bindings has the user and so on. -This repository contains default providers. However, you are able to implement your own providers. In the following, -you will find out how each default provider works and how you can implement your own provider. To use your own Penelope -defined providers use `AppStartArguments` and pass it to the run function of the `github.com/ottogroup/penelope/cmd` package. +This section is specifically tell you about the special Penelope providers. As mentioned before, there are four +providers which provide Penelope with information like where to store the backup, which role bindings has the user and +so on. +This repository contains default providers. However, you are able to implement your own providers. In the following, +you will find out how each default provider works and how you can implement your own provider. To use your own Penelope +defined providers use `AppStartArguments` and pass it to the run function of the `github.com/ottogroup/penelope/cmd` +package. ```go package main import ( - "github.com/ottogroup/penelope/cmd" + "github.com/ottogroup/penelope/cmd" ) - func main() { - // Create all your providers here ... + // Create all your providers here ... - appStartArguments := app.AppStartArguments{ + appStartArguments := app.AppStartArguments{ PrincipalProvider: principalProvider, SinkGCPProjectProvider: sinkGCPProjectProvider, TargetPrincipalForProjectProvider: targetPrincipalForProjectProvider, SecretProvider: secretProvider, - } + } - cmd.Run(appStartArguments) + cmd.Run(appStartArguments) } ``` -## The Secret Provider +## The Secret Provider -Let's have a look at the first provider. The secret provider, specified by the `SecretProvider` interface, provides Penelope with the database +Let's have a look at the first provider. The secret provider, specified by the `SecretProvider` interface, provides +Penelope with the database password. This provider defines only one method. It receives a `context.Context` and `string` argument and returns -a `string` and `error` type. You can probably guess the meaning of each argument. However, we will go through -each parameter to be clear. The first expected argument is a context, which is created for each (http) request. This is -golang specific. If you want to find out more about the Context type, you can read the [Package Context](https://golang.org/pkg/context/) -documentation. The next argument contains the database user name. All you have to do is to return the +a `string` and `error` type. You can probably guess the meaning of each argument. However, we will go through +each parameter to be clear. The first expected argument is a context, which is created for each (http) request. This is +golang specific. If you want to find out more about the Context type, you can read +the [Package Context](https://golang.org/pkg/context/) +documentation. The next argument contains the database user name. All you have to do is to return the password for this user. If you are not able to return the database password, you can return an error value. - ```go package secret import "context" type SecretProvider interface { - GetSecret(ctxIn context.Context, user string) (string, error) + GetSecret(ctxIn context.Context, user string) (string, error) } ``` ### Default -The default provider is actually pretty straight forward. It basically doesn't care about the user argument. It just returns the -value you have specified in the `POSTGRES_PASSWORD` environment variable. If this default provider is not advance enough for your +The default provider is actually pretty straight forward. It basically doesn't care about the user argument. It just +returns the +value you have specified in the `POSTGRES_PASSWORD` environment variable. If this default provider is not advance enough +for your need, then feel free to implement your own secret provider. -## Backup Provider +## Backup Provider The tasks of the backup sink provider is to provide Penelope with a GCP project where the backup should be stored. -This provider is defined by the `SinkGCPProjectProvider` interface. The first argument is the same for all provider methods, -which is again context. The next argument is the source GCP project id. It is the project of the source data, which should -be backup on a target project. The task of this interface is to return the target project for the received source project. +This provider is defined by the `SinkGCPProjectProvider` interface. The first argument is the same for all provider +methods, +which is again context. The next argument is the source GCP project id. It is the project of the source data, which +should +be backup on a target project. The task of this interface is to return the target project for the received source +project. ```go package provider @@ -256,14 +278,15 @@ package provider import "context" type SinkGCPProjectProvider interface { - GetSinkGCPProjectID(ctxIn context.Context, sourceGCPProjectID string) (string, error) + GetSinkGCPProjectID(ctxIn context.Context, sourceGCPProjectID string) (string, error) } ``` ### Default -The default provide is a bit more complex this time. You will not only have to define the environment variables -`DEFAULT_PROVIDER_BUCKET` and `DEFAULT_BACKUP_SINK_PROVIDER_FOR_PROJECT_FILE_PATH`, you also have to store a `.yaml` file +The default provide is a bit more complex this time. You will not only have to define the environment variables +`DEFAULT_PROVIDER_BUCKET` and `DEFAULT_BACKUP_SINK_PROVIDER_FOR_PROJECT_FILE_PATH`, you also have to store a `.yaml` +file in the specified bucket. The content of the file should look like this. ```yaml @@ -273,18 +296,19 @@ in the specified bucket. The content of the file should look like this. backup: project-two-backup ``` -For each project you define a backup project (actually not that complex, huh?). But what happens, if a source -project is not listed in the file? Then the default implementation returns an error. You think there are other solutions? -Maybe you would like to create a backup projects on-the-fly or just use the source project as the target project. Then +For each project you define a backup project (actually not that complex, huh?). But what happens, if a source +project is not listed in the file? Then the default implementation returns an error. You think there are other +solutions? +Maybe you would like to create a backup projects on-the-fly or just use the source project as the target project. Then feel free to implement your own `SinkGCPProjectProvider`. ## Target Principal Provider -This provider can be more difficult to comprehend than the previous providers. Behind the scenes, Penelope uses -impersonation to create all the backup sinks and so on in GCP. And what does it impersonate to do all these tasks? +This provider can be more difficult to comprehend than the previous providers. Behind the scenes, Penelope uses +impersonation to create all the backup sinks and so on in GCP. And what does it impersonate to do all these tasks? [Service accounts](https://cloud.google.com/iam/docs/understanding-service-accounts), which are special google account -to represent non-human user like applications. To determine which service account should be impersonated by Penelope, -the `TargetPrincipalForProjectProvider` interface is required. It returns the service account for a target project. +to represent non-human user like applications. To determine which service account should be impersonated by Penelope, +the `TargetPrincipalForProjectProvider` interface is required. It returns the service account for a target project. ```go package impersonate @@ -292,7 +316,7 @@ package impersonate import "context" type TargetPrincipalForProjectProvider interface { - GetTargetPrincipalForProject(ctxIn context.Context, projectID string) (string, error) + GetTargetPrincipalForProject(ctxIn context.Context, projectID string) (string, error) } ``` @@ -309,51 +333,51 @@ This section explains the concept of a user principal and the role of the `Princ * A `Principal` data type represents a user's identity and access rights within the system. * It contains two components: - * `email`: A string representing the user's email address (unique identifier). - * `role_bindings`: A list of role bindings, which define a user's role within each project. + * `email`: A string representing the user's email address (unique identifier). + * `role_bindings`: A list of role bindings, which define a user's role within each project. ### Role Binding: * A role binding associates a project ID with a user's role for that specific project. * Possible roles are: - * `None`: User has no access to the project. - * `Viewer`: User can view project data but cannot modify it. - * `Owner`: User has full access to the project, including editing and backup privileges. - + * `None`: User has no access to the project. + * `Viewer`: User can view project data but cannot modify it. + * `Owner`: User has full access to the project, including editing and backup privileges. + ### PrincipalProvider Interface: * The `PrincipalProvider interface defines a single method: - * `GetPrincipal(email: string) -> Principal`: This method takes a user's email address and returns their corresponding Principal data type. - + * `GetPrincipal(email: string) -> Principal`: This method takes a user's email address and returns their + corresponding Principal data type. + ### Importance of PrincipalProvider: * The `PrincipalProvider` plays a crucial role in access control. * By retrieving a user's principal data, the system can determine their roles for specific projects. * This information is critical for authorizing actions: - * Only `Owner` users can perform backups. - * Users without the appropriate role (e.g., `None` or `Viewer`) cannot edit project data. + * Only `Owner` users can perform backups. + * Users without the appropriate role (e.g., `None` or `Viewer`) cannot edit project data. ### In summary: * The Principal data type stores user identity and project access levels. * The PrincipalProvider interface provides access to this information for authorization purposes. - ```go package provider import ( "context" - "github.com/ottogroup/penelope/pkg/http/auth/model" + "github.com/ottogroup/penelope/pkg/http/auth/model" ) type PrincipalProvider interface { - GetPrincipalForEmail(ctxIn context.Context, email string) (*model.Principal, error) + GetPrincipalForEmail(ctxIn context.Context, email string) (*model.Principal, error) } ``` -The data type `Principal` is shown in following source code, which contains additionally all relevant information. -You can see it consist of a `User` and a list of `ProjectRoleBinding`s. Furthermore, you can see that `User` only +The data type `Principal` is shown in following source code, which contains additionally all relevant information. +You can see it consist of a `User` and a list of `ProjectRoleBinding`s. Furthermore, you can see that `User` only consists of the email address. The `ProjectRoleBinding` contains the role for each project. ```go @@ -362,31 +386,31 @@ package model type Role string type Principal struct { - User User - RoleBindings []ProjectRoleBinding + User User + RoleBindings []ProjectRoleBinding } type User struct { - Email string + Email string } var ( - None Role = "none" - Viewer Role = "viewer" - Owner Role = "owner" + None Role = "none" + Viewer Role = "viewer" + Owner Role = "owner" ) type ProjectRoleBinding struct { - Role Role - Project string + Role Role + Project string } ``` ### Default -Now let's have a look at the default implementation. The default is very similar to the `SinkGCPProjectProvider`. It +Now let's have a look at the default implementation. The default is very similar to the `SinkGCPProjectProvider`. It also needs the path to a `.yaml` file. Therefore `DEFAULT_USER_PRINCIPAL_PROVIDER_FILE_PATH` needs to be set. -The content can look like this. +The content can look like this. ```yaml - user: @@ -407,8 +431,10 @@ The content can look like this. ## Source Project Provider -source project orincipal is used to retrieve additioanl information about the source project. The `SourceGCPProjectProvider` -represents the interface for this provider. It contains only one method, which returns the `SourceGCPProject` for a given +source project orincipal is used to retrieve additioanl information about the source project. +The `SourceGCPProjectProvider` +represents the interface for this provider. It contains only one method, which returns the `SourceGCPProject` for a +given project id. ```go @@ -419,15 +445,15 @@ import ( ) type SourceGCPProjectProvider interface { - GetSourceGCPProject(ctxIn context.Context, gcpProjectID string) (SourceGCPProject, error) + GetSourceGCPProject(ctxIn context.Context, gcpProjectID string) (SourceGCPProject, error) } ``` ### Default -Now let's have a look at the default implementation. The default is very similar to the `SinkGCPProjectProvider`. It +Now let's have a look at the default implementation. The default is very similar to the `SinkGCPProjectProvider`. It also needs the path to a `.yaml` file. Therefore `DEFAULT_BACKUP_SINK_PROVIDER_FOR_PROJECT_FILE_PATH` needs to be set. -The content can look like this. +The content can look like this. ```yaml - project: local-account @@ -447,12 +473,12 @@ backup source a job is either implemented as Penelope keeps track of jobs in the `jobs` table. A fk relation to the corresponding backup indentifies which backup definition lead to a certain job. -Both db entities - `backups` and `jobs` - have a `status` field representing the current state they are in. The `status` +Both db entities - `backups` and `jobs` - have a `status` field representing the current state they are in. The `status` field different [Penelope tasks](pkg/tasks/tasks_multiplexer.go) operate on it and trigger a state chang in the model. State changes of `backups` are well-defined in the [processor.go](pkg/processor/processor.go). Since a StorageTransferJob is handled by GCP, it runs asynchronously. Penelope checks job statuses in regular intervals -when the task `CheckJobsStatus` is invoked. It monitors StorageTransferJobs and corresponding TransferOperations. The +when the task `CheckJobsStatus` is invoked. It monitors StorageTransferJobs and corresponding TransferOperations. The following diagram shows how the job status is assessed. ```mermaid @@ -467,3 +493,110 @@ flowchart LR D--> |No| SPen ``` +# Role and rights concept + +## Service accounts + +### Runner + +There should be one custom service account ```runner``` that is used to run the Penelope application. This service +account should have the following roles in the project it runs: + +* CloudSQL Client (`roles/cloudsql.client) + * to be able to connect to the database running in the same project as Penelope application +* Cloud Trace Agent + * to be able to connect to write traces to Google Monitoring + +### Backup + +The GCP Projects that are created for the pure purpose to store only backup data will be accessed by the ```backup``` +service accounts. + +#### permission for runner service account + +The ```runner``` service account should to be able to impersonate the```backup``` service account with the role: Service +Account Token Creator (`roles/roles/iam.serviceAccountTokenCreator`) + +#### permission in data source + +The ```backup``` service account should have the following roles in the source project: + +* to be able to evaluate data store costs during backup creation + * Monitoring Viewer (`roles/monitoring.viewer`) +* to be able to access project metadata + * `resourcemanager.projects.get` + * `resourcemanager.projects.list` +* to be able to list and get GCS bucket metadata + * `storage.buckets.get` + * `storage.buckets.list` +* to be able to make GCS Mirroring + * Private Logs Viewer (`roles/logging.privateLogViewer`) +* to be able to for GCS Mirroring strategy to check logging.list quota usage + * `serviceusage.services.list` +* to be able to list and get BigQuery datasets/table/views metadata + * `bigquery.datasets.get` + * `bigquery.tables.get` + * `bigquery.tables.list` +* to be able to list and export from BigQuery + * `bigquery.tables.createSnapshot` + * `bigquery.tables.export` + * `bigquery.tables.getData` + * `bigquery.tables.replicateData` + +#### permission in data sink + +The ```backup``` service account should have the following roles in the target (backup only) project: + +* to be able to manage GCS buckets and its objects + * `storage.buckets.create` + * `storage.buckets.delete` + * `storage.buckets.enableObjectRetention` + * `storage.buckets.get` + * `storage.buckets.getIamPolicy` + * `storage.buckets.list` + * `storage.buckets.setIamPolicy` + * `storage.buckets.update` + * `storage.objects.create` + * `storage.objects.delete` + * `storage.objects.get` + * `storage.objects.list` + * `storage.objects.update` +* to be able to trigger export jobs in BigQuery from source project(s) + * BigQuery Job User (`roles/bigquery.jobUser`) +* to be able to create&update Storage Transfer jobs + * Storage Transfer User (`roles/storagetransfer.user`) +* to be able to clean up backups that transit to status `BackupDeleted` + * `storagetransfer.jobs.delete` + * `storagetransfer.jobs.get` + * `storagetransfer.jobs.list` + * `bigquery.jobs.delete` + * `bigquery.jobs.get` + * `bigquery.jobs.list` +* to be able to check compliance permissions + * `iam.denypolicies.list` + * `iam.denypolicies.get` + +### Storage Transfer Service Account + +For the backup that are made for the bucket in the source project the service account - created by the Storage Transfer +Service, is used. The service agent's email uses the format +```project-SINK_PROJECT_NUMBER@storage-transfer-service.iam.gserviceaccount.com```. + +#### permission in data source + +Google's managed service account need following permission in the source project: + +* Storage Object Viewer (```roles/storage.objectViewer```) + +#### permission in data sink + +Google's managed service account need following permission in the target (backup only) project: + +* on project level a role wit the following IAM permissions: + * Storage Transfer User (`roles/storagetransfer.user`) + +* on sink bucket level: + * Storage Legacy Bucket Reader (```roles/storage.legacyBucketReader```) + * **NOTE**: it is done automatically set by the ```runner``` service account + * Storage Legacy Bucket Writer (```roles/storage.legacyBucketWriter```) + * **NOTE**: it is done automatically set by the ```runner``` service account diff --git a/frontend/src/components/BackupCreateDialog.vue b/frontend/src/components/BackupCreateDialog.vue index fb3539b..6388e3c 100644 --- a/frontend/src/components/BackupCreateDialog.vue +++ b/frontend/src/components/BackupCreateDialog.vue @@ -128,7 +128,12 @@ const apiRequestBody = () => { recovery_time_objective: Number(request.value.recovery_time_objective), type: request.value.type, strategy: request.value.strategy, - target: request.value.target, + target: { + storage_class: request.value.target?.storage_class, + region: request.value.target?.region, + dual_region: request.value.target?.dual_region, + archive_ttm: Number(request.value.target?.archive_ttm), + }, snapshot_options: { lifetime_in_days: Number(request.value.snapshot_options?.lifetime_in_days), frequency_in_hours: Number(request.value.snapshot_options?.frequency_in_hours), diff --git a/go.mod b/go.mod index bc000fd..e4918d5 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( cloud.google.com/go/iam v1.1.7 cloud.google.com/go/logging v1.9.0 cloud.google.com/go/monitoring v1.18.2 + cloud.google.com/go/resourcemanager v1.9.6 cloud.google.com/go/storage v1.40.0 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 github.com/aws/aws-sdk-go v1.51.23 diff --git a/go.sum b/go.sum index 6d942cf..e8746e2 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXm cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= cloud.google.com/go/monitoring v1.18.2 h1:nIQdZdf1/9M0cEmXSlLB2jMq/k3CRh9p3oUzS06VDG8= cloud.google.com/go/monitoring v1.18.2/go.mod h1:MuL95M6d9HtXQOaWP9JxhFZJKP+fdTF0Gt5xl4IDsew= +cloud.google.com/go/resourcemanager v1.9.6 h1:VPfJFbWxrTYQzEXCDbJNpcvSB8eZhTSM0YHH146fIB8= +cloud.google.com/go/resourcemanager v1.9.6/go.mod h1:d+XUOGbxg6Aka3lmC4fDiserslux3d15uX08C6a0MBg= cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= cloud.google.com/go/trace v1.10.6 h1:XF0Ejdw0NpRfAvuZUeQe3ClAG4R/9w5JYICo7l2weaw= diff --git a/pkg/http/mock/fixtures.go b/pkg/http/mock/fixtures.go index b65d066..94c4a0f 100644 --- a/pkg/http/mock/fixtures.go +++ b/pkg/http/mock/fixtures.go @@ -30,7 +30,8 @@ var ( // BucketAttrsHTTPMock request BucketAttrsHTTPMock = NewMockedHTTPRequest("GET", "/storage/v1/b/.*", bucketAttrsResponse) // PatchBucketAttrsHTTPMock request - PatchBucketAttrsHTTPMock = NewMockedHTTPRequest("PATCH", "/storage/v1/b/.*", patchBucketAttrsResponse) + PatchBucketAttrsHTTPMock = NewMockedHTTPRequest("PATCH", "/storage/v1/b/.*", patchBucketAttrsResponse) + BucketSetIAMPolicyHTTPMock = NewMockedHTTPRequest("POST", "/storage/v1/b/.*/iam", bucketSetIAMPolicy) // SinkCreatedHTTPpMock request SinkCreatedHTTPpMock = NewMockedHTTPRequest("POST", "/storage/v1/b", sinkCreatedResponse) // SinkDeletedHTTPMock request @@ -60,6 +61,7 @@ var ( ListPoliciesSafeHTTPMock = NewMockedHTTPRequest("GET", "policies/cloudresourcemanager.googleapis.com%252Fprojects%252Ftest-example-safe/denypolicies", listPoliciesResultResponse) ListServiceUsageHTTPMock = NewMockedHTTPRequest("GET", "projects/.*/services", listServiceUsageOkResponse) + GetPRojectHTTPMock = NewMockedHTTPRequest("GET", "/v3/projects/.*/", getProjectOkResponse) ) const ( @@ -201,6 +203,47 @@ Content-Type: application/json; charset=UTF-8 {"services": [],"nextPageToken": null}` + getProjectOkResponse = `HTTP/1.1 200 +Content-Type: application/json; charset=UTF-8 + +{ + "name": "projects/00000000000", + "parent": "folders/00000000001", + "projectId": "project1", + "state": "ACTIVE", + "displayName": "project1", + "createTime": "2021-03-12T21:16:05.027Z", + "updateTime": "2023-07-05T07:58:52.363128Z", + "etag": "W/\"tagValue\"", + "labels": { + "env": "dev", + "service_owner": "owner@owner.com", + "team": "team1" + } +}` + bucketSetIAMPolicy = `HTTP/1.1 200 +Content-Type: application/json; charset=UTF-8 + +{ + "version": 10, + "kind": "storage#policy", + "resourceId": "string", + "bindings": [ + { + "role": "string", + "members": [ + "string" + ] + "condition": { + "title": "string", + "description": "string", + "expression": "2023-08-13 16:08:44+02:00" + } + } + ], + "etag": "string" +}` + listPoliciesResultResponse = `HTTP/1.1 200 Content-Type: application/json; charset=UTF-8 diff --git a/pkg/processor/creating_processor_factory.go b/pkg/processor/creating_processor_factory.go index ed0c8a1..d133ee2 100644 --- a/pkg/processor/creating_processor_factory.go +++ b/pkg/processor/creating_processor_factory.go @@ -1,6 +1,7 @@ package processor import ( + "cloud.google.com/go/iam" "context" "fmt" "strings" @@ -420,8 +421,29 @@ func prepareSink(ctxIn context.Context, cloudStorageClient gcs.CloudStorageClien } err = cloudStorageClient.CreateBucket(ctx, backup.TargetProject, backup.Sink, backup.Region, backup.DualRegion, backup.StorageClass, lifetimeInDays, backup.ArchiveTTM) - if err == nil { - return cloudStorageClient.CreateObject(ctx, backup.Sink, fmt.Sprintf("%s/THIS_TRASHCAN_CONTAINS_DELETED_OBJECTS_FROM_SOURCE", backup.GetTrashcanPath()), "") + if err != nil { + return err + } + err = cloudStorageClient.CreateObject(ctx, backup.Sink, fmt.Sprintf("%s/THIS_TRASHCAN_CONTAINS_DELETED_OBJECTS_FROM_SOURCE", backup.GetTrashcanPath()), "") + if err != nil { + return err + } + if backup.Type != repository.CloudStorage { + return nil + } + project, err := cloudStorageClient.GetProject(ctx, backup.TargetProject) + if err != nil { + return err + } + projectNumber := strings.ReplaceAll(project.Name, "projects/", "") + bucketPolicy := &iam.Policy{} + // Storage Transfer Service needs to write to and read from the sink bucket + storageTransferGSABinding := fmt.Sprintf("serviceAccount:project-%s@storage-transfer-service.iam.gserviceaccount.com", projectNumber) + bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketWriter") + bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketReader") + err = cloudStorageClient.SetBucketIAMPolicy(ctx, backup.Sink, bucketPolicy) + if err != nil { + return err } } diff --git a/pkg/processor/schedule_processor_test.go b/pkg/processor/schedule_processor_test.go index 89c88ca..067a40e 100644 --- a/pkg/processor/schedule_processor_test.go +++ b/pkg/processor/schedule_processor_test.go @@ -1,6 +1,8 @@ package processor import ( + "cloud.google.com/go/iam" + "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" "context" "regexp" "testing" @@ -443,6 +445,14 @@ type stubGcsClient struct { fDeleteObjectsErr error } +func (g *stubGcsClient) GetProject(ctxIn context.Context, projectID string) (*resourcemanagerpb.Project, error) { + panic("implement me") +} + +func (g *stubGcsClient) SetBucketIAMPolicy(ctxIn context.Context, bucket string, policy *iam.Policy) error { + panic("implement me") +} + func (g *stubGcsClient) Close(context.Context) { panic("implement me") } diff --git a/pkg/service/gcs/gcs_client.go b/pkg/service/gcs/gcs_client.go index bc9bbb6..82af21a 100644 --- a/pkg/service/gcs/gcs_client.go +++ b/pkg/service/gcs/gcs_client.go @@ -1,6 +1,9 @@ package gcs import ( + "cloud.google.com/go/iam" + resourcemanager "cloud.google.com/go/resourcemanager/apiv3" + "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" "context" "fmt" "io" @@ -31,6 +34,8 @@ type CloudStorageClient interface { DoesBucketExist(ctxIn context.Context, project string, bucket string) (bool, error) BucketUsageInBytes(ctxIn context.Context, project string, bucket string) (float64, error) CreateBucket(ctxIn context.Context, project, bucket, location, dualLocation, storageClass string, lifetimeInDays uint, archiveTTM uint) error + GetProject(ctxIn context.Context, projectID string) (*resourcemanagerpb.Project, error) + SetBucketIAMPolicy(ctxIn context.Context, bucket string, policy *iam.Policy) error CreateObject(ctxIn context.Context, bucketName, objectName, content string) error DeleteBucket(ctxIn context.Context, bucket string) error DeleteObject(ctxIn context.Context, bucketName string, objectName string) error @@ -50,17 +55,26 @@ type CloudStorageClientFactory interface { // defaultGcsClient defines client to interact with the GCS type defaultGcsClient struct { - client *storage.Client - metricClient *monitoring.MetricClient + client *storage.Client + metricClient *monitoring.MetricClient + projectClient *resourcemanager.ProjectsClient } -// Close terminates terminates all resources in use +func (c *defaultGcsClient) GetProject(ctxIn context.Context, projectID string) (*resourcemanagerpb.Project, error) { + _, span := trace.StartSpan(ctxIn, "(*defaultGcsClient).GetProject") + defer span.End() + + return c.projectClient.GetProject(ctxIn, &resourcemanagerpb.GetProjectRequest{Name: fmt.Sprintf("projects/%s", projectID)}) +} + +// Close terminates all resources in use func (c *defaultGcsClient) Close(ctxIn context.Context) { _, span := trace.StartSpan(ctxIn, "(*defaultGcsClient).Close") defer span.End() c.client.Close() c.metricClient.Close() + c.projectClient.Close() } // NewCloudStorageClient create a new CloudStorageClient @@ -88,6 +102,11 @@ func createCloudStorageClient(ctxIn context.Context) (CloudStorageClient, error) monitoringOptions = append(monitoringOptions, option.WithoutAuthentication(), option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials()))) } + var projectsClientOptions = []option.ClientOption{option.WithScopes(metricAPIScope)} + if config.UseGrpcWithoutAuthentication.GetBoolOrDefault(false) { + projectsClientOptions = append(projectsClientOptions, option.WithoutAuthentication(), option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials()))) + } + client, err := storage.NewClient(ctx, storageOptions...) if err != nil { return &defaultGcsClient{}, fmt.Errorf("failed to create storage.Client: %v", err) @@ -98,7 +117,12 @@ func createCloudStorageClient(ctxIn context.Context) (CloudStorageClient, error) return &defaultGcsClient{}, fmt.Errorf("failed to create monitoring.MetricClient: %v", err) } - return &defaultGcsClient{client: client, metricClient: metricClient}, nil + projectClient, err := resourcemanager.NewProjectsClient(ctx, projectsClientOptions...) + if err != nil { + return &defaultGcsClient{}, fmt.Errorf("failed to create resourcemanager.ProjectsClient: %v", err) + } + + return &defaultGcsClient{client: client, metricClient: metricClient, projectClient: projectClient}, nil } func createImpersonatedCloudStorageClient(ctxIn context.Context, targetPrincipalProvider impersonate.TargetPrincipalForProjectProvider, targetProjectID string) (CloudStorageClient, error) { @@ -158,7 +182,38 @@ func createImpersonatedCloudStorageClient(ctxIn context.Context, targetPrincipal return &defaultGcsClient{}, fmt.Errorf("failed to create monitoring.MetricClient: %v", err) } - return &defaultGcsClient{client: client, metricClient: metricClient}, nil + var projectsClientOptions []option.ClientOption + if config.UseGrpcWithoutAuthentication.GetBoolOrDefault(false) { + projectsClientOptions = []option.ClientOption{ + option.WithoutAuthentication(), + option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), + } + } else { + tokenSource, err := gimpersonate.CredentialsTokenSource(ctx, gimpersonate.CredentialsConfig{ + TargetPrincipal: target, + Scopes: []string{cloudPlatformAPIScope, defaultAPIScope}, + }) + if err != nil { + return nil, err + } + + projectsClientOptions = []option.ClientOption{ + option.WithTokenSource(tokenSource), + } + } + projectsClient, err := resourcemanager.NewProjectsClient(ctx, projectsClientOptions...) + if err != nil { + return &defaultGcsClient{}, fmt.Errorf("failed to create resourcemanager.NewProjectsClient: %v", err) + } + + return &defaultGcsClient{client: client, metricClient: metricClient, projectClient: projectsClient}, nil +} + +func (c *defaultGcsClient) SetBucketIAMPolicy(ctxIn context.Context, bucket string, policy *iam.Policy) error { + _, span := trace.StartSpan(ctxIn, "(*defaultGcsClient).SetBucketIAMPolicy") + defer span.End() + + return c.client.Bucket(bucket).IAM().SetPolicy(ctxIn, policy) } // IsInitialized check if client is initialized diff --git a/pkg/service/gcs/gcs_mock.go b/pkg/service/gcs/gcs_mock.go index 68d309d..b3bcd85 100644 --- a/pkg/service/gcs/gcs_mock.go +++ b/pkg/service/gcs/gcs_mock.go @@ -1,6 +1,8 @@ package gcs import ( + "cloud.google.com/go/iam" + "cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb" "context" "fmt" "regexp" @@ -14,6 +16,14 @@ type MockGcsClient struct { ObjectContent []byte } +func (c *MockGcsClient) GetProject(ctxIn context.Context, projectID string) (*resourcemanagerpb.Project, error) { + panic("implement me") +} + +func (c *MockGcsClient) SetBucketIAMPolicy(ctxIn context.Context, bucket string, policy *iam.Policy) error { + panic("implement me") +} + func (c *MockGcsClient) Close(ctxIn context.Context) { panic("implement me") } diff --git a/pkg/service/logging/logging_client.go b/pkg/service/logging/logging_client.go index 1e4dac7..1591fc4 100644 --- a/pkg/service/logging/logging_client.go +++ b/pkg/service/logging/logging_client.go @@ -16,11 +16,11 @@ import ( "google.golang.org/grpc/credentials/insecure" logging "cloud.google.com/go/logging/apiv2" + "cloud.google.com/go/logging/apiv2/loggingpb" "github.com/pkg/errors" gimpersonate "google.golang.org/api/impersonate" "google.golang.org/api/iterator" "google.golang.org/api/option" - loggingpb "google.golang.org/genproto/googleapis/logging/v2" ) // DefaultLoggingClient represent logging client @@ -201,7 +201,7 @@ func (l *DefaultLoggingClient) IterateOverBucketObjectEvents(ctxIn context.Conte return lastTimestamp, nil } -// Close terminates terminates all resources in use +// Close terminates all resources in use func (l *DefaultLoggingClient) Close() { l.client.Close() } From 5cbbc73b79a075f4c1d89d7f9b03fbfccba89c2f Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Tue, 23 Apr 2024 07:45:49 +0200 Subject: [PATCH 02/10] UpdateBackup: update selective columns --- pkg/http/actions/updating.go | 3 --- pkg/repository/backup_repository.go | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/http/actions/updating.go b/pkg/http/actions/updating.go index c017212..39c1f8d 100644 --- a/pkg/http/actions/updating.go +++ b/pkg/http/actions/updating.go @@ -37,8 +37,5 @@ func (dl *UpdateBackupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) prepareResponse(w, logMsg, respMsg, http.StatusBadRequest) return } - if !checkRecoveryPointsAreValid(w, request.RecoveryPointObjective, request.RecoveryTimeObjective) { - return - } handleRequestByProcessor(ctx, w, r, request, http.StatusOK, dl.processorBuilder.ProcessorForUpdating) } diff --git a/pkg/repository/backup_repository.go b/pkg/repository/backup_repository.go index eacbc52..ec507b5 100644 --- a/pkg/repository/backup_repository.go +++ b/pkg/repository/backup_repository.go @@ -212,19 +212,29 @@ func (d *defaultBackupRepository) UpdateBackup(ctxIn context.Context, fields Upd } columns := []string{ - "snapshot_lifetime_in_days", "bigquery_table", "bigquery_excluded_tables", "cloudstorage_include_path", "cloudstorage_exclude_path", "audit_updated_timestamp", "audit_deleted_timestamp", - "mirror_lifetime_in_days", - "archive_ttm", - "recovery_point_objective", - "recovery_time_objective", } + if fields.RecoveryPointObjective > 0 { + columns = append(columns, "recovery_point_objective") + } + if fields.RecoveryTimeObjective > 0 { + columns = append(columns, "recovery_time_objective") + } + if fields.ArchiveTTM > 0 { + columns = append(columns, "archive_ttm") + } + if fields.MirrorTTL > 0 { + columns = append(columns, "mirror_lifetime_in_days") + } + if fields.SnapshotTTL > 0 { + columns = append(columns, "snapshot_lifetime_in_days") + } if fields.Status != "" { columns = append(columns, "status") } From d7c571488e2da615fba3d90de861d03c283650d1 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Tue, 23 Apr 2024 10:45:27 +0200 Subject: [PATCH 03/10] Compliance checks allow Storage Transfer Service to have write permissions in backup sink's bucket --- Readme.md | 16 ++++++---- pkg/processor/compliance_processor_factory.go | 29 ++++++++++++++++--- pkg/processor/creating_processor_factory.go | 4 ++- pkg/processor/updating_processor_factory.go | 2 +- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Readme.md b/Readme.md index 51e18a9..5663582 100644 --- a/Readme.md +++ b/Readme.md @@ -585,8 +585,12 @@ Service, is used. The service agent's email uses the format #### permission in data source Google's managed service account need following permission in the source project: - -* Storage Object Viewer (```roles/storage.objectViewer```) + +* to be able to export GCS date from source + * on project level permissions that are part of following GCP roles + * Storage Object Viewer (```roles/storage.objectViewer```) + * Storage Legacy Bucket Reader (```roles/storage.legacyBucketReader```) + * **NOTE**: these roles can be set only on the bucket level you need to define custom role #### permission in data sink @@ -596,7 +600,7 @@ Google's managed service account need following permission in the target (backup * Storage Transfer User (`roles/storagetransfer.user`) * on sink bucket level: - * Storage Legacy Bucket Reader (```roles/storage.legacyBucketReader```) - * **NOTE**: it is done automatically set by the ```runner``` service account - * Storage Legacy Bucket Writer (```roles/storage.legacyBucketWriter```) - * **NOTE**: it is done automatically set by the ```runner``` service account + * Storage Legacy Bucket Reader (```roles/storage.legacyBucketReader```) + * **NOTE**: it is done automatically set by the ```runner``` service account + * Storage Legacy Bucket Writer (```roles/storage.legacyBucketWriter```) + * **NOTE**: it is done automatically set by the ```runner``` service account diff --git a/pkg/processor/compliance_processor_factory.go b/pkg/processor/compliance_processor_factory.go index 3482be9..f69afe3 100644 --- a/pkg/processor/compliance_processor_factory.go +++ b/pkg/processor/compliance_processor_factory.go @@ -321,7 +321,18 @@ func (c *backupWithSingleWriterCheck) Check(ctx context.Context, request request it := policiesClient.ListPolicies(ctx, &iampb.ListPoliciesRequest{ Parent: fmt.Sprintf("policies/%s/denypolicies", attachmentPoint), }) - + gcsClient, err := gcs.NewCloudStorageClient(ctx, c.tokenSourceProvider, targetProject) + if err != nil { + glog.Errorf("could not create new GCS client: %s", targetProject, err) + return requestobjects.ComplianceCheck{}, err + } + defer gcsClient.Close(ctx) + project, err := gcsClient.GetProject(ctx, targetProject) + if err != nil { + glog.Errorf("could not get project %s info: %s", targetProject, err) + return requestobjects.ComplianceCheck{}, err + } + stsSinkAccount := fmt.Sprintf(sinkSTSAccountScheme, strings.ReplaceAll(project.Name, "projects/", "")) for { policy, err := it.Next() if errors.Is(err, iterator.Done) { @@ -347,7 +358,7 @@ func (c *backupWithSingleWriterCheck) Check(ctx context.Context, request request continue } - if !containsOnlyBackupServiceAccountAsException(targetPrincipal, exceptionPrincipals) { + if !containsOnlyBackupServiceAccountsAsException(stsSinkAccount, targetPrincipal, exceptionPrincipals) { continue } @@ -384,8 +395,18 @@ const ( allPrincipals = "principalSet://goog/public:all" ) -func containsOnlyBackupServiceAccountAsException(targetPrincipal string, principals []string) bool { - return len(principals) == 1 && strings.EqualFold(principals[0], fmt.Sprintf("principal://iam.googleapis.com/projects/-/serviceAccounts/%s", targetPrincipal)) +func containsOnlyBackupServiceAccountsAsException(stsServiceAccount, targetPrincipal string, principals []string) bool { + // sts & backup service accounts can write + allowedPrincipals := []string{ + fmt.Sprintf("principal://iam.googleapis.com/projects/-/serviceAccounts/%s", targetPrincipal), + fmt.Sprintf("principal://iam.googleapis.com/projects/-/serviceAccounts/%s", stsServiceAccount), + } + for _, principal := range principals { + if !contains(allowedPrincipals, principal) { + return false + } + } + return len(principals) == len(allowedPrincipals) } func containsAllPrincipals(principals []string) bool { diff --git a/pkg/processor/creating_processor_factory.go b/pkg/processor/creating_processor_factory.go index d133ee2..b8cf9e9 100644 --- a/pkg/processor/creating_processor_factory.go +++ b/pkg/processor/creating_processor_factory.go @@ -21,6 +21,8 @@ import ( "go.opencensus.io/trace" ) +const sinkSTSAccountScheme = "serviceAccount:project-%s@storage-transfer-service.iam.gserviceaccount.com" + type CreatingProcessorFactory interface { CreateProcessor(ctxIn context.Context) (Operation[requestobjects.CreateRequest, requestobjects.BackupResponse], error) } @@ -438,7 +440,7 @@ func prepareSink(ctxIn context.Context, cloudStorageClient gcs.CloudStorageClien projectNumber := strings.ReplaceAll(project.Name, "projects/", "") bucketPolicy := &iam.Policy{} // Storage Transfer Service needs to write to and read from the sink bucket - storageTransferGSABinding := fmt.Sprintf("serviceAccount:project-%s@storage-transfer-service.iam.gserviceaccount.com", projectNumber) + storageTransferGSABinding := fmt.Sprintf(sinkSTSAccountScheme, projectNumber) bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketWriter") bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketReader") err = cloudStorageClient.SetBucketIAMPolicy(ctx, backup.Sink, bucketPolicy) diff --git a/pkg/processor/updating_processor_factory.go b/pkg/processor/updating_processor_factory.go index 506ca63..c5d80fe 100644 --- a/pkg/processor/updating_processor_factory.go +++ b/pkg/processor/updating_processor_factory.go @@ -85,7 +85,7 @@ func (c updatingProcessor) Process(ctxIn context.Context, args *Argument[request // handle status change if request.Status != "" && !backup.Status.EqualTo(request.Status) { if !isBackupStatusTransitionValid(backup.Status, repository.BackupStatus(request.Status)) { - return requestobjects.UpdateResponse{}, fmt.Errorf("backup status update not allowed from %s to %s", request.BackupID, request.Status) + return requestobjects.UpdateResponse{}, fmt.Errorf("backup status update not allowed from %s to %s", backup.Status, request.Status) } // make a shortcut from NotStarted -> ToDelete to NotStarted -> BackupDeleted if repository.NotStarted == backup.Status && repository.ToDelete.EqualTo(request.Status) { From d7dee5a5fe073d89dbab191d4f403860eb9f7b64 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Wed, 24 Apr 2024 08:40:43 +0200 Subject: [PATCH 04/10] New Penelope logo --- frontend/dist/penelope_48.png | Bin 0 -> 6003 bytes frontend/index.html | 2 +- frontend/public/Penelope.jpeg | Bin 0 -> 91776 bytes frontend/public/Penelope_250.jpeg | Bin 0 -> 18253 bytes frontend/public/penelope_48.png | Bin 0 -> 6003 bytes frontend/src/App.vue | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 frontend/dist/penelope_48.png create mode 100644 frontend/public/Penelope.jpeg create mode 100644 frontend/public/Penelope_250.jpeg create mode 100644 frontend/public/penelope_48.png diff --git a/frontend/dist/penelope_48.png b/frontend/dist/penelope_48.png new file mode 100644 index 0000000000000000000000000000000000000000..463de9ebb235ecb5c726adba1cbd34eec7ded986 GIT binary patch literal 6003 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 - + Penelope diff --git a/frontend/public/Penelope.jpeg b/frontend/public/Penelope.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1c80201ae19c11d6e8d2cd9eecef7dff42380718 GIT binary patch literal 91776 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 literal 0 HcmV?d00001 diff --git a/frontend/public/Penelope_250.jpeg b/frontend/public/Penelope_250.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ed4c3a9aac20084f254f234c3a995cdb650cce49 GIT binary patch literal 18253 zcmeIYbyywG(my!3Yj6U=LvT5`yF0=CaBz1Cfgr&l5G+W7ySqz(K(OEhmjt&EG?2g! zNxt{qXZL-7`|RI)`<$8iOm|gvcXd}!RnOdgzFPxu6=fA<0XR4~zzqBWck95UjE}7q z04OLh0jK}~pab}D2ml<2?hi6Jq~F(Mj#T;r6^PsYTS=%~^&>XjQ(NNi1iqPotsBoybNWpAu<^9}XT7Ih9P(OR9fF+HX zD3!2}ppT=ABh15`%E!^c$z9M#gyy$!K@i_(v(r%hCh@Qrq0s}IE#>S6qk^zO*f>~0 z6?<{ffHK@HtpqitW&cV5??h<+Hr3nPo6Vb>&DqVGol`(SfSrSjor{YVq+oUTb@DLx zVRdq+{YQc{%pK}x>*8VS>_l}h(cHq>(?f&?tp88L99>jY{w4T-)PbYpeQSSHyL-rZ zfz1CWjk#<4y1>{qVeZbJZcvzv7tG0n_8<295&9SReJ8AK9bJBR1Ju^PDzSwAi^j#% z&Ea>sC6pcJ0CNO|xr5`v`7c_K?_b3KW8?3u274$d?F{w2XIGFGp}DVB(9#)dYbp2} zT5v-6xS$-CtQ=5tPF4tnOMsOh!o$JJ!^_FV!w==RfN)v-NW1o3cHsBN@lYK zh4AuoSeSG3^04yra|^ISpd41L77z{yD~~zU!km*2%FD;Y{SUvIn=Lqv%pLx-J@;}f zK{-4emR1mRC>N^*6k@>&v9jb~wJ_(hWaYH76fn1hLM`|NIB2Nul_)5ot{_6g#rDsw zx`Vlgm9v|p2#tz4luAwOFO{~fBTUP~{9Z?#e4N|@;E#iokB5(oo9i!y4$RFRoEi7B ze%lQVl_gYA7L;TT4zsPJxiyU4&IM-uyY+%nZZLBXXE$wUX9p3Qe_7j~G8J%ATbg^A zOPhPZKxzLnKJ9;ukC}r@kOOQl*jYgpXG>cv-~Tt#do%hycJj9FV70z~O+pLi`WHFa zQvIHgg67cs=`BLzZtevGN9s=|)W+P&8U`9FI9dMW*!~Y=C&0(g$<1TI%__hRjyS}M zlbh9?3kGF{@j#(42#k}Ln;-TMzq_-Qhqt*KOu`zRNT3G5Y4m%&pDj<$d))_Yn1 zGtK{+i)9IhAz-*h1yYDZUEDlCYzAUpZx5GyoCab7&||X%aXE-t+`tBc`15_f#UFfd zkF9^>`%n=eaMRY50{eC!LsD7)2e$YR47G7{1Zf~34V|T<6DS{E?+>=T$ItGuqk|{d zw%_SKp2UPXJ=FrQbl~w2kOLF|6+j)J0xSSez!q=-JOEbk>ICMv1DasD zH#v8Azj6TpVF>`bo&L@5oDBf{=V1P;f9KJ?0|1;*0BG#{cb-Km05pXI0Li?Ixtsa# zdJw=fycHPKe=h<6EPViYFb)RxhJW-1mfe>Fd2;}u4eCmH6ae0&0|0|HDBI|N>G%E4 zz`ykNf28@Vzq>_13P6B|zyE^?5&R>eA|W9nBB7(8AfsZUV`5^UV_;xm)4le+!o1EdZEcK0Gc0E;y6- z9QuR)#SWk$-eWkH9_onSnF{x3{4wrGLrzu%j9vXbavrA`9cyH&`|Nv#V$O52vK#;0 zX#Al4MNC#J?FuTQVb$oJW?FY&iuejV*~%M_yz0!*wHvOhQsBRqg>=!R?~^Mj2(7hH#GlFs@i^8Jm1i=Ik4hKP^7hSHC)7(-FfikDkKmH&GwW^sUEBS$&>gTw zG%5hjvaeZ?+-HT@k5`a;==vs@coA~6NM_)lBfB=AdVHZ$qQOvz~1!>qks6Q)j=r<`d* z8twAy*|sgJKWr3z%|snTeh0fFANzRFi)(MXh$lk8w~mLeedI%B8dSL2b&5%$x( znvCmrd|VMAC8$gQU0Eslin0l<$o>7orR)H$=hGq?*)bHXG5h(Yokjj1?2ilHUB6?0 zS|k3K0ZXP}W}DJ9XP(&o(mu<(r?3>5iqS2p>nmmI3Oew(7gOBBBT=M&0rep{8 z_D;W)$cAoOz8|(SdtUQq8Et2@q+dT0^IjbXh2M`4oZa|xe>25u`@qUj2VVV z>%xVS?J7j0_QxV=U|`s@U3ZZ1w~=btII5pEuBS6OXUDdGv^Jc@FUs=KnK2AB>FCK3 zZffi8Iu|het8UiJUyEY2c_gDmu+i`P3!3wO6IYp#muHPUJkD5{u&hpAg}E<5e^k!7 z$LnFr-IAtZ;{R0d8K1z#STcia-FeNk7}=&SUh*%K4+p8#e6eU+yN{b9__kl>=f5|hOQMkQok&A};k|ES}Zw|gGc5gph-hRYgUaanNz zI7D~=>HbmiXWd1FgGWFDkWujPABYnWQWMc|)6#Q7=s3)X!9^GqT%h5Q;O~Ign$N*y z35&6A2+*!f>7 zMdZ(+_^Gn-0x4|O0gM_65cfu&mO{^%{26(!?1$eEqx7nvdD_l80+pli2duv5#MO(B zL;AQVdE~wyZ7(&G(oX7PIb)uRGAP8qNaW(6iN>l6@^0sFA{sOm*Vb&{^5qWW8cBVQ z-G^%h=dF7v!^DOxn4)eR`#^)%ucpoe|8lM0M1knQ(;*>&7>_49Xq=|icl(nXHadJo zNaB0L+aFst;|`0y+;4=xXVI07dItF_i^nWBh~-v5GsRQ$ro(3V$D-+`$}JgE>UJvJ zZ6376S}W4+-%u!N;5R-Fy92UB zust0#g07Q3mb@ma5OlUD%D?qNO?)JpTzTXE3@chHXv;)L#z5y)g=5;!RNBqR_*gnM zd8YRtXz{e@rQZuMIOFFjc@OOsh%ze%C+&TLKM(8^)i-f;{Zz?Txba{;qCF&>%a_UVX_DL>GsFON-#p|r zosax^zB+w^{nIh5EJoT+QBp2yTP4<7t7*S-aI?w@S4<3Z`Nmun=fG^R6+~5)2 zXBizy_61YycjFR|5>u~NsK;cR+9TgROVe!~?NG*U4(sS|c}n|@?~kyfKnps?^6 z%`}SV7d!gO9$7?%Hv)!Nre!$F(o2?>`S1!@|1L3C5Ju4}2{K`aD>*ikSzeiZdgRW) zr6Ck1_TaLQydf#>o2gHDWwnvaTT&DSOadmldJe@QD;NB=%^=`O0j5SLq9` zUIsb)m5eaYhMTF0gU4$Ig*~4Pkq^YspmrOvlzzOH=?xC~e#qO|PT(oWNkJcTgficp zbQjw9A}vPx4k!iJNH)-ag@=Q`UnAjvyYK+`whaLf5g+#fHK#ZYmjn{G1_7<4rkN|1 z`HSC+Bmx|q7~CCzg|56{R>>vFApGhv6Z9dU@`&XY2cpWW7@wh*Ajb>>%+FSW*FMlN zkB+RIoSG#U^NkH#-N^$K4Kx-vSotGF>E&S|DDC4`%aLKysN$vYTiPr|N=j!v6 zuZ!U16so2Y!XNTM!$P*ZDYggDnYKDVS2B~}9lzYQCw{}*F)* zC8M7{Oy4}yAMA6+aC{p+HzUctgYUG>e z`Hu_yyZw%BO=Si!TJ30cn5=X;% zU{!Y1vtYsr_32VYNLPrpDDt(c4kQ_yeWbncV3~lJGoGpQwUTLm+B-M9+pnr+JR)q) z#A*qN4BSYM+dMMfd&rj>&^V^R7HwY0DBKo^{wSqvQLDAPJvs4cS;eS6#8&f3CQ3sa zm;6kcuGo6abvorxw911p^WGh3H) z#g1J2YvYZnQ^D?PMha!p=fOI`H9~328!;RyakZUOdyhPPTFn!!KJMxjJ8-r32Od2* z>AM)I_KXzGJ2u5mC;Re(d4dKYZd5T94K_ zs@BQ1^5lN-;K4)kdJ3z`*Pa1Fy@pk;oNH6%n=vFjK|iIZ=bB1lOK)@8asrbCTlq`80*nu4CB;Ki#TGjj!6et~+t3O}(AU zwB93oUQo2B{7XvJpLEQ2)VCvknXRZ{bhk;C5``7OEKgxhpg+e{YIAoJnJ;o@!nM$Y#g9r`Ak8?@2Z1e_}0 zQSX50Bq0Z_-4SyLIsj2uuO28VIuu5rVq0Tq77NptBRAL<`n0w(tEbQH(axB?6NO}9 z$wFp0MD^~wx`m)h5#q;?RNVv3-ZrTS*G+_H);8n^@oN`i52#r+%`GGJCHh=K26KoR$x3-lWFvY=$*w?a1-=w6 z77|f<4Rs5bz#Jr(BH2AFqh}9=imec^2TR(~;}h`i;~js8B?bLOVjJU(IVpkl#CB9YaSmU@&nJjT6doOfC2rQi0^L)C%Lqd?+BxO8%Hn{T#!X2z(~u1@Q1B zUTL7owO<%W?lyQBwEAoQYSTSj=yD>KD3*lhgnVyRF(5xH4rBuDN)8tQpPt}$5(WYq zGRl1b0^R}O9yJki@+A zlP7$r)hzhItWo^@x?~Ypye}*{a$**ccX&T-!_WiJXhF} zeNukZ6XDP#@$9DrLhFYLE3E~=v1cEz($2J987jy`Iejv7Is8lYH{JBp8gcFS?P~;}4d|#D4fE0pbG#tLv#+MW5-m}-sP(7QKlXYl( zZl1e4M5M5dkt84X#nWE8l#WpF1v0f69va+=h6>=DEd56>{U==)+R39~B0ll?S%#ynUn%+wlo%YiTlJ;-F!rA8&x zkUzRNLhY!42r_|pT$${yr5F|&l=s;yI`D8h-^j8Alcj((t0LzPSgT01Fz=TYj)wH)Bmd|YG;71qpd@SK=%+otr@LTB|YSk-CN|`SH64XJ$_=Z70r_} z+75alMfLB(ENXD@doIQhjg*K|C%;VFP`T4jb6R~_YeH8z;m=o4HA`4E%rJUuB7ZqE zd0XR@Ls#h#Q4o1hG~fC0<>f|KZF}3*%-4iXCpVt`fc*hF3c~L~c4Bp!GSL*F4in7e z)?|xI_6!U-L?$c;VtGnr*rE3J-w9Jccy1>xgq`b<;WbYv(@}D@qbKN1_sMr9o%FJe zs(i-?JDUbx$_)1LOz+E}1srnRt_e`g52p=qJfMC?aXvcJdSP);w^P9J>5kUQ4n~HARqN?JyC&qDA$%_o1-s;p$ zRY&2E@8>yRxpSzD$_9f-he-5y>JQs5< zNG{J?RpGYn#+K?q=Fz;IWuVRNs(+r0O({l0huYCwoOYbFGqe;jturRy%6$FA!k-Ic z^-1T+&|B_jEolf#{m-A)zmy{P(y`4WS{we*{A*SZU>|ML@cqiRijE$o_ZB{E!$FwR z<9jsY^`#Cad;zN)UQ;o{ny11;rrJUGB-W%dS0)GV?}Z7j`m9?`N~*t}&>9c1Uh?E2 zKiqN_*k@YiD7#j8F4@KVuD?}OrV^`^&z>T~dev>zk$u^{zRIYZ57hu zU|HeA+Biq`4c**C2B%bX2T{CMN{`HaYl?@4@n&U(5 z*M$^#UE-gV^=Fe~GTAy;O%|NECZe9(8b;=aQ7GGQ!5%S9lklXet|^?_OZrn_ei-2s2K5(fj7;dUBYNKd^54_^hq7o+Hf?PdazOrRX(wV|)}& zME;R;7$X?QtbXx>;E0xV`4F0Q z86$EBL^Zks*|vq7<&o{vwMTA^Ca02o4zsd?m)gC*ylx4rp2{uHm>GT+et&KHqCCw( zI4$*P^r#@Pp8mw<7hG8Yk|7qV)Cbv__{#Eh_M)>2MpI9T7pB~W%RiZ9&eY>QEyOl( zzEz;dm*!efD~vy!w=~?>hswnIGFF#SE%3oiphekyx(cw|Kyj4$BX;ta{%$~h56%KD#w93h-_%YEg*{`W@<$8C3 z?xx&{`te@V>=(~6PY>Pfv|<2NBrK1V~FsD`E#2cQk~*u8-fJ*Z4lF_jRlEIRpnLwy{@M;anL} zy=D8=8$EBxT_1lp+?3w;Vz#aQxBw$oKs({b;#_A~e}qt}H)#09R{w^gt zYF-@_-=#tIDw4ND2=muWHt_+0d&6p|QfI5x$E^=-Why@nlaVym*;+Zv3ECgNI3&tt zdU~|yW+fOh+&w{}9AnCtY{yKPZ92btSU%?cW7H_TeH=lp#cpQ@>L}E#trgF&A5QzB zaz$c1eNslWvmnbAUhIeWkGcJ>i>@q)tL$m^(i&V~fGPey?pHyTv#Xi4B(a-}J!MF$ zEiJD@dQ_ZcZ>Jn-*yY^bq(Aey*ZEvx$yIu-+RL3s42EY0F;@(h{nSGrd5?~_mCd7k zdRE#*a^J!9p|GF)R@XY6e0P9?T7d_dkC5_@=g1WoFPQ|^zp7eMwqX}E>m0*nRoB$d zby~Cs=B=$7?gzjUq^cN*%U|`?u+tG$%zT|aKD~G8nQ%cD9u5H>836!8^53opfJY5_ z9ps0@E7jRdFqS;yyqNsj-xJ(Au_(W= zS7mzeq}k*R<2Yc=7F%iUGevO+q}&06lZ|D{lo4M%vDUK5AC+U3Y_sgHO;|n#D0nv525lVJ?@FIzrw8 z9DUObsaE)8Va_#n&{Mgn_n)cQ2dP}Wc7^1a?OiKADqkUD=w|w}&Edc!4*WD5>^!g& zrL6fzm&WvvjO`-a#yLl7O}{i*fVRP`ix|PuN;CTLE2E6&4tjkX1zrL3<$V{(Tq*HY ztpQTc^Kz$1on}J0a<$X3r?vJkm@VTHXYkGHt?1=#-&82_PJbhyKb>H`WP+_WnZiA@ zV}0QurX;albqJAF-zwEOTB$x7d;Nt>mM-0HW@Lck2p6ShX#+_kGjE(ch6H{TSb=jH z>U?jeeBt6@F`{dzs}cB>0++}eem`0ymvHYaHmy^ZOR2dyh5(cndDp1G%w~?DEw3(E|p$V+J9Qcre65m92n_>8ZI28&Ep0yT_gEV=s z26Kzd86N_#5O1}$k;d}9qdi$!C+38gUwxxrB6Fk8l9sPgXnBO}Guq?4o$~BKxE6cK zXQJoNaF)&;*Gyd2*#C$UY5Db_x$vtO(QYr3Lm8ho7EHHQ8+Qzxrupc4R{304&Jq{8 z_K9&+oeZwPKR$d+%((kmTD%*pjH^_s_N>DQvzo}iS=$q_vuT~R{S~)(D^PFsa!|dY zyxXq+g$on?R}lvKn4)?~rdG2|!eTKNDVsFUTga(-nI6W*Q|QRl^1iC1He3*&)4C!X z!qK-ro;zTf0QNogMPu`bLRza*V^FVaH-~@00wPs|YTSVFMM{lpo=La~sv1#~ugUGY9Jqs~@a?1=Xr{)O!!ky-G z_ZWFJpf^oHs(R3c;lad%i2S*O#5s$#!%8W84(|B+gUNydPV>3N)>BJ_w}p%m!Y z@W`=oFyVsEH>b}vG~oE`=(n@gOEV+AcfU0be|S^Ew{P;wYI@U@0oCj}urQEZhOKBU zP=%pmd-LatrVHxW*xV*dM{r$=Na{W(mIyY73+FGb<)|B+Phq9?;`B_rNAK5efS~%d zseL>_l#wmitz_V17hJ*7(DgLq_*=wh6VEO4{Wi+3>nJW)-(eWTIAkB&$&iFgVutTT zd|v&r$;j*SIb3z_!{XL;{59L&N@m9XGot3g=EOh}7&z=miQ;OUv zw1~wIdeJ)+GP}AX{lt{+Q9ZG@oZvOQ=$GRX$Z{Y`n6&sZ3MaC3R}dE}XIHRt1Y4C> zdEb@Ymy0dzaTz_hqMh5zR5RlW<&hZ)gpup41cgA7&K2w?_7;)(_vj80gBhE4H!&T! z6r=)|Yj13D;BL$x_+w{1HffW}ekyJ``+!3&Gozn5Eszq1J7V4x>VL)j(vqn`OZ7C4 zF~ku0#W@SJbL#mdy_3F*X^Z6(N8Kw_`3$P2SQ(Ce6Dx}m_=DEi*LDoB$@ZiiCDGUA z7Z@CJa^3XF#Js%YJLZwLShTsB1C_XQ7f)+R9xW=I4fNTU!7{lR(x-WP=4?Q*{4eq<(AMBN8Vu&C12j_Zn3wHi(L;V~+6~$lN6z|!{^tU~Y zcfdzc^nY%$Bklm9!#~{hue0z(|0!5GkI-=Zo1dt$k@FWfP6 z8e;uJe=NF%7Z&>)JJ-E?@%GEx-`J*4?DTV;L(1>oqgeYbs4&iag@kjv&!=F)La|YJY6oDknWIzk!9Et7CDRIM1ivY@lFuj@l1p zZBDBwmNu+*{*;?Y)$<_h!R`BnP%8~rirTuh1!jkt2M%Zc?_tN|K_7eA7g$264*X;) zKJMZW!Kd-0=qnB3KBUbKT+BhTSST@R+(&+3-vP%yZ_vLZH7X>nO z^E4-1id@;g821NXx{}Zr7&f3%0;fbjnp@UKFZSikX6dnDP5xe zS#Str_Zu}Y>?VzwY2V!+=B|XlgEm>9V(;?xkb+w+OLx{0uTcxL>e$9=djfBz1`f^n z159{sBuuDaN_mols5YW@82|M4nMHo`_tZ31ziNa_i?c+nXS5F(7~uG+T5`NSM(I4@ z@tikCbGn3{|D-=LTCzO(@l&}H-BW3CCZ^egVIZ>O2BHm-#`C~N9n`}-#SR5(kE zJh`-5jSzWJXsa(fc8Qlf@Pw+l@5ddW&W^|+_8yKLe?TY?i$z;B=oy0YB<}!5iZj}d z4m(>{m6HX znpRf{yE@`lNBtUcsek(s&awC;j+O2#f?$b|R*evMMBKOJL!y0(udK_HVwd|rwu?aX ze)8A6??2@D^Cib0^OnE`qn#Hu2{{wrsMLE-%zj(<{ksd?R5qg)x6R@I;c_iDE?7)& zorq4vW>Qy}KW4mLK4i(5Lf)CsA*UGe^-Uee)7%@}Y?r;}$fR$uYuRSiy<)0bmOVjc zV8(Pu_WhyWn&YJ|aMMA$t=tcAUfbiQR_+Q!-P0QV>1co#`5>ne&-qm~~_elWwJ- z6L((w62=ide^f9gBnw@~Eb#+breZMD#snAX#>@^2#hODR9Goj2p`vbnOGHVN!mYpZ z=Xxu0arVs>LrgxJ>5I50DbUvVS5*0*=;YrbQmg6=UIcMNIUCd2PrWjRMu$1O@HPzk z+=g4M?3~#)<(zir=$1It-4u<#;5;SuFvf6`9bMNE>fU`4sf!6L{Mw9;nP-VTI)7-> ziajx6J<6E*6up~kragz$Y}X2b^0ouxsb(1&Td(qTor*h)l z2nv%8eW(X<@hSRjN=k~XI5aLYsu$bG5no(ow=xPd!{0mC^QB2Gvr(S>`gMH5^y#O} z+Q3K?iQSM#WSGm|68Q>yf*g@L|*5NH&A~Z>&CpoY%lr8_yr%j zyH*16C%j{{qej~yO-sQW?-e;e7VZKa2@?-pF&1xsiE-EBwCw{sakpPZofs*L&6z%O zmHQX;_7v{ox^cG66>T}nJnKRA0au(zO&aU8s~l~&(d@b7cJFKV!d-quUzT}tiAH}{ zw1}!m%S*G8RaD#~WrfrJ=f`6i1tI<`G>b}1>cu{ZlV85#IjV1-M5eY!h58OWuRAT= zrQC634UDp961Nfdi-xPf%VID^iO7gDaFkE-5N%W11^bsZ$%t-jPPAFH zn$+nAtC7%|2WzgEPzF8m)g9omj3`%sqzJL*2DG_xVU9PlWzcapGAD!$(UWDRl?xKj zjAKNv2@lHs90Yib)JaD#rCAy{+Qk%C?4?%jw&wMCbsS8g<8pWB!w2(6qp#G|R%1~V zj0d~$v}~0zgkOIYJ{={h*$MJ_aR}#ND>g26h4XiB0`C5k0s!S7d&L8O?@e&HzQ{@F z!KJF1SpRk+{^xNFes(2>dk1v$i>=kYOcXrA&hc{^R3db#HfD#U%is<@!R&!2Z1%VX(0mM9ntIxt*= ze-08QF;Tv##vyu-I~~h}_O7&?Rll#pfA~0bD&X)>e9OqfBJDCR4-GY=1q^}AXXHI9%gNni>5~= z1w3`fK6SZ$0jp8v1pwDLt>Q#K61|Tvg=OJew54f8K*tVm;3tmB>Jpo(Y~iReIo;)o z9uj5dmNrMHoKn9>x5M`;RyLA=e5uK5HT7Be+Bw`K5Ht>q6X6Z)87IoAFU*#j{35De70AJu3^tIe-ktE0+{5TmCh&zM(FZxB50zf!>w$40AUyos zflx9Sm-^+{bY%e^(K>nPwx4hyUyfcDzH`pFTmn93ZGuU}TSx z<1d~V)PD1-mgd>?83$%TP@j?##iH!dNS*MnE?#BlDE?1=15U68>l?qQaAgx=4)x1F;ZjO;;@M*hU!f13Cs*? z)X7M6dFif};~%o*e*bi$NT@1scJ^rA9)sm#eD9&J zg!nR#nfRvyx`lUZUb^@Y5=onx;(46eS2AhsrDq^x4CTZ>SVhoRPWh z1Z^eE8oMb<=KBdq03HJqI)fl;q!W7FSv%Cch{qHali1iv0n?XoaPB8~3?_|RdIiC9 zM99=_yaZb{ja{BdwbHGuN@!9yDy*bgMg~=i{U?%E%H#;a-_}y$+jt@$BG`J2i}aQ4 z&LgHs!_TJ}@9?_#V>3eFUMT`}8bJf^{hYADw>S=K55CuI#o`JfJxBLP%E=U>SU*wk zg;^t$5uX(&tKnwq6;!#Zq*XA_Y^^SBztoa{coKn0$+FHhw^tQR1shW$$`Sk+iTt85 z!7fUl99bMsgt$$XTLy80z%aYcb5=-6W3=w|E^S`H2Y+;VX=yIE z2l~PAdj?O*=_~Y|B!&uRY_jXQ1h|kDsK&|baXKeZJoKp(nj<#e6!@?ag)_7h2s&YB zD5S|Nv}h$FVSxVfaf8Kzep``)cI{6@39 zDP>oyOJf}qQ!2@ukpmTV>vAuHD|#M$kLNcY7V#iJ?xukf_3=j$J`~02Op#w@0I@c@ z5hHQ(_+)JMP42Ugl$UgT&JDq))j%}v=B^Wz1(x3+$to~H{ogPq3HlY!qB zP}0Q_4Cg8>J#yDl?Y#q>rg`I)pBPs^qb*$9m4yt_0QhFY^ zG)dXp7mNfoS{N(`1~&*gv*)w2?;WrZqU3Skz91bygsUdRyLDhZ-OLIzHj6$&In*+G zq;w4S6#=`e_DyZTU^;xaMuLCCPj|JRF+JNBe`MW_SHZh}^B&z*a0PH$$ILdS^1C<|GBUuXSp=^WrBs?4hE|L0Ke7LRiv1mf{y5+G|gxR26%HL{D=z2M3KI zi5#4d$_|6!Fq47qaXId$-UH{)4IGJp!jh5Dx3A+-MhHr)U2GMOxQvLI<3+wmS>tL! zwit0!?L~E-aN{x`A~X`+;Yok6tNN+pe6p}Y3y#mn0Bl+h>w5~|>8q&yQ1u2mIpzbi zka<0|zYheRDxC>#)cFjxDM`|2*csg>1S~Xu?GB1ToR~NN6W}q$Gip#$>8Mo<#eI13HfcU zg|70>cH0)BFo{0ax@lzd8Ej#=7CRCYu{=ps=_?dZU}Mu38B9WZY0htf?p(euinaH; zOEgk)yd*^_B(O63`;3-M|C}h|4!__NYQ(Qqz69gec243QI|;E9Xm-TLBN=!U!JN2S z)jrwp%6{Y-Q%-TeOr#!cE6 literal 0 HcmV?d00001 diff --git a/frontend/public/penelope_48.png b/frontend/public/penelope_48.png new file mode 100644 index 0000000000000000000000000000000000000000..463de9ebb235ecb5c726adba1cbd34eec7ded986 GIT binary patch literal 6003 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 -import logo from "@/assets/penelope_32.png"; +import logo from "@/assets/penelope_48.png"; import Sandbox from "@/views/Sandbox.vue"; import { ref } from "vue"; From 9476287493410eb9cb0e095cd97b98943487f352 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Wed, 24 Apr 2024 08:40:55 +0200 Subject: [PATCH 05/10] Fix compliance check --- Readme.md | 38 +++++++++++++++++-- pkg/processor/compliance_processor_factory.go | 10 ++++- pkg/processor/creating_processor_factory.go | 4 +- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index 5663582..bdfd9bc 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,7 @@ # Penelope - GCP Backup Solution +![image](frontend/public/Penelope_250.jpeg) + - [Penelope - GCP Backup Solution](#penelope---gcp-backup-solution) - [Introduction](#introduction) - [Requirements](#requirements) @@ -495,6 +497,36 @@ flowchart LR # Role and rights concept +```mermaid +flowchart LR + subgraph GCP_source_Project + BigQuery + GCS(Cloud Storage) + end + + subgraph GCP_sink_Project + SinkBucket + end + subgraph GCP_runtime_Project + Backup(Backup) + Runner--"impersonate"-->Backup + Runner--"runs PenelopeApplication in"-->AppEngine + end + STS--"member of"-->GCP_sink_Project + Backup--"PenelopeBackupManager"-->GCP_sink_Project + Backup--"PenelopeDataExporter"-->GCP_source_Project + STS(Storage Transfer Service SA)--"`storage.legacyBucketWriter + storage.legacyBucketReader`"-->SinkBucket + STS--"storagetransfer.user"-->GCP_sink_Project + STS--"storage.legacyBucketReader + storage.objectViewer"-->GCP_source_Project + + classDef blue fill:#4285F4 + class GCP_sink_Project blue + class GCP_source_Project blue + class GCP_runtime_Project blue +``` + ## Service accounts ### Runner @@ -517,7 +549,7 @@ service accounts. The ```runner``` service account should to be able to impersonate the```backup``` service account with the role: Service Account Token Creator (`roles/roles/iam.serviceAccountTokenCreator`) -#### permission in data source +#### permission in data source (PenelopeDataExporter) The ```backup``` service account should have the following roles in the source project: @@ -543,7 +575,7 @@ The ```backup``` service account should have the following roles in the source p * `bigquery.tables.getData` * `bigquery.tables.replicateData` -#### permission in data sink +#### permission in data sink (PenelopeBackupManager) The ```backup``` service account should have the following roles in the target (backup only) project: @@ -590,7 +622,7 @@ Google's managed service account need following permission in the source project * on project level permissions that are part of following GCP roles * Storage Object Viewer (```roles/storage.objectViewer```) * Storage Legacy Bucket Reader (```roles/storage.legacyBucketReader```) - * **NOTE**: these roles can be set only on the bucket level you need to define custom role + * **NOTE**: this role can be set only on the bucket level you need to define custom role #### permission in data sink diff --git a/pkg/processor/compliance_processor_factory.go b/pkg/processor/compliance_processor_factory.go index f69afe3..1dd3a1d 100644 --- a/pkg/processor/compliance_processor_factory.go +++ b/pkg/processor/compliance_processor_factory.go @@ -342,10 +342,18 @@ func (c *backupWithSingleWriterCheck) Check(ctx context.Context, request request glog.Errorf("could not get next policy: %s", err) break } + // list policies for the target project comes without rules + fullPolicy, err := policiesClient.GetPolicy(ctx, &iampb.GetPolicyRequest{ + Name: policy.Name, + }) + if err != nil { + glog.Errorf("could not get full policy: %s", err) + break + } // We need to check if deny edit permission for cloud storage is set for all principals except for the // target backup service account. - for _, rule := range policy.Rules { + for _, rule := range fullPolicy.Rules { deniedPermissions := rule.GetDenyRule().GetDeniedPermissions() deniedPrincipals := rule.GetDenyRule().GetDeniedPrincipals() exceptionPrincipals := rule.GetDenyRule().GetExceptionPrincipals() diff --git a/pkg/processor/creating_processor_factory.go b/pkg/processor/creating_processor_factory.go index b8cf9e9..80acccd 100644 --- a/pkg/processor/creating_processor_factory.go +++ b/pkg/processor/creating_processor_factory.go @@ -21,7 +21,7 @@ import ( "go.opencensus.io/trace" ) -const sinkSTSAccountScheme = "serviceAccount:project-%s@storage-transfer-service.iam.gserviceaccount.com" +const sinkSTSAccountScheme = "project-%s@storage-transfer-service.iam.gserviceaccount.com" type CreatingProcessorFactory interface { CreateProcessor(ctxIn context.Context) (Operation[requestobjects.CreateRequest, requestobjects.BackupResponse], error) @@ -440,7 +440,7 @@ func prepareSink(ctxIn context.Context, cloudStorageClient gcs.CloudStorageClien projectNumber := strings.ReplaceAll(project.Name, "projects/", "") bucketPolicy := &iam.Policy{} // Storage Transfer Service needs to write to and read from the sink bucket - storageTransferGSABinding := fmt.Sprintf(sinkSTSAccountScheme, projectNumber) + storageTransferGSABinding := "serviceAccount:" + fmt.Sprintf(sinkSTSAccountScheme, projectNumber) bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketWriter") bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketReader") err = cloudStorageClient.SetBucketIAMPolicy(ctx, backup.Sink, bucketPolicy) From b1f836016d2f75373460343d2a0de644ef811f94 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Wed, 24 Apr 2024 08:44:44 +0200 Subject: [PATCH 06/10] fix flowchart for Role and rights concept --- Readme.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Readme.md b/Readme.md index bdfd9bc..fa43634 100644 --- a/Readme.md +++ b/Readme.md @@ -520,11 +520,6 @@ flowchart LR STS--"storagetransfer.user"-->GCP_sink_Project STS--"storage.legacyBucketReader storage.objectViewer"-->GCP_source_Project - - classDef blue fill:#4285F4 - class GCP_sink_Project blue - class GCP_source_Project blue - class GCP_runtime_Project blue ``` ## Service accounts From 5a42393cb03adcc8f5cf840a4a951375520ec308 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Wed, 24 Apr 2024 08:46:50 +0200 Subject: [PATCH 07/10] Apply suggestions from code review Co-authored-by: Mahmoud Rahbar Azad --- pkg/processor/creating_processor_factory.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/processor/creating_processor_factory.go b/pkg/processor/creating_processor_factory.go index 80acccd..ac44afe 100644 --- a/pkg/processor/creating_processor_factory.go +++ b/pkg/processor/creating_processor_factory.go @@ -440,6 +440,7 @@ func prepareSink(ctxIn context.Context, cloudStorageClient gcs.CloudStorageClien projectNumber := strings.ReplaceAll(project.Name, "projects/", "") bucketPolicy := &iam.Policy{} // Storage Transfer Service needs to write to and read from the sink bucket + // based on https://cloud.google.com/storage-transfer/docs/sink-cloud-storage#required_permissions storageTransferGSABinding := "serviceAccount:" + fmt.Sprintf(sinkSTSAccountScheme, projectNumber) bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketWriter") bucketPolicy.Add(storageTransferGSABinding, "roles/storage.legacyBucketReader") From 2ad1fb1ac8d293d7de81f2304aeb24d14f83d153 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Wed, 24 Apr 2024 08:47:07 +0200 Subject: [PATCH 08/10] Update Readme.md Co-authored-by: Mahmoud Rahbar Azad --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index fa43634..c4fffdb 100644 --- a/Readme.md +++ b/Readme.md @@ -529,7 +529,7 @@ flowchart LR There should be one custom service account ```runner``` that is used to run the Penelope application. This service account should have the following roles in the project it runs: -* CloudSQL Client (`roles/cloudsql.client) +* CloudSQL Client (`roles/cloudsql.client`) * to be able to connect to the database running in the same project as Penelope application * Cloud Trace Agent * to be able to connect to write traces to Google Monitoring From 2d828119cc0ef292d5de3618e73eeebb03cfe300 Mon Sep 17 00:00:00 2001 From: Grzegorz Rygielski Date: Wed, 24 Apr 2024 09:26:21 +0200 Subject: [PATCH 09/10] add target="_blank" for other links --- frontend/src/components/BackupTable.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/BackupTable.vue b/frontend/src/components/BackupTable.vue index 9939d05..c933f94 100644 --- a/frontend/src/components/BackupTable.vue +++ b/frontend/src/components/BackupTable.vue @@ -88,13 +88,13 @@ const projectLink = (project: string) => {