diff --git a/.forgejo/workflows/build-deployer.yaml b/.forgejo/workflows/build-deployer.yaml new file mode 100644 index 0000000..4822b27 --- /dev/null +++ b/.forgejo/workflows/build-deployer.yaml @@ -0,0 +1,22 @@ +on: + push: + paths: + - containers/deployer/** + - .forgejo/workflows/build-deployer.yaml +jobs: + build-deployer: + runs-on: docker + container: + image: library/docker:dind + steps: + - run: apk add --no-cache nodejs git + - name: login to container registry + run: echo "${{ secrets.DEPLOY_SECRET }}" | docker login --username ${{ secrets.DEPLOY_USER }} --password-stdin git.janky.solutions + - name: build container image + uses: docker/build-push-action@v6 + with: + file: Containerfile + context: "{{defaultContext}}:containers/deployer" + tags: git.janky.solutions/jankysolutions/infra/deployer:latest + platforms: linux/amd64 + push: true diff --git a/.forgejo/workflows/k8s-diff-and-deploy.yaml b/.forgejo/workflows/k8s-diff-and-deploy.yaml new file mode 100644 index 0000000..9284dcd --- /dev/null +++ b/.forgejo/workflows/k8s-diff-and-deploy.yaml @@ -0,0 +1,37 @@ +on: + push: + paths: + - k8s/** + - .forgejo/workflows/k8s-diff-and-deploy.yaml +jobs: + diff-and-deploy: + runs-on: ubuntu-latest + container: + image: git.devhack.net/devhack/containers/deployer:latest + steps: + - uses: actions/checkout@v4 + - name: kubectl diff and deploy + run: | + set -euo pipefail + echo "${{ secrets.KUBERNETES_CLIENT_CONFIG }}" > ~/.kube/config + + for component in k8s/*; do + if [ ! -d "${component}" ]; then + continue + fi + + touch "${component}/secrets.yaml" + + echo "👀 $ kubectl diff -k ${component}" + kubectl diff -k "${component}" || echo + + # if [[ "${GITHUB_REF_NAME}" == "main" ]]; then + # echo "🚀 $ kubectl apply -k ${component}" + # if [[ "${component}" == "k8s/operators" ]]; then + # kubectl apply -k "${component}" --server-side + # else + # kubectl apply -k "${component}" + # fi + # echo + # fi + done diff --git a/containers/deployer/Containerfile b/containers/deployer/Containerfile new file mode 100644 index 0000000..306c123 --- /dev/null +++ b/containers/deployer/Containerfile @@ -0,0 +1,3 @@ +FROM library/alpine:3.20 +RUN apk add --no-cache nodejs git bash helm kubectl +RUN mkdir -p ~/.kube diff --git a/k8s/forgejo/forgejo-secret-sync.yaml b/k8s/forgejo/forgejo-secret-sync.yaml new file mode 100644 index 0000000..24cae7b --- /dev/null +++ b/k8s/forgejo/forgejo-secret-sync.yaml @@ -0,0 +1,62 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: forgejo-secret-sync +spec: + schedule: "0 0 * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: secret-sync + image: library/python:3 + command: + - bash + - -c + - pip install requests && python /code/forgejo-secret-sync.py + env: + - name: REPO_MAPPINGS + value: | + [ + {"k8s_name": "infra-deployer", "owner": "JankySolutions", "repo": "infra"} + ] + envFrom: + - secretRef: + name: forgejo-secret-sync + volumeMounts: + - name: code + mountPath: /code + - name: host-tls + mountPath: /var/lib/rancher/k3s/server/tls + restartPolicy: OnFailure + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: In + values: ["true"] + volumes: + - name: code + configMap: + name: forgejo-secret-sync + - name: host-tls + hostPath: + path: /var/lib/rancher/k3s/server/tls +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: forgejo-secret-sync +spec: + secretStoreRef: + kind: SecretStore + name: openbao + target: + name: forgejo-secret-sync + creationPolicy: Owner + dataFrom: + - extract: + key: forgejo/default/secret-sync diff --git a/k8s/forgejo/forgejo-secret-sync/forgejo-secret-sync.py b/k8s/forgejo/forgejo-secret-sync/forgejo-secret-sync.py new file mode 100644 index 0000000..27a23f2 --- /dev/null +++ b/k8s/forgejo/forgejo-secret-sync/forgejo-secret-sync.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +import subprocess +import logging +import base64 +import os +import requests +import json + +logging.basicConfig(level=logging.DEBUG) + +with open("/var/lib/rancher/k3s/server/tls/server-ca.crt") as f: + ca = base64.b64encode(f.read().encode()).decode() + +forgejo_token = os.getenv("FORGEJO_TOKEN") + + +def run(cmd: list[str], stdin=None) -> str: + logging.debug("executing %s", cmd) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + out = p.communicate(stdin) + if p.returncode != 0: + logging.critical("{} exited with code {}", cmd, p.returncode) + os.exit(1) + return out[0] + + +def update_cert(k8s_name: str, owner: str, repo: str): + key = run(["openssl", "genrsa", "4096"]) + req = run( + ["openssl", "req", "-key", "/dev/stdin", "-new", "-nodes", "-subj", f"/CN={k8s_name}"], stdin=key + ) + cert = run( + [ + "openssl", + "x509", + "-req", + "-CA", + "/var/lib/rancher/k3s/server/tls/client-ca.nochain.crt", + "-CAkey", + "/var/lib/rancher/k3s/server/tls/client-ca.key", + "-CAcreateserial", + "-days", + "2", + ], + stdin=req, + ) + + keyb64 = base64.b64encode(key).decode() + certb64 = base64.b64encode(cert).decode() + + kubeconfig = f""" +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: {ca} + server: https://k8s-node-1:6443 + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +kind: Config +preferences: {"{}"} +users: +- name: default + user: + client-certificate-data: {certb64} + client-key-data: {keyb64} +""" + logging.info(f"updating secret for {owner}/{repo}") + requests.put( + f"https://git.janky.solutions/api/v1/repos/{owner}/{repo}/actions/secrets/KUBERNETES_CLIENT_CONFIG", + data=json.dumps( + {"data": kubeconfig}, + ), + headers={ + "Authorization": f"token {forgejo_token}", + "Content-Type": "application/json", + }, + ).raise_for_status() + + +for entry in json.loads(os.getenv("REPO_MAPPINGS")): + update_cert(**entry) diff --git a/k8s/forgejo/kustomization.yaml b/k8s/forgejo/kustomization.yaml index f93527b..e96175e 100644 --- a/k8s/forgejo/kustomization.yaml +++ b/k8s/forgejo/kustomization.yaml @@ -5,8 +5,10 @@ resources: - namespace.yaml - config.yaml - ingress.yaml + - forgejo-secret-sync.yaml - services.yaml - statefulset.yaml + - secret-store.yaml - secrets.yaml - renovatebot.yaml configMapGenerator: @@ -16,3 +18,6 @@ configMapGenerator: - name: renovate-config files: - renovate/config.js + - name: forgejo-secret-sync + files: + - forgejo-secret-sync/forgejo-secret-sync.py diff --git a/k8s/forgejo/secret-store.yaml b/k8s/forgejo/secret-store.yaml new file mode 100644 index 0000000..a44b0bc --- /dev/null +++ b/k8s/forgejo/secret-store.yaml @@ -0,0 +1,16 @@ +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: openbao +spec: + provider: + vault: + server: http://openbao.openbao:8200 + path: static-secrets + version: v2 + auth: + kubernetes: + mountPath: kubernetes + role: kubernetes-default + serviceAccountRef: + name: default