Skip to content

A guide to service infrastructure and product factories

Each service in DevOps will need some service infrastructure. Our managed service infrastructure thinks in terms of "products" which are names given to groups of resources. Unfortunately what "product" means is overloaded in DevOps, both from a project management and a technical perspective. This document covers a few different technical meanings of "product" in DevOps and how you can decide how to group service infrastructure resources into products.

There are quicker task-focused guides if all you want to do is start a new service or ensure an existing service is up-to-date.

This document is long, dense and necessarily pedantic with terminology. We'd recommend getting your drink of choice and reading the document a few times from top-to-bottom.

Product factories

A lot of our managed infrastructure follows a "product factory" model. In this model, the term "product" means a name given to sets of resources. Creating a new "product" creates a new set of resources associated with that name.

With our current managed infrastructure, we have the following resources which can be created and associated with some "product" name:

  • Google user accounts and Google groups for delivery teams,
  • Google Cloud resources,
  • GitLab projects and groups, and
  • GitLab CI/CD runners.

Each of these are managed by their own infrastructure as code (IaC) projects and each IaC project uses an independent set of "product" names for their resource sets.

In theory, "products" in each of these sets do not need to have a 1:1 relationship with each other. You could, for example, have a single GitLab "product" with multiple GitLab CI runner "products" or multiple Google Cloud "products" sharing a single GitLab "product".

The common case, and the preferable one, is to align Google's, GitLab's and GitLab CI's idea of a "product" with one another and to have exactly one "product" for a given service. That situation is covered in a dedicated how-to guide.

As services split and merge, however, we may find that the case becomes more complex. This document explains what we mean by "product" in each of these cases, what the relationship between products are and what we mean by a "product identity".

Service infrastructure

"Service infrastructure" is loosely defined as a set of resources managed by a single team in order to deliver a service. A service will usually need the following bits of managed infrastructure:

  • Google user accounts and Google groups. These are used to grant access to resources in the Google Cloud Console. They are managed by configuration in the gcp-workspace-management GitLab project which is, in turn, driven by a JSON configuration file. Team members managing resources in Google Cloud are encouraged to do so via a @gcloudadmin.g.apps.cam.ac.uk account and gcp-workspace-management is responsible for creating these accounts.
  • Google Cloud team folders. These contain Google Cloud Folders and Projects for services. They, along with the associated IAM policies, are managed by the gcp-infra GitLab project which, in turn, sets IAM policies for the groups created by gcp-workspace-management.
  • Google Cloud resources. These provide the service for users. Commonplace resources for services are managed by configuration in the gcp-product-factory GitLab project which is, in turn, driven by per-product configuration files. Additional Cloud resources are usually provisioned by service-specific terraform configurations.
  • GitLab projects and groups. These facilitate project management and host code implementing the services. They are managed by configuration in the gitlab-project-factory GitLab project which is, in turn, driven by per-product configuration files.
  • GitLab CI runners. These perform actions related to service development. They are managed by configuration in the gitlab-runner-infrastructure GitLab project driven by configuration in a single file within that project.

A service also consists of a large amount of information, expertise, know-how and user support infrastructure but these are not yet things we manage through infrastructure as code.

Each of the IaC projects above have their own independent concept of a "product". These need not align 1:1 with each other but things are a lot simpler for everyone if they do.

Guidance for choosing product sets

Generally we strongly recommend having the concept of "product" be aligned between gcp-product-factory, gitlab-project-factory and gitlab-runner-infrastructure so that a single "Google product" maps to a single "GitLab product" which uses a single "GitLab runner product".

We also recommend that gitlab-project-factory products have parent_group_id = 5 which corresponds to the uis/devops group. Tempting though it is to group products by team, that tends to lead to pain in the long-run.

What about managing permissions in GitLab?

One strong motivator for grouping GitLab projects by team is that permissions can be set at the team group level and cascade down to the projects. Unfortunately, for the moment, one needs to take the hit and manage GitLab product group's permissions manually.

