From 7088f9cd31b34688dbd9397262099e73cf435f74 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 10 Dec 2025 15:04:31 +0900 Subject: [PATCH 01/16] chore: improve wrapper scripts for flexibility Signed-off-by: Chris Butler --- rhdp/rhdp-cluster-define.py | 34 ++++++++++++++++++--- rhdp/wrapper.sh | 61 ++++++++++++++++++++++++++++++++----- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/rhdp/rhdp-cluster-define.py b/rhdp/rhdp-cluster-define.py index 522c0bb3..b65cb4b3 100644 --- a/rhdp/rhdp-cluster-define.py +++ b/rhdp/rhdp-cluster-define.py @@ -13,8 +13,22 @@ from typing_extensions import Annotated -def get_default_cluster_configs() -> List[Dict]: - """Get default cluster configurations""" +def get_default_cluster_configs(prefix: str = "") -> List[Dict]: + """Get default cluster configurations + + Args: + prefix: Optional prefix to add to cluster name and directory + """ + if prefix: + return [ + { + "name": f"coco-{prefix}", + "directory": f"openshift-install-{prefix}", + "cluster_network_cidr": "10.128.0.0/14", + "machine_network_cidr": "10.0.0.0/16", + "service_network_cidr": "172.30.0.0/16", + } + ] return [ { "name": "coco", @@ -135,6 +149,9 @@ def run( multicluster: Annotated[ bool, typer.Option("--multicluster", help="Deploy hub and spoke clusters") ] = False, + prefix: Annotated[ + str, typer.Option("--prefix", help="Prefix for cluster name and directory") + ] = "", ): """ Region flag requires an azure region key which can be (authoritatively) @@ -142,16 +159,25 @@ def run( Use --multicluster flag to deploy both hub (coco-hub) and spoke (coco-spoke) clusters. + + Use --prefix to add a prefix to cluster name and install directory, enabling + multiple cluster deployments (e.g., --prefix cluster1 creates coco-cluster1 + in openshift-install-cluster1). """ validate_dir() # Choose cluster configurations based on multicluster flag if multicluster: + if prefix: + rprint("WARNING: --prefix is ignored when using --multicluster") cluster_configs = get_multicluster_configs() rprint("Setting up multicluster deployment (hub and spoke)") else: - cluster_configs = get_default_cluster_configs() - rprint("Setting up single cluster deployment") + cluster_configs = get_default_cluster_configs(prefix) + if prefix: + rprint(f"Setting up single cluster deployment with prefix: {prefix}") + else: + rprint("Setting up single cluster deployment") cleanup(pathlib.Path.cwd(), cluster_configs) setup_install( diff --git a/rhdp/wrapper.sh b/rhdp/wrapper.sh index 5fbf6994..5bc1f992 100755 --- a/rhdp/wrapper.sh +++ b/rhdp/wrapper.sh @@ -14,13 +14,56 @@ get_python_cmd() { fi } -if [ "$#" -ne 1 ]; then - echo "Error: Exactly one argument is required." - echo "Usage: $0 {azure-region-code}" +# Parse arguments +AZUREREGION="" +PREFIX="" + +while [[ $# -gt 0 ]]; do + case $1 in + --prefix) + PREFIX="$2" + shift 2 + ;; + --prefix=*) + PREFIX="${1#*=}" + shift + ;; + -*) + echo "Error: Unknown option $1" + echo "Usage: $0 [--prefix ] {azure-region-code}" + echo "Example: $0 eastasia" + echo "Example: $0 --prefix cluster1 eastasia" + exit 1 + ;; + *) + if [ -z "$AZUREREGION" ]; then + AZUREREGION="$1" + else + echo "Error: Too many positional arguments." + echo "Usage: $0 [--prefix ] {azure-region-code}" + exit 1 + fi + shift + ;; + esac +done + +if [ -z "$AZUREREGION" ]; then + echo "Error: Azure region is required." + echo "Usage: $0 [--prefix ] {azure-region-code}" echo "Example: $0 eastasia" + echo "Example: $0 --prefix cluster1 eastasia" exit 1 fi -AZUREREGION=$1 + +# Set install directory based on prefix +if [ -n "$PREFIX" ]; then + INSTALL_DIR="openshift-install-${PREFIX}" + echo "Using prefix: $PREFIX" + echo "Install directory: $INSTALL_DIR" +else + INSTALL_DIR="openshift-install" +fi echo "---------------------" echo "Validating configuration" @@ -113,7 +156,11 @@ echo "---------------------" echo "defining cluster" echo "---------------------" PYTHON_CMD=$(get_python_cmd) -$PYTHON_CMD rhdp/rhdp-cluster-define.py ${AZUREREGION} +if [ -n "$PREFIX" ]; then + $PYTHON_CMD rhdp/rhdp-cluster-define.py --prefix "${PREFIX}" ${AZUREREGION} +else + $PYTHON_CMD rhdp/rhdp-cluster-define.py ${AZUREREGION} +fi echo "---------------------" echo "cluster defined" echo "---------------------" @@ -121,7 +168,7 @@ sleep 10 echo "---------------------" echo "openshift-install" echo "---------------------" -openshift-install create cluster --dir=./openshift-install +openshift-install create cluster --dir=./${INSTALL_DIR} echo "openshift-install done" echo "---------------------" echo "setting up secrets" @@ -133,7 +180,7 @@ sleep 60 echo "---------------------" echo "pattern install" echo "---------------------" -export KUBECONFIG="$(pwd)/openshift-install/auth/kubeconfig" +export KUBECONFIG="$(pwd)/${INSTALL_DIR}/auth/kubeconfig" ./pattern.sh make install From 4b6407225c2ff7a85b28a7cf066433c286c5d8c8 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 10 Dec 2025 15:13:24 +0900 Subject: [PATCH 02/16] feat: trustee GA Signed-off-by: Chris Butler --- values-simple.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/values-simple.yaml b/values-simple.yaml index a6405a2d..9e14023f 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -26,14 +26,14 @@ clusterGroup: source: redhat-operators channel: stable installPlanApproval: Manual - csv: sandboxed-containers-operator.v1.10.1 + csv: sandboxed-containers-operator.v1.11.0 trustee: name: trustee-operator namespace: trustee-operator-system source: redhat-operators channel: stable installPlanApproval: Manual - csv: trustee-operator.v0.4.1 + csv: trustee-operator.v1.0.0 cert-manager: name: openshift-cert-manager-operator namespace: cert-manager-operator From 9f0074bb7d510d3662d6acb08e329205316a5e79 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 17 Dec 2025 11:23:27 +0900 Subject: [PATCH 03/16] fix: test uplifting posture Signed-off-by: Chris Butler --- ansible/init-data-gzipper.yaml | 19 ++++++++++++++++++- ansible/initdata-default.toml.tpl | 23 ++++++++++++++++++++--- values-secret.yaml.template | 30 +++++++++++++++++++++--------- values-simple.yaml | 26 +++++++++++++++++++++----- 4 files changed, 80 insertions(+), 18 deletions(-) diff --git a/ansible/init-data-gzipper.yaml b/ansible/init-data-gzipper.yaml index 20459b84..c6e04f05 100644 --- a/ansible/init-data-gzipper.yaml +++ b/ansible/init-data-gzipper.yaml @@ -1,4 +1,4 @@ -- name: Gzip initdata +- name: Gzip initdata and register init data become: false connection: local hosts: localhost @@ -55,6 +55,22 @@ path: "{{ gz_path }}" register: gz_slurped + # This block runs a shell script that calculates a hash value (PCR8_HASH) derived from the contents of 'initdata.toml'. + # The script performs the following steps: + # 1. hash=$(sha256sum initdata.toml | cut -d' ' -f1): Computes the sha256 hash of 'initdata.toml' and assigns it to $hash. + # 2. initial_pcr=000000000000000000000000000000000000000000000000000000000000000: Initializes a string of zeros as the initial PCR value. + # 3. PCR8_HASH=$(echo -n "$initial_pcr$hash" | xxd -r -p | sha256sum | cut -d' ' -f1): Concatenates initial_pcr and $hash, converts from hex to binary, computes its sha256 hash, and stores the result as PCR8_HASH. + # 4. echo $PCR8_HASH: Outputs the PCR hash value. + # The important part: The 'register: pcr8_hash' registers the **stdout of the command**, which is the value output by 'echo $PCR8_HASH', as 'pcr8_hash.stdout' in Ansible. + # It does NOT register an environment variable, but rather the value actually printed by 'echo'. + - name: Register init data pcr into a var + ansible.builtin.shell: | + hash=$(sha256sum initdata.toml | cut -d' ' -f1) + initial_pcr=000000000000000000000000000000000000000000000000000000000000000 + PCR8_HASH=$(echo -n "$initial_pcr$hash" | xxd -r -p | sha256sum | cut -d' ' -f1) && echo $PCR8_HASH + register: pcr8_hash + + - name: Create/update ConfigMap with gzipped+base64 content kubernetes.core.k8s: kubeconfig: "{{ kubeconfig | default(omit) }}" @@ -67,3 +83,4 @@ namespace: "imperative" data: INITDATA: "{{ gz_slurped.content }}" + PCR8_HASH: "{{ pcr8_hash.stdout }}" diff --git a/ansible/initdata-default.toml.tpl b/ansible/initdata-default.toml.tpl index 9cadbc1c..b1cc5a0b 100644 --- a/ansible/initdata-default.toml.tpl +++ b/ansible/initdata-default.toml.tpl @@ -26,6 +26,10 @@ kbs_cert = """ """ ''' +[image] +image_security_policy_uri = 'kbs:///default/security-policy/osc +''' + "policy.rego" = ''' package agent_policy @@ -36,7 +40,6 @@ default CopyFileRequest := true default CreateContainerRequest := true default CreateSandboxRequest := true default DestroySandboxRequest := true -default ExecProcessRequest := false default GetMetricsRequest := true default GetOOMEventRequest := true default GuestDetailsRequest := true @@ -52,7 +55,6 @@ default RemoveStaleVirtiofsShareMountsRequest := true default ReseedRandomDevRequest := true default ResumeContainerRequest := true default SetGuestDateTimeRequest := true -default SetPolicyRequest := true default SignalProcessRequest := true default StartContainerRequest := true default StartTracingRequest := true @@ -64,5 +66,20 @@ default UpdateEphemeralMountsRequest := true default UpdateInterfaceRequest := true default UpdateRoutesRequest := true default WaitProcessRequest := true -default WriteStreamRequest := true +default ExecProcessRequest := false +default SetPolicyRequest := false +default WriteStreamRequest := false + +ExecProcessRequest if { + input_command = concat(" ", input.process.Args) + some allowed_command in policy_data.allowed_commands + input_command == allowed_command +} + +policy_data := { + "allowed_commands": [ + "curl http://127.0.0.1:8006/cdh/resource/default/attestation-status/status", + "curl http://127.0.0.1:8006/cdh/resource/default/attestation-status/random" + ] +} ''' \ No newline at end of file diff --git a/values-secret.yaml.template b/values-secret.yaml.template index fe410d42..8d614d8b 100644 --- a/values-secret.yaml.template +++ b/values-secret.yaml.template @@ -6,21 +6,32 @@ version: "2.0" # automatically generated inside the vault this should not really matter) secrets: - - name: 'sshKey' - vaultPrefixes: - - global - fields: - - name: id_rsa.pub - path: ~/.coco-pattern/id_rsa.pub - - name: id_rsa - path: ~/.coco-pattern/id_rsa + - name: 'securityPolicyConfig' vaultPrefixes: - hub fields: - name: osc - path: ~/.coco-pattern/security-policy-config.json + value: | + { + "default": [ + { + "type": "insecureAcceptAnything" + }], + "transports": {} + } + + - name: attestationStatus + vaultPrefixes: + - hub + fields: + - name: status + value: 'attested' + - name: random + value: '' + onMissingValue: generate + vaultPolicy: validatedPatternDefaultPolicy - name: kbsPublicKey vaultPrefixes: @@ -61,3 +72,4 @@ secrets: value: '' onMissingValue: generate vaultPolicy: validatedPatternDefaultPolicy + diff --git a/values-simple.yaml b/values-simple.yaml index 9e14023f..99f90c3c 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -77,23 +77,34 @@ clusterGroup: name: trustee namespace: trustee-operator-system #upstream config project: trustee - chart: trustee - chartVersion: 0.1.* + repoURL: https://github.com/butler54/trustee-chart.git + targetRevision: merge-certs + # chart: trustee + # chartVersion: 0.1.* # Use the override file to specify the list of secrets accessible to trustee from the ESO backend (today by default, Vault). extraValueFiles: - '$patternref/overrides/values-trustee.yaml' + # sandbox: + # name: sandbox + # namespace: openshift-sandboxed-containers-operator #upstream config + # project: sandbox + # chart: sandboxed-containers + # chartVersion: 0.0.* sandbox: name: sandbox namespace: openshift-sandboxed-containers-operator #upstream config project: sandbox - chart: sandboxed-containers - chartVersion: 0.0.* + repoURL: https://github.com/butler54/sandboxed-containers-chart.git + targetRevision: remove-ssh + sandbox-policies: name: sandbox-policies namespace: openshift-sandboxed-containers-operator #upstream config chart: sandboxed-policies chartVersion: 0.0.* - +# path: applications/pipeline +# repoURL: https://github.com/you/applications.git +# targetRevision: stable # Letsencrypt is not required anymore for trustee. # It's only here if you need it for your needs. letsencrypt: @@ -117,6 +128,11 @@ clusterGroup: project: workloads path: charts/coco-supported/kbs-access + tmp: + name: tmp + namespace: default + project: hub + path: charts/tmp imperative: # NOTE: We *must* use lists and not hashes. As hashes lose ordering once parsed by helm From 04c9e3c628501b26f6bcfbea20345a38d14cdbed Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 17 Dec 2025 11:52:23 +0900 Subject: [PATCH 04/16] fix: add url path for a repo Signed-off-by: Chris Butler --- values-simple.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/values-simple.yaml b/values-simple.yaml index 99f90c3c..65a45dac 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -79,6 +79,7 @@ clusterGroup: project: trustee repoURL: https://github.com/butler54/trustee-chart.git targetRevision: merge-certs + path: / # chart: trustee # chartVersion: 0.1.* # Use the override file to specify the list of secrets accessible to trustee from the ESO backend (today by default, Vault). @@ -96,6 +97,7 @@ clusterGroup: project: sandbox repoURL: https://github.com/butler54/sandboxed-containers-chart.git targetRevision: remove-ssh + path: / sandbox-policies: name: sandbox-policies From af4185179d4e6d056f93a9902955928eb23eb8ac Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 17 Dec 2025 11:54:53 +0900 Subject: [PATCH 05/16] fix: remove dead chart Signed-off-by: Chris Butler --- values-simple.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/values-simple.yaml b/values-simple.yaml index 65a45dac..8bbd7832 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -130,12 +130,6 @@ clusterGroup: project: workloads path: charts/coco-supported/kbs-access - tmp: - name: tmp - namespace: default - project: hub - path: charts/tmp - imperative: # NOTE: We *must* use lists and not hashes. As hashes lose ordering once parsed by helm # The default schedule is every 10 minutes: imperative.schedule From 71dcba59f67b5770d22f1092a44181f21bb002fe Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 17 Dec 2025 11:56:58 +0900 Subject: [PATCH 06/16] fix: relative urls Signed-off-by: Chris Butler --- values-simple.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/values-simple.yaml b/values-simple.yaml index 8bbd7832..595c848f 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -79,7 +79,7 @@ clusterGroup: project: trustee repoURL: https://github.com/butler54/trustee-chart.git targetRevision: merge-certs - path: / + path: ./ # chart: trustee # chartVersion: 0.1.* # Use the override file to specify the list of secrets accessible to trustee from the ESO backend (today by default, Vault). @@ -97,7 +97,7 @@ clusterGroup: project: sandbox repoURL: https://github.com/butler54/sandboxed-containers-chart.git targetRevision: remove-ssh - path: / + path: ./ sandbox-policies: name: sandbox-policies From a3a23c80544abd5989426b824bdc793a6b148fde Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 17 Dec 2025 14:41:28 +0900 Subject: [PATCH 07/16] fix: update with security policies Signed-off-by: Chris Butler --- ansible/init-data-gzipper.yaml | 1 + ansible/initdata-default.toml.tpl | 2 +- values-global.yaml | 2 ++ values-secret.yaml.template | 50 ++++++++++++++++++++++++++----- values-simple.yaml | 5 ++-- 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/ansible/init-data-gzipper.yaml b/ansible/init-data-gzipper.yaml index c6e04f05..1d22dfea 100644 --- a/ansible/init-data-gzipper.yaml +++ b/ansible/init-data-gzipper.yaml @@ -7,6 +7,7 @@ kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}" cluster_platform: "{{ global.clusterPlatform | default('none') | lower }}" hub_domain: "{{ global.hubClusterDomain | default('none') | lower}}" + security_policy_flavour: "{{ global.coco.securityPolicyFlavour | default('insecure') }}" template_src: "initdata-default.toml.tpl" tasks: - name: Create temporary working directory diff --git a/ansible/initdata-default.toml.tpl b/ansible/initdata-default.toml.tpl index b1cc5a0b..9d9442dd 100644 --- a/ansible/initdata-default.toml.tpl +++ b/ansible/initdata-default.toml.tpl @@ -27,7 +27,7 @@ kbs_cert = """ ''' [image] -image_security_policy_uri = 'kbs:///default/security-policy/osc +image_security_policy_uri = 'kbs:///default/security-policy/{{ security_policy_flavour }} ''' "policy.rego" = ''' diff --git a/values-global.yaml b/values-global.yaml index e91c7f0c..bead30eb 100644 --- a/values-global.yaml +++ b/values-global.yaml @@ -11,6 +11,7 @@ global: # This defines whether or not to use upstream resources for CoCo. # Defines whether or not the hub cluster can be used for confidential containers coco: + securityPolicyFlavour: "insecure" # insecure, signed or reject is expected. azure: defaultVMFlavour: "Standard_DC2as_v5" VMFlavours: "Standard_DC2as_v5,Standard_DC4as_v5,Standard_DC8as_v5,Standard_DC16as_v5" @@ -24,6 +25,7 @@ main: clusterGroupChartVersion: 0.9.* # Common secret store configuration used across multiple charts +# Warning do not rely on this. it does not consistently apply. secretStore: name: vault-backend kind: ClusterSecretStore diff --git a/values-secret.yaml.template b/values-secret.yaml.template index 8d614d8b..288b78f5 100644 --- a/values-secret.yaml.template +++ b/values-secret.yaml.template @@ -8,19 +8,55 @@ version: "2.0" secrets: - - name: 'securityPolicyConfig' + - name: securityPolicyConfig vaultPrefixes: - hub fields: - - name: osc + # Accept all images without verification (INSECURE - dev/testing only) + - name: insecure value: | { - "default": [ - { - "type": "insecureAcceptAnything" - }], - "transports": {} + "default": [{"type": "insecureAcceptAnything"}], + "transports": {} } + # Reject all images (useful for testing policy enforcement) + - name: reject + value: | + { + "default": [{"type": "reject"}], + "transports": {} + } + # Only accept signed images (production) + # Edit the transports section to add your signed images. + # Each image needs a corresponding cosign public key in cosign-keys secret. + # The keys much line up with the keys below + - name: signed + value: | + { + "default": [{"type": "reject"}], + "transports": { + "docker": { + "registry.example.com/my-image": [ + { + "type": "sigstoreSigned", + "keyPath": "kbs:///default/cosign-keys/key-0" + } + ] + } + } + } + + # Cosign public keys for image signature verification + # Required when using the "signed" policy above. + # Add your cosign public key files here. + # Generate a cosign key pair: cosign generate-key-pair + #- name: cosign-keys + # vaultPrefixes: + # - hub + # fields: + # - name: key-0 + # path: ~/.coco-pattern/trustee/cosign-key-0.pub + - name: attestationStatus vaultPrefixes: diff --git a/values-simple.yaml b/values-simple.yaml index 595c848f..09dda1e3 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -80,11 +80,12 @@ clusterGroup: repoURL: https://github.com/butler54/trustee-chart.git targetRevision: merge-certs path: ./ + extraValueFiles: + - '$patternref/overrides/values-trustee.yaml' # chart: trustee # chartVersion: 0.1.* # Use the override file to specify the list of secrets accessible to trustee from the ESO backend (today by default, Vault). - extraValueFiles: - - '$patternref/overrides/values-trustee.yaml' + # sandbox: # name: sandbox # namespace: openshift-sandboxed-containers-operator #upstream config From 7051aa942ed230b1ede6e2216981f7c447a8371d Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Wed, 17 Dec 2025 14:51:54 +0900 Subject: [PATCH 08/16] fix: clean up secrets and gen secrets script Signed-off-by: Chris Butler --- scripts/gen-secrets.sh | 22 ---------------------- values-secret.yaml.template | 8 +------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/scripts/gen-secrets.sh b/scripts/gen-secrets.sh index 25c4713a..1196b51d 100755 --- a/scripts/gen-secrets.sh +++ b/scripts/gen-secrets.sh @@ -4,8 +4,6 @@ echo "Creating secrets as required" echo COCO_SECRETS_DIR="${HOME}/.coco-pattern" -SECURITY_POLICY_FILE="${COCO_SECRETS_DIR}/security-policy-config.json" -SSH_KEY_FILE="${COCO_SECRETS_DIR}/id_rsa" KBS_PRIVATE_KEY="${COCO_SECRETS_DIR}/kbsPrivateKey" KBS_PUBLIC_KEY="${COCO_SECRETS_DIR}/kbsPublicKey" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -13,19 +11,6 @@ VALUES_FILE="${HOME}/values-secret-coco-pattern.yaml" mkdir -p ${COCO_SECRETS_DIR} -if [ ! -f "${SECURITY_POLICY_FILE}" ]; then -echo "Creating security policy" -cat > ${SECURITY_POLICY_FILE} < Date: Mon, 22 Dec 2025 16:59:18 +0900 Subject: [PATCH 09/16] fix: correct charts Signed-off-by: Chris Butler --- ansible/initdata-default.toml.tpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible/initdata-default.toml.tpl b/ansible/initdata-default.toml.tpl index 9d9442dd..47246798 100644 --- a/ansible/initdata-default.toml.tpl +++ b/ansible/initdata-default.toml.tpl @@ -24,10 +24,10 @@ url = "https://kbs.{{ hub_domain }}" kbs_cert = """ {{ trustee_cert }} """ -''' + [image] -image_security_policy_uri = 'kbs:///default/security-policy/{{ security_policy_flavour }} +image_security_policy_uri = 'kbs:///default/security-policy/{{ security_policy_flavour }}' ''' "policy.rego" = ''' From 7c90be737be15945d17aa265bbe135ac66d5ae75 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Mon, 5 Jan 2026 14:33:44 +0900 Subject: [PATCH 10/16] chore: add flag Signed-off-by: Chris Butler --- values-global.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/values-global.yaml b/values-global.yaml index bead30eb..ceeb1241 100644 --- a/values-global.yaml +++ b/values-global.yaml @@ -12,6 +12,7 @@ global: # Defines whether or not the hub cluster can be used for confidential containers coco: securityPolicyFlavour: "insecure" # insecure, signed or reject is expected. + secured: true # true or false. If true, the cluster will be secured. If false, the cluster will be insecure. azure: defaultVMFlavour: "Standard_DC2as_v5" VMFlavours: "Standard_DC2as_v5,Standard_DC4as_v5,Standard_DC8as_v5,Standard_DC16as_v5" From 73d8cac1e6c77f5faadbb08c22c3c351b1d659b6 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Mon, 5 Jan 2026 16:32:54 +0900 Subject: [PATCH 11/16] feat: add components required for pcr securing Signed-off-by: Chris Butler --- scripts/get-pcr.sh | 103 ++++++++++++++++++++++++++++++++++++ values-secret.yaml.template | 12 +++++ 2 files changed, 115 insertions(+) create mode 100755 scripts/get-pcr.sh diff --git a/scripts/get-pcr.sh b/scripts/get-pcr.sh new file mode 100755 index 00000000..acb34986 --- /dev/null +++ b/scripts/get-pcr.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -e + +# Script to retrieve the sandboxed container operator CSV for the current clusterGroup +# using the pull secret for authentication if needed. + +# 1. Locate pull secret +PULL_SECRET_PATH="${HOME}/pull-secret.json" +if [ ! -f "$PULL_SECRET_PATH" ]; then + if [ -n "${PULL_SECRET}" ]; then + PULL_SECRET_PATH="${PULL_SECRET}" + if [ ! -f "$PULL_SECRET_PATH" ]; then + echo "ERROR: Pull secret file not found at path specified in PULL_SECRET: $PULL_SECRET_PATH" + exit 1 + fi + else + echo "ERROR: Pull secret not found at ~/pull-secret.json" + echo "Please either place your pull secret at ~/pull-secret.json or set the PULL_SECRET environment variable" + exit 1 + fi +fi + +echo "Using pull secret: $PULL_SECRET_PATH" + +# 2. Check for required tools +if ! command -v yq &> /dev/null; then + echo "ERROR: yq is required but not installed" + echo "Please install yq: https://github.com/mikefarah/yq#install" + exit 1 +fi + +# 3. Check values-global.yaml exists +if [ ! -f "values-global.yaml" ]; then + echo "ERROR: values-global.yaml not found in current directory" + echo "Please run this script from the root directory of the project" + exit 1 +fi + +# 4. Get the active clusterGroupName from values-global.yaml +CLUSTER_GROUP_NAME=$(yq eval '.main.clusterGroupName' values-global.yaml) + +if [ -z "$CLUSTER_GROUP_NAME" ] || [ "$CLUSTER_GROUP_NAME" == "null" ]; then + echo "ERROR: Could not determine clusterGroupName from values-global.yaml" + echo "Expected: main.clusterGroupName to be set" + exit 1 +fi + +echo "Active clusterGroup: $CLUSTER_GROUP_NAME" + +# 5. Locate the values file for the active clusterGroup +VALUES_FILE="values-${CLUSTER_GROUP_NAME}.yaml" + +if [ ! -f "$VALUES_FILE" ]; then + echo "ERROR: Values file for clusterGroup not found: $VALUES_FILE" + exit 1 +fi + +# 6. Get the sandboxed container operator CSV from the clusterGroup values +SANDBOX_CSV=$(yq eval '.clusterGroup.subscriptions.sandbox.csv' "$VALUES_FILE") + +if [ -z "$SANDBOX_CSV" ] || [ "$SANDBOX_CSV" == "null" ]; then + echo "WARNING: No sandboxed container operator CSV found in $VALUES_FILE" + echo "The subscription clusterGroup.subscriptions.sandbox.csv is not defined" + exit 0 +fi + +# Extract version from CSV (e.g., "sandboxed-containers-operator.v1.11.0" -> "1.11.0") +# Remove everything up to and including ".v" +SANDBOX_VERSION="${SANDBOX_CSV##*.v}" + +echo "Sandboxed container operator CSV: $SANDBOX_CSV" +echo "Version: $SANDBOX_VERSION" +# alternatively, use the operator-version tag. +# OSC_VERSION=1.11.1 +VERITY_IMAGE=registry.redhat.io/openshift-sandboxed-containers/osc-dm-verity-image + +TAG=$(skopeo inspect --authfile $PULL_SECRET_PATH docker://${VERITY_IMAGE}:${SANDBOX_VERSION} | jq -r .Digest) + +IMAGE=${VERITY_IMAGE}@${TAG} + +echo "IMAGE: $IMAGE" + +curl -L https://tuf-default.apps.rosa.rekor-prod.2jng.p3.openshiftapps.com/targets/rekor.pub -o rekor.pub +curl -L https://security.access.redhat.com/data/63405576.txt -o cosign-pub-key.pem +# export REGISTRY_AUTH_FILE=${PULL_SECRET_PATH} +# echo "REGISTRY_AUTH_FILE: $REGISTRY_AUTH_FILE" +# export SIGSTORE_REKOR_PUBLIC_KEY=${PWD}/rekor.pub +# echo "SIGSTORE_REKOR_PUBLIC_KEY: $SIGSTORE_REKOR_PUBLIC_KEY" +# cosign verify --key cosign-pub-key.pem --output json --rekor-url=https://rekor-server-default.apps.rosa.rekor-prod.2jng.p3.openshiftapps.com $IMAGE > cosign_verify.log + + +# Ensure output directory exists +mkdir -p ~/.coco-pattern + +# Download the measurements using podman cp (works on macOS with remote podman) +podman pull --authfile $PULL_SECRET_PATH $IMAGE + +cid=$(podman create --entrypoint /bin/true $IMAGE) +echo "CID: ${cid}" +podman cp $cid:/image/measurements.json ~/.coco-pattern/measurements.json +podman rm $cid + +echo "Measurements saved to ~/.coco-pattern/measurements.json" \ No newline at end of file diff --git a/values-secret.yaml.template b/values-secret.yaml.template index 1a017a10..b3df87dd 100644 --- a/values-secret.yaml.template +++ b/values-secret.yaml.template @@ -58,6 +58,18 @@ secrets: # path: ~/.coco-pattern/cosign-key-0.pub + # Cosign public keys for image signature verification + # Required when using the "signed" policy above. + # Add your cosign public key files here. + # Generate a cosign key pair: cosign generate-key-pair + #- name: pcrStash + # vaultPrefixes: + # - hub + # fields: + # - name: json + # path: ~/.coco-pattern/measurements.json + + - name: attestationStatus vaultPrefixes: - hub From c51b7ac3a942d597b470dac29f179155602a2160 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Tue, 6 Jan 2026 11:57:45 +0900 Subject: [PATCH 12/16] fix: Add corrected health check Signed-off-by: Chris Butler --- values-simple.yaml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/values-simple.yaml b/values-simple.yaml index 09dda1e3..c6fd4ce5 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -3,6 +3,36 @@ clusterGroup: name: simple isHubCluster: true + # Override health check for Subscriptions to treat UpgradePending as healthy + # Only applies to pinned CSV subscriptions (sandbox and trustee) + argoCD: + resourceHealthChecks: + - group: operators.coreos.com + kind: Subscription + check: | + local hs = {} + -- Only apply custom logic to pinned subscriptions + local isPinned = (obj.metadata.name == "sandboxed-containers-operator" or + obj.metadata.name == "trustee-operator") + if obj.status ~= nil and obj.status.state ~= nil then + local state = obj.status.state + if state == "AtLatestKnown" then + hs.status = "Healthy" + hs.message = state + return hs + elseif state == "UpgradePending" and isPinned then + hs.status = "Healthy" + hs.message = "Pinned subscription at desired version" + return hs + elseif state == "UpgradePending" then + hs.status = "Progressing" + hs.message = "Upgrade pending approval" + return hs + end + end + hs.status = "Progressing" + hs.message = "Waiting for Subscription to be ready" + return hs namespaces: - open-cluster-management - vault From 0a7ecd90df96c53be2443512108253f52ed43ce9 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Tue, 6 Jan 2026 16:10:21 +0900 Subject: [PATCH 13/16] fix: correctly override values Signed-off-by: Chris Butler --- overrides/values-trustee.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/overrides/values-trustee.yaml b/overrides/values-trustee.yaml index ee42e416..03dd120a 100644 --- a/overrides/values-trustee.yaml +++ b/overrides/values-trustee.yaml @@ -6,4 +6,9 @@ kbs: - name: "kbsres1" # name is the name of the k8s secret that will be presented to trustee and accessible via the CDH key: "secret/data/hub/kbsres1" # this is the path to the secret in vault. - name: "passphrase" - key: "secret/data/hub/passphrase" \ No newline at end of file + key: "secret/data/hub/passphrase" +# Override the default values for the coco pattern this is because when testing against a branch strange stuff happens +# FIXME: Don't commit this to main +global: + coco: + secured: true # true or false. If true, the cluster will be secured. If false, the cluster will be insecure. \ No newline at end of file From 0f11573658b44a63ace1646ac0b1876e125b1773 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Tue, 6 Jan 2026 16:24:34 +0900 Subject: [PATCH 14/16] fix: temporarily change for single source charts Signed-off-by: Chris Butler --- values-simple.yaml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/values-simple.yaml b/values-simple.yaml index c6fd4ce5..4689805d 100644 --- a/values-simple.yaml +++ b/values-simple.yaml @@ -110,11 +110,19 @@ clusterGroup: repoURL: https://github.com/butler54/trustee-chart.git targetRevision: merge-certs path: ./ - extraValueFiles: - - '$patternref/overrides/values-trustee.yaml' - # chart: trustee - # chartVersion: 0.1.* - # Use the override file to specify the list of secrets accessible to trustee from the ESO backend (today by default, Vault). + # Note: extraValueFiles with $patternref don't work for external repoURL (single-source app) + # Using overrides instead to pass values directly + overrides: + - name: global.coco.secured + value: "true" + - name: kbs.secretResources[0].name + value: kbsres1 + - name: kbs.secretResources[0].key + value: secret/data/hub/kbsres1 + - name: kbs.secretResources[1].name + value: passphrase + - name: kbs.secretResources[1].key + value: secret/data/hub/passphrase # sandbox: # name: sandbox From fd031b37f50cdf6474633579e954f0f2df30d98b Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Mon, 12 Jan 2026 10:34:00 +0900 Subject: [PATCH 15/16] fix: clean up measurements Signed-off-by: Chris Butler --- scripts/get-pcr.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/get-pcr.sh b/scripts/get-pcr.sh index acb34986..f55bd5f5 100755 --- a/scripts/get-pcr.sh +++ b/scripts/get-pcr.sh @@ -97,7 +97,11 @@ podman pull --authfile $PULL_SECRET_PATH $IMAGE cid=$(podman create --entrypoint /bin/true $IMAGE) echo "CID: ${cid}" -podman cp $cid:/image/measurements.json ~/.coco-pattern/measurements.json +podman cp $cid:/image/measurements.json ~/.coco-pattern/measurements-raw.json podman rm $cid -echo "Measurements saved to ~/.coco-pattern/measurements.json" \ No newline at end of file +# Trim leading "0x" from all measurement values +jq 'walk(if type == "string" and startswith("0x") then .[2:] else . end)' \ + ~/.coco-pattern/measurements-raw.json > ~/.coco-pattern/measurements.json + +echo "Measurements saved to ~/.coco-pattern/measurements.json (0x prefixes removed)" \ No newline at end of file From fda5a3ef6fdc222a2444a1aa8c28f917f6727495 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Mon, 12 Jan 2026 10:57:04 +0900 Subject: [PATCH 16/16] fix: deal with existing file Signed-off-by: Chris Butler --- scripts/get-pcr.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/get-pcr.sh b/scripts/get-pcr.sh index f55bd5f5..c55baa7d 100755 --- a/scripts/get-pcr.sh +++ b/scripts/get-pcr.sh @@ -92,6 +92,9 @@ curl -L https://security.access.redhat.com/data/63405576.txt -o cosign-pub-key.p # Ensure output directory exists mkdir -p ~/.coco-pattern +# Clean up any existing measurement files +rm -f ~/.coco-pattern/measurements-raw.json ~/.coco-pattern/measurements.json + # Download the measurements using podman cp (works on macOS with remote podman) podman pull --authfile $PULL_SECRET_PATH $IMAGE