Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e7a052293 | |||
| ba5f49f358 | |||
| 92b8fdd0dd |
@@ -22,18 +22,3 @@ repos:
|
||||
"-d {extends: relaxed, rules: {line-length: disable}, ignore: chart}",
|
||||
"-s",
|
||||
]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: vault-yaml-tests
|
||||
name: vault yaml definitions pass their unit tests
|
||||
entry: python3 -m unittest discover -s tests -t .
|
||||
language: python
|
||||
additional_dependencies: [pyyaml, pydantic]
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
- id: tofu-module-tests
|
||||
name: tofu module tests pass
|
||||
entry: sh -c 'for m in auth_jwt_backend auth_jwt_role; do (cd modules/vault_cluster/modules/$m && tofu init -backend=false -input=false >/dev/null && tofu test) || exit 1; done'
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
|
||||
@@ -4,7 +4,7 @@ when:
|
||||
|
||||
steps:
|
||||
- name: apply
|
||||
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/opentofu:0.1.0
|
||||
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||
environment:
|
||||
VAULT_AUTH_METHOD: kubernetes
|
||||
VAULT_VERSION: "1.20.0"
|
||||
|
||||
@@ -3,7 +3,7 @@ when:
|
||||
|
||||
steps:
|
||||
- name: plan
|
||||
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/opentofu:0.1.0
|
||||
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||
environment:
|
||||
VAULT_AUTH_METHOD: kubernetes
|
||||
VAULT_VERSION: "1.20.0"
|
||||
|
||||
@@ -3,7 +3,7 @@ when:
|
||||
|
||||
steps:
|
||||
- name: pre-commit
|
||||
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/opentofu:0.1.0
|
||||
image: git.unkin.net/unkin/almalinux9-opentofu:20260606
|
||||
commands:
|
||||
- uvx pre-commit run --all-files
|
||||
backend_options:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: init plan apply format test
|
||||
.PHONY: init plan apply format
|
||||
|
||||
VAULT_AUTH_METHOD ?= approle
|
||||
VAULT_K8S_ROLE ?= woodpecker_terraform_vault
|
||||
@@ -22,16 +22,12 @@ init:
|
||||
|
||||
plan: init
|
||||
@$(call vault_env) && \
|
||||
terragrunt run --all --parallelism 4 --non-interactive plan -- -lock=false
|
||||
terragrunt run --all --parallelism 4 --non-interactive plan
|
||||
|
||||
apply: init
|
||||
@$(call vault_env) && \
|
||||
terragrunt run --all --parallelism 2 --non-interactive apply
|
||||
|
||||
test:
|
||||
@uv run --with pyyaml --with pydantic python -m unittest discover -s tests -t .
|
||||
@for m in auth_jwt_backend auth_jwt_role; do (cd modules/vault_cluster/modules/$$m && tofu init -backend=false -input=false >/dev/null && tofu test) || exit 1; done
|
||||
|
||||
format:
|
||||
@echo "Formatting OpenTofu files..."
|
||||
@tofu fmt -recursive .
|
||||
|
||||
@@ -35,35 +35,3 @@ You can check the Consul KV store by accessing the Consul UI or using the consul
|
||||
```bash
|
||||
consul kv get terraform/state
|
||||
```
|
||||
|
||||
# GitHub Actions JWT roles
|
||||
|
||||
`auth/jwt/github/iuclabs` trusts GitHub Actions OIDC tokens. One org-wide role, `default` (`config/auth_jwt_role/jwt/github/iuclabs/default.yaml`, the mount's `default_role`, so workflows needn't name it), accepts any IUCLabs repo for `push`, `release`, `schedule` and `pull_request` runs on `refs/heads/*`, `refs/tags/*` or `refs/pull/*/merge`. Role validation rejects any other event (`pull_request_target`, `workflow_run`, comment events), any other ref glob, and globs in any other claim. Fork pull requests can't mint OIDC tokens unless "Send write tokens to workflows from pull requests" is enabled on the repo.
|
||||
|
||||
Workflows log in with audience `https://vault.service.consul:8200`:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: hashicorp/vault-action@v3
|
||||
with:
|
||||
url: https://vault.service.consul:8200
|
||||
method: jwt
|
||||
path: jwt/github/iuclabs
|
||||
jwtGithubAudience: https://vault.service.consul:8200
|
||||
secrets: |
|
||||
kv/data/github/IUCLabs/<repo>/ref/refs/heads/main/.secrets/deploy token | DEPLOY_TOKEN
|
||||
```
|
||||
|
||||
The role attaches a templated read-only policy, so each run reads only its own KV-v2 secrets:
|
||||
|
||||
- `kv/github/<org>/<repo>/event/<event_name>/*` is not ref-scoped: any run of that event in the repo, on any branch or tag a writer pushes, can read it; keep it for secrets every such run may use
|
||||
- `kv/github/<org>/<repo>/ref/<full ref>/.secrets/*`, e.g. `ref/refs/heads/main/.secrets/`, `ref/refs/tags/v1.2.3/.secrets/`; git refnames can't contain a `.`-prefixed component, so branch `x` can never read branch `x/y`'s secrets
|
||||
|
||||
On OpenBao older than the fork's v2.6, a ref path component of exactly `+` acts as a single-segment wildcard in the templated policy. Public IUCLabs repos block such refs via a terraform-git ruleset; private repos are not protected, so a writer in a private repo can read that repo's other refs' secrets.
|
||||
|
||||
`user_claim` is `jti`, so every run creates its own Vault identity entity; Vault never garbage-collects these, so entity count grows with CI volume.
|
||||
|
||||
Extra policies are granted via a policy yaml `auth: {jwt/github/iuclabs: [default]}`; the KV read policy is attached automatically.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Mounts the arrstack dynamic secrets engine at "arrstack" and writes its config.
|
||||
# The arrproxy admin token is sensitive and read from KV, not stored here:
|
||||
# kv/kubernetes/namespace/arrstack/default/arrproxy-admin-token -> key "token"
|
||||
# (seeded by argocd-apps #384). arrstack.unkin.net terminates on traefik-external
|
||||
# with an internal-CA cert the OpenBao nodes already trust, so ca_cert is omitted
|
||||
# (system trust store), mirroring the gitea engine against git.unkin.net.
|
||||
description: "arrstack dynamic arrproxy API keys"
|
||||
base_url: "https://arrstack.unkin.net"
|
||||
request_timeout_seconds: 30
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
# Mints an arrproxy API key scoped to all three arr apps.
|
||||
apps:
|
||||
- prowlarr
|
||||
- radarr
|
||||
- sonarr
|
||||
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||
# arrproxy admin token's fixed mint expiry.
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
# Mints an arrproxy API key for the mediamark kids-content marking UI, scoped to
|
||||
# Sonarr and Radarr only (no Prowlarr: mediamark never touches indexers).
|
||||
#
|
||||
# mediamark only lists series/movies and reads metadata + artwork, so its key is
|
||||
# read-only in intent. The engine role cannot express that today: an
|
||||
# arrstack_secret_backend_role carries apps/ttl/max_ttl only, and arrproxy scopes
|
||||
# machine tokens by app, not by HTTP method (the GET/HEAD restriction on the
|
||||
# cheeztv/kids tier is a grant on an OIDC *group*, not on a minted token). Until
|
||||
# the engine and arrproxy grow per-token method scoping, mediamark's read-only
|
||||
# posture is enforced by the app itself and by the arrproxy tier it routes to.
|
||||
apps:
|
||||
- radarr
|
||||
- sonarr
|
||||
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||
# arrproxy admin token's fixed mint expiry.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# Mints an arrproxy API key scoped to Prowlarr only.
|
||||
apps:
|
||||
- prowlarr
|
||||
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||
# arrproxy admin token's fixed mint expiry.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# Mints an arrproxy API key scoped to Radarr only.
|
||||
apps:
|
||||
- radarr
|
||||
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||
# arrproxy admin token's fixed mint expiry.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# Mints an arrproxy API key scoped to Sonarr only.
|
||||
apps:
|
||||
- sonarr
|
||||
ttl: 60 # seconds (1m); short-lived by design, renewed on demand
|
||||
max_ttl: 86400 # seconds (24h); the engine additionally caps renewal at the
|
||||
# arrproxy admin token's fixed mint expiry.
|
||||
@@ -1,9 +0,0 @@
|
||||
token_ttl: 120
|
||||
token_max_ttl: 120
|
||||
bind_secret_id: false
|
||||
token_bound_cidrs:
|
||||
- "10.10.12.200/32"
|
||||
- "198.18.25.102/32"
|
||||
- "198.18.26.91/32"
|
||||
- "198.18.27.40/32"
|
||||
use_deterministic_role_id: true
|
||||
@@ -1,9 +0,0 @@
|
||||
token_ttl: 120
|
||||
token_max_ttl: 120
|
||||
bind_secret_id: false
|
||||
token_bound_cidrs:
|
||||
- "10.10.12.200/32"
|
||||
- "198.18.25.102/32"
|
||||
- "198.18.26.91/32"
|
||||
- "198.18.27.40/32"
|
||||
use_deterministic_role_id: true
|
||||
@@ -1,7 +0,0 @@
|
||||
# GitHub Actions OIDC for the IUCLabs org; the org-wide role lives in
|
||||
# config/auth_jwt_role/jwt/github/iuclabs/.
|
||||
---
|
||||
oidc_discovery_url: "https://token.actions.githubusercontent.com"
|
||||
bound_issuer: "https://token.actions.githubusercontent.com"
|
||||
description: "GitHub Actions OIDC - IUCLabs"
|
||||
default_role: "default"
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
bound_audiences: ["https://vault.service.consul:8200"]
|
||||
bound_claims:
|
||||
repository_owner: IUCLabs
|
||||
event_name: push,release,schedule,pull_request
|
||||
ref: refs/heads/*,refs/tags/*,refs/pull/*/merge
|
||||
claim_mappings:
|
||||
repository: repository
|
||||
repository_owner: repository_owner
|
||||
ref: ref
|
||||
ref_type: ref_type
|
||||
event_name: event_name
|
||||
workflow: workflow
|
||||
job_workflow_ref: job_workflow_ref
|
||||
run_id: run_id
|
||||
run_attempt: run_attempt
|
||||
sha: sha
|
||||
actor: actor
|
||||
environment: environment
|
||||
user_claim: jti
|
||||
token_ttl: 900
|
||||
token_max_ttl: 3600
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
# Kubernetes auth role for the mediamark app: the default service account in the
|
||||
# mediamark namespace, used by VaultDynamicSecret to read arrstack/creds/mediamark.
|
||||
bound_service_account_names:
|
||||
- default
|
||||
bound_service_account_namespaces:
|
||||
- mediamark
|
||||
token_ttl: 600
|
||||
token_max_ttl: 600
|
||||
audience: vault
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
# Kubernetes auth for the puppetserver compilers, which run certmanager
|
||||
# server-side during catalog compilation. The AppRole equivalent is pinned to
|
||||
# the legacy VM master IPs and cannot be used from autoscaled compiler pods.
|
||||
bound_service_account_names:
|
||||
- default
|
||||
bound_service_account_namespaces:
|
||||
- puppet
|
||||
token_ttl: 30
|
||||
token_max_ttl: 30
|
||||
audience: https://kubernetes.default.svc.cluster.local
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
# Kubernetes auth for the puppetserver compilers, which run sshsignhost
|
||||
# server-side during catalog compilation.
|
||||
bound_service_account_names:
|
||||
- default
|
||||
bound_service_account_namespaces:
|
||||
- puppet
|
||||
token_ttl: 30
|
||||
token_max_ttl: 30
|
||||
audience: https://kubernetes.default.svc.cluster.local
|
||||
@@ -1,7 +0,0 @@
|
||||
bound_service_account_names:
|
||||
- repospawner
|
||||
bound_service_account_namespaces:
|
||||
- repospawner
|
||||
token_ttl: 600
|
||||
token_max_ttl: 600
|
||||
audience: vault
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
# Kubernetes auth role for rspamd: the rspamd service account in the mail
|
||||
# namespace, used by VaultDynamicSecret to read litellm/creds/rspamd.
|
||||
bound_service_account_names:
|
||||
- rspamd
|
||||
bound_service_account_namespaces:
|
||||
- mail
|
||||
token_ttl: 600
|
||||
token_max_ttl: 600
|
||||
audience: vault
|
||||
@@ -1,15 +0,0 @@
|
||||
bound_service_account_names:
|
||||
- default
|
||||
- arrproxy-ci
|
||||
- autobackup-operator-ci
|
||||
- ghp
|
||||
- jellyfin-plugin-sso
|
||||
- kea-operator-ci
|
||||
- mediamark-ci
|
||||
- plugin-docker-buildx
|
||||
- repospawner-ci
|
||||
bound_service_account_namespaces:
|
||||
- woodpecker
|
||||
token_ttl: 600
|
||||
token_max_ttl: 600
|
||||
audience: https://kubernetes.default.svc.cluster.local
|
||||
@@ -1,7 +0,0 @@
|
||||
bound_service_account_names:
|
||||
- terraform-binarylane
|
||||
bound_service_account_namespaces:
|
||||
- woodpecker
|
||||
token_ttl: 600
|
||||
token_max_ttl: 600
|
||||
audience: https://kubernetes.default.svc.cluster.local
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
bound_service_account_names:
|
||||
- terraform-netbox
|
||||
- terraform-infra
|
||||
bound_service_account_namespaces:
|
||||
- woodpecker
|
||||
token_ttl: 600
|
||||
@@ -1,17 +0,0 @@
|
||||
# Authentik (OIDC) human SSO. The mount path is the filename and MUST stay
|
||||
# "oidc": the Authentik provider registers strict redirect URIs containing
|
||||
# /ui/vault/auth/oidc/oidc/callback, which encode the mount path.
|
||||
#
|
||||
# identity.unkin.net serves the public LetsEncrypt wildcard, so the OpenBao VMs
|
||||
# validate discovery against the system roots with no CA bundle work. Do not
|
||||
# swap it for identity.k8s.syd1.au.unkin.net (internal CA).
|
||||
#
|
||||
# listing_visibility unauth makes OIDC the offered default on the UI login page.
|
||||
# client_id/client_secret are read from kv/service/authentik/oidc-vault.
|
||||
---
|
||||
oidc_discovery_url: "https://identity.unkin.net/application/o/vault/"
|
||||
default_role: "default"
|
||||
description: "Authentik SSO - default human login"
|
||||
listing_visibility: "unauth"
|
||||
default_lease_ttl: 24h
|
||||
max_lease_ttl: 168h
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
# this file doesnt need anything in it, so this data is just to make sure yamlencode reads some yaml data
|
||||
# The filename is the Authentik group name asserted in the ak_groups claim.
|
||||
# Only akP-* permission groups may appear here; akR-* roles are grouping-only.
|
||||
description: foo
|
||||
@@ -1,25 +0,0 @@
|
||||
# Default OIDC login role (the mount's default_role), used by both the web UI
|
||||
# and `bao login -method=oidc`.
|
||||
#
|
||||
# The role grants no policies of its own: authorization comes from the external
|
||||
# identity groups under config/auth_oidc_group, matched on the ak_groups claim.
|
||||
# ak_groups is Authentik's hierarchy-expanding claim (plain `groups` only carries
|
||||
# direct memberships), so nested akP-* permission groups resolve.
|
||||
#
|
||||
# allowed_redirect_uris must match the provider's strict URIs exactly.
|
||||
---
|
||||
user_claim: "email"
|
||||
groups_claim: "ak_groups"
|
||||
oidc_scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- ak_groups
|
||||
bound_audiences:
|
||||
- vault
|
||||
allowed_redirect_uris:
|
||||
- "http://localhost:8250/oidc/callback"
|
||||
- "https://vault.k8s.syd1.au.unkin.net/ui/vault/auth/oidc/oidc/callback"
|
||||
- "https://vault.service.consul:8200/ui/vault/auth/oidc/oidc/callback"
|
||||
token_ttl: 86400
|
||||
token_max_ttl: 604800
|
||||
@@ -97,40 +97,6 @@ locals {
|
||||
})
|
||||
if startswith(file_path, "auth_ldap_group/")
|
||||
}
|
||||
auth_oidc_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "auth_oidc_backend/")
|
||||
}
|
||||
auth_oidc_role = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_oidc_role/", ""), ".yaml") => merge(content, {
|
||||
role_name = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = dirname(replace(file_path, "auth_oidc_role/", ""))
|
||||
})
|
||||
if startswith(file_path, "auth_oidc_role/")
|
||||
}
|
||||
auth_oidc_group = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_oidc_group/", ""), ".yaml") => merge(content, {
|
||||
groupname = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = split("/", replace(file_path, "auth_oidc_group/", ""))[0]
|
||||
})
|
||||
if startswith(file_path, "auth_oidc_group/")
|
||||
}
|
||||
auth_jwt_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_jwt_backend/", ""), ".yaml") => content
|
||||
if startswith(file_path, "auth_jwt_backend/")
|
||||
}
|
||||
auth_jwt_role = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_jwt_role/", ""), ".yaml") => merge(content, {
|
||||
role_name = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = dirname(replace(file_path, "auth_jwt_role/", ""))
|
||||
})
|
||||
if startswith(file_path, "auth_jwt_role/")
|
||||
}
|
||||
auth_kubernetes_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_kubernetes_backend/", ""), ".yaml") => content
|
||||
@@ -232,19 +198,6 @@ locals {
|
||||
})
|
||||
if startswith(file_path, "litellm_secret_backend_role/")
|
||||
}
|
||||
arrstack_secret_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "arrstack_secret_backend/")
|
||||
}
|
||||
arrstack_secret_backend_role = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "arrstack_secret_backend_role/", ""), ".yaml") => merge(content, {
|
||||
name = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = dirname(replace(file_path, "arrstack_secret_backend_role/", ""))
|
||||
})
|
||||
if startswith(file_path, "arrstack_secret_backend_role/")
|
||||
}
|
||||
plugins = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => merge(content, {
|
||||
@@ -299,73 +252,5 @@ locals {
|
||||
})
|
||||
if startswith(file_path, "gitea_secret_backend_role/")
|
||||
}
|
||||
netbox_secret_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "netbox_secret_backend/")
|
||||
}
|
||||
netbox_secret_backend_role = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "netbox_secret_backend_role/", ""), ".yaml") => merge(content, {
|
||||
name = trimsuffix(basename(file_path), ".yaml")
|
||||
netbox_username = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = dirname(replace(file_path, "netbox_secret_backend_role/", ""))
|
||||
})
|
||||
if startswith(file_path, "netbox_secret_backend_role/")
|
||||
}
|
||||
identity_oidc = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "identity_oidc/")
|
||||
}
|
||||
identity_oidc_key = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "identity_oidc_key/")
|
||||
}
|
||||
identity_oidc_role = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "identity_oidc_role/", ""), ".yaml") => merge(content, {
|
||||
name = trimsuffix(basename(file_path), ".yaml")
|
||||
key = dirname(replace(file_path, "identity_oidc_role/", ""))
|
||||
})
|
||||
if startswith(file_path, "identity_oidc_role/")
|
||||
}
|
||||
identity_oidc_client = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "identity_oidc_client/", ""), ".yaml") => merge(content, {
|
||||
name = trimsuffix(basename(file_path), ".yaml")
|
||||
key = dirname(replace(file_path, "identity_oidc_client/", ""))
|
||||
})
|
||||
if startswith(file_path, "identity_oidc_client/")
|
||||
}
|
||||
identity_oidc_scope = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "identity_oidc_scope/")
|
||||
}
|
||||
identity_oidc_assignment = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "identity_oidc_assignment/")
|
||||
}
|
||||
identity_oidc_provider = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "identity_oidc_provider/")
|
||||
}
|
||||
ghp_secret_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "ghp_secret_backend/")
|
||||
}
|
||||
ghp_secret_backend_role = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "ghp_secret_backend_role/", ""), ".yaml") => merge(content, {
|
||||
name = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = dirname(replace(file_path, "ghp_secret_backend_role/", ""))
|
||||
})
|
||||
if startswith(file_path, "ghp_secret_backend_role/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
consul_roles:
|
||||
- terraform-binarylane
|
||||
ttl: 120
|
||||
max_ttl: 300
|
||||
datacenters: []
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
consul_roles:
|
||||
- terraform-netbox
|
||||
- terraform-infra
|
||||
ttl: 120
|
||||
max_ttl: 300
|
||||
datacenters: []
|
||||
@@ -1,15 +0,0 @@
|
||||
# Mounts the ghp token secrets engine at "ghp" and writes its config.
|
||||
# The seeded ghp service token is sensitive and read from KV, not stored here:
|
||||
# kv/service/vault/au/syd1/secret_backend/ghp/config
|
||||
# -> key: admin_token (required) the shared ghpsvc_... service token
|
||||
#
|
||||
# admin_token is a static shared secret provisioned into KV by an operator. The
|
||||
# SAME token value must also be present in the running ghp deployment's accepted
|
||||
# service tokens (GHP_AUTH_SERVICE_TOKENS) so ghp ACCEPTS what this engine
|
||||
# PRESENTS. ghp has no rotate endpoint, so the engine never rotates it in place;
|
||||
# the mount uses ignore_changes=[admin_token], making the KV seed create-only
|
||||
# (re-reading a stale KV value never re-pushes it to a live mount).
|
||||
description: "ghp ephemeral scoped agent token engine"
|
||||
base_url: "https://ghp.unkin.net"
|
||||
tls_skip_verify: false
|
||||
request_timeout_seconds: 30
|
||||
@@ -1,19 +0,0 @@
|
||||
# Role minting ephemeral tokens for the repospawner service user. repospawner
|
||||
# creates and seeds Gitea repositories on demand, so it gets write on
|
||||
# repositories (create + push) and write on issues (issue/PR comments). Read is
|
||||
# implied by write. No admin/org/user-write scopes.
|
||||
# read:user is required because most API clients validate the login via
|
||||
# GET /api/v1/user, which 403s without it.
|
||||
# Reading gitea/creds/repospawner mints a lease-bound token deleted from Gitea
|
||||
# on revoke/expiry. Consumed by the repospawner pods via Kubernetes auth (see
|
||||
# policies/gitea/creds/repospawner.yaml) -- the agents AppRole is CIDR-bound to
|
||||
# Ben's workstation and cannot be used from in-cluster.
|
||||
---
|
||||
username: repospawner
|
||||
scopes:
|
||||
- write:repository
|
||||
- write:issue
|
||||
- read:user
|
||||
token_name_prefix: vault-repospawner
|
||||
ttl: 3600 # 1h
|
||||
max_ttl: 14400 # 4h
|
||||
@@ -1,20 +0,0 @@
|
||||
# Role minting ephemeral tokens for the unkin-agent bot user -- the shared
|
||||
# identity Ben's AI coding agents use to submit work. The agent clones/pushes
|
||||
# code and opens pull requests, so it gets write on repositories (clone + push +
|
||||
# PR create) and write on issues (PR/issue comments). Read is implied by write.
|
||||
# No admin/org/user-write scopes, so it can never merge via API privilege; merge
|
||||
# is blocked separately by branch protection (merge whitelist = Owners).
|
||||
# read:user is required because tea (and most API clients) validate the login
|
||||
# via GET /api/v1/user, which 403s without it.
|
||||
# Reading gitea/creds/unkin-agent mints a lease-bound token deleted from Gitea
|
||||
# on revoke/expiry. Consumed by the "agents" AppRole (see
|
||||
# policies/gitea/creds/unkin-agent.yaml).
|
||||
---
|
||||
username: unkin-agent
|
||||
scopes:
|
||||
- write:repository
|
||||
- write:issue
|
||||
- read:user
|
||||
token_name_prefix: vault-unkin-agent
|
||||
ttl: 3600 # 1h
|
||||
max_ttl: 14400 # 4h
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
issuer: https://vault.service.consul:8200
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
algorithm: RS256
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
algorithm: RS256
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
client_id: artifactapi
|
||||
ttl: 900
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
client_id: artifactapi
|
||||
ttl: 900
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
client_id: encapi-testing
|
||||
ttl: 900
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
client_id: encapi
|
||||
ttl: 900
|
||||
@@ -1,6 +0,0 @@
|
||||
allowed_kubernetes_namespaces:
|
||||
- "vm-system"
|
||||
- "observability"
|
||||
- "logging"
|
||||
kubernetes_role_type: "Role"
|
||||
extra_labels: {}
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
models:
|
||||
- claude-haiku-4-5
|
||||
- claude-sonnet-4-6
|
||||
max_budget: 50
|
||||
ttl: 3600 # seconds (1h)
|
||||
max_ttl: 86400 # seconds (24h)
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Mounts the netbox token secrets engine at "netbox" and writes its config.
|
||||
# The seeded NetBox admin token is sensitive and read from KV, not stored here:
|
||||
# kv/service/vault/au/syd1/secret_backend/netbox/config
|
||||
# -> key: admin_token (required) the SINGLE static admin credential
|
||||
#
|
||||
# admin_token must be a BARE NetBox token with NO scheme prefix: do not prepend
|
||||
# "Bearer " or "Token ". NetBox infers the version from the value's nbt_ prefix,
|
||||
# so one bare token authenticates under either scheme; the plugin adds the keyword
|
||||
# itself. A prefixed value yields a malformed header + 403.
|
||||
#
|
||||
# Populate admin_token with a purpose-built NetBox superuser token (add_user +
|
||||
# add_token + grant_token, or superuser) BEFORE applying, then run
|
||||
# `vault write -f netbox/config/rotate` after the first apply so only Vault holds
|
||||
# the live admin token.
|
||||
#
|
||||
# Only ONE static admin token exists. netbox_user_management does NOT re-read this
|
||||
# token; instead the engine mints it a short-lived user-admin token per apply from
|
||||
# netbox/roles/vault-user-mgmt (see user_mgmt_username below), so rotating
|
||||
# admin_token never breaks user management. Set user_mgmt_username to the
|
||||
# pre-existing NetBox superuser the static admin_token belongs to (or another
|
||||
# superuser). Leaving it unset falls back to using admin_token directly, which is
|
||||
# only a bootstrap/degraded path and breaks after rotation.
|
||||
#
|
||||
# Bootstrap ordering: the vault-user-mgmt role must exist before the netbox
|
||||
# provider is configured from its creds, so on a brand-new backend apply the mount
|
||||
# + role first (e.g. `tofu apply -target=...netbox_secret_backend
|
||||
# -target=...netbox_user_mgmt_role`) once, then apply normally.
|
||||
#
|
||||
# token_version 2 is the NetBox 4.6.5 default and requires API_TOKEN_PEPPERS to
|
||||
# be configured on the NetBox server; set token_version: 1 here if the server
|
||||
# has no peppers. token_version does NOT change how the plugin authenticates its
|
||||
# own calls (that scheme comes from the admin_token value's nbt_ prefix); it only
|
||||
# sets the version of the per-user tokens the engine mints. It must still MATCH
|
||||
# the admin_token kind: nbt_ v2 token -> token_version 2; bare v1 token -> 1.
|
||||
#
|
||||
# The mount uses ignore_changes=[token], so editing KV alone does NOT reach the
|
||||
# live mount. To push a corrected/rotated admin token into a running mount:
|
||||
# vault write netbox/config token=<BARE_TOKEN>
|
||||
# (netbox_url/token_version are preserved on a partial update). Do NOT -replace
|
||||
# the mount to force a re-read - that recreates it and drops all roles/config.
|
||||
description: "NetBox ephemeral scoped API token engine"
|
||||
netbox_url: "https://netbox.k8s.syd1.au.unkin.net"
|
||||
token_version: 2
|
||||
request_timeout_seconds: 30
|
||||
# Set to the pre-existing NetBox superuser admin_token belongs to, to mint the
|
||||
# user-management credential dynamically (recommended). Until set, user management
|
||||
# uses admin_token directly and a check block warns that rotation will break it.
|
||||
# user_mgmt_username: "vault-netbox-admin"
|
||||
@@ -1,44 +0,0 @@
|
||||
# Single declarative source for the terraform-netbox NetBox service identity. The
|
||||
# filename stem is the engine role name AND the NetBox username (1:1); config.hcl
|
||||
# derives both from it, so neither is repeated below. Creating this file creates
|
||||
# the user: the netbox_user_management module synthesizes the NetBox user + object
|
||||
# permissions from the permissions block, and the engine role mints ephemeral
|
||||
# tokens for that same user. write_enabled true because terraform-netbox manages
|
||||
# NetBox IPAM/DCIM; very short TTLs because a token is minted per plan/apply and
|
||||
# revoked when the run's lease ends.
|
||||
#
|
||||
# extras.customfield is what lets the runner declare the custom fields bootapi
|
||||
# reads (pxe_enabled, provision_template, domain, gateway, nameservers), and
|
||||
# dcim.platform is how a device gets the platform slug bootapi selects a distro
|
||||
# catalog entry by.
|
||||
---
|
||||
write_enabled: true
|
||||
ttl: 120 # 2m
|
||||
max_ttl: 300 # 5m
|
||||
permissions:
|
||||
- name: terraform-netbox-write
|
||||
object_types:
|
||||
- ipam.prefix
|
||||
- ipam.ipaddress
|
||||
- ipam.iprange
|
||||
- ipam.role
|
||||
- dcim.device
|
||||
- dcim.devicerole
|
||||
- dcim.devicetype
|
||||
- dcim.interface
|
||||
- dcim.macaddress
|
||||
- dcim.manufacturer
|
||||
- dcim.platform
|
||||
- extras.customfield
|
||||
- extras.tag
|
||||
actions:
|
||||
- view
|
||||
- add
|
||||
- change
|
||||
- delete
|
||||
# Sites are looked up with a data source, never created here.
|
||||
- name: terraform-netbox-read
|
||||
object_types:
|
||||
- dcim.site
|
||||
actions:
|
||||
- view
|
||||
@@ -1,19 +0,0 @@
|
||||
# config/plugins/vault-plugin-secrets-arrstack.yaml
|
||||
# Imports (registers) the arrstack secrets plugin in the catalog. Filename =
|
||||
# catalog name = mount type. The binary is installed on the OpenBao nodes by
|
||||
# Puppet (openbao-plugin-secrets-arrstack RPM ->
|
||||
# /opt/openbao-plugins/vault-plugin-secrets-arrstack).
|
||||
#
|
||||
# sha256 pins the released v0.2.0 binary; bump it in lockstep with any RPM
|
||||
# upgrade or OpenBao will refuse to launch the plugin. Registration only
|
||||
# succeeds once the Puppet PR has installed the binary on the nodes.
|
||||
#
|
||||
# Terraform retunes mounts onto a bumped version; after apply, run
|
||||
# `bao write sys/plugins/reload/backend mounts=arrstack scope=global`.
|
||||
#
|
||||
# Once versioned, keep `version` set: dropping it leaves the mount pinned to a
|
||||
# deregistered version.
|
||||
type: secret
|
||||
command: vault-plugin-secrets-arrstack
|
||||
version: "0.2.0"
|
||||
sha256: "9ea7f16013b3bcbadfcb15b08fd1545f8069893cf864bfdaa57e7e7e72612fa1"
|
||||
@@ -1,11 +0,0 @@
|
||||
# config/plugins/vault-plugin-secrets-ghp.yaml
|
||||
# Imports (registers) the ghp secrets plugin in the catalog. Filename =
|
||||
# catalog name = mount type. The binary is installed on the OpenBao nodes by
|
||||
# Puppet (openbao-plugin-secrets-ghp RPM ->
|
||||
# /opt/openbao-plugins/vault-plugin-secrets-ghp).
|
||||
#
|
||||
# sha256 pins the released v0.1.0 binary; bump it in lockstep with any RPM
|
||||
# upgrade or OpenBao will refuse to launch the plugin.
|
||||
type: secret
|
||||
command: vault-plugin-secrets-ghp
|
||||
sha256: "85761421cd532788ed28fb57e93d3868f3577320a538289936d9ed3be5f396de"
|
||||
@@ -1,11 +0,0 @@
|
||||
# config/plugins/vault-plugin-secrets-netbox.yaml
|
||||
# Imports (registers) the netbox secrets plugin in the catalog. Filename =
|
||||
# catalog name = mount type. The binary is installed on the OpenBao nodes by
|
||||
# Puppet (openbao-plugin-secrets-netbox RPM ->
|
||||
# /opt/openbao-plugins/vault-plugin-secrets-netbox).
|
||||
#
|
||||
# sha256 pins the released v0.1.0 binary; bump it in lockstep with any RPM
|
||||
# upgrade or OpenBao will refuse to launch the plugin.
|
||||
type: secret
|
||||
command: vault-plugin-secrets-netbox
|
||||
sha256: "362b7f6c9e21179ad51d2d810684d9387fe50e3a1887f171700122a0b2a05cef"
|
||||
@@ -3,8 +3,6 @@ algorithm_signer: rsa-sha2-256
|
||||
ttl: 315360000 # 87600 * 3600
|
||||
allow_host_certificates: true
|
||||
allow_user_certificates: false
|
||||
# "*" must be the sole entry: OpenBao only treats allowed_domains as unrestricted
|
||||
# when the whole string is "*", and otherwise matches entries exactly or by suffix.
|
||||
allowed_domains: "*"
|
||||
allowed_domains: "main.unkin.net,consul"
|
||||
allow_subdomains: true
|
||||
allow_bare_domains: false
|
||||
|
||||
@@ -39,12 +39,6 @@ locals {
|
||||
for backend_name, _ in local.config.consul_secret_backend :
|
||||
backend_name => replace(backend_name, "/", "_")
|
||||
}
|
||||
|
||||
# Same sanitized alias mapping for the NetBox providers.
|
||||
netbox_backend_aliases = {
|
||||
for backend_name, _ in local.config.netbox_secret_backend :
|
||||
backend_name => replace(backend_name, "/", "_")
|
||||
}
|
||||
}
|
||||
|
||||
terraform {
|
||||
@@ -60,11 +54,6 @@ inputs = {
|
||||
auth_approle_role = local.config.auth_approle_role
|
||||
auth_ldap_backend = local.config.auth_ldap_backend
|
||||
auth_ldap_group = local.config.auth_ldap_group
|
||||
auth_oidc_backend = local.config.auth_oidc_backend
|
||||
auth_oidc_role = local.config.auth_oidc_role
|
||||
auth_oidc_group = local.config.auth_oidc_group
|
||||
auth_jwt_backend = local.config.auth_jwt_backend
|
||||
auth_jwt_role = local.config.auth_jwt_role
|
||||
auth_kubernetes_backend = local.config.auth_kubernetes_backend
|
||||
auth_kubernetes_role = local.config.auth_kubernetes_role
|
||||
kv_secret_backend = local.config.kv_secret_backend
|
||||
@@ -81,8 +70,6 @@ inputs = {
|
||||
pki_mount_only = local.config.pki_mount_only
|
||||
litellm_secret_backend = local.config.litellm_secret_backend
|
||||
litellm_secret_backend_role = local.config.litellm_secret_backend_role
|
||||
arrstack_secret_backend = local.config.arrstack_secret_backend
|
||||
arrstack_secret_backend_role = local.config.arrstack_secret_backend_role
|
||||
plugins = local.config.plugins
|
||||
gpg_secret_backend = local.config.gpg_secret_backend
|
||||
gpg_key = local.config.gpg_key
|
||||
@@ -94,27 +81,10 @@ inputs = {
|
||||
gitea_secret_backend = local.config.gitea_secret_backend
|
||||
gitea_secret_backend_role = local.config.gitea_secret_backend_role
|
||||
|
||||
netbox_secret_backend = local.config.netbox_secret_backend
|
||||
netbox_secret_backend_role = local.config.netbox_secret_backend_role
|
||||
|
||||
ghp_secret_backend = local.config.ghp_secret_backend
|
||||
ghp_secret_backend_role = local.config.ghp_secret_backend_role
|
||||
|
||||
identity_oidc = local.config.identity_oidc
|
||||
identity_oidc_key = local.config.identity_oidc_key
|
||||
identity_oidc_role = local.config.identity_oidc_role
|
||||
identity_oidc_client = local.config.identity_oidc_client
|
||||
identity_oidc_scope = local.config.identity_oidc_scope
|
||||
identity_oidc_assignment = local.config.identity_oidc_assignment
|
||||
identity_oidc_provider = local.config.identity_oidc_provider
|
||||
|
||||
# Pass policy maps to vault_cluster module
|
||||
policy_auth_map = local.policies.policy_auth_map
|
||||
policy_rules_map = local.policies.policy_rules_map
|
||||
|
||||
# Pass sanitized consul backend aliases for provider configuration
|
||||
consul_backend_aliases = local.consul_backend_aliases
|
||||
|
||||
# Pass sanitized netbox backend aliases for provider configuration
|
||||
netbox_backend_aliases = local.netbox_backend_aliases
|
||||
}
|
||||
|
||||
@@ -29,12 +29,6 @@ provider "rancher" {
|
||||
address = local.vault_addr
|
||||
}
|
||||
|
||||
# The arrstack (arrproxy API key) secrets engine is managed through its own
|
||||
# provider (same Vault server; token falls back to VAULT_TOKEN).
|
||||
provider "arrstack" {
|
||||
address = local.vault_addr
|
||||
}
|
||||
|
||||
terraform {
|
||||
backend "consul" {
|
||||
address = "https://consul.service.consul"
|
||||
@@ -65,10 +59,6 @@ terraform {
|
||||
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/ranchervaultsecret"
|
||||
version = "0.1.0"
|
||||
}
|
||||
arrstack = {
|
||||
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack"
|
||||
version = "0.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -64,89 +64,6 @@ module "auth_ldap_group" {
|
||||
depends_on = [module.auth_ldap_backend]
|
||||
}
|
||||
|
||||
module "auth_oidc_backend" {
|
||||
source = "./modules/auth_oidc_backend"
|
||||
|
||||
for_each = var.auth_oidc_backend
|
||||
|
||||
path = each.key
|
||||
description = each.value.description
|
||||
oidc_discovery_url = each.value.oidc_discovery_url
|
||||
client_secret_mount = each.value.client_secret_mount
|
||||
client_secret_path = each.value.client_secret_path
|
||||
default_role = each.value.default_role
|
||||
listing_visibility = each.value.listing_visibility
|
||||
default_lease_ttl = each.value.default_lease_ttl
|
||||
max_lease_ttl = each.value.max_lease_ttl
|
||||
}
|
||||
|
||||
module "auth_oidc_role" {
|
||||
source = "./modules/auth_oidc_role"
|
||||
|
||||
for_each = var.auth_oidc_role
|
||||
|
||||
backend = each.value.backend
|
||||
role_name = each.value.role_name
|
||||
user_claim = each.value.user_claim
|
||||
groups_claim = each.value.groups_claim
|
||||
oidc_scopes = each.value.oidc_scopes
|
||||
bound_audiences = each.value.bound_audiences
|
||||
allowed_redirect_uris = each.value.allowed_redirect_uris
|
||||
token_ttl = each.value.token_ttl
|
||||
token_max_ttl = each.value.token_max_ttl
|
||||
# Human authorization comes from the external identity groups below, so a
|
||||
# login role carrying no policies is the normal case.
|
||||
token_policies = try(var.policy_auth_map[each.value.backend][each.value.role_name], [])
|
||||
|
||||
depends_on = [module.auth_oidc_backend]
|
||||
}
|
||||
|
||||
module "auth_oidc_group" {
|
||||
source = "./modules/auth_oidc_group"
|
||||
|
||||
for_each = var.auth_oidc_group
|
||||
|
||||
groupname = each.value.groupname
|
||||
policies = var.policy_auth_map[each.value.backend][each.value.groupname]
|
||||
mount_accessor = module.auth_oidc_backend[each.value.backend].accessor
|
||||
|
||||
depends_on = [module.auth_oidc_backend]
|
||||
}
|
||||
|
||||
module "auth_jwt_backend" {
|
||||
source = "./modules/auth_jwt_backend"
|
||||
|
||||
for_each = var.auth_jwt_backend
|
||||
|
||||
path = each.key
|
||||
description = each.value.description
|
||||
oidc_discovery_url = each.value.oidc_discovery_url
|
||||
bound_issuer = each.value.bound_issuer
|
||||
default_role = each.value.default_role
|
||||
listing_visibility = each.value.listing_visibility
|
||||
default_lease_ttl = each.value.default_lease_ttl
|
||||
max_lease_ttl = each.value.max_lease_ttl
|
||||
}
|
||||
|
||||
module "auth_jwt_role" {
|
||||
source = "./modules/auth_jwt_role"
|
||||
|
||||
for_each = var.auth_jwt_role
|
||||
|
||||
backend = each.value.backend
|
||||
role_name = each.value.role_name
|
||||
user_claim = each.value.user_claim
|
||||
bound_audiences = each.value.bound_audiences
|
||||
backend_accessor = module.auth_jwt_backend[each.value.backend].accessor
|
||||
bound_claims = each.value.bound_claims
|
||||
claim_mappings = each.value.claim_mappings
|
||||
token_ttl = each.value.token_ttl
|
||||
token_max_ttl = each.value.token_max_ttl
|
||||
token_policies = try(var.policy_auth_map[each.value.backend][each.value.role_name], [])
|
||||
|
||||
depends_on = [module.auth_jwt_backend]
|
||||
}
|
||||
|
||||
module "auth_kubernetes_backend" {
|
||||
source = "./modules/auth_kubernetes_backend"
|
||||
|
||||
@@ -430,73 +347,6 @@ module "plugin" {
|
||||
plugin_version = each.value.version
|
||||
}
|
||||
|
||||
locals {
|
||||
plugin_mounts = merge(
|
||||
{ for path, mount in var.arrstack_secret_backend : path => mount.plugin },
|
||||
{ for path, mount in var.ghp_secret_backend : path => mount.plugin },
|
||||
{ for path, mount in var.gitea_secret_backend : path => mount.plugin },
|
||||
{ for path, mount in var.gpg_secret_backend : path => mount.plugin },
|
||||
{ for path, mount in var.litellm_secret_backend : path => mount.plugin },
|
||||
{ for path, mount in var.netbox_secret_backend : path => mount.plugin },
|
||||
{ for path, mount in var.rancher_secret_backend : path => mount.plugin },
|
||||
)
|
||||
}
|
||||
|
||||
# Pins each plugin-backed mount to its plugin's catalog version. Unversioned
|
||||
# catalog entries need no pin (OpenBao rejects an empty plugin_version tune).
|
||||
module "mount_plugin_version" {
|
||||
source = "./modules/mount_plugin_version"
|
||||
|
||||
for_each = {
|
||||
for path, plugin in local.plugin_mounts : path => var.plugins[plugin].version
|
||||
if try(var.plugins[plugin].version, null) != null
|
||||
}
|
||||
|
||||
path = each.key
|
||||
plugin_version = each.value
|
||||
|
||||
depends_on = [
|
||||
module.plugin,
|
||||
module.arrstack_secret_backend,
|
||||
module.ghp_secret_backend,
|
||||
module.gitea_secret_backend,
|
||||
module.gpg_secret_backend,
|
||||
module.litellm_secret_backend,
|
||||
module.netbox_secret_backend,
|
||||
module.rancher_secret_backend,
|
||||
]
|
||||
}
|
||||
|
||||
module "arrstack_secret_backend" {
|
||||
source = "./modules/arrstack_secret_backend"
|
||||
|
||||
for_each = var.arrstack_secret_backend
|
||||
|
||||
path = each.key
|
||||
plugin = each.value.plugin
|
||||
description = each.value.description
|
||||
base_url = each.value.base_url
|
||||
ca_cert = each.value.ca_cert
|
||||
request_timeout_seconds = each.value.request_timeout_seconds
|
||||
|
||||
depends_on = [module.plugin]
|
||||
}
|
||||
|
||||
module "arrstack_secret_backend_role" {
|
||||
source = "./modules/arrstack_secret_backend_role"
|
||||
|
||||
for_each = var.arrstack_secret_backend_role
|
||||
|
||||
name = each.value.name
|
||||
backend = each.value.backend
|
||||
apps = each.value.apps
|
||||
methods = each.value.methods
|
||||
ttl = each.value.ttl
|
||||
max_ttl = each.value.max_ttl
|
||||
|
||||
depends_on = [module.arrstack_secret_backend]
|
||||
}
|
||||
|
||||
module "gpg_secret_backend" {
|
||||
source = "./modules/gpg_secret_backend"
|
||||
|
||||
@@ -606,123 +456,6 @@ module "gitea_secret_backend_role" {
|
||||
depends_on = [module.gitea_secret_backend]
|
||||
}
|
||||
|
||||
module "netbox_secret_backend" {
|
||||
source = "./modules/netbox_secret_backend"
|
||||
|
||||
for_each = var.netbox_secret_backend
|
||||
|
||||
path = each.key
|
||||
plugin = each.value.plugin
|
||||
description = each.value.description
|
||||
netbox_url = each.value.netbox_url
|
||||
token_version = each.value.token_version
|
||||
country = var.country
|
||||
region = var.region
|
||||
ca_cert = each.value.ca_cert
|
||||
tls_skip_verify = each.value.tls_skip_verify
|
||||
request_timeout_seconds = each.value.request_timeout_seconds
|
||||
|
||||
depends_on = [module.plugin]
|
||||
}
|
||||
|
||||
# Dedicated engine role that mints an ephemeral, user-admin-capable token for the
|
||||
# pre-existing NetBox superuser named on each backend (user_mgmt_username).
|
||||
# netbox_user_management reads netbox/creds/vault-user-mgmt from it, so it
|
||||
# authenticates with a short-lived Vault-minted token derived from the single
|
||||
# static admin token - never a second static credential, and unaffected by
|
||||
# rotation of the engine's admin seed. Created before user management so the role
|
||||
# exists when it reads creds.
|
||||
module "netbox_user_mgmt_role" {
|
||||
source = "./modules/netbox_secret_backend_role"
|
||||
|
||||
for_each = { for k, v in var.netbox_secret_backend : k => v if v.user_mgmt_username != null }
|
||||
|
||||
backend = each.key
|
||||
name = "vault-user-mgmt"
|
||||
netbox_username = each.value.user_mgmt_username
|
||||
write_enabled = true
|
||||
description = "Ephemeral user-admin token for netbox_user_management (Vault-minted per apply)"
|
||||
ttl = 600
|
||||
max_ttl = 1200
|
||||
|
||||
depends_on = [module.netbox_secret_backend]
|
||||
}
|
||||
|
||||
# Declaratively manage the NetBox service users + object permissions the engine
|
||||
# roles mint tokens for, authenticating with the Vault-minted user-admin token
|
||||
# above (mirrors consul_acl_management). Consumes the SAME role config as
|
||||
# netbox_secret_backend_role: one file per identity, filename-derived username,
|
||||
# inline permissions.
|
||||
module "netbox_user_management" {
|
||||
source = "./modules/netbox_user_management"
|
||||
|
||||
country = var.country
|
||||
region = var.region
|
||||
netbox_backends = var.netbox_secret_backend
|
||||
netbox_roles = var.netbox_secret_backend_role
|
||||
netbox_backend_aliases = var.netbox_backend_aliases
|
||||
|
||||
# This module declares its own netbox provider, so it is a legacy module and
|
||||
# cannot take depends_on. Ordering vs the vault-user-mgmt role is not needed on
|
||||
# steady state (the role pre-exists, so reading its creds succeeds regardless);
|
||||
# on first enablement the role must be created first via the one-time targeted
|
||||
# bootstrap documented in config/netbox_secret_backend/netbox.yaml.
|
||||
}
|
||||
|
||||
module "netbox_secret_backend_role" {
|
||||
source = "./modules/netbox_secret_backend_role"
|
||||
|
||||
for_each = var.netbox_secret_backend_role
|
||||
|
||||
backend = each.value.backend
|
||||
name = each.value.name
|
||||
netbox_username = each.value.netbox_username
|
||||
netbox_user_id = each.value.netbox_user_id
|
||||
write_enabled = each.value.write_enabled
|
||||
description = each.value.description
|
||||
ttl = each.value.ttl
|
||||
max_ttl = each.value.max_ttl
|
||||
|
||||
depends_on = [module.netbox_secret_backend, module.netbox_user_management]
|
||||
}
|
||||
|
||||
module "ghp_secret_backend" {
|
||||
source = "./modules/ghp_secret_backend"
|
||||
|
||||
for_each = var.ghp_secret_backend
|
||||
|
||||
path = each.key
|
||||
plugin = each.value.plugin
|
||||
description = each.value.description
|
||||
base_url = each.value.base_url
|
||||
country = var.country
|
||||
region = var.region
|
||||
ca_cert = each.value.ca_cert
|
||||
tls_skip_verify = each.value.tls_skip_verify
|
||||
request_timeout_seconds = each.value.request_timeout_seconds
|
||||
|
||||
depends_on = [module.plugin]
|
||||
}
|
||||
|
||||
module "ghp_secret_backend_role" {
|
||||
source = "./modules/ghp_secret_backend_role"
|
||||
|
||||
for_each = var.ghp_secret_backend_role
|
||||
|
||||
backend = each.value.backend
|
||||
name = each.value.name
|
||||
token_type = each.value.token_type
|
||||
installation_id = each.value.installation_id
|
||||
app_record_id = each.value.app_record_id
|
||||
repositories = each.value.repositories
|
||||
scopes = each.value.scopes
|
||||
session_prefix = each.value.session_prefix
|
||||
ttl = each.value.ttl
|
||||
max_ttl = each.value.max_ttl
|
||||
|
||||
depends_on = [module.ghp_secret_backend]
|
||||
}
|
||||
|
||||
module "vault_policy" {
|
||||
source = "./modules/vault_policy"
|
||||
|
||||
@@ -754,105 +487,3 @@ module "pki_mount_only" {
|
||||
delta_rebuild_interval = each.value.delta_rebuild_interval
|
||||
}
|
||||
|
||||
|
||||
module "identity_oidc" {
|
||||
source = "./modules/identity_oidc"
|
||||
|
||||
for_each = var.identity_oidc
|
||||
|
||||
issuer = each.value.issuer
|
||||
}
|
||||
|
||||
module "identity_oidc_key" {
|
||||
source = "./modules/identity_oidc_key"
|
||||
|
||||
for_each = var.identity_oidc_key
|
||||
|
||||
name = each.key
|
||||
algorithm = each.value.algorithm
|
||||
rotation_period = each.value.rotation_period
|
||||
verification_ttl = each.value.verification_ttl
|
||||
|
||||
depends_on = [module.identity_oidc]
|
||||
}
|
||||
|
||||
module "identity_oidc_role" {
|
||||
source = "./modules/identity_oidc_role"
|
||||
|
||||
for_each = var.identity_oidc_role
|
||||
|
||||
name = each.value.name
|
||||
key = each.value.key
|
||||
template = each.value.template
|
||||
ttl = each.value.ttl
|
||||
client_id = each.value.client_id
|
||||
|
||||
depends_on = [module.identity_oidc, module.identity_oidc_key]
|
||||
}
|
||||
|
||||
module "identity_oidc_assignment" {
|
||||
source = "./modules/identity_oidc_assignment"
|
||||
|
||||
for_each = var.identity_oidc_assignment
|
||||
|
||||
name = each.key
|
||||
entity_ids = each.value.entity_ids
|
||||
group_ids = each.value.group_ids
|
||||
}
|
||||
|
||||
module "identity_oidc_client" {
|
||||
source = "./modules/identity_oidc_client"
|
||||
|
||||
for_each = var.identity_oidc_client
|
||||
|
||||
name = each.value.name
|
||||
key = each.value.key
|
||||
redirect_uris = each.value.redirect_uris
|
||||
assignments = each.value.assignments
|
||||
client_type = each.value.client_type
|
||||
id_token_ttl = each.value.id_token_ttl
|
||||
access_token_ttl = each.value.access_token_ttl
|
||||
|
||||
depends_on = [module.identity_oidc, module.identity_oidc_key, module.identity_oidc_assignment]
|
||||
}
|
||||
|
||||
module "identity_oidc_key_allowed_client_id" {
|
||||
source = "./modules/identity_oidc_key_allowed_client_id"
|
||||
|
||||
for_each = merge(
|
||||
{ for k, v in var.identity_oidc_role : "role/${k}" => { key = v.key, client_id = module.identity_oidc_role[k].client_id } },
|
||||
{ for k, v in var.identity_oidc_client : "client/${k}" => { key = v.key, client_id = module.identity_oidc_client[k].client_id } },
|
||||
)
|
||||
|
||||
key_name = each.value.key
|
||||
allowed_client_id = each.value.client_id
|
||||
|
||||
depends_on = [module.identity_oidc_key]
|
||||
}
|
||||
|
||||
module "identity_oidc_scope" {
|
||||
source = "./modules/identity_oidc_scope"
|
||||
|
||||
for_each = var.identity_oidc_scope
|
||||
|
||||
name = each.key
|
||||
description = each.value.description
|
||||
template = each.value.template
|
||||
}
|
||||
|
||||
module "identity_oidc_provider" {
|
||||
source = "./modules/identity_oidc_provider"
|
||||
|
||||
for_each = var.identity_oidc_provider
|
||||
|
||||
name = each.key
|
||||
issuer_host = each.value.issuer_host
|
||||
https_enabled = each.value.https_enabled
|
||||
scopes_supported = each.value.scopes_supported
|
||||
allowed_client_ids = [
|
||||
for c in each.value.allowed_clients :
|
||||
c == "*" ? c : one([for k, v in var.identity_oidc_client : module.identity_oidc_client[k].client_id if v.name == c])
|
||||
]
|
||||
|
||||
depends_on = [module.identity_oidc, module.identity_oidc_scope, module.identity_oidc_client]
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Mounts the arrstack dynamic secrets engine and writes its config via the
|
||||
# vault-secrets-arrstack provider. The plugin is registered in the catalog
|
||||
# separately (config/plugins/vault-plugin-secrets-arrstack.yaml). The arrproxy
|
||||
# admin token is sensitive and read from KV, not stored in git:
|
||||
# kv/kubernetes/namespace/arrstack/default/arrproxy-admin-token -> key "token"
|
||||
# (seeded by argocd-apps #384).
|
||||
data "vault_kv_secret_v2" "admin_token" {
|
||||
mount = "kv"
|
||||
name = var.admin_token_kv_name
|
||||
}
|
||||
|
||||
resource "arrstack_secret_backend" "this" {
|
||||
path = var.path
|
||||
plugin = var.plugin
|
||||
description = var.description
|
||||
base_url = var.base_url
|
||||
admin_token = data.vault_kv_secret_v2.admin_token.data[var.admin_token_kv_key]
|
||||
ca_cert = var.ca_cert
|
||||
request_timeout_seconds = var.request_timeout_seconds
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
arrstack = {
|
||||
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack"
|
||||
version = "0.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
variable "path" {
|
||||
description = "Mount path of the arrstack secrets engine (e.g. \"arrstack\")"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "plugin" {
|
||||
description = "Registered plugin name/type to mount (the catalog name = mount type)"
|
||||
type = string
|
||||
default = "vault-plugin-secrets-arrstack"
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Human-friendly description of the mount"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "base_url" {
|
||||
description = "Base URL of the arrproxy front door (e.g. https://arrstack.unkin.net)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "admin_token_kv_name" {
|
||||
description = "kv-v2 secret name (relative to the \"kv\" mount) holding the seeded arrproxy admin token"
|
||||
type = string
|
||||
default = "kubernetes/namespace/arrstack/default/arrproxy-admin-token"
|
||||
}
|
||||
|
||||
variable "admin_token_kv_key" {
|
||||
description = "Key within the KV secret that holds the arrproxy admin token"
|
||||
type = string
|
||||
default = "token"
|
||||
}
|
||||
|
||||
variable "ca_cert" {
|
||||
description = "PEM CA certificate that signed the arrproxy server's TLS cert (optional; omit to use the system trust store)"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "request_timeout_seconds" {
|
||||
description = "HTTP timeout in seconds for calls from the plugin to arrproxy"
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
resource "arrstack_secret_backend_role" "this" {
|
||||
backend = var.backend
|
||||
name = var.name
|
||||
apps = var.apps
|
||||
methods = var.methods
|
||||
ttl = var.ttl
|
||||
max_ttl = var.max_ttl
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
arrstack = {
|
||||
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-arrstack"
|
||||
version = "0.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
variable "name" {
|
||||
description = "Name of the role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "backend" {
|
||||
description = "Mount path of the arrstack secrets engine this role belongs to"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "apps" {
|
||||
description = "arr apps a generated key may access (subset of sonarr, radarr, prowlarr)"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "methods" {
|
||||
description = "HTTP methods a generated key is limited to (subset of GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS). Null leaves the role unrestricted"
|
||||
type = set(string)
|
||||
# null, not [], so a role yaml that omits methods matches the provider's null read-back and shows no drift.
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "ttl" {
|
||||
description = "Default lease TTL in seconds for keys generated from this role"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "max_ttl" {
|
||||
description = "Maximum lease TTL in seconds for keys generated from this role"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
resource "vault_jwt_auth_backend" "jwt" {
|
||||
path = var.path
|
||||
type = "jwt"
|
||||
description = var.description
|
||||
oidc_discovery_url = var.oidc_discovery_url
|
||||
bound_issuer = var.bound_issuer
|
||||
default_role = var.default_role
|
||||
|
||||
# provider 5.6.0 panics on an all-null tune block
|
||||
dynamic "tune" {
|
||||
for_each = anytrue([for v in [var.default_lease_ttl, var.max_lease_ttl, var.listing_visibility] : v != null]) ? [1] : []
|
||||
content {
|
||||
default_lease_ttl = var.default_lease_ttl
|
||||
max_lease_ttl = var.max_lease_ttl
|
||||
listing_visibility = var.listing_visibility
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
output "accessor" {
|
||||
value = vault_jwt_auth_backend.jwt.accessor
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
mock_provider "vault" {}
|
||||
|
||||
variables {
|
||||
path = "jwt/github/iuclabs"
|
||||
oidc_discovery_url = "https://token.actions.githubusercontent.com"
|
||||
bound_issuer = "https://token.actions.githubusercontent.com"
|
||||
}
|
||||
|
||||
run "omit_tune_when_unset" {
|
||||
command = plan
|
||||
|
||||
assert {
|
||||
condition = length(vault_jwt_auth_backend.jwt.tune) == 0
|
||||
error_message = "no tune block may render when every tune value is null"
|
||||
}
|
||||
}
|
||||
|
||||
run "render_tune_when_set" {
|
||||
command = plan
|
||||
variables { max_lease_ttl = "1h" }
|
||||
|
||||
assert {
|
||||
condition = length(vault_jwt_auth_backend.jwt.tune) == 1 && vault_jwt_auth_backend.jwt.tune[0].max_lease_ttl == "1h"
|
||||
error_message = "tune block must render with the configured values"
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
variable "path" {
|
||||
description = "Mount path of the JWT auth backend"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Human-readable description of the auth mount"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "oidc_discovery_url" {
|
||||
description = "OIDC discovery URL used to fetch the issuer's signing keys"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "bound_issuer" {
|
||||
description = "Value the iss claim of every presented token must match"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "default_role" {
|
||||
description = "Role used when a login request names none"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "listing_visibility" {
|
||||
description = "Specifies whether to show this mount in the UI-specific listing endpoint. Valid values are 'unauth' or 'hidden'"
|
||||
type = string
|
||||
default = null
|
||||
validation {
|
||||
condition = var.listing_visibility == null || contains(["unauth", "hidden"], var.listing_visibility)
|
||||
error_message = "listing_visibility must be either 'unauth' or 'hidden'."
|
||||
}
|
||||
}
|
||||
|
||||
variable "default_lease_ttl" {
|
||||
description = "Specifies the default time-to-live. If set, this overrides the global default. Must be a valid duration string"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "max_lease_ttl" {
|
||||
description = "Specifies the maximum time-to-live. If set, this overrides the global default. Must be a valid duration string"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
locals {
|
||||
alias = "identity.entity.aliases.${var.backend_accessor}.metadata"
|
||||
kv_prefixes = [
|
||||
"github/{{${local.alias}.repository}}/event/{{${local.alias}.event_name}}",
|
||||
"github/{{${local.alias}.repository}}/ref/{{${local.alias}.ref}}/.secrets",
|
||||
]
|
||||
}
|
||||
|
||||
resource "vault_policy" "kv" {
|
||||
name = "${var.backend}/${var.role_name}/kv-read"
|
||||
policy = join("\n", flatten([for p in local.kv_prefixes : [
|
||||
"path \"kv/data/${p}/*\" {\n capabilities = [\"read\"]\n}",
|
||||
"path \"kv/metadata/${p}/*\" {\n capabilities = [\"read\", \"list\"]\n}",
|
||||
]]))
|
||||
}
|
||||
|
||||
resource "vault_jwt_auth_backend_role" "role" {
|
||||
backend = var.backend
|
||||
role_name = var.role_name
|
||||
role_type = "jwt"
|
||||
user_claim = var.user_claim
|
||||
bound_audiences = var.bound_audiences
|
||||
bound_claims = var.bound_claims
|
||||
bound_claims_type = "glob"
|
||||
claim_mappings = var.claim_mappings
|
||||
token_ttl = var.token_ttl
|
||||
token_max_ttl = var.token_max_ttl
|
||||
token_policies = concat(var.token_policies, [vault_policy.kv.name])
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
mock_provider "vault" {}
|
||||
|
||||
variables {
|
||||
backend = "jwt/github/iuclabs"
|
||||
role_name = "default"
|
||||
backend_accessor = "auth_jwt_test"
|
||||
bound_audiences = ["https://vault.service.consul:8200"]
|
||||
}
|
||||
|
||||
run "accept_org_role" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
|
||||
assert {
|
||||
condition = vault_policy.kv.policy == chomp(<<-EOT
|
||||
path "kv/data/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/event/{{identity.entity.aliases.auth_jwt_test.metadata.event_name}}/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "kv/metadata/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/event/{{identity.entity.aliases.auth_jwt_test.metadata.event_name}}/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
path "kv/data/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/ref/{{identity.entity.aliases.auth_jwt_test.metadata.ref}}/.secrets/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "kv/metadata/github/{{identity.entity.aliases.auth_jwt_test.metadata.repository}}/ref/{{identity.entity.aliases.auth_jwt_test.metadata.ref}}/.secrets/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
EOT
|
||||
)
|
||||
error_message = "rendered KV policy paths changed"
|
||||
}
|
||||
assert {
|
||||
condition = contains(vault_jwt_auth_backend_role.role.token_policies, "jwt/github/iuclabs/default/kv-read")
|
||||
error_message = "role must carry the KV read policy"
|
||||
}
|
||||
assert {
|
||||
condition = vault_jwt_auth_backend_role.role.bound_claims_type == "glob" && vault_jwt_auth_backend_role.role.user_claim == "jti"
|
||||
error_message = "role must glob-match claims and alias on jti"
|
||||
}
|
||||
}
|
||||
|
||||
run "ref_paths_cannot_overlap_child_refs" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push", ref = "refs/heads/*" } }
|
||||
|
||||
assert {
|
||||
condition = length(regexall("metadata\\.ref}}/[^.]", vault_policy.kv.policy)) == 0
|
||||
error_message = "ref paths must end in a dot-prefixed segment git refnames cannot contain"
|
||||
}
|
||||
}
|
||||
|
||||
run "accept_exact_refs" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/main,refs/tags/v1.2.3,refs/pull/7/merge" } }
|
||||
|
||||
assert {
|
||||
condition = vault_jwt_auth_backend_role.role.bound_claims.ref == "refs/heads/main,refs/tags/v1.2.3,refs/pull/7/merge"
|
||||
error_message = "exact refs must pass through to bound_claims unchanged"
|
||||
}
|
||||
}
|
||||
|
||||
run "accept_with_repository" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge", repository = "IUCLabs/app" } }
|
||||
}
|
||||
|
||||
run "reject_missing_repository_owner" {
|
||||
command = plan
|
||||
variables { bound_claims = { event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_missing_ref" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_missing_event_name" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_repository_owner_wildcard" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "*", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_repository_owner_glob" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUC*", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_event_pull_request_target" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,pull_request_target", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_event_workflow_run" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "workflow_run", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_event_issue_comment" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "issue_comment", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_event_workflow_dispatch" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "workflow_dispatch", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_ref_star" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/*" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_ref_bare_star" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "*" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_ref_pull_star" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/pull/*" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_ref_pull_head" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/pull/*/head" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_ref_heads_prefix_glob" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/rel*" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_ref_empty" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_repository_glob" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge", repository = "IUCLabs/*" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
|
||||
run "reject_environment_empty" {
|
||||
command = plan
|
||||
variables { bound_claims = { repository_owner = "IUCLabs", event_name = "push,release,schedule,pull_request", ref = "refs/heads/*,refs/tags/*,refs/pull/*/merge", environment = "" } }
|
||||
expect_failures = [var.bound_claims]
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
variable "backend" {
|
||||
description = "The unique path of the JWT auth backend to configure"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "role_name" {
|
||||
description = "The name of the role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "backend_accessor" {
|
||||
description = "Accessor of the JWT auth mount, used to template the KV read policy"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "user_claim" {
|
||||
description = "Claim used as the entity alias name"
|
||||
type = string
|
||||
default = "jti"
|
||||
}
|
||||
|
||||
variable "claim_mappings" {
|
||||
description = "Token claims copied into entity alias metadata"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "bound_audiences" {
|
||||
description = "List of audiences (aud claim) accepted in the token"
|
||||
type = list(string)
|
||||
validation {
|
||||
condition = length(var.bound_audiences) > 0
|
||||
error_message = "bound_audiences must not be empty."
|
||||
}
|
||||
}
|
||||
|
||||
variable "bound_claims" {
|
||||
description = "Claims the token must carry, glob-matched; only ref may use the allowed glob patterns, comma-separated values form a list"
|
||||
type = map(string)
|
||||
validation {
|
||||
condition = can(regex("^[A-Za-z0-9-]+$", lookup(var.bound_claims, "repository_owner", "")))
|
||||
error_message = "bound_claims.repository_owner must be one exact org name."
|
||||
}
|
||||
validation {
|
||||
condition = alltrue([for v in split(",", lookup(var.bound_claims, "event_name", "")) : contains(["push", "release", "schedule", "pull_request"], trimspace(v))])
|
||||
error_message = "bound_claims.event_name must only list push, release, schedule or pull_request."
|
||||
}
|
||||
validation {
|
||||
condition = alltrue([for v in split(",", lookup(var.bound_claims, "ref", "")) :
|
||||
contains(["refs/heads/*", "refs/tags/*", "refs/pull/*/merge"], trimspace(v)) ||
|
||||
can(regex("^(refs/(heads|tags)/[A-Za-z0-9._/-]+|refs/pull/[0-9]+/merge)$", trimspace(v)))
|
||||
])
|
||||
error_message = "bound_claims.ref must be refs/heads/*, refs/tags/*, refs/pull/*/merge or exact refs of those shapes."
|
||||
}
|
||||
validation {
|
||||
condition = alltrue(flatten([for k, v in var.bound_claims : [
|
||||
for e in split(",", v) : trimspace(e) != "" && !strcontains(e, "*")
|
||||
] if k != "ref"]))
|
||||
error_message = "bound_claims values other than ref must be exact and non-empty."
|
||||
}
|
||||
}
|
||||
|
||||
variable "token_ttl" {
|
||||
description = "The TTL period of tokens issued using this role, in seconds"
|
||||
type = number
|
||||
default = 900
|
||||
}
|
||||
|
||||
variable "token_max_ttl" {
|
||||
description = "The maximum lifetime for generated tokens in number of seconds"
|
||||
type = number
|
||||
default = 3600
|
||||
}
|
||||
|
||||
variable "token_policies" {
|
||||
description = "List of policies to assign to the role (passed from policy_auth_map)"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
# Expected keys in KV secret: client_id, client_secret (generated and written by
|
||||
# terraform-authentik's oauth2 provider module, never seeded by hand).
|
||||
data "vault_kv_secret_v2" "oidc_client" {
|
||||
mount = var.client_secret_mount
|
||||
name = var.client_secret_path
|
||||
}
|
||||
|
||||
resource "vault_jwt_auth_backend" "oidc" {
|
||||
path = var.path
|
||||
type = "oidc"
|
||||
description = var.description
|
||||
oidc_discovery_url = var.oidc_discovery_url
|
||||
oidc_client_id = data.vault_kv_secret_v2.oidc_client.data["client_id"]
|
||||
oidc_client_secret = data.vault_kv_secret_v2.oidc_client.data["client_secret"]
|
||||
default_role = var.default_role
|
||||
|
||||
tune {
|
||||
default_lease_ttl = var.default_lease_ttl
|
||||
max_lease_ttl = var.max_lease_ttl
|
||||
listing_visibility = var.listing_visibility
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
output "accessor" {
|
||||
description = "Accessor of the OIDC auth mount, used to bind identity group aliases"
|
||||
value = vault_jwt_auth_backend.oidc.accessor
|
||||
}
|
||||
|
||||
output "path" {
|
||||
description = "Mount path of the OIDC auth backend"
|
||||
value = vault_jwt_auth_backend.oidc.path
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
variable "path" {
|
||||
description = "Mount path of the OIDC auth backend"
|
||||
type = string
|
||||
default = "oidc"
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Human-readable description of the auth mount"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "oidc_discovery_url" {
|
||||
description = "OIDC issuer discovery URL of the identity provider"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "client_secret_mount" {
|
||||
description = "KV-v2 mount holding the OIDC client credentials"
|
||||
type = string
|
||||
default = "kv"
|
||||
}
|
||||
|
||||
variable "client_secret_path" {
|
||||
description = "Path within the KV-v2 mount holding client_id and client_secret"
|
||||
type = string
|
||||
default = "service/authentik/oidc-vault"
|
||||
}
|
||||
|
||||
variable "default_role" {
|
||||
description = "Role used when none is supplied at login (the UI/CLI default)"
|
||||
type = string
|
||||
default = "default"
|
||||
}
|
||||
|
||||
variable "listing_visibility" {
|
||||
description = "Specifies whether to show this mount in the UI-specific listing endpoint. Valid values are 'unauth' or 'hidden'"
|
||||
type = string
|
||||
default = null
|
||||
validation {
|
||||
condition = var.listing_visibility == null || contains(["unauth", "hidden"], var.listing_visibility)
|
||||
error_message = "listing_visibility must be either 'unauth' or 'hidden'."
|
||||
}
|
||||
}
|
||||
|
||||
variable "default_lease_ttl" {
|
||||
description = "Specifies the default time-to-live. If set, this overrides the global default. Must be a valid duration string"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "max_lease_ttl" {
|
||||
description = "Specifies the maximum time-to-live. If set, this overrides the global default. Must be a valid duration string"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# External identity group: membership is asserted by the IdP through the role's
|
||||
# groups_claim, matched by the alias name below.
|
||||
resource "vault_identity_group" "group" {
|
||||
name = var.groupname
|
||||
type = "external"
|
||||
policies = var.policies
|
||||
}
|
||||
|
||||
resource "vault_identity_group_alias" "alias" {
|
||||
name = var.groupname
|
||||
mount_accessor = var.mount_accessor
|
||||
canonical_id = vault_identity_group.group.id
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
variable "groupname" {
|
||||
description = "Name of the IdP group, as it appears in the groups claim"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "policies" {
|
||||
description = "List of policies to assign to the identity group"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "mount_accessor" {
|
||||
description = "Accessor of the OIDC auth mount the alias is bound to"
|
||||
type = string
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
resource "vault_jwt_auth_backend_role" "role" {
|
||||
backend = var.backend
|
||||
role_name = var.role_name
|
||||
role_type = "oidc"
|
||||
user_claim = var.user_claim
|
||||
groups_claim = var.groups_claim
|
||||
oidc_scopes = var.oidc_scopes
|
||||
bound_audiences = var.bound_audiences
|
||||
allowed_redirect_uris = var.allowed_redirect_uris
|
||||
token_ttl = var.token_ttl
|
||||
token_max_ttl = var.token_max_ttl
|
||||
token_policies = var.token_policies
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
variable "backend" {
|
||||
description = "The unique path of the OIDC auth backend to configure"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "role_name" {
|
||||
description = "The name of the role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "user_claim" {
|
||||
description = "Claim used as the entity alias name (the Vault identity of the human)"
|
||||
type = string
|
||||
default = "email"
|
||||
}
|
||||
|
||||
variable "groups_claim" {
|
||||
description = "Claim holding the caller's group memberships, matched against identity group aliases"
|
||||
type = string
|
||||
default = "ak_groups"
|
||||
}
|
||||
|
||||
variable "oidc_scopes" {
|
||||
description = "Scopes requested from the identity provider during the authorization request"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "bound_audiences" {
|
||||
description = "List of audiences (aud claim) accepted in the ID token"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "allowed_redirect_uris" {
|
||||
description = "Redirect URIs accepted for this role. Must match the provider's registered URIs exactly"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "token_ttl" {
|
||||
description = "The TTL period of tokens issued using this role, in seconds"
|
||||
type = number
|
||||
default = 3600
|
||||
}
|
||||
|
||||
variable "token_max_ttl" {
|
||||
description = "The maximum lifetime for generated tokens in number of seconds. Its current value will be referenced at renewal time."
|
||||
type = number
|
||||
default = 28800
|
||||
}
|
||||
|
||||
variable "token_policies" {
|
||||
description = "List of policies to assign to the role (passed from policy_auth_map). Human authorization normally comes from external identity groups instead"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# Mounts the ghp secrets engine and writes its connection config via the
|
||||
# vault-secrets-ghp provider. The plugin is registered ("imported") in the
|
||||
# catalog separately (config/plugins/vault-plugin-secrets-ghp.yaml). The seeded
|
||||
# ghp service token is sensitive and read from KV, not stored in git:
|
||||
# kv/service/vault/<country>/<region>/secret_backend/<path>/config
|
||||
# Expected key: admin_token (a ghpsvc_... service token that ghp accepts via its
|
||||
# GHP_AUTH_SERVICE_TOKENS list). ghp has no rotate endpoint, so this static
|
||||
# shared secret is the single credential the engine authenticates with.
|
||||
data "vault_kv_secret_v2" "config" {
|
||||
mount = "kv"
|
||||
name = "service/vault/${var.country}/${var.region}/secret_backend/${var.path}/config"
|
||||
}
|
||||
|
||||
resource "ghp_secret_backend" "this" {
|
||||
path = var.path
|
||||
plugin = var.plugin
|
||||
description = var.description
|
||||
base_url = var.base_url
|
||||
admin_token = data.vault_kv_secret_v2.config.data["admin_token"]
|
||||
ca_cert = var.ca_cert
|
||||
tls_skip_verify = var.tls_skip_verify
|
||||
request_timeout_seconds = var.request_timeout_seconds
|
||||
|
||||
lifecycle {
|
||||
# The KV seed is a bootstrap credential consumed only when the engine config
|
||||
# is first created. ghp has no rotate endpoint, so re-reading a (possibly
|
||||
# stale) KV value must never re-push it into the live mount. Ignoring the
|
||||
# token makes this module create-only for it (mirrors gitea/netbox config).
|
||||
ignore_changes = [admin_token]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
ghp = {
|
||||
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-ghp"
|
||||
version = "0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
variable "path" {
|
||||
description = "Mount path of the ghp secrets engine (e.g. \"ghp\")"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "plugin" {
|
||||
description = "Registered plugin name to mount (the catalog name = mount type)"
|
||||
type = string
|
||||
default = "vault-plugin-secrets-ghp"
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Human-friendly description of the mount"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "base_url" {
|
||||
description = "Base URL of the ghp server (e.g. https://ghp.unkin.net)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "country" {
|
||||
description = "Country segment of the KV path holding the seeded admin token"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "Region segment of the KV path holding the seeded admin token"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "ca_cert" {
|
||||
description = "PEM CA certificate that signed the ghp server's TLS cert (optional; omit to use the system trust store)"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "tls_skip_verify" {
|
||||
description = "Skip TLS verification of the ghp server (not recommended)"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "request_timeout_seconds" {
|
||||
description = "HTTP timeout in seconds for calls from the plugin to ghp"
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# A role that mints short-lived, scoped ghp tokens. Reading ghp/creds/<name>
|
||||
# produces a lease-bound token that is revoked from ghp when the lease is
|
||||
# revoked or reaches max_ttl.
|
||||
resource "ghp_secret_role" "this" {
|
||||
backend = var.backend
|
||||
name = var.name
|
||||
token_type = var.token_type
|
||||
installation_id = var.installation_id
|
||||
app_record_id = var.app_record_id
|
||||
repositories = var.repositories
|
||||
scopes = var.scopes
|
||||
session_prefix = var.session_prefix
|
||||
ttl = var.ttl
|
||||
max_ttl = var.max_ttl
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
ghp = {
|
||||
source = "artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/vault-secrets-ghp"
|
||||
version = "0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
variable "backend" {
|
||||
description = "Mount path of the ghp secrets engine this role belongs to"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
description = "Role name (read ghp/creds/<name> to mint a token)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "token_type" {
|
||||
description = "ghp token type to mint: \"agent\" (default) or \"proxy\""
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "installation_id" {
|
||||
description = "ghp App installation id the minted agent token is bound to (required when token_type is \"agent\")"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "app_record_id" {
|
||||
description = "Optional ghp App record id (UUID) to pin agent tokens to; empty selects ghp's default app"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "repositories" {
|
||||
description = "Optional repositories the minted token is restricted to; empty is open-scoped (all repositories)"
|
||||
type = list(string)
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "scopes" {
|
||||
description = "Optional ghp permission:level scopes (e.g. [\"contents:read\"]); empty is open-scoped"
|
||||
type = list(string)
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "session_prefix" {
|
||||
description = "Prefix for the ghp session id of each minted token (default \"vault\")"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "ttl" {
|
||||
description = "Default lease TTL in seconds for minted tokens"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "max_ttl" {
|
||||
description = "Maximum lease TTL in seconds for minted tokens"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
resource "vault_identity_oidc" "this" {
|
||||
issuer = var.issuer
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
variable "issuer" {
|
||||
description = "Issuer URL for identity tokens (scheme://host:port, no path); null uses Vault's api_addr"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
resource "vault_identity_oidc_assignment" "this" {
|
||||
name = var.name
|
||||
entity_ids = var.entity_ids
|
||||
group_ids = var.group_ids
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
variable "name" {
|
||||
description = "Assignment name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "entity_ids" {
|
||||
description = "Identity entity ids in this assignment"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "group_ids" {
|
||||
description = "Identity group ids in this assignment"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
resource "vault_identity_oidc_client" "this" {
|
||||
name = var.name
|
||||
key = var.key
|
||||
redirect_uris = var.redirect_uris
|
||||
assignments = var.assignments
|
||||
client_type = var.client_type
|
||||
id_token_ttl = var.id_token_ttl
|
||||
access_token_ttl = var.access_token_ttl
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
output "client_id" {
|
||||
value = vault_identity_oidc_client.this.client_id
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
variable "name" {
|
||||
description = "OIDC client name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "key" {
|
||||
description = "Named key that signs this client's ID tokens"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "redirect_uris" {
|
||||
description = "Allowed redirect URIs"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "assignments" {
|
||||
description = "Assignment names allowed to authenticate with this client"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "client_type" {
|
||||
description = "confidential or public"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "id_token_ttl" {
|
||||
description = "ID token TTL in seconds"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "access_token_ttl" {
|
||||
description = "Access token TTL in seconds"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
resource "vault_identity_oidc_key" "this" {
|
||||
name = var.name
|
||||
algorithm = var.algorithm
|
||||
rotation_period = var.rotation_period
|
||||
verification_ttl = var.verification_ttl
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
variable "name" {
|
||||
description = "Name of the named key"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "algorithm" {
|
||||
description = "Signing algorithm (RS256, RS384, RS512, ES256, ES384, ES512, EdDSA)"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "rotation_period" {
|
||||
description = "Seconds between key rotations"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "verification_ttl" {
|
||||
description = "Seconds a rotated public key stays published for verification"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
resource "vault_identity_oidc_key_allowed_client_id" "this" {
|
||||
key_name = var.key_name
|
||||
allowed_client_id = var.allowed_client_id
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
variable "key_name" {
|
||||
description = "Named key the client id may sign with"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "allowed_client_id" {
|
||||
description = "Role or client id allowed to use the key"
|
||||
type = string
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
resource "vault_identity_oidc_provider" "this" {
|
||||
name = var.name
|
||||
issuer_host = var.issuer_host
|
||||
https_enabled = var.https_enabled
|
||||
scopes_supported = var.scopes_supported
|
||||
allowed_client_ids = var.allowed_client_ids
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user