Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
# The arc-openbao-ca ClusterIssuer signs OpenBao's transport (server)
# certificate. As a ClusterIssuer it is reachable from every namespace, so only
# Certificates and CertificateRequests in the openbao namespace may use it; a
# certificate it issued anywhere else could impersonate OpenBao.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: restrict-arc-openbao-certificate
annotations:
policies.kyverno.io/title: Restrict ARC OpenBao Certificate Issuance
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/minversion: 1.17.0
spec:
background: true
rules:
- name: only-openbao-may-use-credential-ca
match:
any:
- resources:
kinds:
- cert-manager.io/v1/Certificate
- cert-manager.io/v1/CertificateRequest
preconditions:
all:
- key: "{{ request.object.spec.issuerRef.name || '' }}"
operator: Equals
value: arc-openbao-ca
- key: "{{ request.object.spec.issuerRef.kind || 'Issuer' }}"
operator: Equals
value: ClusterIssuer
validate:
failureAction: Enforce
message: The dedicated credential-service CA may issue only in OpenBao.
deny:
conditions:
any:
- key: "{{ request.object.metadata.namespace || '' }}"
operator: NotEquals
value: openbao
validationActions:
- Deny
evaluation:
background:
enabled: true
matchConstraints:
resourceRules:
- apiGroups: ["cert-manager.io"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["certificates", "certificaterequests"]
variables:
# Only requests for the dedicated credential-service CA are restricted. An
# unset issuerRef.kind means a namespaced Issuer, which is a different CA.
# This is part of the validation rather than a matchCondition so every
# certificate keeps a current report result (docs/policy-reports.md).
- name: usesCredentialCA
expression: >-
object.spec.?issuerRef.?name.orValue('') == 'arc-openbao-ca' &&
object.spec.?issuerRef.?kind.orValue('Issuer') == 'ClusterIssuer'
validations:
- expression: >-
!variables.usesCredentialCA ||
object.metadata.?namespace.orValue('') == 'openbao'
message: The dedicated credential-service CA may issue only in OpenBao.
36 changes: 17 additions & 19 deletions scripts/tests/test-verify-arc-transport.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ go build -o "$scratch/evidence" ./scripts/verify-arc-transport
export PROOF_TEST_ROOT="$scratch"
readonly head=1111111111111111111111111111111111111111
export GITHUB_ACTIONS=true GITHUB_REPOSITORY=devantler-tech/platform GITHUB_REF=refs/heads/main GITHUB_SHA="$head" GITHUB_RUN_ID=12345 GITHUB_RUN_ATTEMPT=1
# Native Kyverno v1.19.1 admission adds both rule defaults. Keep the fixture
# independent of the verifier's expected-spec construction.
# The issuance restriction is a ValidatingPolicy: its CRD adds no spec defaults
# and it reports readiness in status.conditionStatus (as Kyverno v1.19.1 does).
yq -o=json '.spec' k8s/providers/hetzner/infrastructure/cluster-policies/restrict-arc-openbao-certificate.yaml \
| jq '.rules |= map(.skipBackgroundRequests=true | .validate.allowExistingViolations=true) |
{spec:(. + {admission:true,emitWarning:false,validationFailureAction:"Audit"}),
status:{conditions:[{type:"Ready",status:"True"}]}}' >"$scratch/policy.json"
| jq '{metadata:{uid:"issuance-policy-uid"},spec:.,status:{conditionStatus:{ready:true}}}' >"$scratch/policy.json"
# Native Kyverno v1.19.1 admission adds both rule defaults to the listener
# ClusterPolicy. Keep the fixture independent of the verifier's expected-spec
# construction.
yq -o=json '.spec' k8s/bases/infrastructure/cluster-policies/best-practices/restrict-arc-openbao-listener.yaml \
| jq '.rules |= map(.skipBackgroundRequests=true | .validate.allowExistingViolations=true) |
{metadata:{uid:"policy-uid"},spec:(. + {admission:true,emitWarning:false,validationFailureAction:"Audit"}),
Expand All @@ -29,14 +30,12 @@ for file in k8s/providers/hetzner/infrastructure/cluster-policies/restrict-arc-o
k8s/providers/hetzner/infrastructure/controllers/openbao/transport/config-map-listener.yaml; do
mkdir -p "$scratch/source/$(dirname "$file")"
cp "$file" "$scratch/source/$file"
if [[ "$file" == *cluster-policies* ]]; then
if [[ "$file" == *restrict-arc-openbao-listener* ]]; then
yq -i '.spec.rules[].skipBackgroundRequests = false | .spec.rules[].validate.allowExistingViolations = false' "$scratch/source/$file"
fi
done
for policy in policy listener-policy; do
jq '.spec.rules |= map(.skipBackgroundRequests=false | .validate.allowExistingViolations=false)' \
"$scratch/$policy.json" >"$scratch/explicit-$policy.json"
done
jq '.spec.rules |= map(.skipBackgroundRequests=false | .validate.allowExistingViolations=false)' \
"$scratch/listener-policy.json" >"$scratch/explicit-listener-policy.json"
cat >"$scratch/bin/gh" <<'MOCK'
#!/usr/bin/env bash
printf '%s\n' "${GITHUB_SHA}"
Expand Down Expand Up @@ -103,15 +102,14 @@ case "$args" in
elif [[ "${PROOF_CASE:-}" == listener_failure_open ]]; then jq '.spec.failurePolicy="Ignore"' "$PROOF_TEST_ROOT/listener-policy.json"
elif [[ "${PROOF_CASE:-}" == listener_override ]]; then jq '.spec.rules[0].validate.failureActionOverrides=[{action:"Audit",namespaces:["openbao"]}]' "$PROOF_TEST_ROOT/listener-policy.json"
else cat "$PROOF_TEST_ROOT/listener-policy.json"; fi ;;
'get clusterpolicy '*)
if [[ "${PROOF_CASE:-}" == source_explicit_false ]]; then cat "$PROOF_TEST_ROOT/explicit-policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_admission_disabled ]]; then jq '.spec.admission=false' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_background_drift ]]; then jq '.spec.rules[0].skipBackgroundRequests=false' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_existing_drift ]]; then jq '.spec.rules[0].validate.allowExistingViolations=false' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_unknown_field ]]; then jq '.spec.rules[0].unexpected=true' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_exclusion ]]; then jq '.spec.rules[0].exclude={any:[{resources:{namespaces:["arc-runners"]}}]}' "$PROOF_TEST_ROOT/policy.json"
'get validatingpolicies.policies.kyverno.io restrict-arc-openbao-certificate '*)
if [[ "${PROOF_CASE:-}" == issuance_admission_disabled ]]; then jq '.spec.evaluation.admission={enabled:false}' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_background_drift ]]; then jq '.spec.evaluation.background.enabled=false' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_not_ready ]]; then jq '.status.conditionStatus.ready=false' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_unknown_field ]]; then jq '.spec.unexpected=true' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_exclusion ]]; then jq '.spec.matchConditions=[{name:"not-arc-runners",expression:"object.metadata.namespace != \"arc-runners\""}]' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_failure_open ]]; then jq '.spec.failurePolicy="Ignore"' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_override ]]; then jq '.spec.rules[0].validate.failureActionOverrides=[{action:"Audit",namespaces:["arc-runners"]}]' "$PROOF_TEST_ROOT/policy.json"
elif [[ "${PROOF_CASE:-}" == issuance_override ]]; then jq '.spec.validationActions=["Audit"]' "$PROOF_TEST_ROOT/policy.json"
else cat "$PROOF_TEST_ROOT/policy.json"; fi ;;
'get kustomization '*) printf '{"metadata":{"generation":1},"status":{"observedGeneration":1,"lastAppliedRevision":"latest@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","conditions":[{"type":"Ready","status":"True"}]}}' ;;
'get service '*) printf '{"spec":{"selector":{"app.kubernetes.io/name":"openbao","app.kubernetes.io/instance":"openbao","statefulset.kubernetes.io/pod-name":"openbao-2"},"ports":[{"name":"arc-tls","port":8204,"targetPort":8204,"protocol":"TCP"}]}}' ;;
Expand Down Expand Up @@ -296,7 +294,7 @@ for name in sealed split_health stale_probe admit_privilege api_failure api_vali
listener_content listener_churn listener_audit listener_admission_disabled listener_failure_open listener_override \
listener_admit listener_api_error issuance_admission_disabled issuance_exclusion issuance_failure_open issuance_override \
listener_background_drift listener_existing_drift listener_unknown_field \
issuance_background_drift issuance_existing_drift issuance_unknown_field \
issuance_background_drift issuance_not_ready issuance_unknown_field \
identity_stale identity_ordinal_only; do run_case "$name" 1; done
run_case replacement 4
run_case cleanup_failure 4
Expand Down
11 changes: 7 additions & 4 deletions scripts/verify-arc-transport.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,13 @@ jq -e --arg server "https://$host:8204" '.spec.provider.vault.server == $server
.spec.provider.vault.auth.kubernetes == {mountPath:"kubernetes",role:"arc-secret-reader",serviceAccountRef:{name:"arc-secret-reader"}}' "$scratch/store.json" >/dev/null
kc -n "$namespace" get configmap arc-openbao-ca -o json >"$scratch/ca.json"
jq -er '.data["ca.crt"] | select(startswith("-----BEGIN CERTIFICATE-----"))' "$scratch/ca.json" >"$scratch/ca.pem"
kc get clusterpolicy restrict-arc-openbao-certificate -o json >"$scratch/policy.json"
policy_spec k8s/providers/hetzner/infrastructure/cluster-policies/restrict-arc-openbao-certificate.yaml >"$scratch/issuance-spec.json"
# The issuance restriction is a ValidatingPolicy: its CRD adds no spec
# defaults, so the live spec must equal the source spec exactly, and its
# readiness lives in status.conditionStatus.ready.
kc get validatingpolicies.policies.kyverno.io restrict-arc-openbao-certificate -o json >"$scratch/policy.json"
yq -o=json '.spec' k8s/providers/hetzner/infrastructure/cluster-policies/restrict-arc-openbao-certificate.yaml >"$scratch/issuance-spec.json"
Comment thread
devantler marked this conversation as resolved.
jq -e --slurpfile expected "$scratch/issuance-spec.json" '.spec == $expected[0] and
any(.status.conditions[]; .type == "Ready" and .status == "True")' "$scratch/policy.json" >/dev/null
.status.conditionStatus.ready == true' "$scratch/policy.json" >/dev/null
for layer in infrastructure infrastructure-controllers; do
kc -n flux-system get kustomization "$layer" -o json | jq -ce 'select(.status.observedGeneration == .metadata.generation and
any(.status.conditions[]; .type == "Ready" and .status == "True")) | .status.lastAppliedRevision | select(test("^latest@sha256:[0-9a-f]{64}$"))' >"$scratch/$layer-revision"
Expand Down Expand Up @@ -299,7 +302,7 @@ for object in 'secretstore openbao' 'configmap arc-openbao-ca'; do
jq -Sc '{uid:.metadata.uid,spec:.spec,data:.data}' "$scratch/$original.json" >"$scratch/before"
cmp -s "$scratch/before" "$scratch/after" || fail transport-churn
done
kc get clusterpolicy restrict-arc-openbao-certificate -o json | jq -Sc '{uid:.metadata.uid,spec:.spec}' >"$scratch/after"
kc get validatingpolicies.policies.kyverno.io restrict-arc-openbao-certificate -o json | jq -Sc '{uid:.metadata.uid,spec:.spec}' >"$scratch/after"
jq -Sc '{uid:.metadata.uid,spec:.spec}' "$scratch/policy.json" >"$scratch/before"
cmp -s "$scratch/before" "$scratch/after" || fail issuance-policy-churn
kc -n external-secrets get pods -l app.kubernetes.io/name=external-secrets -o json | jq -Sc '[.items[]|{uid:.metadata.uid,ip:.status.podIP,node:.spec.nodeName,containers:.status.containerStatuses}] | sort_by(.uid)' >"$scratch/after"
Expand Down
2 changes: 1 addition & 1 deletion tests/policy-failure-actions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"restrict-explicit-dns-records/approved-platform-records": "Enforce",
"restrict-arc-openbao-certificate/only-openbao-may-use-credential-ca": "Enforce",
"restrict-arc-openbao-certificate": "Enforce",
"restrict-arc-openbao-listener/public-listener-settings-only": "Enforce",
"require-baseline-workers-for-hcloud-volumes/require-baseline-worker-affinity": "Enforce",
"require-baseline-workers-for-hcloud-volumes/report-claim-lookup-failure": "Audit",
Expand Down
15 changes: 9 additions & 6 deletions tests/restrict-arc-openbao-certificate/kyverno-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,35 @@ resources:
- resources.yaml
results:
- policy: restrict-arc-openbao-certificate
rule: only-openbao-may-use-credential-ca
resources:
- openbao/server-certificate
kind: Certificate
result: pass
- policy: restrict-arc-openbao-certificate
rule: only-openbao-may-use-credential-ca
resources:
- openbao/server-request
kind: CertificateRequest
result: pass
- policy: restrict-arc-openbao-certificate
rule: only-openbao-may-use-credential-ca
resources:
- arc-runners/impersonated-server
kind: Certificate
result: fail
- policy: restrict-arc-openbao-certificate
rule: only-openbao-may-use-credential-ca
resources:
- arc-runners/impersonated-request
kind: CertificateRequest
result: fail
# Same issuer name, but a namespaced Issuer (explicit or by default) is a
# different CA and not restricted.
- policy: restrict-arc-openbao-certificate
rule: only-openbao-may-use-credential-ca
resources:
- cert-manager/unrelated-ca
- arc-runners/same-name-namespaced-issuer
kind: Certificate
result: skip
result: pass
- policy: restrict-arc-openbao-certificate
resources:
- arc-runners/same-name-default-kind
kind: CertificateRequest
result: pass
25 changes: 25 additions & 0 deletions tests/restrict-arc-openbao-certificate/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,28 @@ spec:
name: unrelated-selfsigned
kind: Issuer
group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: same-name-namespaced-issuer
namespace: arc-runners
spec:
secretName: synthetic-namespaced-tls
dnsNames:
- namespaced.arc-runners.svc.cluster.local
issuerRef:
name: arc-openbao-ca
kind: Issuer
group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
name: same-name-default-kind
namespace: arc-runners
spec:
request: dGVzdA==
issuerRef:
name: arc-openbao-ca
group: cert-manager.io
Loading