English | 简体中文
Terraform implementation of Tencent Cloud Control Center Landing Zone best practices. Deploys a multi-account cloud environment — organization (with nested departments), core accounts, finance, identities, guardrails, account baselines, and central audit logging — in a versionable, auditable, repeatable way.
┌──────────────────────────────┐
│ Management Account (terraform)│
└──────────────┬───────────────┘
│ Enable Organization
┌──────────────▼───────────────┐
│ Organization Service │
└──────────────┬───────────────┘
│
┌──────────────┬───────────┴───────────┬──────────────┐
│ │ │ │
┌────▼────┐ ┌─────▼─────┐ ┌──────▼─────┐ ┌────▼─────┐
│ Core │ │ Business │ │ Sandbox │ │ Guardrails│
│ Accounts │ │ Division │ │ Division │ │ (SCP) │
│ Security │ │ (nested │ │ Dev/Test │ │ Bound to │
│ Logging │ │ sub- │ │ │ │ divisions │
│ Finance │ │ divs) │ │ │ │ │
└────┬────┘ └─────┬─────┘ └──────┬─────┘ └──────────┘
│ │ │
└──────┬───────┴───────────┬───────────┘
│ │
┌──────▼──────┐ ┌───────▼────────┐ ┌──────────────────┐
│ Identities │ │ Account Factory │ │ Central Audit │
│ & Permissions│ │ Baselines │ │ Logging (opt-in) │
│ Permission │ │ Tags/VPC/ │ │ Org-wide │
│ Sets/Members │ │ Contacts │ │ CloudAudit track │
│ Policies/ │ │ Batch apply │ │ -> log account │
│ Login Auth │ │ to accounts │ │ COS bucket │
└─────────────┘ └────────────────┘ └──────────────────┘
Maps to the 8 Control Center Landing Zone configuration areas:
| Control Center Config | Implementation | Terraform Resource |
|---|---|---|
| Departments | organization.tf |
tencentcloud_organization_org_node (nesting supported) |
| Core Accounts | organization.tf |
tencentcloud_organization_org_member |
| Account Creation & Invitation | organization.tf |
tencentcloud_organization_org_member |
| Finance | organization.tf + variables.tf |
permission_ids, pay_uin |
| Identities & Permissions | identities.tf |
org_identity / org_member_policy_attachment / org_member_auth_identity_attachment |
| Guardrails | guardrails.tf |
org_manage_policy + org_manage_policy_target |
| Baseline Management | baselines.tf |
controlcenter_account_factory_baseline_config + batch_apply_account_baselines |
| Compliance Audit (Log Shipping) | logging.tf + modules/audit-logging |
tencentcloud_audit_track (organization-wide) + tencentcloud_cos_bucket |
| File | Description |
|---|---|
main.tf |
Provider config, enable organization service |
organization.tf |
Departments (parent nesting, up to 3 levels), core/business/sandbox accounts, finance |
identities.tf |
Identities (permission sets), policy attachments, login authorization |
guardrails.tf |
Service Control Policies (SCP) and attachments |
baselines.tf |
Account factory baseline configs and batch application |
logging.tf |
Central audit logging (opt-in): log account provider alias + audit module |
locals.tf |
Shared locals: member account set and UIN chunking (10 per call) |
checks.tf |
Post-deploy runtime assertions (warnings only; config validation lives in variables.tf) |
modules/audit-logging/ |
Audit logging module: log archive COS bucket + organization-wide CloudAudit track |
examples/ |
Runnable scenario examples (minimal / complete) |
scripts/tests/ |
Preflight unit tests (pytest) |
VERSION / CHANGELOG.md |
Release version and change history (Keep a Changelog) |
CONTRIBUTING.md / SECURITY.md / CODEOWNERS |
Contribution conventions, vulnerability reporting, ownership |
.terraform-docs.yml / .tflint.hcl / .pre-commit-config.yaml |
Docs generation, lint and pre-commit tooling configuration |
variables.tf / outputs.tf |
Input variables (with native validations) and outputs |
terraform.tfvars.example |
Variable examples |
backend.tf.example |
COS remote state example (with locking); copy to backend.tf to enable |
scripts/validate-config.py |
Config preflight: validates tfvars reference consistency before apply |
Makefile |
Command entry points and staged deployment (make help) |
.github/workflows/terraform-ci.yml |
CI: fmt + validate + preflight + optional plan + checkov scan |
.github/workflows/drift-detection.yml |
Scheduled drift detection (weekly; requires credentials and remote backend) |
- Management account has completed enterprise real-name verification (Control Center only supports enterprise accounts)
- Terraform ≥ 1.5 installed locally
- Configure Tencent Cloud API credentials (choose one):
- Environment variables:
export TENCENTCLOUD_SECRET_ID=... TENCENTCLOUD_SECRET_KEY=... terraform.tfvars: copyterraform.tfvars.exampleand fill in
- Environment variables:
- Preflight script requires
python3+python-hcl2:pip install python-hcl2(auto-installed in CI)
cp terraform.tfvars.example terraform.tfvars # Customize as needed
terraform init
terraform plan # Review changes (variable validation blocks fail the plan on config errors)
terraform applyOr use the Makefile: make plan / make apply / make stage1..4 (see make help).
First apply will sequentially: enable organization → create departments (including nested ones) → create core/business accounts → create identities and bind → enable SCP and attach → configure baselines and batch-apply.
Root Management Account
├── Core Accounts
│ ├── Security Account # Centralized security products
│ ├── Log Account # Centralized audit log collection (audit log archive)
│ └── Finance Account # Finance reconciliation & cost analysis
├── Business Division
│ └── AppA-Production
└── Sandbox Division
└── DevTest
departments entries accept an optional parent field pointing to another department key, creating sub-departments (up to 3 levels):
departments = [
{ key = "core", name = "Core", remark = "Hosts security, logging and finance core accounts" },
{ key = "business", name = "Business", remark = "All business accounts" },
{ key = "prod", name = "Prod", remark = "Production", parent = "business" }, # L2
{ key = "proj-x", name = "ProjectX", remark = "Key project", parent = "prod" }, # L3
]- Account
departmentand guardrailbound_tomay reference departments at any level. - Dangling parents, self-references, cycles and nesting deeper than 3 levels are rejected by the variable
validationblocks invariables.tf(hard-fail at plan) and mirrored by the preflight script. - Upgrading from older versions: flat departments are treated as L1 and resource addresses are unchanged (
org_node.this), so no state migration is needed.
For team collaboration or CI, always enable remote state:
- Create a COS bucket once (versioning, default encryption and private ACL recommended).
cp backend.tf.example backend.tfand fill in bucket / prefix / region.terraform init -migrate-stateto move the local state into COS.
The COS backend is built into Terraform and supports state locking (see the Terraform docs).
- Creates a COS bucket in the log archive account (versioning + AES256 encryption + retention lifecycle).
- Creates an organization-wide CloudAudit track in the management account (
track_for_all_members = 1), delivering operation logs of every member account to that bucket.
Prerequisite (one-time manual step): log in to the log archive account console via login authorization (the OrganizationAdmin identity) and create a CAM role (default name LandingZoneLogArchiveAccess) trusting the management account UIN, with COS permissions (QcloudCOSFullAccess recommended).
Then enable it in tfvars:
enable_central_logging = true
audit_log_bucket_name = "my-org-audit-logs" # without the -APPID suffix
log_archive_appid = "1300xxxxxx"
audit_log_retention_days = 365 # 0 = keep foreverIf log delivery fails, check that the log archive bucket grants write access to the CloudAudit service (one-click authorization is available on the CloudAudit console page).
Data-driven design with feature flags. Modify terraform.tfvars only — no code changes needed.
| Requirement | Approach |
|---|---|
| Disable a module | Set enable_guardrails / enable_baselines / enable_login_permission / enable_auditor_policy / enable_central_logging to false |
| Add/remove (nested) departments | Modify departments (parent controls the level, up to 3) |
| Add/remove accounts | Modify core_accounts / business_accounts |
| Add/remove SCP guardrails | Modify guardrail_policies map (includes bound_to attachment targets) |
| Add/remove baseline items | Modify baseline_config_items list |
| Adjust finance permissions | Modify member_permission_ids / pay_uin |
| Enable central audit logging | enable_central_logging = true + bucket name / APPID (prerequisite above) |
| Add/remove identities | Copy/delete identity resource blocks and bindings in identities.tf |
Minimal example (organization + accounts only, no governance modules):
enable_guardrails = false
enable_baselines = false
enable_login_permission = false
enable_auditor_policy = false
# enable_central_logging defaults to falseConfiguration errors are caught before apply:
- Variable
validationblocks (variables.tf): hard-fail at plan time — region format, unique department keys, department nesting/resolvability, accountdepartmentreferences, guardrailbound_toreferences, finance permissions including 1/2,pay_uinrequired when delegated payment (7) is set, central-logging prerequisites, and guardrail content must be valid JSON. - Preflight script (
scripts/validate-config.py): standalone tfvars validation without Terraform, ideal for CI and quick local checks (make preflight). It mirrors the variable validation rules above. checkblocks (checks.tf): post-deploy runtime assertions. These are warnings only and do NOT block plan/apply — Terraform runs them after resources are created and surfaces drift or unexpected state. They are intentionally not used for config validation, which lives in layer 1.
For first-time deployment, apply in dependency order to reduce blast radius and verify each stage (make stage1 … make stage4 run the same commands):
| Stage | Content | Command |
|---|---|---|
| 1 Org & Accounts | Enable org, departments (incl. nested), core/business accounts | terraform apply -target=tencentcloud_organization_instance.this -target=tencentcloud_organization_org_node.this -target=tencentcloud_organization_org_node.level2 -target=tencentcloud_organization_org_node.level3 -target=tencentcloud_organization_org_member.core -target=tencentcloud_organization_org_member.business |
| 2 Identities & Guardrails | Identities, policy bindings, login auth, SCP | terraform apply -target=tencentcloud_organization_org_identity.* -target=tencentcloud_organization_org_member_policy_attachment.* -target=tencentcloud_organization_org_member_auth_identity_attachment.* -target=tencentcloud_organization_org_manage_policy.* |
| 3 Baselines | Account factory baselines + batch apply | terraform apply -target=tencentcloud_controlcenter_account_factory_baseline_config.default -target=tencentcloud_batch_apply_account_baselines.all |
| 4 Finalize | Full reconciliation (incl. optional central audit logging) | terraform apply |
After each stage, run
terraform planto confirm no unexpected changes before proceeding. With many accounts, pilot 1-2 business accounts first with-targetto validate baseline effects, then apply fully.
- CI Validation:
.github/workflows/terraform-ci.ymlrunsfmt -check,validate, tflint, config preflight, preflight unit tests (pytest), scenario example validation and advisory checkov/trivy security scans on every PR/push; a docs check fails the build if the generated module reference drifts from the README. The plan step auto-enables after configuringTENCENTCLOUD_SECRET_ID/TENCENTCLOUD_SECRET_KEYin repository Settings → Secrets (skips if not configured). - Drift Detection:
.github/workflows/drift-detection.ymlrunsterraform plan -detailed-exitcodeweekly; the job fails and notifies when manual console operations have drifted the environment. Requires the credentials above and a committed remote backend (backend.tf). - Convergence: review detected drift manually, then run
terraform applyto converge back to the codified state.
- Department nesting is capped at 3 levels (root → L1 → L2 → L3); deeper structures are rejected by the variable validation blocks in
variables.tf. - Member accounts are created, not invited. Inviting existing accounts requires the invitee's confirmation in the Control Center console.
- Central audit logging needs a one-time manual CAM role in the log archive account — no organization API can create it from the management account. The bucket must also authorize the CloudAudit service to write.
- The security scans (checkov / trivy) are advisory: their rulesets primarily target AWS/Azure/GCP and cover Tencent Cloud resources only partially.
- CI authentication uses long-lived credentials from repository Secrets; an OIDC-based bootstrap stage is on the roadmap.
- Account UINs are chunked into batches of 10 for policy attachment and baseline application (
count-based, notfor_each, to preserve old state addresses). Removing an account can reshuffle which UINs land in which chunk, so Terraform may re-apply the affected batches to unrelated accounts. UINs are sorted for determinism; for fully stable addressing, switch tofor_eachwith a state migration (see roadmap).
| Policy | Attached to | Description |
|---|---|---|
DenyOrganizationTampering |
core / business / sandbox | Deny member accounts quitting the organization or mutating organization members/nodes |
DenyDeleteSecurityResources |
business | Deny deleting CAM identities, CloudAudit (incl. tracks), Config, logging and COS storage resources |
DenyDeleteCoreNetwork |
business | Deny deleting core network resources such as VPCs, subnets and NAT gateways |
Add or remove map entries in guardrail_policies to extend; bound_to supports departments at any level.
baselines.tf is driven by var.baseline_config_items. Defaults include two account factory baseline items (extensible):
TCC-AF_PRESET_TAG: Preset tag baseline for uniform management tags across all accountsTCC-AF_VPC_SUBNET: VPC/subnet baseline creating default network per account
Query available baseline items and their configuration schema via console or:
data "tencentcloud_controlcenter_account_factory_baseline_items" "all" {}Other common baseline items: TCC-AF_SHARE_IMAGE (shared image), ACS-BP_ACCOUNT_FACTORY_ACCOUNT_CONTACT (account contact).
- SCP defines permission boundaries only, does not grant permissions: Effective permissions = SCP ∩ CAM permissions within the account. CAM still needs to be configured inside each member account.
- After SCP is enabled, the system policy
FullQcloudAccessis bound by default (allow-all). This repo adds Deny rules on top, ensuring safety. - Batch APIs are chunked automatically: member policy attachment and baseline batch application accept max 10 UINs per call;
locals.tfchunks the lists, so more than 10 accounts need no manual splitting. - Login authorization (
org_member_auth_identity_attachment) allows max 5 identities per account. - Deleting member accounts: To also deregister the Tencent Cloud account when deleting a Terraform-created member, add
force_delete_account = trueto the resource. - Inviting existing accounts: Invited accounts require acceptee confirmation via Control Center console. This repo creates new accounts.
- Payment delegation:
member_permission_idscontaining7(delegation) requirespay_uinto be set (enforced by a check block at plan time). - Organization/Control Center are global services;
regionis only used for regional resources such as baselines and audit logging.
- Terraform ≥ 1.9 (variable validation blocks reference other variables; check blocks also require ≥ 1.5)
- tencentcloud provider ≥ 1.82.50 (account factory baseline resources are available since v1.82.44)
| Name | Version |
|---|---|
| terraform | >= 1.5 |
| tencentcloud | >= 1.82.50 |
| Name | Version |
|---|---|
| tencentcloud | 1.83.21 |
| Name | Source | Version |
|---|---|---|
| audit_logging | ./modules/audit-logging | n/a |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| audit_log_bucket_name | Audit log COS bucket name (without the -APPID suffix, globally unique); required when enable_central_logging = true | string |
null |
no |
| audit_log_retention_days | Audit log retention in days, expired objects are deleted by a COS lifecycle rule; 0 keeps logs forever | number |
365 |
no |
| baseline_config_items | Account factory baseline item list (identifier + configuration JSON); add or remove as needed. Identifiers can be listed via data source tencentcloud_controlcenter_account_factory_baseline_items. |
list(object({ |
[ |
no |
| baseline_name | Account factory baseline name; must be unique | string |
"default" |
no |
| business_accounts | Business/sandbox accounts. Map key is the resource identifier; department must be a departments key. Maps to the "Account Creation / Invitation" section of the Control Center Landing Zone. |
map(object({ |
{ |
no |
| core_accounts | Core accounts (key accounts managed by the management account), mapping to the "Core Accounts" section of the Control Center Landing Zone. Each element: key (resource identifier), name, department (must be a departments key), remark. |
list(object({ |
[ |
no |
| departments | Department (organizational unit) list. key is the resource identifier, name is the display name. Maps to the "Departments" section of the Control Center Landing Zone. parent is optional: set it to another department key to create a sub-department (up to 3 levels: root -> L1 -> L2 -> L3); null (default) attaches to the organization root. |
list(object({ |
[ |
no |
| enable_auditor_policy | Whether to bind the read-only auditor identity to all accounts | bool |
true |
no |
| enable_baselines | Whether to configure and batch-apply account factory baselines; false disables the whole baseline module | bool |
true |
no |
| enable_central_logging | Whether to create an organization-wide CloudAudit track (track_for_all_members) delivering all member operation logs to a COS bucket in the log archive account | bool |
false |
no |
| enable_guardrails | Whether to create and attach guardrails (Service Control Policies); false disables the whole guardrail module | bool |
true |
no |
| enable_login_permission | Whether to allow the management account to log in to business accounts as administrator (login authorization) | bool |
true |
no |
| guardrail_policies | Guardrail (SCP) map. Map key is the policy name; bound_to lists department keys to attach (must be departments keys). content is a CAM policy syntax JSON document. |
map(object({ |
{ |
no |
| log_archive_account_key | Key of the log archive account in core_accounts (its UIN is used as the audit log storage account) | string |
"logging" |
no |
| log_archive_appid | APPID of the log archive account (COS bucket names require the -APPID suffix); required when enable_central_logging = true | string |
null |
no |
| log_archive_role_name | Name of the CAM role pre-created in the log archive account; the management account assumes it to create/maintain the COS bucket | string |
"LandingZoneLogArchiveAccess" |
no |
| member_permission_ids | Member finance permissions (maps to the Landing Zone "Finance" section): 1 view bills, 2 view balance, 3 fund transfer, 4 consolidated billing, 5 invoicing, 6 discount inheritance, 7 delegated payment. Must include 1 and 2; pay_uin is required when 7 is included. |
list(number) |
[ |
no |
| pay_uin | Delegated payment account UIN; only required when member_permission_ids includes 7 | string |
null |
no |
| region | Tencent Cloud region. Organization / Control Center are global services; this is only used for regional resources (e.g. the baseline VPC) | string |
"ap-guangzhou" |
no |
| root_node_name | Organization root node name (root department under the management account) | string |
"Root" |
no |
| tags | Global tags applied to taggable resources | map(string) |
{ |
no |
| tencentcloud_secret_id | Tencent Cloud API SecretId; leave null to use the TENCENTCLOUD_SECRET_ID environment variable | string |
null |
no |
| tencentcloud_secret_key | Tencent Cloud API SecretKey; leave null to use the TENCENTCLOUD_SECRET_KEY environment variable | string |
null |
no |
| Name | Description |
|---|---|
| audit_log_bucket | Central audit log COS bucket, full name with -APPID suffix (null when enable_central_logging = false) |
| baseline_id | Account factory baseline configuration ID (null when enable_baselines = false) |
| business_account_uins | Business/sandbox account UINs |
| core_account_uins | Core account UINs, keyed by core_accounts key |
| department_nodes | Department (organizational unit) IDs, keyed by departments key (including nested sub-departments) |
| identity_ids | Custom identity (permission set) IDs |
| manage_policy_ids | Guardrail (SCP) policy IDs, keyed by guardrail_policies policy name |
| organization_id | Organization (group account management) ID |
| root_node_id | Organization root node ID |