feat: authenticate GitHub Actions to Scaleway via a scoped IAM key#22
Merged
Conversation
New `github-ci/` Terraform root that stands up the Scaleway identity GitHub Actions uses to authenticate to Scaleway: - `scaleway_iam_application` + `scaleway_iam_policy` (`ObjectStorageReadOnly`, project-scoped) + `scaleway_iam_api_key` - key written into Infisical (`staging` env, `/ci` folder); secret half is sensitive, state-only - `workflow_dispatch` smoke test that authenticates with SCW_ACCESS_KEY/SCW_SECRET_KEY and lists fr-par buckets, failing clearly when the secrets are absent Keyless GitHub-OIDC -> Scaleway is a non-goal: blocked upstream (Scaleway IAM is not an OIDC relying party). Static least-privilege key is Scaleway's supported pattern; mitigated with a dedicated, independently-revocable identity. Wires the new root into `mise run lock`, the terraform-lock CI matrix, convenience mise tasks, and CLAUDE.md. Closes #21
nbrieussel
commented
Jun 11, 2026
- Add a `pull_request` trigger (paths: github-ci/**, the workflow file) so the Scaleway auth check can be validated before merge — workflow_dispatch can't be triggered from a branch, only once on the default branch. - Run the job in a dedicated `scaleway` GitHub Environment, scoping secret usage (resolves zizmor's secrets-outside-env finding). - Document both triggers + the validation order in the README.
The org enforces an expiration date on every Scaleway API key, so a key
with no expires_at fails at create ("expiration date required"). Drive
expires_at from a time_rotating resource (default 365d,
var.api_key_rotation_days): the timestamp holds steady until the window
lapses, then the next apply rotates the key (expires_at is ForceNew).
Adds the hashicorp/time provider (lock re-generated for both platforms)
and documents the rotation behaviour in the README.
infisical_secret doesn't create missing folders, so writing to /ci 404'd
("Folder with path '/ci' ... not found"). Add an infisical_secret_folder
resource for the CI folder and point the secrets at its computed path so
the folder is created first.
scw object bucket list needs a default organization ID even for a
project-scoped key ("organization ID is required"). Pass it (and the
project ID) as action inputs — public identifiers, not secrets (same
UUID already committed in github-ci/variables.tf).
nbrieussel
commented
Jun 11, 2026
…vars - Trigger the auth check on pull_request only (workflow_dispatch was un-triggerable from a branch anyway). - Read default-organization-id / default-project-id from repo variables (vars.SCW_DEFAULT_ORGANIZATION_ID / SCW_DEFAULT_PROJECT_ID) instead of hardcoding the UUID — public identifiers, not secrets. - Document the two repo variables in the README.
nbrieussel
marked this pull request as ready for review
June 11, 2026 12:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Implements #21. GitHub Actions needs to authenticate to Scaleway. Keyless GitHub-OIDC → Scaleway is not possible (Scaleway IAM is not an OIDC relying party — feature request still open), so this uses Scaleway's supported pattern: a dedicated, least-privilege API key consumed from GitHub Actions secrets, stood up as versioned Terraform.
Changes
github-ci/root (org-wide S3 backend, Scaleway + Infisical providers):scaleway_iam_application— the CI identityscaleway_iam_policy—permission_set_names = ["ObjectStorageReadOnly"], scoped to a single project (no broader set)scaleway_iam_api_key— withdefault_project_idbaked ininfisical_secret× 2 — writesSCW_ACCESS_KEY/SCW_SECRET_KEYto Infisical (stagingenv,/cifolder); secret half issensitive, state-only.github/workflows/scaleway-auth-check.yml—workflow_dispatchthat authenticates with the key and runsscw object bucket list region=fr-par; asserts the secrets are present so it fails clearly on missing/invalid credsmise run lock, theterraform-lockCI matrix, conveniencegithub-ci-*mise tasks, andCLAUDE.mdgithub-ci/README.mddocuments the manualgh secret setstep + the OIDC-to-Scaleway non-goalVerification done locally
terraform init(backend connects),validate✅,plan→ 5 to add, policy scoped toObjectStorageReadOnlyon the one project, secret values(sensitive value).terraform.lock.hclcoversdarwin_arm64+linux_amd64terraform fmtclean,actionlintcleanterraform applyrun — left for explicit approvalRemaining (needs approval / can't be done from here)
These are the post-apply steps from #21, requiring a human:
mise run github-ci-apply(billable: creates the IAM key)gh secret set SCW_ACCESS_KEY/SCW_SECRET_KEY(exact commands ingithub-ci/README.md)fr-parbuckets, job greenOpen questions from the issue — decisions taken
stagingenv, new/cifolder (keeps CI secrets separate from cluster-bootstrap secrets). Overridable via vars.6283c05b-…), where the state bucket lives. Overridable viavar.project_id.scaleway/action-scwpinned to a commit SHA.🤖 Generated with Claude Code