It is likely that GitLab project factory will learn about team_data.json groups at some point and so be able to set permissions automatically.

In advanced cases you may want to vary this arrangement. Some examples:

  • The uis/devops/lib, uis/devops/tools and uis/devops/django GitLab groups contain reusable libraries, tools and Django modules. Each of these could be considered a separate "product". Our common CI pipelines need some Google Cloud resources to work but none of these products need to deploy to Google. As such we have a shared code Google product which is shared between all the projects in those groups.
  • We have a lot of Identity and Access Management (IAM) services. There is a strong ontological argument to be made that it is less confusing to have a uis/devops/iam group containing these products. In that case the GitLab project factory "product" folders would live under uis/devops/iam and have parent_group_id = 938. Note that this grouping is based on the nature of the products and not on the delivery team managing them.
  • A single "product" may be better represented in GitLab via nested groups. For example, it may make sense to have the Raven "product" be split into uis/raven/saml2 and uis/raven/legacy sub-groups which each contain multiple projects. In that case the subgroup facility of gitlab-project-factory can be used; there is still a single GitLab and Google "product" but multiple groups.
  • Continuing with the example of Raven. As a sensitive service, we may want to limit the ability of CI jobs in uis/raven/saml2 to affect projects in uis/raven/legacy. In that case we can configure separate GitLab CI runners for uis/raven/saml2 and uis/raven/legacy. So the Raven service now has a single Google "product", a single GitLab "product" but two GitLab CI/CD "products".

It bears repeating that the common case will be for services to have a single gcp-product-factory product, a single gitlab-project-factory product and a single gitlab-runner-infrastructure runner.

What if my CI/CD jobs need to access resources from other products?

If you need to get GitLab API tokens scoped to other GitLab projects or groups, you can do so via the "additional token mechanism" provided by gitlab-project-factory which is documented in its README.

The identities section below covers how you specify Google Cloud IAM permissions to grant access to additional tokens or Google Cloud resources from other products.

Delivery team user accounts and groups

Although not termed a "product" in the configuration per se, the gcp-workspace-management repository has the concept of mapping a name to a group of user accounts. These names are usually aligned with delivery teams rather than services but, as a name mapping to a set of service infrastructure resources managed by IaC, it matches our working definition of "product".

Team membership is driven by a JSON configuration file owned by teamCloud.

Generally there should be exactly one entry in team_data.json for each delivery team.

While gcp-workspace-management is responsible for maintaining a set of Google user accounts and role groups, it is gcp-infra and gcp-product-factory which actually contain IAM policies granting permissions on Cloud resources to role group.

This relationship is summarised in the following diagram:

flowchart TB
  subgraph team_data_tf[Team data terraform module]
    team_data[team_data.json]
  end

  subgraph gcp_workspace_manager[gcp-workspace-management]
    user_accounts["<tt>...@gcloudadmin</tt>\nGoogle user accounts"]

    subgraph gcp_workspace_manager_team[Team]
      role_groups[Role-specific \nGoogle Groups]
    end

    user_accounts -->|members of| role_groups
  end
  team_data -->|specifies existance of| user_accounts
  team_data -->|determines membership of| role_groups

  subgraph gcp_infra_team[Team in gcp-infra]
    iam_policies[IAM policies]
    gcp_folder[Team Google Cloud Folder]
  end
  role_groups -->|subject of| iam_policies
  iam_policies -->|control access to| gcp_folder

  subgraph gcp_product_factory[Product in gcp-product-factory]
    subgraph gcp_product_folder[Product-specific Google Cloud Folder]
      subgraph gcp_project[Google Cloud Projects]
        resources[Google Cloud\nResources]
      end
    end
  end
  gcp_folder -->|contains| gcp_product_folder

Remember that there can be multiple teams and multiple products in gcp-product-factory. There does not need to be a 1:1 mapping between them; a single team will often have IAM permissions on multiple products.

