cloud-foundation-fabric/fast/stages-multitenant/0-bootstrap-tenant/README.md

18 KiB

Tenant bootstrap

The primary purpose of this stage is to decouple a single tenant from centrally managed resources in the organization, so that subsequent management of the tenant's own hierarchy and resources can be implemented with a high degree of autonomy.

It is logically equivalent to organization-level bootstrap as it's concerned with setting up IAM bindings on a root node and creating supporting projects attached to it, but it depends on the organization-level resource management stage and uses the same service account and permissions since it operates at the hierarchy level (folders, tags, organization policies).

The resources and policies managed here are:

  • the tag value in the tenant key used in IAM conditions
  • the billing IAM bindings for the tenant-specific automation service accounts
  • the organization-level IAM binding that allows conditional managing of org policies on the tenant folder
  • the top-level tenant folder which acts as the root of the tenant's hierarchy
  • any organization policy that needs to be set for the tenant on its root folder
  • the tenant automation and logging projects
  • service accounts for all tenant stages
  • GCS buckets for bootstrap and resource management state
  • optional CI/CD setup for this and the resource management tenant stages
  • tenant-specific Workload Identity Federation pool and providers (planned)

One notable difference compared to organization-level bootstrap is the creation of service accounts for all tenant stages: this is done here so that Billing and Organization Policy Admin bindings can be set, leveraging permissions of the org-level resman service account which is used to run this stage. Doing this here avoids the need to grant broad scoped permissions on the organization to tenant-level service accounts, and effectively decouples the tenant from the organization.

The following diagram is a high level reference of what this stage manages, showing one hypothetical tenant (additional tenants require additional instances of this stage being deployed):

%%{init: {'theme':'base'}}%%
classDiagram
    Organization~🏢~ -- Tenant 0~📁~
    Tenant 0~📁~ -- tn0_automation
    Tenant 0~📁~ -- tn0_logging
    class Organization~🏢~ {
        - tag value
        - IAM bindings()
        - org policies()
    }
    class Tenant 0~📁~ {
        - log sinks
        - IAM bindings()
        - tag bindings()
    }
    class tn0_automation {
        - GCS buckets
        - service accounts
        - optional CI/CD
        - IAM bindings()
    }
    class tn0_logging {
        - log sink destinations
    }

As most of the features of this stage follow the same design and configurations of the organization-level bootstrap stage, we will only focus on the tenant-specific configuration in this document.

Naming

This stage sets the prefix used to name tenant resources, and passes it downstream to the other tenant stages together with the other globals needed by the tenant. The default is to append the tenant short name (a 3 or 4 letter acronym or abbreviation) to the organization-level prefix, if that is not desired this can be changed by editing local definitions in the main.tf file. Just be aware that some resources have name length constraints.

How to run this stage

The tenant bootstrap stage is the effective boundary between organization and tenant-level resources: it uses the same inputs as the organization-level resource management stage, and produces outputs which provide the needed context to all other tenant stages.

Output files and cross-stage variables

As mentioned above, the organization-level set of output files are used here with one exception: the provider file is different since state is specific to this stage. The stage-links.sh script can be used to get the commands needed for the provider and output files, just pass a single argument with your FAST output files folder path, or GCS bucket URI:

../../stage-links.sh ~/fast-config

The script output can be copy/pasted to a terminal:

# copy and paste the following commands for '0-bootstrap-tenant'

cp ~/fast-config/providers/0-bootstrap-tenant-providers.tf ./
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json ./
ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json ./
ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json ./

# ---> remember to set the prefix in the provider file <---

As shown in the script output above, the provider file is a template used as a source for potentially multiple tenant installations, so it needs to be specifically configured for this tenant by setting the backend prefix to a unique string so that the Terraform state file will not overlap with other tenants. Open it in an editor and perform the change before proceeding.

Global overrides

The globals variable file linekd above contains definition which were set for the organization, for example the locations used for log sink destinations. These might not be correct for each tenant, so this stage allows overriding them via the tenant configuration variable described in the next section.

Tenant-level configuration

The tenant configuration resides in the tenant_config variable, this is an example configuration for a tenant with comments explaining the different choices that need to be made:

