This commit is contained in:
@@ -15,9 +15,6 @@ jobs:
|
|||||||
curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz \
|
curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz \
|
||||||
| tar xz -C /usr/local/bin
|
| tar xz -C /usr/local/bin
|
||||||
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||||
curl -fsSL https://github.com/getsops/sops/releases/latest/download/sops-v3.x.linux.amd64 \
|
|
||||||
-o /usr/local/bin/sops
|
|
||||||
chmod +x /usr/local/bin/sops
|
|
||||||
curl -fsSL https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 \
|
curl -fsSL https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 \
|
||||||
-o /usr/local/bin/yq
|
-o /usr/local/bin/yq
|
||||||
chmod +x /usr/local/bin/yq
|
chmod +x /usr/local/bin/yq
|
||||||
@@ -68,11 +65,9 @@ jobs:
|
|||||||
mapfile -t manifests < <(
|
mapfile -t manifests < <(
|
||||||
find . -type f -name '*.yaml' \
|
find . -type f -name '*.yaml' \
|
||||||
! -path './.gitea/*' \
|
! -path './.gitea/*' \
|
||||||
! -name '.sops.yaml' \
|
|
||||||
! -name '*.enc.yaml' \
|
! -name '*.enc.yaml' \
|
||||||
! -name '*.secret.yaml' \
|
! -name '*.secret.yaml' \
|
||||||
! -name '*kustomization.yaml' \
|
! -name '*kustomization.yaml' \
|
||||||
! -name 'secret-generator.yaml' \
|
|
||||||
! -path './bootstrap/config.yaml' \
|
! -path './bootstrap/config.yaml' \
|
||||||
| sort
|
| sort
|
||||||
)
|
)
|
||||||
@@ -95,31 +90,12 @@ jobs:
|
|||||||
| xargs kubeconform \
|
| xargs kubeconform \
|
||||||
-strict \
|
-strict \
|
||||||
-kubernetes-version 1.35.0 \
|
-kubernetes-version 1.35.0 \
|
||||||
|
-ignore-missing-schemas \
|
||||||
-schema-location default \
|
-schema-location default \
|
||||||
-schema-location '.ci-schemas/{{.Group}}/{{.ResourceKind}}{{.KindSuffix}}.json' \
|
-schema-location '.ci-schemas/{{.Group}}/{{.ResourceKind}}{{.KindSuffix}}.json' \
|
||||||
-schema-location '.ci-schemas/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
|
-schema-location '.ci-schemas/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
|
||||||
-summary
|
-summary
|
||||||
|
|
||||||
- name: SOPS - check no secret files committed unencrypted
|
|
||||||
run: |
|
|
||||||
fail=0
|
|
||||||
|
|
||||||
while IFS= read -r file; do
|
|
||||||
if ! grep -q 'sops:' "$file"; then
|
|
||||||
echo "ERROR: $file appears to be unencrypted"
|
|
||||||
fail=1
|
|
||||||
fi
|
|
||||||
done < <(
|
|
||||||
find . -type f \( -name '*.secret.yaml' -o -name '*.enc.yaml' \) | sort
|
|
||||||
)
|
|
||||||
|
|
||||||
exit "$fail"
|
|
||||||
|
|
||||||
- name: Check SOPS sync
|
|
||||||
env:
|
|
||||||
SOPS_SYNC_HMAC_KEY: ${{ secrets.SOPS_SYNC_HMAC_KEY }}
|
|
||||||
run: scripts/ci/check-sops-sync
|
|
||||||
|
|
||||||
- name: Apply bootstrap ApplicationSet
|
- name: Apply bootstrap ApplicationSet
|
||||||
env:
|
env:
|
||||||
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }}
|
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# Replace the age recipient below with the public key used by ArgoCD / ksops
|
|
||||||
# in the target environment before storing real secrets in this repository.
|
|
||||||
creation_rules:
|
|
||||||
- path_regex: manifest/(overlays|components)/.*/.*\.enc\.yaml$
|
|
||||||
age: age1s0gzgh8c00tgnkgxrqyz2nu0k56xvc9ev3jdenkmu90egux9xfmsxvvj43
|
|
||||||
- path_regex: manifest/(overlays|components)/.*/secret\.secret\.yaml$
|
|
||||||
age: age1s0gzgh8c00tgnkgxrqyz2nu0k56xvc9ev3jdenkmu90egux9xfmsxvvj43
|
|
||||||
@@ -20,13 +20,11 @@ spec:
|
|||||||
app.kubernetes.io/managed-by: argocd
|
app.kubernetes.io/managed-by: argocd
|
||||||
app.kubernetes.io/name: authentik
|
app.kubernetes.io/name: authentik
|
||||||
spec:
|
spec:
|
||||||
project: default
|
project: dormant
|
||||||
source:
|
source:
|
||||||
repoURL: http://gitea-ha-http.apps:3000/olb42/authentik.git
|
repoURL: http://gitea-ha-http.apps:3000/olb42/authentik.git
|
||||||
targetRevision: main
|
targetRevision: main
|
||||||
path: '{{ .path }}'
|
path: '{{ .path }}'
|
||||||
plugin:
|
|
||||||
name: ksops
|
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: '{{ .namespace }}'
|
namespace: '{{ .namespace }}'
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# +argocd:skip-file-rendering
|
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
|
|
||||||
configMapGenerator:
|
|
||||||
- files:
|
|
||||||
- example-test.env
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# +argocd:skip-file-rendering
|
|
||||||
|
|
||||||
test=works
|
|
||||||
@@ -7,6 +7,4 @@ resources:
|
|||||||
# - ../../base
|
# - ../../base
|
||||||
# - ingressroute.yaml
|
# - ingressroute.yaml
|
||||||
- storage/persistentvolumeclaim.yaml
|
- storage/persistentvolumeclaim.yaml
|
||||||
|
- secret.sealed.secret.yaml
|
||||||
# generators:
|
|
||||||
# - secret-generator.yaml
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
apiVersion: viaduct.ai/v1
|
|
||||||
kind: ksops
|
|
||||||
metadata:
|
|
||||||
name: authentik-secret-generator
|
|
||||||
annotations:
|
|
||||||
config.kubernetes.io/function: |
|
|
||||||
exec:
|
|
||||||
path: ksops
|
|
||||||
files:
|
|
||||||
- ./secret.secret.yaml
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
name: authentik-secrets
|
||||||
|
namespace: security
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
db_password: AgAaBgA3OsRTJxySD4aEjWnSJAyxS0MT5mIGxg9hscwIaDnFd/PwTRGWP/dqpGpFbbrfQrDv/v4XoiKIPpqaOVNlT8k50NZifmPXx3+g3Z9Vh2hAIBoYNBqr3Cppo29yxQUnH0RccLWSI7wK3jp95LBIdrTPRnCSL4E6ma8gkRRiA0ZZBcf6wk2dBzSoV3NBzx5UYvLX0R16JbA+5U9FndeMw8dyzmqSxCLJoh9oTATodaQ0+vMY/WYGl4SdeC33AicLSLp9sx3A8ypJOVjtz1wMhIEK0y6BZ7YDU5/Wj5IMhAbjKpIIn4tUez2DN+JojsAGvLdje9ccZaWWB8aimGoRK9OAu2SjlmB+NXIlJ8Qn6UUCkF+BdT+AXNOnQKHvbpE1DxUWX6zTsb2f2rWoCJPb+E4iLBu4f4lLJot3CtWbX0G/OeFxdnm0eF5rT4YtskeG987rskkvMzIllQWjB4tCrGD1Zt5i/V4YqWx2hpmDrF5PUhWAlW3a5SsNidQPEawLp9ua6/ZaWzF+hQDjNNOXDEQij+9QF40ckB8xe5XB6xRVzBP7Lz/NKCITRPphEPjRdbqCil2fJ/mywN9BDRvWsltSAO3yGS0+s5n14DB/ehTqQDviIA87hjv0G4rP2Rl8QMSa2VtwKp5k7B0pQa8KxCiHXG7XyUQ2GKIr1VPssCR4KPPash72IyggATcWNV3WcR9LzGdNlUQLSi45TnNEEah4akWJ94fM3f0fQJSqzilVE022168bDpOpjcEacVY=
|
||||||
|
secret_key: AgAfJ3iwLbOfrslj9BRrOxq2bLIR12rwhNJn110TOCIwlb7IMnV2cFC9seS1yxt87HHYsQ62MiLioMwaW9Ih9+KIqFeMQDn/FRQEC7ZHMZRL2EVeNtTgWcx/SozzMfX7Azf3hbwvq7uVvijzeNGTriVseC6BxHiDyL6QWCb3LhfuFU5pNg9kPV8+nhc2y2zkBpXga2xvmkD+Wz4s+SVNcLiauYdwT0IiAcDS/yvN4ztw7+1NFBPp1yeVTK4MAHR2Znnfryq95QuT6bOWy5eHtJ4yQuvadRouWvunLGTJld6meVWFxNAjZudIvcDbnN5iuuZK+ILIiQX+IOsfOy/2ecHM0E3nuh6vnuO4bcRA73IkthFmTuG80El8U8LyYFa8cvAfxS9EUUtAyhOuW9q7K0VTgB2LYgUGeIhONWt6xSkPuW/S4WSRWrvKLX29dJz/r5a5s72uE2xM4OxVlHZt0+LE4XvLgCxAa9DTk8YjVLv2Y1lvuJoAQM83IeGMnXjA1FoOQOdj0pnmkgSRfuI+ElELkiU6wCvbYslLatSsG9EhSO2/QR041S09WiPEf9Io0aIboS3dWDP4X0HAkrKiDRp3rtHlXYPmXMjWLkc6MbtlVcD14c2oJoWy+twjXqJfHWpVpWXS+MQ2JSyZcOQNFuHERUiJ93RQIrl71wAnQEL8f/defpMyYkOI3vwU7r8AiFDsJRRIRXdA+EtG4E5k92o/VQxwEP0phsSUEPUtAJRM/K4rKycl+JlwbxK+iuyV4eQmH21afSS4/XXFFsCLqyA6Bk4Z5mHHBC68sSK5D1f+Og==
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
name: authentik-secrets
|
||||||
|
namespace: security
|
||||||
|
type: Opaque
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
apiVersion: ENC[AES256_GCM,data:nhU=,iv:+igd9RTOMy7mwVDjx05WQIdytoCrWLjcnP2Wq8xBS1s=,tag:61hSvgd/g88pjdhPW6OQsg==,type:str]
|
|
||||||
kind: ENC[AES256_GCM,data:T9Wrg72s,iv:1+cSHl0Dwwc1ddnw4dQI26r8/aEfMAPpsmz6BxxetrQ=,tag:y8HSeCa9gl7i+rW1Qpzc0Q==,type:str]
|
|
||||||
metadata:
|
|
||||||
name: ENC[AES256_GCM,data:VNJIfO/EtW0Pd3xVKG2R1VM=,iv:53UVCYVV07Qdlbq7j9qghT/DHqv+98muUFpapoD7VBs=,tag:hKBPwm7dudPUlRCgp4D9/g==,type:str]
|
|
||||||
namespace: ENC[AES256_GCM,data:/STezL4E9q4=,iv:mImCvy0lheb1px4+VEm7Z2c6qgQFerRnciPUy+arG2o=,tag:ZJxPwZ7pO6j+8xgHSDK/Og==,type:str]
|
|
||||||
type: ENC[AES256_GCM,data:ypwwk80m,iv:gbfQojFcmmGCtALVkCcICc4Z4dDXQK8clTGIMkOMOkY=,tag:Y0AskWriaazAlDkNN6Ufuw==,type:str]
|
|
||||||
stringData:
|
|
||||||
db_password: ENC[AES256_GCM,data:vx+4cvOPHycDmgp3fhTqsRC6ZjdviJy3svt5Kuhtzg48l8DNlxQpTvLgtdGYZmGS,iv:k84Nwxnuyc3D9ceXuMPhSTwzRrCucyUpJ/b7lrUTBlI=,tag:JOMAcJN9frgVkpeV8pm39A==,type:str]
|
|
||||||
secret_key: ENC[AES256_GCM,data:ZhH2xkHf/wLXiooCBP+9ZctsGSOMW1de4xz8IGPS0z1GSTReM9h7sSHhw1vJl7iHsr8ne4ehXQYvLd/QcvLVShS2WVDSt1wXcKejnHgRTBk=,iv:xFSp0TRzBl0c4hx6UPv2CdFx9fMQ/oAes8JpWf1Zj7U=,tag:j8jrfilCe2gfyQr1kls4WQ==,type:str]
|
|
||||||
sops:
|
|
||||||
age:
|
|
||||||
- recipient: age1s0gzgh8c00tgnkgxrqyz2nu0k56xvc9ev3jdenkmu90egux9xfmsxvvj43
|
|
||||||
enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSdWlWdTRWOWhTNnFocWlI
|
|
||||||
QTlLNmJtUkZLSUNOdi9yQmNVQkVkQi94WFc4Cno2NjJCeURlc3FZd0hTK0IzUXJq
|
|
||||||
STZtU0pBZ25SaFJqMFZNaE41a1p0SkEKLS0tIFg2VlphajFlSjdtSFM1NTVZdjZm
|
|
||||||
SmVrN2pFQ0MzVS9aUUtrVnFUaUd6TzAKksOI8fvGkE8/Qmk8yT7jvvakFWSLDYKa
|
|
||||||
7/pcTR0msB9P8zowsfe4pL+BJIObctXpyTRPCyyJg7/vOHqhuUv9Wg==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
lastmodified: "2026-04-19T23:31:55Z"
|
|
||||||
mac: ENC[AES256_GCM,data:sRk5KdZXohSwRWs6jP/jUA1OMBgnxKQu82Wp2w/4q1K0XXe1KRSDyiIIklgdK6JAW5U0291EzKLg8KgTks3YsibteA4L7eFbwFDTXOn7OJwjhKlzTZXqxEjmGHmsKLa7v8Y8nKuJqn2CUrfUuLzyvUaQN2tXfigq/DPC0HyROQs=,iv:zzWBrUcOumbaDArX9xRPzz3J98fA9wAgC1cKafejWT8=,tag:DL9o2cA5Uslq6WWGzlt8QA==,type:str]
|
|
||||||
unencrypted_suffix: _unencrypted
|
|
||||||
version: 3.12.2
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Optional static PV example for NFS-backed storage.
|
|
||||||
# This file is not referenced from kustomization.yaml by default because the
|
|
||||||
# default storage flow uses dynamic provisioning via the Longhorn PVC.
|
|
||||||
#
|
|
||||||
# To use this file:
|
|
||||||
# 1. Add storage/persistentvolume-nfs.yaml to resources in this overlay.
|
|
||||||
# 2. Update persistentvolumeclaim.yaml to set:
|
|
||||||
# storageClassName: nfs-shared
|
|
||||||
# volumeName: app-data
|
|
||||||
# accessModes: [ReadWriteMany]
|
|
||||||
# 3. Replace the placeholder NFS server and export path below.
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolume
|
|
||||||
metadata:
|
|
||||||
name: app-data
|
|
||||||
spec:
|
|
||||||
capacity:
|
|
||||||
storage: 10Gi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteMany
|
|
||||||
persistentVolumeReclaimPolicy: Retain
|
|
||||||
claimRef:
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
name: app-data
|
|
||||||
namespace: security
|
|
||||||
storageClassName: nfs-shared
|
|
||||||
mountOptions:
|
|
||||||
- nfsvers=4.1
|
|
||||||
nfs:
|
|
||||||
server: nfs.example.internal
|
|
||||||
path: /exports/app-data
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
#!/usr/bin/env zsh
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
for cmd in git sops openssl; do
|
|
||||||
if ! command -v "${cmd}" >/dev/null 2>&1; then
|
|
||||||
echo "check-sops-sync: required command missing: ${cmd}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "${SOPS_SYNC_HMAC_KEY:-}" ]; then
|
|
||||||
echo "check-sops-sync: SOPS_SYNC_HMAC_KEY is required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo_root=$(git rev-parse --show-toplevel)
|
|
||||||
regex_script="${repo_root}/scripts/lib/sops-path-regexes"
|
|
||||||
lib_script="${repo_root}/scripts/lib/sops-sync-lib"
|
|
||||||
|
|
||||||
if [ ! -x "${regex_script}" ]; then
|
|
||||||
echo "check-sops-sync: missing helper ${regex_script}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "${lib_script}" ]; then
|
|
||||||
echo "check-sops-sync: missing helper ${lib_script}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
. "${lib_script}"
|
|
||||||
|
|
||||||
regexes=("${(@f)$("${regex_script}")}")
|
|
||||||
fail=0
|
|
||||||
|
|
||||||
while IFS= read -r tracked_path; do
|
|
||||||
[ -n "${tracked_path}" ] || continue
|
|
||||||
[[ "${tracked_path}" == *.enc.* ]] || continue
|
|
||||||
|
|
||||||
if ! matches_any_rule "${tracked_path}" "${regexes[@]}"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
sidecar_path=$(hmac_sidecar_for_enc "${tracked_path}")
|
|
||||||
|
|
||||||
if ! git ls-files --error-unmatch -- "${sidecar_path}" >/dev/null 2>&1; then
|
|
||||||
echo "check-sops-sync: missing sync sidecar for ${tracked_path}" >&2
|
|
||||||
fail=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! is_sops_encrypted_file "${tracked_path}"; then
|
|
||||||
echo "check-sops-sync: file is not valid SOPS-encrypted content: ${tracked_path}" >&2
|
|
||||||
fail=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
sidecar_value=$(read_sidecar "${sidecar_path}" || true)
|
|
||||||
if [ -z "${sidecar_value}" ]; then
|
|
||||||
echo "check-sops-sync: sidecar is empty: ${sidecar_path}" >&2
|
|
||||||
fail=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! computed_hmac=$(decrypt_enc_to_plain "${tracked_path}" | compute_hmac_for_stdin); then
|
|
||||||
echo "check-sops-sync: failed to decrypt ${tracked_path}" >&2
|
|
||||||
fail=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${computed_hmac}" != "${sidecar_value}" ]; then
|
|
||||||
echo "check-sops-sync: decrypted plaintext HMAC does not match sidecar for ${tracked_path}" >&2
|
|
||||||
fail=1
|
|
||||||
fi
|
|
||||||
done < <(git ls-files)
|
|
||||||
|
|
||||||
exit "${fail}"
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
#!/usr/bin/env zsh
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
for cmd in git sops openssl; do
|
|
||||||
if ! command -v "${cmd}" >/dev/null 2>&1; then
|
|
||||||
echo "pre-commit: required command missing: ${cmd}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "${SOPS_SYNC_HMAC_KEY:-}" ]; then
|
|
||||||
echo "pre-commit: SOPS_SYNC_HMAC_KEY is required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo_root=$(git rev-parse --show-toplevel)
|
|
||||||
regex_script="${repo_root}/scripts/lib/sops-path-regexes"
|
|
||||||
lib_script="${repo_root}/scripts/lib/sops-sync-lib"
|
|
||||||
|
|
||||||
if [ ! -x "${regex_script}" ]; then
|
|
||||||
echo "pre-commit: missing helper ${regex_script}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "${lib_script}" ]; then
|
|
||||||
echo "pre-commit: missing helper ${lib_script}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
. "${lib_script}"
|
|
||||||
|
|
||||||
regexes=("${(@f)$("${regex_script}")}")
|
|
||||||
typeset -A candidates
|
|
||||||
|
|
||||||
while IFS= read -r staged_path; do
|
|
||||||
[ -n "${staged_path}" ] || continue
|
|
||||||
|
|
||||||
candidate_enc=""
|
|
||||||
if [[ "${staged_path}" == *.enc.yaml ]]; then
|
|
||||||
candidate_enc="${staged_path}"
|
|
||||||
elif [[ "${staged_path}" == *.yaml ]]; then
|
|
||||||
if git ls-files --error-unmatch -- "${staged_path}" >/dev/null 2>&1; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
candidate_enc=$(plain_to_enc "${staged_path}") || continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -n "${candidate_enc}" ] || continue
|
|
||||||
if matches_any_rule "${candidate_enc}" "${regexes[@]}"; then
|
|
||||||
candidates["${candidate_enc}"]=1
|
|
||||||
fi
|
|
||||||
done < <(git diff --cached --name-only --diff-filter=ACMR)
|
|
||||||
|
|
||||||
for enc_path in "${(@k)candidates}"; do
|
|
||||||
plain_path=$(enc_to_plain "${enc_path}") || continue
|
|
||||||
sidecar_path=$(hmac_sidecar_for_enc "${enc_path}")
|
|
||||||
|
|
||||||
if [ ! -f "${plain_path}" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if has_unstaged_changes "${plain_path}"; then
|
|
||||||
echo "pre-commit: plaintext file has unstaged changes: ${plain_path}" >&2
|
|
||||||
echo "pre-commit: stage or revert the plaintext change before committing" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
current_hmac=$(compute_hmac_for_file "${plain_path}")
|
|
||||||
tracked_hmac=""
|
|
||||||
if [ -f "${sidecar_path}" ]; then
|
|
||||||
tracked_hmac=$(read_sidecar "${sidecar_path}" || true)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${current_hmac}" = "${tracked_hmac}" ] && [ -f "${enc_path}" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
encrypt_plain_to_enc "${plain_path}" "${enc_path}"
|
|
||||||
write_sidecar "${sidecar_path}" "${current_hmac}"
|
|
||||||
git add "${enc_path}" "${sidecar_path}"
|
|
||||||
done
|
|
||||||
|
|
||||||
for enc_path in "${(@k)candidates}"; do
|
|
||||||
sidecar_path=$(hmac_sidecar_for_enc "${enc_path}")
|
|
||||||
|
|
||||||
if ! git cat-file -e ":${enc_path}" 2>/dev/null; then
|
|
||||||
echo "pre-commit: staged encrypted file missing: ${enc_path}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! git cat-file -e ":${sidecar_path}" 2>/dev/null; then
|
|
||||||
echo "pre-commit: staged sync sidecar missing: ${sidecar_path}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
tmp_enc=$(mktemp)
|
|
||||||
trap 'rm -f "${tmp_enc}"' EXIT INT TERM
|
|
||||||
git show ":${enc_path}" > "${tmp_enc}"
|
|
||||||
|
|
||||||
staged_hmac=$(git show ":${sidecar_path}" | tr -d '\r\n')
|
|
||||||
computed_hmac=$(decrypt_enc_to_plain "${tmp_enc}" | compute_hmac_for_stdin)
|
|
||||||
|
|
||||||
rm -f "${tmp_enc}"
|
|
||||||
trap - EXIT INT TERM
|
|
||||||
|
|
||||||
if [ "${computed_hmac}" != "${staged_hmac}" ]; then
|
|
||||||
echo "pre-commit: staged encrypted file and sidecar are out of sync: ${enc_path}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env zsh
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
repo_root=$(git rev-parse --show-toplevel)
|
|
||||||
hook_path="${repo_root}/.git/hooks/pre-commit"
|
|
||||||
target="${repo_root}/scripts/git-hooks/pre-commit"
|
|
||||||
|
|
||||||
mkdir -p "${repo_root}/.git/hooks"
|
|
||||||
cat > "${hook_path}" <<EOF
|
|
||||||
#!/usr/bin/env zsh
|
|
||||||
exec "${target}" "\$@"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x "${hook_path}"
|
|
||||||
echo "Installed git hook: ${hook_path}"
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#!/usr/bin/env zsh
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
repo_root=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
||||||
sops_file="${repo_root}/.sops.yaml"
|
|
||||||
|
|
||||||
if [ ! -f "${sops_file}" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if command -v yq >/dev/null 2>&1; then
|
|
||||||
yq -r '.creation_rules[]?.path_regex | select(. != null)' "${sops_file}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if command -v python3 >/dev/null 2>&1; then
|
|
||||||
python3 - "${sops_file}" <<'PY'
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
import yaml
|
|
||||||
except Exception as exc:
|
|
||||||
raise SystemExit(f"python3 fallback requires PyYAML: {exc}")
|
|
||||||
|
|
||||||
with open(sys.argv[1], "r", encoding="utf-8") as handle:
|
|
||||||
data = yaml.safe_load(handle) or {}
|
|
||||||
|
|
||||||
for rule in data.get("creation_rules") or []:
|
|
||||||
regex = rule.get("path_regex")
|
|
||||||
if regex:
|
|
||||||
print(regex)
|
|
||||||
PY
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Unable to read .sops.yaml path_regex values: install yq or python3 with PyYAML" >&2
|
|
||||||
exit 1
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#!/usr/bin/env zsh
|
|
||||||
|
|
||||||
matches_any_rule() {
|
|
||||||
local path="$1"
|
|
||||||
shift
|
|
||||||
local regex
|
|
||||||
for regex in "$@"; do
|
|
||||||
[[ -n "${regex}" ]] || continue
|
|
||||||
if [[ "${path}" =~ ${regex} ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
enc_to_plain() {
|
|
||||||
local enc="$1"
|
|
||||||
[[ "${enc}" == *.enc.yaml ]] || return 1
|
|
||||||
print -r -- "${enc%.enc.yaml}.yaml"
|
|
||||||
}
|
|
||||||
|
|
||||||
plain_to_enc() {
|
|
||||||
local plain="$1"
|
|
||||||
[[ "${plain}" == *.yaml ]] || return 1
|
|
||||||
if [[ "${plain}" == *.enc.yaml ]]; then
|
|
||||||
print -r -- "${plain}"
|
|
||||||
else
|
|
||||||
print -r -- "${plain%.yaml}.enc.yaml"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
hmac_sidecar_for_enc() {
|
|
||||||
local enc="$1"
|
|
||||||
print -r -- "${enc}.sync-hmac"
|
|
||||||
}
|
|
||||||
|
|
||||||
encrypt_plain_to_enc() {
|
|
||||||
local plain="$1"
|
|
||||||
local enc="$2"
|
|
||||||
sops --encrypt --input-type yaml --output-type yaml --output "${enc}" "${plain}"
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypt_enc_to_plain() {
|
|
||||||
local enc="$1"
|
|
||||||
sops --decrypt "${enc}"
|
|
||||||
}
|
|
||||||
|
|
||||||
compute_hmac_for_file() {
|
|
||||||
local path="$1"
|
|
||||||
openssl dgst -sha256 -hmac "${SOPS_SYNC_HMAC_KEY}" "${path}" | awk '{print $NF}'
|
|
||||||
}
|
|
||||||
|
|
||||||
compute_hmac_for_stdin() {
|
|
||||||
openssl dgst -sha256 -hmac "${SOPS_SYNC_HMAC_KEY}" | awk '{print $NF}'
|
|
||||||
}
|
|
||||||
|
|
||||||
read_sidecar() {
|
|
||||||
local sidecar="$1"
|
|
||||||
[ -f "${sidecar}" ] || return 1
|
|
||||||
tr -d '\r\n' < "${sidecar}"
|
|
||||||
}
|
|
||||||
|
|
||||||
write_sidecar() {
|
|
||||||
local sidecar="$1"
|
|
||||||
local value="$2"
|
|
||||||
print -r -- "${value}" > "${sidecar}"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_sops_encrypted_file() {
|
|
||||||
local path="$1"
|
|
||||||
[ -f "${path}" ] || return 1
|
|
||||||
grep -q 'sops:' "${path}" && grep -q 'ENC\[' "${path}"
|
|
||||||
}
|
|
||||||
|
|
||||||
has_unstaged_changes() {
|
|
||||||
local path="$1"
|
|
||||||
if git ls-files --error-unmatch -- "${path}" >/dev/null 2>&1; then
|
|
||||||
git diff --quiet -- "${path}" >/dev/null 2>&1
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if git diff --cached --name-only -- "${path}" | grep -Fxq "${path}"; then
|
|
||||||
git diff --quiet -- "${path}" >/dev/null 2>&1
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user