Google Cloud Projects

Tip

More detail on excatly what Google Cloud resources are created for a product can be found in a dedicated reference guide. This section focuses on resources which interact with other bits of service infrastructure.

The configuration in gcp-product-factory is responsible for creating:

  • A single product-specific Google Project known as the meta project.
  • A Google Project for each environment, for example "production", "staging" and "development".
  • Within the meta project:
    • A Cloud Storage bucket which holds machine-readable information on the product
    • A Secret which holds a GitLab API token.
    • A Service Account which can access the token.
    • A Service Account representing CI jobs for the product.

Note

There is an issue to move the GitLab API token secret to the GitLab project factory.

The CI service account and GitLab token secret are intended to be used by GitLab CI runners configured in gitlab-runner-infrastructure. The token secret is intended to be updated by the gitlab-project-factory configuration.

This diagram summarises the interaction between gitlab-project-factory, gitlab-runner-infrastructure and gitlab-project-factory in the case where a product has a "production", "staging" and "development" environment:

flowchart LR
  subgraph gcp_product_factory[Product in gcp-product-factory]
    subgraph gcp_meta_project[Product-specific Google Project]
      gitlab_token_secret[GitLab API Token Secret]
      gitlab_token_sa[GitLab Token Accessor\nService Account]
      ci_sa[CI/CD Service Account]
      gitlab_token_sa -->|can access| gitlab_token_secret
    end
    subgraph gcp_prod_project[Production Google Project]
      prod_deploy_sa[Terraform\nservice account]
      prod_resources[Google Cloud\nResources]
      prod_deploy_sa -->|can manage| prod_resources
    end
    subgraph gcp_test_project[Staging Google Project]
      test_deploy_sa[Terraform\nservice account]
      test_resources[Google Cloud\nResources]
      test_deploy_sa -->|can manage| test_resources
    end
    subgraph gcp_devel_project[Development Google Project]
      devel_deploy_sa[Terraform\nservice account]
      devel_resources[Google Cloud\nResources]
      devel_deploy_sa -->|can manage| devel_resources
    end
    ci_sa -->|can impersonate| prod_deploy_sa
    ci_sa -->|can impersonate| test_deploy_sa
    ci_sa -->|can impersonate| devel_deploy_sa
  end

  subgraph gke_runner_infra[Product in gitlab-runner-infrastructure]
    gke_runner_identity[Google Kubernetes Engine\nWorkload identity]
    subgraph gke_runner[GitLab CI Runner Workload]
      ci_job[CI/CD Job Pod]
    end
    gke_runner -->|has| gke_runner_identity
  end
  gke_runner_identity -->|can impersonate| ci_sa
  gke_runner_identity -->|can impersonate| prod_deploy_sa
  gke_runner_identity -->|can impersonate| test_deploy_sa
  gke_runner_identity -->|can impersonate| devel_deploy_sa
  gke_runner_identity -->|can impersonate| gitlab_token_sa

  subgraph gitlab_product[Product in gitlab-project-factory]
    gitlab_token[GitLab API Token]
    gitlab_group[GitLab Product Group]
    gitlab_token -->|scoped to| gitlab_group
  end
  gitlab_token -->|written to| gitlab_token_secret

Note

Any CI job can, by impersonation, manage resources in all Google Cloud projects. You may want to limit this to CI jobs running in infrastructure projects. This is an example where you may want multiple GitLab Runner products for a single gcp-product-factory product: you can have a general GitLab Runner product using the "shared code" Google project for most GitLab projects but a dedicated deploy GitLab Runner product, scoped to a single GitLab project, using your service's gcp-product-factory product for deployment.

GitLab projects and groups

The gitlab-project-factory configuration manages a single GitLab group representing the "product" in GitLab and projects and sub-groups within that product group.

Danger

Nothing stops someone manually creating GitLab projects or groups within a product group which are not managed by gitlab-project-factory. Once we transition fully to gitlab-project-factory, manual creation of projects will be limited to a small set of users.

