Skip to content

feat: add Twilio state backend foundation - #43

Merged
xnoto merged 2 commits into
mainfrom
feat/twilio-state-backend
Sep 4, 2026
Merged

feat: add Twilio state backend foundation#43
xnoto merged 2 commits into
mainfrom
feat/twilio-state-backend

Conversation

@xnoto

@xnoto xnoto commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Create the AWS-owned producer foundation for tfroot-twilio remote state: an isolated private S3 bucket with public access blocked, bucket-owner enforcement, SSE-S3 encryption, versioning, and prevent_destroy; plus a repository-ID-qualified GitHub OIDC role restricted to the exact state object, lockfile, and SOPS KMS decrypt/describe access.

Fixes # N/A — establishes the reviewed backend prerequisite before any Twilio provider configuration, credentials, phone numbers, or webhooks.

Type of change

  • Bug fix
  • Feature / enhancement
  • Documentation
  • Infrastructure (OpenTofu root or module)
  • GitOps desired state (manifests, kustomize, charts, SOPS/KSOPS secrets)
  • Container image
  • CI / reusable workflow
  • Refactor / cleanup
  • Breaking change

Validation

  • Required pull-request checks pass — opentofu / test and opentofu / plan passed after the approved retry. The plan is 7 to add, 1 to change, 0 to destroy.
  • Generated or centrally distributed files were regenerated by their owning automation, not hand-edited — canonical pre-commit regenerated README.md in 323d049a.

The initial plan attempt failed only while downloading the AWS provider after a network connection reset; no configuration error was reported. No local OpenTofu, Makefile, SOPS, state, import, plan, or apply operation was run. GitHub Advanced Security secret scanning is unavailable; the narrow diff was manually inspected and contains no credentials, decrypted values, tokens, state, or sensitive plan output.

Impact and rollout

Producer: tfroot-aws owns the S3 state bucket, its protection/encryption controls, and the narrow GitHub OIDC role.

Consumer: a later tfroot-twilio PR may select this state backend and pass github-actions-twilio-state through the existing reusable workflow's aws-role-to-assume input. The shared workflow already supports that input, so this PR changes no reusable workflow contract. The consumer must not be opened until this producer PR is merged and its environment-gated main apply succeeds.

This PR does not add a Twilio provider configuration, SOPS secret file, Twilio API key, phone number, messaging webhook, bridge credential, runtime map, allowlist, GitOps workload, Cloudflare route, or public endpoint. Its one planned change extends the managed OpenCode MCP role's existing state-read deny to the new bucket; the seven adds are the state bucket, its four hardening controls, and the dedicated OIDC role and policy.

After a confirmed merge, the main workflow will request an environment-gated AWS apply that creates the bucket and role. A later consumer PR must validate backend initialization through its PR CI plan before any credential-delivery or Twilio inventory work. Rollback before consumer adoption is a reviewed revert; the bucket is intentionally prevent_destroy, so rollback does not delete created state infrastructure.

Safety and secrets

  • Contains no plaintext secrets, decrypted SOPS values, state files, kubeconfigs, tokens, or private endpoints.
  • No local OpenTofu init/plan/apply/destroy/import/state operations were run or claimed — plans come from pull-request checks.
  • Breaking or irreversible effects are described above with rollback notes.

AI agent materially produced this change; reviewers should confirm the exact ID-qualified OIDC subject, least-privilege S3 object scope, absence of any Twilio-provider permission, and the preserved MCP state-read deny.

@xnoto
xnoto requested a review from a team as a code owner September 4, 2026 17:44
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

OpenTofu Test

OpenTofu test passed.

View the workflow run.

