diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..fbbca05 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,53 @@ +repos: + # General file checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-executables-have-shebangs + - id: check-json + - id: check-added-large-files + args: ['--maxkb=500'] + - id: check-merge-conflict + - id: check-shebang-scripts-are-executable + - id: check-symlinks + - id: check-toml + - id: check-yaml + args: [--allow-multiple-documents] + - id: detect-aws-credentials + args: [--allow-missing-credentials] + - id: detect-private-key + - id: end-of-file-fixer + - id: forbid-new-submodules + - id: no-commit-to-branch + - id: pretty-format-json + - id: trailing-whitespace + + # YAML linting + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.37.1 + hooks: + - id: yamllint + args: + [ + "-d {extends: relaxed, rules: {line-length: disable}, ignore: chart}", + "-s", + ] + + # Kubernetes manifest validation + - repo: local + hooks: + - id: kubeconform_validate_apps + name: kubeconform validate apps + entry: ci/validate-apps.sh + language: system + pass_filenames: false + - id: kubeconform_validate_clusters + name: kubeconform validate clusters + entry: ci/validate-clusters.sh + language: system + pass_filenames: false + - id: no_plain_secrets + name: prevent plain kubernetes secrets + entry: ci/validate-no-secrets.sh + language: system + pass_filenames: false diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..2be7501 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,29 @@ +# .yamllint.yaml +extends: default + +rules: + # Allow long lines for base64 encoded values and URLs + line-length: + max: 200 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true + + # Kubernetes manifests use 2-space indentation + indentation: + spaces: 2 + indent-sequences: consistent + + # Allow multiple documents (---) in a single file + document-start: enable + + # Be lenient with comments + comments: + require-starting-space: true + min-spaces-from-content: 1 + + # Allow empty values (common in Kustomize patches) + empty-values: enable + + truthy: + # Allow 'on' and 'yes' values (common in Kubernetes) + allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off'] diff --git a/ci/validate-apps.sh b/ci/validate-apps.sh new file mode 100755 index 0000000..bfdcbf1 --- /dev/null +++ b/ci/validate-apps.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +KUBE_VERSION="1.33.7" + +schema_args=( + -schema-location "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{.NormalizedKubernetesVersion}}-standalone{{.StrictSuffix}}/{{.ResourceKind}}{{.KindSuffix}}.json" + -schema-location "https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" +) + +while IFS= read -r -d "" k; do + dir="$(dirname "$k")" + echo "==> kubeconform: $dir" >&2 + + kustomize build --enable-helm "$dir" \ + | kubeconform \ + -kubernetes-version "$KUBE_VERSION" \ + -summary \ + -output pretty \ + -verbose \ + -skip CustomResourceDefinition \ + "${schema_args[@]}" +done < <(find apps/overlays -name kustomization.yaml -print0) diff --git a/ci/validate-clusters.sh b/ci/validate-clusters.sh new file mode 100755 index 0000000..92f4924 --- /dev/null +++ b/ci/validate-clusters.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +KUBE_VERSION="1.33.7" + +schema_args=( + -schema-location "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{.NormalizedKubernetesVersion}}-standalone{{.StrictSuffix}}/{{.ResourceKind}}{{.KindSuffix}}.json" + -schema-location "https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" +) + +while IFS= read -r -d "" k; do + dir="$(dirname "$k")" + echo "==> kubeconform: $dir" >&2 + + kustomize build --enable-helm "$dir" \ + | kubeconform \ + -kubernetes-version "$KUBE_VERSION" \ + -summary \ + -output pretty \ + -verbose \ + -skip CustomResourceDefinition \ + "${schema_args[@]}" +done < <(find clusters -name kustomization.yaml -print0) diff --git a/ci/validate-no-secrets.sh b/ci/validate-no-secrets.sh new file mode 100755 index 0000000..711276d --- /dev/null +++ b/ci/validate-no-secrets.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Check staged files for plain Kubernetes Secrets +ERRORS=0 + +while IFS= read -r -d '' file; do + # Skip if file doesn't exist (e.g., deleted files) + [[ -f "$file" ]] || continue + + # Check if the file contains a plain Kubernetes Secret + if grep -q "^kind: Secret" "$file"; then + # Allow secure secret types + if ! grep -q -E "^kind: (SealedSecret|ExternalSecret|VaultStaticSecret|VaultDynamicSecret)" "$file"; then + echo "BLOCKED: $file contains a plain Kubernetes Secret" >&2 + echo " Use VaultStaticSecret or VaultDynamicSecret instead" >&2 + ((ERRORS++)) + fi + fi +done < <(git diff --cached --name-only --diff-filter=ACM -z | grep -zE '\.(yaml|yml)$') + +exit $ERRORS