docker-30: gitea CI/CD integration with Vault and Kanidm, misc updates
vault: - Add JWT auth backend bound to Gitea (jwks_url from gitea OIDC keys) - Add gitea-ci-read policy scoped to secret/data/gitea/* - Add JWT role gitea-ci (sub claim, bound to Gitea audience, 10m TTL) - Add AppRole gitea-ci as alternative auth method for the same policy - Add gitea-access-into-vault.md documenting the setup end-to-end - Update terraform.tfstate (OpenTofu 1.11.5, new gitea-ci resources) kanidm: - Add run.sh with docker run command (pinned to v1.9.1) - Add gitea-action-kubernetes-access.md documenting how to set up a Kanidm service account and OAuth2 client for Gitea CI k8s access - readme: add upgrade procedure, recover-account command, and service account + API token setup for gitea-ci-token maru-hleda-byt: - Add --restart=always to docker run command fuj-management: - Add run.sh (new service config) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
10
docker-30/fuj-management/run.sh
Normal file
10
docker-30/fuj-management/run.sh
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker rm -f fuj-management
|
||||||
|
|
||||||
|
# gitea registry login with kacerr / token
|
||||||
|
docker run -d --name fuj-management \
|
||||||
|
--restart=always \
|
||||||
|
-p 8081:5001 \
|
||||||
|
-v /srv/fuj-management/data:/app/data \
|
||||||
|
gitea.home.hrajfrisbee.cz/kacerr/fuj-management:latest
|
||||||
84
docker-30/kanidm/gitea-action-kubernetes-access.md
Normal file
84
docker-30/kanidm/gitea-action-kubernetes-access.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
## 1. Create Kanidm service account + OAuth2 client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a service account for CI
|
||||||
|
kanidm service-account create gitea_ci "Gitea CI Deploy" idm_admins --name idm_admin
|
||||||
|
|
||||||
|
# Create a group and add the service account
|
||||||
|
kanidm group create k8s_deployers
|
||||||
|
kanidm group add-members k8s_deployers gitea_ci
|
||||||
|
|
||||||
|
# Create the OAuth2 client (or reuse existing k8s one)
|
||||||
|
# If you already have a k8s OIDC client, just add scope maps:
|
||||||
|
kanidm system oauth2 update-scope-map k8s k8s_deployers openid groups
|
||||||
|
|
||||||
|
# Generate an API token for the service account
|
||||||
|
kanidm service-account api-token generate --name idm_admin gitea_ci "gitea-ci-token"
|
||||||
|
# ⚠️ Save the output token — this is the subject_token for exchange
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. RBAC in Kubernetes
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: gitea-ci-deploy
|
||||||
|
subjects:
|
||||||
|
- kind: User
|
||||||
|
name: "gitea_ci@idm.home.hrajfrisbee.cz" # matches preferred_username claim
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
roleRef:
|
||||||
|
kind: ClusterRole
|
||||||
|
name: edit # scope down as needed
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Token exchange + kubeconfig setup (test in bash)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vault-login # prepared alias
|
||||||
|
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# set -euo pipefail
|
||||||
|
|
||||||
|
KANIDM_URL="https://idm.home.hrajfrisbee.cz"
|
||||||
|
OAUTH2_CLIENT_ID="k8s" # your k8s OIDC client name in Kanidm
|
||||||
|
API_TOKEN=$(vault kv get -format=json -mount="secret" "k8s_home/gitea/gitea-ci-token" |jq -r .data.data.token)
|
||||||
|
K8S_API="https://192.168.0.31:6443"
|
||||||
|
|
||||||
|
# Exchange the API token for an OIDC token via RFC 8693
|
||||||
|
RESPONSE=$(curl -sf -X POST "${KANIDM_URL}/oauth2/token" \
|
||||||
|
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
|
||||||
|
-d "client_id=${OAUTH2_CLIENT_ID}" \
|
||||||
|
-d "subject_token=${API_TOKEN}" \
|
||||||
|
-d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
|
||||||
|
-d "audience=${OAUTH2_CLIENT_ID}" \
|
||||||
|
-d "scope=openid groups")
|
||||||
|
|
||||||
|
ID_TOKEN=$(echo "$RESPONSE" | jq -r '.id_token')
|
||||||
|
|
||||||
|
# Inspect claims (sanity check)
|
||||||
|
echo "$ID_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
|
||||||
|
|
||||||
|
# Build kubeconfig
|
||||||
|
export KUBECONFIG=$(mktemp)
|
||||||
|
kubectl config set-cluster mycluster \
|
||||||
|
--server="${K8S_API}" \
|
||||||
|
--certificate-authority=/path/to/ca.crt
|
||||||
|
|
||||||
|
kubectl config set-credentials gitea-ci \
|
||||||
|
--token="${ID_TOKEN}"
|
||||||
|
|
||||||
|
kubectl config set-context gitea-ci \
|
||||||
|
--cluster=mycluster \
|
||||||
|
--user=gitea-ci
|
||||||
|
|
||||||
|
kubectl config use-context gitea-ci
|
||||||
|
|
||||||
|
# Test
|
||||||
|
kubectl auth whoami
|
||||||
|
kubectl get ns
|
||||||
|
|
||||||
|
```
|
||||||
@@ -1,3 +1,27 @@
|
|||||||
|
## Upgrade
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it kanidmd kanidmd domain upgrade-check
|
||||||
|
# make sure backup exists: /srv/docker/kanidm/data/kanidm/backups
|
||||||
|
|
||||||
|
# change container image in: /srv/docker/kanidm/run.sh
|
||||||
|
|
||||||
|
# kanidm data restore
|
||||||
|
docker stop kanidmd
|
||||||
|
docker run --rm -it \
|
||||||
|
-v kanidmd:/data \
|
||||||
|
-v kanidmd_backups:/backup \
|
||||||
|
kanidm/server:latest \
|
||||||
|
/sbin/kanidmd database restore -c /data/server.toml /backup/kanidm.backup.json
|
||||||
|
docker start kanidmd
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recover passwords from kanidm instance
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -i -t kanidmd kanidmd recover-account idm_admin
|
||||||
|
```
|
||||||
|
|
||||||
## add user to k8s group
|
## add user to k8s group
|
||||||
|
|
||||||
based on: https://blog.kammel.dev/post/k8s_home_lab_2025_06/
|
based on: https://blog.kammel.dev/post/k8s_home_lab_2025_06/
|
||||||
@@ -116,3 +140,30 @@ docker run --rm -i -t -v --restart=always \
|
|||||||
kanidmd cert-generate
|
kanidmd cert-generate
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Service account for gitea runner
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create service account
|
||||||
|
#kanidm service-account create \
|
||||||
|
# gitea_ci \ # account name
|
||||||
|
# "Gitea CI Deploy" \ # display name
|
||||||
|
# idm_admins \ # entry-managed-by (delegation group)
|
||||||
|
# --name idm_admin # authenticate as this user
|
||||||
|
|
||||||
|
kanidm service-account create gitea_ci "Gitea CI Deploy" idm_admins --name idm_admin
|
||||||
|
|
||||||
|
# Create a group and add the service account
|
||||||
|
kanidm group create k8s_deployers
|
||||||
|
kanidm group add-members k8s_deployers gitea_ci
|
||||||
|
|
||||||
|
# Create the OAuth2 client (or reuse existing k8s one)
|
||||||
|
# If you already have a k8s OIDC client, just add scope maps:
|
||||||
|
kanidm system oauth2 update-scope-map k8s k8s_deployers openid groups
|
||||||
|
|
||||||
|
# Generate an API token for the service account
|
||||||
|
kanidm service-account api-token generate --name idm_admin gitea_ci "gitea-ci-token"
|
||||||
|
# ⚠️ Save the output token — this is the subject_token for exchange
|
||||||
|
|
||||||
|
```
|
||||||
9
docker-30/kanidm/run.sh
Normal file
9
docker-30/kanidm/run.sh
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
docker rm -f kanidmd
|
||||||
|
|
||||||
|
docker run -d --name=kanidmd --restart=always \
|
||||||
|
-p '8443:8443' \
|
||||||
|
-p '3636:3636' \
|
||||||
|
--volume /srv/docker/kanidm/data:/data \
|
||||||
|
docker.io/kanidm/server:1.9.1
|
||||||
|
|
||||||
|
# previous version: 1.8.5
|
||||||
@@ -4,6 +4,7 @@ docker rm -f maru-hleda-byt
|
|||||||
|
|
||||||
# gitea registry login with kacerr / token
|
# gitea registry login with kacerr / token
|
||||||
docker run -d --name maru-hleda-byt \
|
docker run -d --name maru-hleda-byt \
|
||||||
|
--restart=always \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-v /srv/maru-hleda-byt/data:/app/data \
|
-v /srv/maru-hleda-byt/data:/app/data \
|
||||||
gitea.home.hrajfrisbee.cz/littlemeat/maru-hleda-byt:0.01
|
gitea.home.hrajfrisbee.cz/littlemeat/maru-hleda-byt:0.01
|
||||||
10
docker-30/vault/gitea-access-into-vault.md
Normal file
10
docker-30/vault/gitea-access-into-vault.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 1. Enable & configure JWT auth in Vault
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vault auth enable jwt
|
||||||
|
|
||||||
|
vault write auth/jwt/config \
|
||||||
|
bound_issuer="https://gitea.home.hrajfrisbee.cz" \
|
||||||
|
jwks_url="https://gitea.home.hrajfrisbee.cz/login/oauth/keys"
|
||||||
|
|
||||||
|
```
|
||||||
@@ -4,6 +4,13 @@ resource "vault_mount" "kv" {
|
|||||||
description = "KV v2 secrets engine"
|
description = "KV v2 secrets engine"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "vault_jwt_auth_backend" "gitea" {
|
||||||
|
path = "jwt"
|
||||||
|
type = "jwt"
|
||||||
|
bound_issuer = "https://gitea.home.hrajfrisbee.cz"
|
||||||
|
jwks_url = "https://gitea.home.hrajfrisbee.cz/login/oauth/keys"
|
||||||
|
}
|
||||||
|
|
||||||
resource "vault_policy" "eso_read" {
|
resource "vault_policy" "eso_read" {
|
||||||
name = "external-secrets-read"
|
name = "external-secrets-read"
|
||||||
policy = <<-EOT
|
policy = <<-EOT
|
||||||
@@ -16,6 +23,37 @@ resource "vault_policy" "eso_read" {
|
|||||||
EOT
|
EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# for now i allow my gitea to read everything in /v1/secret/data/gitea
|
||||||
|
resource "vault_policy" "gitea_ci_read" {
|
||||||
|
name = "gitea-ci-read"
|
||||||
|
policy = <<-EOT
|
||||||
|
path "${vault_mount.kv.path}/data/gitea/*" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
path "${vault_mount.kv.path}/metadata/gitea/*" {
|
||||||
|
capabilities = ["read", "list"]
|
||||||
|
}
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_jwt_auth_backend_role" "gitea_ci" {
|
||||||
|
backend = vault_jwt_auth_backend.gitea.path
|
||||||
|
role_name = "gitea-ci"
|
||||||
|
role_type = "jwt"
|
||||||
|
token_policies = [vault_policy.gitea_ci_read.name]
|
||||||
|
|
||||||
|
user_claim = "sub"
|
||||||
|
bound_audiences = ["https://gitea.home.hrajfrisbee.cz"]
|
||||||
|
|
||||||
|
# allow any valid jwt token when commented out
|
||||||
|
# bound_claims = {
|
||||||
|
# repository = "myorg/repo1,myorg/repo3"
|
||||||
|
# }
|
||||||
|
|
||||||
|
token_ttl = 600
|
||||||
|
token_max_ttl = 1200
|
||||||
|
}
|
||||||
|
|
||||||
resource "vault_auth_backend" "approle" {
|
resource "vault_auth_backend" "approle" {
|
||||||
type = "approle"
|
type = "approle"
|
||||||
}
|
}
|
||||||
@@ -47,3 +85,31 @@ output "secret_id" {
|
|||||||
value = vault_approle_auth_backend_role_secret_id.eso.secret_id
|
value = vault_approle_auth_backend_role_secret_id.eso.secret_id
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "vault_approle_auth_backend_role" "gitea_ci" {
|
||||||
|
backend = vault_auth_backend.approle.path
|
||||||
|
role_name = "gitea-ci"
|
||||||
|
token_policies = [vault_policy.gitea_ci_read.name]
|
||||||
|
token_ttl = 600
|
||||||
|
token_max_ttl = 1200
|
||||||
|
}
|
||||||
|
|
||||||
|
data "vault_approle_auth_backend_role_id" "gitea_ci" {
|
||||||
|
backend = vault_auth_backend.approle.path
|
||||||
|
role_name = vault_approle_auth_backend_role.gitea_ci.role_name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_approle_auth_backend_role_secret_id" "gitea_ci" {
|
||||||
|
backend = vault_auth_backend.approle.path
|
||||||
|
role_name = vault_approle_auth_backend_role.gitea_ci.role_name
|
||||||
|
}
|
||||||
|
|
||||||
|
output "gitea_ci_role_id" {
|
||||||
|
value = data.vault_approle_auth_backend_role_id.gitea_ci.role_id
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output "gitea_ci_secret_id" {
|
||||||
|
value = vault_approle_auth_backend_role_secret_id.gitea_ci.secret_id
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user