The common-case interaction between gitlab-project-factory and gcp-product-factory is fairly minimal:

flowchart LR
  subgraph gcp_product_factory[Product in gcp-product-factory]
    gitlab_token_secret[GitLab token secret]
  end

  subgraph gitlab_product[Product in gitlab-project-factory]
    gitlab_access_token[Default API\naccess token]
    subgraph gitlab_product_group[Product-wide GitLab group]
      gitlab_project[Project A]
      gitlab_project_b[Project B]
      gitlab_project_c[Project C]
    end
    gitlab_access_token -->|scoped to| gitlab_product_group
  end

  gitlab_access_token -->|written to| gitlab_token_secret

In some cases it may make sense to group some projects within a gitlab-project-factory product together. There is a subgroup facility for this. Advanced users may need to obtain GitLab API tokens which are scoped to subsets of projects within the product or even to projects from unrelated services. The gitlab-project-factory documentation covers how to create these tokens.

A more complex case is shown in the following diagram:

flowchart LR
  subgraph gcp_product_factory[Product in gcp-product-factory]
    gitlab_token_secret[GitLab token secret]
  end

  subgraph gitlab_product[Product 1 in gitlab-project-factory]
    gitlab_access_token[Default API\naccess token]
    subgraph gitlab_product_group[Product-wide GitLab group]
      gitlab_project[Project A]
      subgraph gitlab_subgroup[Sub-group 1]
        gitlab_project_b[Project B]
        gitlab_project_c[Project C]
      end
    end
    gitlab_access_token -->|scoped to| gitlab_product_group

    additional_token_1_secret[Additional API\ntoken 1 secret]
    additional_token_1[Additional API\naccess token 1]
    additional_token_1_secret -->|contains| additional_token_1
    additional_token_1 -->|scoped to| gitlab_subgroup

    additional_token_2_secret[Additional API\ntoken 2 secret]
    additional_token_2[Additional API\naccess token 2]
    additional_token_2_secret -->|contains| additional_token_2
  end

  subgraph unrelated_gitlab_product[Product 2 in gitlab-project-factory]
    unrelated_project[GitLab project from\nanother service]
  end

  gitlab_access_token -->|written to| gitlab_token_secret
  additional_token_2 -->|scoped to| unrelated_project

Advanced users are responsible for specifying the IAM principals which can access additional API token secrets. IAM principals are covered in the identities section below.

GitLab CI runners

The gitlab-runner-infrastructure configuration is responsible for managing a set of GitLab CI runners which live within a single Kubernetes cluster.

A "product" in gitlab-runner-infrastructure names one of these runners. The common case is to have a single GitLab runner "product" for a service. Some of our CI jobs need to access a GitLab API token so that they may, for example, create automatic Merge Requests or create new releases when a repository is tagged. Deployment CI jobs need to impersonate an environment-specific terraform deployment service account in order to deploy services.

The relationship between gitlab-runner-infrastructure, gcp-product-factory and gitlab-project-factory is summarised in the following diagram:

flowchart TB
  subgraph gcp_product_factory[Product in gcp-product-factory]
    subgraph gcp_meta[Meta project]
      gitlab_token_secret[GitLab token secret]
      token_sa[Token accessor\nservice account]
      token_sa -->|can access| gitlab_token_secret
    end

    subgraph gcp_prod[Production Google Project]
      prod_resources[Google Cloud\nResources]
      prod_deploy_sa[Deployment\nservice account]
      prod_deploy_sa -->|can manage| prod_resources
    end

    subgraph gcp_test[Staging Google Project]
      test_resources[Google Cloud\nResources]
      test_deploy_sa[Deployment\nservice account]
      test_deploy_sa -->|can manage| test_resources
    end
  end

  subgraph gke_runner_infra[Product in gitlab-runner-infrastructure]
    gke_runner_identity[Google Kubernetes Engine\nWorkload identity]
    subgraph gke_runner[GitLab CI Runner Workload]
      ci_job[GitLab CI/CD Job Pod]
    end

    gke_runner -->|has| gke_runner_identity
  end
  gke_runner -->|runs CI jobs for| gitlab_product_group
  gke_runner_identity -->|can impersonate| token_sa
  gke_runner_identity -->|can impersonate| prod_deploy_sa
  gke_runner_identity -->|can impersonate| test_deploy_sa

  subgraph gitlab_product[Product in gitlab-project-factory]
    gitlab_access_token[GitLab API\naccess token]
    gitlab_product_group[Product-wide\nGitLab group]
    gitlab_project[GitLab Projects]
    gitlab_access_token -->|scoped to| gitlab_product_group
    gitlab_project -->|within| gitlab_product_group
  end
  gitlab_token_secret -->|contains| gitlab_access_token

