diff --git a/.gitea/workflows/kubernetes-deploy.yaml b/.gitea/workflows/kubernetes-deploy.yaml deleted file mode 100644 index 40f0c5e..0000000 --- a/.gitea/workflows/kubernetes-deploy.yaml +++ /dev/null @@ -1,111 +0,0 @@ -name: Deploy to K8s -on: - workflow_dispatch: - push: - branches: - - '**' -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Debug - print Gitea Actions environment - run: | - echo "=== All environment variables ===" - env | sort - echo "" - echo "=== GITHUB_* / GITEA_* / ACTIONS_* vars ===" - env | grep -E '^(GITHUB|GITEA|ACTIONS)_' | sort - - - name: Install kubectl - run: | - curl -sfLO "https://dl.k8s.io/release/$(curl -sfL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" - install kubectl /usr/local/bin/ - - - name: Get Kanidm token from Vault - id: vault - run: | - set -euxo pipefail - - VAULT_AUTH_RESPONSE=$(curl -f --request POST \ - --data '{"role_id":"${{ secrets.VAULT_ROLE_ID }}","secret_id":"${{ secrets.VAULT_SECRET_ID }}"}' \ - https://vault.hrajfrisbee.cz/v1/auth/approle/login) - - echo "Vault auth response: $VAULT_AUTH_RESPONSE" >&2 - VAULT_TOKEN=$(echo "$VAULT_AUTH_RESPONSE" | jq -r '.auth.client_token') - - # Read the kanidm API token - SECRET_RESPONSE=$(curl -f \ - -H "X-Vault-Token: ${VAULT_TOKEN}" \ - https://vault.hrajfrisbee.cz/v1/secret/data/gitea/gitea-ci) - - echo "Secret response: $SECRET_RESPONSE" >&2 - API_TOKEN=$(echo "$SECRET_RESPONSE" | jq -r '.data.data.token') - - echo "::add-mask::${API_TOKEN}" - echo "api_token=${API_TOKEN}" >> "$GITHUB_OUTPUT" - - - name: Exchange for K8s OIDC token via Kanidm - id: k8s - run: | - API_TOKEN="${{ steps.vault.outputs.api_token }}" - echo "api_token length: ${#API_TOKEN}" >&2 - echo "api_token prefix (first 8 chars): ${API_TOKEN:0:8}..." >&2 - - HTTP_BODY=$(mktemp) - HTTP_STATUS=$(curl -sS -X POST "https://idm.home.hrajfrisbee.cz/oauth2/token" \ - -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ - -d "client_id=k8s" \ - -d "subject_token=${API_TOKEN}" \ - -d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \ - -d "audience=k8s" \ - -d "scope=openid groups" \ - -o "$HTTP_BODY" -w "%{http_code}") - - echo "HTTP status: $HTTP_STATUS" >&2 - echo "Response body:" >&2 - cat "$HTTP_BODY" >&2 - - RESPONSE=$(cat "$HTTP_BODY") - ID_TOKEN=$(echo "$RESPONSE" | jq -r '.id_token // empty') - - if [ -z "$ID_TOKEN" ]; then - echo "::error::Kanidm token exchange failed (HTTP $HTTP_STATUS)" - exit 1 - fi - - echo "::add-mask::${ID_TOKEN}" - echo "id_token=${ID_TOKEN}" >> "$GITHUB_OUTPUT" - - # Sanity check - # Warning - this is the part that is failing - # echo "$ID_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq '{sub, groups, exp}' - - - - name: Debug - print environment before kubectl - run: env | sort - - - name: Configure kubectl & deploy - run: | - echo "${{ secrets.K8S_CA_CERT }}" > /tmp/ca.crt - - kubectl config set-cluster mycluster \ - --server=https://192.168.0.31:6443 \ - --insecure-skip-tls-verify=true - # --certificate-authority=/tmp/ca.crt \ - - kubectl config set-credentials gitea-ci \ - --token="${{ steps.k8s.outputs.id_token }}" - - kubectl config set-context gitea-ci \ - --cluster=mycluster --user=gitea-ci - - kubectl config use-context gitea-ci - - kubectl auth whoami - kubectl get ns - - # your deploy here - # kubectl apply -f k8s/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d2f32..8a11e98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026-10-08 11:48 CEST — fix(ci): remove kubernetes-deploy workflow that leaked secrets to logs + +- Deleted `.gitea/workflows/kubernetes-deploy.yaml`. It ran on every push to every branch, deployed nothing, and echoed the Vault login response, the Kanidm API token response and the OIDC token response to the job log. +- Deployment is handled by `gitops-update.yaml`. Repo secrets `VAULT_ROLE_ID`, `VAULT_SECRET_ID` and `K8S_CA_CERT` are now unused. +- Follow-up outside the repo: rotate the Vault AppRole secret and the Kanidm API token, and clear old job logs. + ## 2026-10-08 11:34 CEST — fix(python): return months_to_show from /api/adults and /api/juniors - Go already returned `months_to_show` in the JSON API; Python only passed it to its HTML templates. Backported the key so the API contract matches and `make parity` reports 3/3 routes. diff --git a/docs/by-claude-opus/README.md b/docs/by-claude-opus/README.md index fc95177..89b491f 100644 --- a/docs/by-claude-opus/README.md +++ b/docs/by-claude-opus/README.md @@ -182,7 +182,7 @@ fuj-management/ │ ├── .gitea/workflows/ │ ├── build.yaml # CI: build + push Docker image -│ └── kubernetes-deploy.yaml # CD: deploy to K8s cluster +│ └── gitops-update.yaml # CD: open image-update PR in home-kubernetes │ ├── .secret/ # (gitignored) API credentials ├── docs/ # Project documentation diff --git a/docs/by-claude-opus/deployment.md b/docs/by-claude-opus/deployment.md index 09b095a..e2618b3 100644 --- a/docs/by-claude-opus/deployment.md +++ b/docs/by-claude-opus/deployment.md @@ -129,32 +129,27 @@ The project uses two Gitea Actions workflows: **Tag resolution**: Uses the git tag name. For manual dispatch, uses the provided input. -#### 2. Deploy to Kubernetes (`kubernetes-deploy.yaml`) +#### 2. GitOps image update (`gitops-update.yaml`) **Triggers**: -- Push to any branch -- Manual dispatch +- Completion of a successful "Build and Push" run +- Manual dispatch (with `tag`, `dry_run` and `uh_cli_version` inputs) **Steps**: -1. Checkout code -2. Install kubectl -3. Retrieve Kanidm token from HashiCorp Vault: - - Authenticate to Vault via AppRole (`VAULT_ROLE_ID` / `VAULT_SECRET_ID`) - - Fetch API token from `secret/data/gitea/gitea-ci` -4. Exchange API token for K8s OIDC token via Kanidm: - - POST to `https://idm.home.hrajfrisbee.cz/oauth2/token` - - Token exchange using `urn:ietf:params:oauth:grant-type:token-exchange` -5. Configure kubectl with the OIDC token -6. Run `kubectl auth whoami` and `kubectl get ns` (deploy commands are commented out — WIP) +1. Install `tea` and `uh-cli` +2. Resolve the image tag of the Go image that was just built +3. Open a PR in `kacerr/home-kubernetes` bumping the `fuj-management` Deployment (namespace `fuj`) to that image + +The cluster picks the change up once that PR is merged. CI does not talk to the cluster directly. + +> The earlier `kubernetes-deploy.yaml` workflow (Vault → Kanidm → `kubectl`) was removed on 2026-10-08. It never deployed anything and printed credentials to the job log. **Required secrets**: | Secret | Purpose | |--------|---------| | `REGISTRY_TOKEN` | Docker registry authentication | -| `VAULT_ROLE_ID` | HashiCorp Vault AppRole role ID | -| `VAULT_SECRET_ID` | HashiCorp Vault AppRole secret ID | -| `K8S_CA_CERT` | Kubernetes cluster CA certificate | +| `GITOPS_TOKEN` | Gitea token with write and PR access to `home-kubernetes` | ### Infrastructure Topology @@ -164,8 +159,7 @@ Gitea (git push / tag) ├── build.yaml → Docker Build → Gitea Container Registry │ (gitea.home.hrajfrisbee.cz) │ - └── kubernetes-deploy.yaml → Vault → Kanidm → K8s Cluster - (192.168.0.31:6443) + └── gitops-update.yaml → PR in home-kubernetes → K8s Cluster ``` This is a self-hosted infrastructure stack: diff --git a/docs/plans/2026-10-08-1026-pre-feature-cleanup-tasks.md b/docs/plans/2026-10-08-1026-pre-feature-cleanup-tasks.md index 4457731..0739e69 100644 --- a/docs/plans/2026-10-08-1026-pre-feature-cleanup-tasks.md +++ b/docs/plans/2026-10-08-1026-pre-feature-cleanup-tasks.md @@ -30,13 +30,18 @@ before new feature work starts. Companion to the Go rewrite tracker, ## Fix first -- [ ] **T1** Remove the secret leak in `.gitea/workflows/kubernetes-deploy.yaml`. +- [x] **T1** Remove the secret leak in `.gitea/workflows/kubernetes-deploy.yaml`. + Workflow deleted on 2026-10-08. Still open for the owner: rotate the + credentials and remove the now-unused repo secrets (see below). - It runs on every push to every branch and deploys nothing. - It echoes the raw Vault login response, the Kanidm token response and the OIDC token response body to the log, and dumps the environment twice. - It uses `--insecure-skip-tls-verify=true`. - Delete or disable the workflow. Rotate the Vault AppRole secret and the Kanidm API token if anyone else can read the CI logs. + - After deletion the repo secrets `VAULT_ROLE_ID`, `VAULT_SECRET_ID` and + `K8S_CA_CERT` are unused and can be removed. Old job logs still contain + the leaked tokens until they are deleted or expire. - [ ] **T2** Confirm the fee rates for 2026-09 onward. - `ADULT_FEE_MONTHLY_RATE` ends at 2026-08 in `scripts/attendance.py` and `go/internal/domain/fees/fees.go`.