tenant_config = {
  # used for the top-level folder name
  descriptive_name = "My First Tenant"
  # tenant-specific groups, only the admin group is required
  # the organization domain is automatically added after the group name
  groups = {
    gcp-admins          = "tn01-admins"
    # gcp-devops          = "tn01-devops"
    # gcp-network-admins  = "tn01-networking"
    # gcp-security-admins = "tn01-security"
  }
  # the 3 or 4 letter acronym or abbreviation used in resource names
  short_name = "tn01"
  # optional CI/CD configuration, refer to the org-level stages for information 
  # cicd = {
  #   branch            = null
  #   identity_provider = "foo-provider"
  #   name              = "myorg/tn01-bootstrap"
  #   type              = "github"
  # }
  # optional group-level IAM bindings to add to the top-level folder
  # group_iam = {
  #   tn01-support = ["roles/viewer"]
  # }
  # optional IAM bindings to add to the top-level folder
  # iam       = {
  #   "roles/logging.admin" = [
  #     "serviceAccount:foo@myprj.iam.gserviceaccount.com"
  #   ]
  # }
  # optional location overrides to global locations
  # locations = {
  #   bq      = null
  #   gcs     = null
  #   logging = null
  #   pubsub  = null
  # }
  # optional folder ids for automation and logging project folders, typically
  # added in later stages and entered here once created
  # project_parent_ids = {
  #   automation = "folders/012345678"
  #   logging    = "folders/0123456789"
  # }
}
# tftest skip

Configure the tenant variable in a tfvars file for this stage. A few minor points worth noting:

  • the administrator group is the only one required here, specifying other groups only has the effect of populating the output file with group names for reuse in later stages
  • the iam variable is merged with the IAM bindings for service accounts in the main.tf file, which take precedence; if a role specified in the variable is ignored, that's probably the case
  • locations can be overridden at the attribute level, there's no need to specify those that are equal to the ones in the organization globals file

Running the stage

Once the configuration is done just go through the usual init/apply cycle. On successful apply, a tfvars file specific for this tenant and a set of provider files will be created.

TODO

  • tenant-level Workload Identity Federation pool and providers configuration
  • tenant-level logging project and sinks

Files

name description modules resources
automation-sas.tf Tenant automation stage 2 and 3 service accounts. iam-service-account google_organization_iam_member
automation.tf Tenant automation project and resources. gcs · iam-service-account · project
billing.tf Billing roles for standalone billing accounts. google_billing_account_iam_member
cicd.tf Workload Identity Federation configurations for CI/CD. iam-service-account · source-repository
identity-providers.tf Workload Identity Federation provider definitions. google_iam_workload_identity_pool · google_iam_workload_identity_pool_provider
log-export.tf Audit log project and sink. bigquery-dataset · gcs · logging-bucket · project · pubsub
main.tf Module-level locals and resources. folder
organization.tf Organization tag and conditional IAM grant. organization google_organization_iam_member · google_tags_tag_value_iam_member
outputs-files.tf Output files persistence to local filesystem. local_file
outputs-gcs.tf Output files persistence to automation GCS bucket. google_storage_bucket_object
outputs.tf Module outputs.
variables.tf Module variables.

Variables

name description type required default producer
automation Automation resources created by the organization-level bootstrap stage. object({…}) 0-bootstrap
billing_account Billing account id. If billing account is not part of the same org set is_org_level to false. object({…}) 0-bootstrap
organization Organization details. object({…}) 0-bootstrap
prefix Prefix used for resources that need unique names. Use 9 characters or less. string 0-bootstrap
tag_keys Organization tag keys. object({…}) 1-resman
tag_names Customized names for resource management tags. object({…}) 1-resman
tag_values Organization resource management tag values. map(string) 1-resman
tenant_config Tenant configuration. Short name must be 4 characters or less. object({…})
cicd_repositories CI/CD repository configuration. Identity providers reference keys in the federated_identity_providers variable. Set to null to disable, or set individual repositories to null if not needed. object({…}) null
custom_roles Custom roles defined at the organization level, in key => id format. object({…}) null 0-bootstrap
fast_features Selective control for top-level FAST features. object({…}) {} 0-bootstrap
federated_identity_providers Workload Identity Federation pools. The cicd_repositories variable references keys here. map(object({…})) {}
group_iam Tenant-level custom group IAM settings in group => [roles] format. map(list(string)) {}
iam Tenant-level custom IAM settings in role => [principal] format. map(list(string)) {}
iam_additive Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings. map(list(string)) {}
locations Optional locations for GCS, BigQuery, and logging buckets created here. These are the defaults set at the organization level, and can be overridden via the tenant config variable. object({…}) {…} 0-bootstrap
log_sinks Tenant-level log sinks, in name => {type, filter} format. map(object({…})) {…}
outputs_location Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. string null
project_parent_ids Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent. object({…}) {…}
test_principal Used when testing to bypass the data source returning the current identity. string null

Outputs

name description sensitive consumers
cicd_workflows CI/CD workflows for tenant bootstrap and resource management stages.
federated_identity Workload Identity Federation pool and providers.
provider Terraform provider file for tenant resource management stage. stage-01
tenant_resources Tenant-level resources.
tfvars Terraform variable files for the following tenant stages.