The common case is that there is one GitLab CI/CD Runner for a given GitLab project factory "product" and, similarly, that there is one GitLab CI/CD Runner for a given Google product factory "product".

Identities

Access to Google Cloud Resources and GitLab API tokens is ultimately controlled by Cloud IAM permissions. Access is either granted directly to an IAM principal or, more usually, indirectly by allowing impersonation of a role-specific service account.

For example, all CI/CD runners have an implicit Google Kubernetes Engine Workload Identity and that identity is allowed to impersonate the deployment service accounts in each environment-specific Google Project.

Each CI/CD job itself has a unique identity within Google Cloud. This can allow for very fine-grained IAM policies for CI/CD jobs. This is covered in more detail in dedicated documentation.

This diagram covers how the Google Cloud identities available in our service infrastructure interact. Identities which can appear in IAM policies are marked with "🆔".

flowchart TB
  subgraph gke_runner_prod[gitlab-runner-infrastructure product]
    subgraph gke_runner[GitLab CI/CD Runner Kubernetes Workload]
      ci_job_1[Pod for GitLab CI/CD Job\nin GitLab Project A]
      ci_job_2[Pod for GitLab CI/CD Job\nin GitLab Project B]
      ci_job_1 -->|exchanges\njob id token for| ci_job_1_id
      ci_job_2 -->|exchanges\njob id token for| ci_job_2_id
      ci_job_1_id["🆔 Workload Identity\nfor jobs in Project A"]
      ci_job_2_id["🆔 Workload Identity\nfor jobs in Project B"]
    end
    gke_runner_id["🆔 Runner Kubernetes\nWorkload Identity"]
  end

  gke_runner -->|has| gke_runner_id

  subgraph gcp_product[gcp-product-factory product]
    subgraph gcp_meta_project[Meta Project]
      token_accessor_sa["🆔 Token accessor\nService Account"]
      token_secret[GitLab API token]
      token_accessor_sa -->|can access| token_secret
    end
    gke_runner_id -->|can impersonate| token_accessor_sa

    subgraph gcp_production_project[Environment-specific Project]
      production_terraform_sa["🆔 Terraform deploy\nService Account"]
      production_resources[Cloud Resources]
      production_terraform_sa -->|can manage| production_resources
    end
    gke_runner_id -->|can impersonate| production_terraform_sa
  end

  subgraph gitlab_product[gitlab-project-factory product]
    gitlab_product_group[GitLab Product Group]
  end
  token_secret -->|scoped to| gitlab_product_group