View run output
Terraform validate.......................................................Passed
Terraform validate with tflint...........................................Passed
Checkov..................................................................Passed
Terraform fmt............................................................Passed
Terraform docs...........................................................Passed
Detect hardcoded secrets.................................................Passed
check for case conflicts.................................................Passed
check for merge conflicts................................................Passed
check for broken symlinks............................(no files to check)Skipped
check vcs permalinks.....................................................Passed
detect destroyed symlinks................................................Passed
detect private key.......................................................Passed
fix end of files.........................................................Passed
mixed line ending........................................................Passed
trim trailing whitespace.................................................Passed
don't commit to branch..................................................Skipped
check for added large files..............................................Passed

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

OpenTofu Plan

OpenTofu plan passed.

View the workflow run.

View run output
OpenTofu will perform the following actions:

  # aws_iam_role.github_actions_twilio_state will be created
  + resource "aws_iam_role" "github_actions_twilio_state" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRoleWithWebIdentity"
                      + Condition = {
                          + StringEquals = {
[REDACTED: potentially sensitive plan output]
                            }
                          + StringLike   = {
[REDACTED: potentially sensitive plan output]
                            }
                        }
                      + Effect    = "Allow"
                      + Principal = {
[REDACTED: potentially sensitive plan output]
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "github-actions-twilio-state"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags                  = {
          + "ManagedBy" = "Terraform"
          + "Purpose"   = "twilio-opentofu-state"
        }
      + tags_all              = {
          + "ManagedBy" = "Terraform"
          + "Purpose"   = "twilio-opentofu-state"
        }
      + unique_id             = (known after apply)

      + inline_policy (known after apply)
    }

  # aws_iam_role_policy.github_actions_twilio_state will be created
  + resource "aws_iam_role_policy" "github_actions_twilio_state" {
      + id          = (known after apply)
      + name        = "twilio-opentofu-state"
      + name_prefix = (known after apply)
      + policy      = (known after apply)
      + role        = (known after apply)
    }

