Sign in
Menu
Sign up
Docs
Check before apply

CI/CD integration

For platform engineers adding compliance.tf modules to a pipeline. When you finish, the pipeline's terraform init authenticates to the compliance.tf registry and downloads the modules; plan and apply stay as they are. This page covers what every platform shares. The platform pages below cover the rest.

Evaluating rather than setting up? Rollback is covered in Compatibility with terraform-aws-modules, and what happens if the registry is unreachable in Make the case.

  • Compliance.tf is a private Terraform registry. Any CI/CD system that can run the Terraform CLI can use compliance.tf modules.
  • The only setup required is providing a registry token so terraform init can authenticate.
  • Your Terraform commands (init, plan, apply) stay exactly the same. Only the token configuration is new.

Before you start

When you run terraform login soc2.compliance.tf on your machine, the CLI stores a short-lived token that expires after 1 day. That works for local development but not for a pipeline. A pipeline is something a team depends on, so give it an organization-owned token: it carries the organization's plan on every host and keeps working when the person who created it leaves. A personal token from the Access Tokens page breaks or narrows to one person's plan when its owner leaves; Registry tokens explains the difference. Store the token as a secret in your CI/CD platform and pass it to Terraform as an environment variable.

How authentication works

Which credential Terraform reads depends on the form of your module source.

Registry form (source = "soc2.compliance.tf/...", with a version argument). Terraform reads a TF_TOKEN_* environment variable:

TF_TOKEN_soc2_compliance_tf=ctf_YOUR_TOKEN_HERE

The variable name is the registry hostname with dots replaced by underscores and a TF_TOKEN_ prefix:

Registry hostnameEnvironment variable
soc2.compliance.tfTF_TOKEN_soc2_compliance_tf
hipaa.compliance.tfTF_TOKEN_hipaa_compliance_tf
pcidss.compliance.tfTF_TOKEN_pcidss_compliance_tf
bobthecorp.compliance.tf (your organization's host)TF_TOKEN_bobthecorp_compliance_tf

If you use several hosts in the same configuration, set one TF_TOKEN_* variable per hostname. The same token value works for all of them; only the variable name changes. Every platform page below uses this form. Reference: Terraform CLI TF_TOKEN_* documentation.

HTTPS form (source = "https://soc2.compliance.tf/...?version=..."). On a framework host it is the form that takes ?disable= and ?add_rules=; on your organization's host, the form that takes ?ruleset=. Terraform fetches it with its HTTPS getter, which reads .netrc and ignores TF_TOKEN_*. Multi-environment setup below shows the pipeline step; the .netrc format is on Registry endpoints, and what each host accepts is on Rulesets in the console.

Choose your platform

Every platform uses the same pattern: store your compliance.tf token as a secret, then expose it as TF_TOKEN_soc2_compliance_tf when Terraform runs.

Multi-environment setup

Teams often need different compliance postures for different environments. Production might enforce all SOC 2 controls, while development relaxes controls that slow iteration (like S3 Object Lock, which AWS never turns off once a bucket has it, so a dev bucket would carry it for good). The example below uses the HTTPS form, so the pipeline needs a .netrc entry as well as, or instead of, TF_TOKEN_*.

Same framework, different controls per environment

Use disable= in the HTTPS module source to relax specific controls in non-production environments. Each environment root keeps its own literal source line: Terraform does not accept a variable or a local in a module source, as One source per environment explains. (The examples below assume a logging_bucket module elsewhere in the configuration; substitute your own logging bucket reference.)

module "s3_bucket" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0"

  bucket = "my-app-data-prod"

  logging = {
    target_bucket = module.logging_bucket.s3_bucket_id
    target_prefix = "s3-access-logs/my-app-prod/"
  }

  # Plus the KMS key, replication and lifecycle rule SOC 2 also needs
}
module "s3_bucket" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&disable=s3_bucket_object_lock_enabled"

  bucket = "my-app-data-dev"

  logging = {
    target_bucket = module.logging_bucket.s3_bucket_id
    target_prefix = "s3-access-logs/my-app-dev/"
  }

  # Plus the KMS key, replication and lifecycle rule SOC 2 also needs
}

Each environment directory has its own Terraform configuration, and your CI/CD pipeline runs against the directory for the target environment. The full set of SOC 2 inputs for the S3 bucket module is in the S3 bucket migration playbook.

Token setup for the HTTPS form

Write a .netrc file for the job, point NETRC at it, and delete it when the job ends. Run this in the step that runs terraform init, with the token injected from your platform's secret store as CTF_TOKEN:

# A per-job file, so a shared build agent keeps no credentials
export NETRC="$(mktemp)"
trap 'rm -f "$NETRC"' EXIT

# One machine entry per compliance.tf host in your module sources
printf 'machine soc2.compliance.tf\n  login ci\n  password %s\n' "${CTF_TOKEN}" > "$NETRC"

terraform init
terraform plan -out=tfplan

login can be any string; the token goes in password. If the same configuration also uses registry-form sources, keep the TF_TOKEN_* variable as well.

Token setup for several framework hosts

If your environments use different framework hosts, set a TF_TOKEN_* variable for each registry-form hostname, and add a machine line for each HTTPS-form hostname:

env:
  TF_TOKEN_soc2_compliance_tf: ${{ secrets.CTF_TOKEN }}
  TF_TOKEN_hipaa_compliance_tf: ${{ secrets.CTF_TOKEN }}

A single token value works across all compliance.tf hosts. To customize controls per environment, see Turn controls on and off.

Audit artifacts from CI

Your pipeline already produces evidence an auditor can use. Save the plan output and scan reports as artifacts, and each run leaves a record behind.

Save plan output as JSON

Terraform plan output in JSON format shows which resources get created and what values are set. This includes the settings the compliance.tf modules enforce. It is a record that those settings were in the plan at deployment time.

      - name: Save Plan JSON
        run: terraform show -json tfplan > plan.json

      - name: Upload Plan Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: terraform-plan-${{ github.sha }}
          path: plan.json
          retention-days: 365

Set retention-days to match the evidence retention you agree with your assessor. Common anchors: PCI DSS requires 12 months of audit-log history and HIPAA six years for required documentation; which artifacts count as evidence, and for how long, varies by audit. Note the platform ceilings: GitHub Actions caps artifact retention at 90 days for public repos and 400 days for private repos. For longer horizons, have the pipeline copy artifacts to long-term storage you control, such as an S3 bucket with a lifecycle policy.

Save scan reports

If you run Checkov or another scanner, save its output alongside the plan:

      - name: Upload Scan Report
        uses: actions/upload-artifact@v4
        with:
          name: checkov-report-${{ github.sha }}
          path: checkov-report.json
          retention-days: 365

See Audit evidence to build these artifacts into an evidence package.

Token management for teams

  • One organization-owned token per consumer for CI/CD: one per pipeline or runner, named after it, created on the organization's tokens screen. Revoking one then stops one consumer. See Create a token.
  • Personal tokens for local development. Each engineer generates their own via terraform login or the Access Tokens page.
  • Rotation. Registry tokens are valid until revoked. Rotate them on a regular cadence (quarterly recommended): create a new token, update your CI secrets, then revoke the old token.
  • Revocation. If a token is compromised, revoke it immediately where it was created (the organization's tokens screen, or the Access Tokens page for a personal token), generate a new one, and update every pipeline that used the revoked token.

Troubleshooting

Error accessing remote module registry or Failed to retrieve available versions for module

Module download from the compliance.tf registry failed: the token is not set, or the hostname in the variable name does not match your module source. Check that:

  • The TF_TOKEN_* variable is available in the step that runs terraform init.
  • The hostname in the variable name matches your module source exactly (soc2.compliance.tf becomes TF_TOKEN_soc2_compliance_tf).

401 Unauthorized

The token is invalid or expired.

  • Registry tokens do not expire unless revoked. Check that the token has not been revoked.
  • A token from terraform login expires after 1 day. Use a long-lived CI token instead.
  • Check that the full token value was copied (tokens start with ctf_).

403 Forbidden

The token is valid but your subscription does not include the requested framework.

  • Check your subscription at app.compliance.tf to confirm the framework host is included.
  • If you use a framework host such as hipaa.compliance.tf, confirm your subscription covers HIPAA.

Variable name conversion mistakes

The most common mistake is getting the TF_TOKEN_* variable name wrong. The conversion rule:

  1. Start with the registry hostname: soc2.compliance.tf
  2. Replace every dot with an underscore: soc2_compliance_tf
  3. Add the TF_TOKEN_ prefix: TF_TOKEN_soc2_compliance_tf

Check that you have not used dashes, uppercase letters, or extra characters.

.netrc not working

.netrc is used with the HTTPS form, not the registry form. If your module source looks like source = "soc2.compliance.tf/..." (no https:// prefix), you need TF_TOKEN_* or .terraformrc credentials, not .netrc.

If your source uses source = "https://soc2.compliance.tf/...", check that your .netrc file:

  • Is in your home directory, or at the path in the NETRC environment variable
  • Has a machine value matching the hostname
  • Uses password for the token value (login can be any string)

Next steps

On this page

Ask AI about this

Help improve this page