The following IAM principals can be used in IAM policies to set permissions. Note that some of these require you knowing the gcp-product-factory or gitlab-runner-infrastructure product names.

  • GitLab CI/CD Job principals (documentation):
    • CI jobs running in a specific GitLab project: principalSet://iam.googleapis.com/projects/421963284348/locations/global/workloadIdentityPools/gitlab/attribute.gitlab_project_id/«numeric-gitlab-project-id»
    • CI jobs running in a specific GitLab group: principalSet://iam.googleapis.com/projects/421963284348/locations/global/workloadIdentityPools/gitlab/attribute.gitlab_namespace_id/«numeric-gitlab-namespace-id»
    • CI jobs running triggered by a specific GitLab user: principalSet://iam.googleapis.com/projects/421963284348/locations/global/workloadIdentityPools/gitlab/attribute.gitlab_user_id/«numeric-gitlab-user-id»
  • GitLab CI/CD Runner principals (documentation):
    • Any CI job running within a specific GitLab CI runner: serviceAccount:gitlab-runner-prod-22257483.svc.id.goog[«runner-product-name»/gke-ci-run]
  • Product-wide Google Project principals (documentation):
    • Generic CI service account for CI not based in GitLab or if CI/CD jobs prefer to impersonate: serviceAccount:gke-ci-run@«meta-project-id».iam.gserviceaccount.com
    • GitLab API token secret accessor: serviceAccount:gitlab-token-accessor@«meta-project-id».iam.gserviceaccount.com
  • Principals in the production, staging or development Google projects:
    • Terraform deployment service account: serviceAccount:terraform-deploy@«workspace-project-id».iam.gserviceaccount.com

Advanced use of identities

The default case is that all CI/CD jobs within a product can impersonate the GitLab API token accessor and terraform deployment service accounts. This may be viewed as "overly broad" for some services and some service delivery teams may want to additional restrict deployments to production.

These use cases are, at the moment, hypothetical and more testing is required. A possible example of advanced use of identities is covered below.

Our service has three GitLab projects: infrastructure contains terraform to deploy the service, webapp contains a web application which is part of the service and sharedlib contains code which is used by the webapp project.

We'll assume that we have disabled the "permissive" IAM policies which allow CI jobs to access all GitLab projects and deploy to all environments.

We want:

  • To restrict deployment to CI jobs running in infrastructure.
  • To allow CI jobs in webapp to open issues in sharedlib if compatibility issues are detected in testing jobs.

We can do this by adding the following:

  • An additional API token in gitlab-project-factory which is scoped to a sub-group containing the webapp and sharedlib projects.
  • An iam_policy for this token which allows access from CI/CD jobs running in the webapp project.
  • An IAM policy allowing CI/CD jobs running in infrastructure to impersonate the terraform deploy service account.

In diagram form, this looks like the following:

flowchart TB
  subgraph gke_runner_prod[gitlab-runner-infrastructure product]
    subgraph gke_runner[GitLab CI/CD Runner Kubernetes Workload]
      ci_job_2["Pod for GitLab CI/CD Job\nin 'infrastructure'"]
      ci_job_2_id["🆔 Workload Identity\nfor jobs in 'infrastrucure'"]
      ci_job_2 -->|exchanges\njob id token for| ci_job_2_id
      ci_job_1["Pod for GitLab CI/CD Job\nin 'webapp'"]
      ci_job_1_id["🆔 Workload Identity\nfor jobs in 'webapp'"]
      ci_job_1 -->|exchanges\njob id token for| ci_job_1_id
    end
  end

  subgraph gcp_product[gcp-product-factory product]
    subgraph gcp_production_project[Environment-specific Project]
      production_terraform_sa["🆔 Terraform deploy\nService Account"]
      production_resources[Cloud Resources]
      production_terraform_sa -->|can manage| production_resources
    end
  end

  subgraph gitlab_product[gitlab-project-factory product]
    gitlab_additional_token_secret[Additional Token Secret]
    gitlab_additional_token[Additional API Token\nfor Sub-group]
    gitlab_additional_token_secret -->|contains| gitlab_additional_token
    subgraph gitlab_product_group[GitLab Product Group]
      gitlab_project_a["'infrastructure' project"]
      subgraph gitlab_group[Sub-group]
        gitlab_project_b["'webapp' project"]
        gitlab_project_c["'sharedlib' project"]
      end
    end
    gitlab_additional_token -->|scoped to| gitlab_group
  end
  ci_job_1_id -->|can access| gitlab_additional_token_secret
  ci_job_2_id -->|can impersonate| production_terraform_sa