[REDACTED: potentially sensitive plan output]
[REDACTED: potentially sensitive plan output]
[REDACTED: potentially sensitive plan output]
[REDACTED: potentially sensitive plan output]
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    # (1 unchanged element hidden)
                    {
                        Action    = [
                            "kms:Decrypt",
                        ]
                        Condition = {
                            StringLike = {
[REDACTED: potentially sensitive plan output]
                            }
                        }
                        Effect    = "Allow"
                        Resource  = "*"
[REDACTED: potentially sensitive plan output]
                    },
                  ~ {
                      ~ Resource = [
                            # (5 unchanged elements hidden)
                            "arn:aws:s3:::mitw-tf-namecheap-infra/*",
                          + "arn:aws:s3:::mitw-tf-twilio-infra/*",
                        ]
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # aws_s3_bucket.private["mitw-tf-twilio-infra"] will be created
  + resource "aws_s3_bucket" "private" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "mitw-tf-twilio-infra"
      + bucket_domain_name          = (known after apply)
      + bucket_namespace            = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_region               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = "us-west-2"
      + request_payer               = (known after apply)
      + tags                        = {
          + "ManagedBy" = "Terraform"
        }
      + tags_all                    = {
          + "ManagedBy" = "Terraform"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket_ownership_controls.twilio_state will be created
  + resource "aws_s3_bucket_ownership_controls" "twilio_state" {
      + bucket = (known after apply)
      + id     = (known after apply)
      + region = "us-west-2"

      + rule {
          + object_ownership = "BucketOwnerEnforced"
        }
    }

  # aws_s3_bucket_public_access_block.twilio_state will be created
  + resource "aws_s3_bucket_public_access_block" "twilio_state" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + region                  = "us-west-2"
      + restrict_public_buckets = true
    }

  # aws_s3_bucket_server_side_encryption_configuration.twilio_state will be created
  + resource "aws_s3_bucket_server_side_encryption_configuration" "twilio_state" {
      + bucket = (known after apply)
      + id     = (known after apply)
      + region = "us-west-2"

      + rule {
          + blocked_encryption_types = (known after apply)
          + bucket_key_enabled       = (known after apply)

          + apply_server_side_encryption_by_default {
              + kms_master_key_id = (known after apply)
              + sse_algorithm     = "AES256"
            }
        }
    }

  # aws_s3_bucket_versioning.twilio_state will be created
  + resource "aws_s3_bucket_versioning" "twilio_state" {
      + bucket = (known after apply)
      + id     = (known after apply)
      + region = "us-west-2"

      + versioning_configuration {
          + mfa_delete = (known after apply)
          + status     = "Enabled"
        }
    }

Plan: 7 to add, 1 to change, 0 to destroy.
OpenTofu will perform the following actions:

  # aws_iam_role.github_actions_twilio_state will be created
  + resource "aws_iam_role" "github_actions_twilio_state" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRoleWithWebIdentity"
                      + Condition = {
                          + StringEquals = {
[REDACTED: potentially sensitive plan output]
                            }
                          + StringLike   = {
[REDACTED: potentially sensitive plan output]
                            }
                        }
                      + Effect    = "Allow"
                      + Principal = {
[REDACTED: potentially sensitive plan output]
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "github-actions-twilio-state"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags                  = {
          + "ManagedBy" = "Terraform"
          + "Purpose"   = "twilio-opentofu-state"
        }
      + tags_all              = {
          + "ManagedBy" = "Terraform"
          + "Purpose"   = "twilio-opentofu-state"
        }
      + unique_id             = (known after apply)

      + inline_policy (known after apply)
    }

  # aws_iam_role_policy.github_actions_twilio_state will be created
  + resource "aws_iam_role_policy" "github_actions_twilio_state" {
      + id          = (known after apply)
      + name        = "twilio-opentofu-state"
      + name_prefix = (known after apply)
      + policy      = (known after apply)
      + role        = (known after apply)
    }

[REDACTED: potentially sensitive plan output]
[REDACTED: potentially sensitive plan output]
[REDACTED: potentially sensitive plan output]
[REDACTED: potentially sensitive plan output]
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    # (1 unchanged element hidden)
                    {
                        Action    = [
                            "kms:Decrypt",
                        ]
                        Condition = {
                            StringLike = {
[REDACTED: potentially sensitive plan output]
                            }
                        }
                        Effect    = "Allow"
                        Resource  = "*"
[REDACTED: potentially sensitive plan output]
                    },
                  ~ {
                      ~ Resource = [
                            # (5 unchanged elements hidden)
                            "arn:aws:s3:::mitw-tf-namecheap-infra/*",
                          + "arn:aws:s3:::mitw-tf-twilio-infra/*",
                        ]
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # aws_s3_bucket.private["mitw-tf-twilio-infra"] will be created
  + resource "aws_s3_bucket" "private" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "mitw-tf-twilio-infra"
      + bucket_domain_name          = (known after apply)
      + bucket_namespace            = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_region               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = "us-west-2"
      + request_payer               = (known after apply)
      + tags                        = {
          + "ManagedBy" = "Terraform"
        }
      + tags_all                    = {
          + "ManagedBy" = "Terraform"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket_ownership_controls.twilio_state will be created
  + resource "aws_s3_bucket_ownership_controls" "twilio_state" {
      + bucket = (known after apply)
      + id     = (known after apply)
      + region = "us-west-2"

      + rule {
          + object_ownership = "BucketOwnerEnforced"
        }
    }

  # aws_s3_bucket_public_access_block.twilio_state will be created
  + resource "aws_s3_bucket_public_access_block" "twilio_state" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + region                  = "us-west-2"
      + restrict_public_buckets = true
    }

  # aws_s3_bucket_server_side_encryption_configuration.twilio_state will be created
  + resource "aws_s3_bucket_server_side_encryption_configuration" "twilio_state" {
      + bucket = (known after apply)
      + id     = (known after apply)
      + region = "us-west-2"

      + rule {
          + blocked_encryption_types = (known after apply)
          + bucket_key_enabled       = (known after apply)

          + apply_server_side_encryption_by_default {
              + kms_master_key_id = (known after apply)
              + sse_algorithm     = "AES256"
            }
        }
    }

  # aws_s3_bucket_versioning.twilio_state will be created
  + resource "aws_s3_bucket_versioning" "twilio_state" {
      + bucket = (known after apply)
      + id     = (known after apply)
      + region = "us-west-2"

      + versioning_configuration {
          + mfa_delete = (known after apply)
          + status     = "Enabled"
        }
    }

Plan: 7 to add, 1 to change, 0 to destroy.

@xnoto
xnoto merged commit 5faa314 into main Sep 4, 2026
5 of 6 checks passed
@xnoto
xnoto deleted the feat/twilio-state-backend branch September 4, 2026 17:58
xnoto added a commit to makeitworkcloud/tfroot-twilio that referenced this pull request Sep 4, 2026
## Summary

Select the applied `tfroot-aws` Twilio state backend: configure the S3
backend with native locking, route CI through the dedicated
`github-actions-twilio-state` OIDC role, and retain the
no-provider/no-resource boundary.

Fixes # N/A — validates the consumer half of the encrypted backend
prerequisite before any Twilio credential, provider, phone-number, or
webhook work.

## Type of change

- [ ] Bug fix
- [x] Feature / enhancement
- [ ] Documentation
- [x] Infrastructure (OpenTofu root or module)
- [ ] GitOps desired state (manifests, kustomize, charts, SOPS/KSOPS
secrets)
- [ ] Container image
- [ ] CI / reusable workflow
- [ ] Refactor / cleanup
- [ ] Breaking change

## Validation

- [x] Required pull-request checks pass — [`opentofu / test` and
`opentofu /
plan`](https://github.com/makeitworkcloud/tfroot-twilio/actions/runs/33903966072)
passed. The plan successfully initialized the dedicated backend under
the scoped OIDC role and reported `No changes`.
- [x] Generated or centrally distributed files were regenerated by their
owning automation, not hand-edited — no generated or centrally managed
file is changed.

No local OpenTofu, Makefile, SOPS, state, import, plan, or apply
operation was run. GitHub Advanced Security secret scanning is
unavailable; the narrow diff was manually inspected and contains no
credentials, decrypted values, tokens, state, or sensitive plan output.

## Impact and rollout

Producer: [`tfroot-aws` PR
#43](makeitworkcloud/tfroot-aws#43) merged at
[`5faa314d`](makeitworkcloud/tfroot-aws@5faa314),
and its [main
apply](https://github.com/makeitworkcloud/tfroot-aws/actions/runs/33903363355)
succeeded. It owns the dedicated private/versioned/encrypted state
bucket and exact-repository OIDC role.

Consumer: this root uses the existing `shared-workflows`
`aws-role-to-assume` input; no shared-workflow contract changes. Its
Makefile selects S3 native locking and removes the bootstrap
`-backend=false` / `-lock=false` behavior. The PR deliberately leaves
the Twilio provider unconfigured and adds no encrypted file, provider
credential, resource, phone number, webhook, bridge credential, runtime
map, allowlist, GitOps workload, Cloudflare route, or public endpoint.

A confirmed merge submits the environment-gated main apply, which
initializes the remote backend under the dedicated role. That is
state-backend selection only, not Twilio provider activity. Rollback is
a reviewed revert to backend-free initialization; the producer bucket is
intentionally retained and protected by `prevent_destroy`.

## Safety and secrets

- [x] Contains no plaintext secrets, decrypted SOPS values, state files,
kubeconfigs, tokens, or private endpoints.
- [x] No local OpenTofu init/plan/apply/destroy/import/state operations
were run or claimed — plans come from pull-request checks.
- [x] Breaking or irreversible effects are described above with rollback
notes.

AI agent materially produced this change; reviewers should confirm the
exact OIDC-role input, native S3 locking, no static AWS credential path,
and retained absence of any Twilio provider configuration or resource.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant