tfv server

Rules and permissions

Per-project RBAC and required approvals in repos.yaml, per-command permissions, and repo-level tfv.yaml.

Rules live in repos.yaml on the server (TFV_REPOS_CONFIG, default /data/repos.yaml), or in the repos value of the Helm chart. Because they're on the server, a pull request can't change them.

repos.yaml

repos:
  - id: github.com/acme/*
    require_apply_project: true          # same as the env var, per repo
    apply_requirements: [mergeable]
    projects:
      - match: prod/**
        permissions:
          apply: [team:platform-admins]  # read|write|maintain|admin, team:x, user:x, codeowners, anyone
          apply_destroy: [admin]         # plans that delete or replace
          state_read: [team:platform]    # state show, show, output (optional)
        approvals:
          - { from: team:platform-admins, count: 2 }
          - { from: team:security, count: 1 }
      - match: "**"
        approvals: [{ from: write, count: 1 }]

Who can do something is one of: a repo role (read, write, maintain, admin), team:<name>, user:<name>, codeowners, or anyone.

Approvals

  • Only approvals of the current commit count.
  • The author's own approval never counts.
  • Each project needs only its own approvals.

Commands that edit the state directly (outside a plan and apply) also need the project's approvals, like an apply.

Default permissions

When no rule applies:

Permission Covers Default Not set for a project? Uses
plan plans, validate, providers, fmt, state list write
apply applies write
apply_destroy applies that delete or replace maintain
unlock releasing another PR's lock maintain
state_read state show, show, output write plan
state_write import, taint, untaint write apply
state_remove state rm, state mv, force-unlock maintain apply_destroy

The last three cover the Terraform commands. When a project doesn't set them, they use the rule they extend, so they're never looser: limit apply_destroy to admins and state rm is admin-only too.

Per-command permissions

Each command also has its own key, for exceptions: state_list, state_show, show, output, validate, providers, fmt, import, taint, untaint, state_rm, state_mv, force_unlock. A command that isn't set uses its group; [] means nobody.

permissions:
  state_remove: [admin]   # state rm, state mv, force-unlock: admins
  state_mv: [maintain]    # except state mv: maintainers too
  force_unlock: []        # and nobody may force-unlock

A repo's own tfv.yaml

A repo's tfv.yaml can name projects, pin versions, and add stricter rules, but never looser ones. It's read from the base branch, so a PR can't change its own rules.

In Helm values

The chart takes the same rules under repos:

repos:
  repos:
    - id: github.com/acme/infra
      projects:
        - match: prod/**
          permissions:
            apply: [team:platform-admins]
            apply_destroy: [admin]       # also covers state rm / state mv / force-unlock
            force_unlock: []             # single commands can be set too; [] = nobody
          approvals: [{ from: team:platform-admins, count: 2 }]

Policy checks and the two-person rule go in the same file; see policy checks and safety.

Based on packages/server/README.md and charts/tfv-server/README.md in the tfv repo.