Summary

We can collect all of the infrastructure diagrams so far together to show the full set of inter-relations between our product factory configurations. As above, principals which can be specified in Google Cloud IAM policies are marked with "🆔". Solid lines represent the default relationships. Dotted lines and dotted boxes represent additional resources and relationships which are useful in advanced cases.

flowchart TB
  classDef nonDefault stroke-dasharray:5

  subgraph team_data_tf[Team data terraform module]
    team_data[team_data.json]
  end

  subgraph gcp_workspace_manager[gcp-workspace-management]
    user_accounts["🆔 <tt>...@gcloudadmin</tt>\nGoogle user accounts"]

    subgraph gcp_workspace_manager_team[Team]
      role_groups["🆔 Role-specific \nGoogle Groups"]
    end

    user_accounts -->|members of| role_groups
  end
  team_data -->|specifies existance of| user_accounts
  team_data -->|determines membership of| role_groups

  subgraph gcp_infra_team[Team in gcp-infra]
    iam_policies[IAM policies]
    gcp_folder[Team Google Cloud Folder]
  end
  role_groups -->|subject of| iam_policies
  iam_policies -->|control access to| gcp_folder

  subgraph gke_runner_infra[Product in gitlab-runner-infrastructure]
    gke_runner_identity["🆔 Google Kubernetes Engine\nWorkload identity"]
    subgraph gke_runner[GitLab CI Runner Workload]
      ci_job_2["GitLab CI/CD job A Pod"]
      ci_job_2_id["🆔 Workload Identity for job A"]
      ci_job_2 -->|exchanges\njob id token for| ci_job_2_id
      ci_job["GitLab CI/CD job B Pod"]
      ci_job_id["🆔 Workload Identity for job B"]
      ci_job -->|exchanges\njob id token for| ci_job_id
    end

    gke_runner -->|has| gke_runner_identity
  end


  subgraph gcp_product_factory[Product in gcp-product-factory]
    subgraph gcp_product_folder[Product-specific Google Cloud Folder]
      subgraph gcp_meta[Meta project]
        ci_sa["🆔 Generic CI/CD service account"]
        gitlab_token_secret[GitLab token secret]
        ci_sa -->|can access| gitlab_token_secret
        token_sa["🆔 Token accessor\nservice account"]
        token_sa -->|can access| gitlab_token_secret
      end

      subgraph gcp_env[Environment-specific Google Project]
        env_resources[Google Cloud\nResources]
        env_deploy_sa["🆔 Deployment\nservice account"]
        env_deploy_sa -->|can manage| env_resources
      end

      ci_sa -->|can impersonate| env_deploy_sa
    end
  end

  subgraph gitlab_product[Product in gitlab-project-factory]
    gitlab_additional_token_secret[Additional Token Secret]:::nonDefault
    gitlab_additional_token[Additional API Token\nfor Sub-group]:::nonDefault
    gitlab_additional_token_secret -.->|contains| gitlab_additional_token

    gitlab_access_token[GitLab API\naccess token]
    gitlab_product_group[Product-wide\nGitLab group]
    subgraph gitlab_product_group[Product-wide GitLab group]
      gitlab_project[Project A]
      gitlab_project_b[Project B]
      gitlab_project_c[Project C]
    end
    gitlab_additional_token -->|scoped to| gitlab_project_c
    gitlab_access_token -->|scoped to| gitlab_product_group
  end

  gitlab_token_secret -->|contains| gitlab_access_token
  gke_runner -->|runs CI jobs for| gitlab_product_group
  gke_runner_identity -->|can impersonate| token_sa
  gke_runner_identity -->|can impersonate| env_deploy_sa
  gke_runner_identity -->|can impersonate| ci_sa
  ci_job_id -.->|can access| gitlab_additional_token_secret

  gcp_folder -->|contains| gcp_product_folder

Next steps

After reading this guide, the following pages may be of interest: