Skip to content

Commit 68b27ae

Browse files
beraldolealbutler54
andcommitted
coco: initial integration with ztvp
This adds initial integration for Confidential Containers and Trustee Operators as a separated clustergroup. Co-authored-by: Chris Butler <chris.butler@redhat.com> Signed-off-by: Beraldo Leal <bleal@redhat.com>
1 parent 1e58626 commit 68b27ae

9 files changed

Lines changed: 841 additions & 0 deletions

ansible/azure-nat-gateway.yaml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
3+
- name: Configure Azure NAT Gateway
4+
become: false
5+
connection: local
6+
hosts: localhost
7+
gather_facts: false
8+
vars:
9+
kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}"
10+
resource_prefix: "coco"
11+
tasks:
12+
- name: Get Azure credentials
13+
kubernetes.core.k8s_info:
14+
kind: Secret
15+
namespace: openshift-cloud-controller-manager
16+
name: azure-cloud-credentials
17+
register: azure_credentials
18+
retries: 20
19+
delay: 5
20+
21+
- name: Get Azure configuration
22+
kubernetes.core.k8s_info:
23+
kind: ConfigMap
24+
namespace: openshift-cloud-controller-manager
25+
name: cloud-conf
26+
register: azure_cloud_conf
27+
retries: 20
28+
delay: 5
29+
30+
- name: Set facts
31+
ansible.builtin.set_fact:
32+
azure_subscription_id: "{{ (azure_cloud_conf.resources[0]['data']['cloud.conf'] | from_json)['subscriptionId'] }}"
33+
azure_tenant_id: "{{ (azure_cloud_conf.resources[0]['data']['cloud.conf'] | from_json)['tenantId'] }}"
34+
azure_resource_group: "{{ (azure_cloud_conf.resources[0]['data']['cloud.conf'] | from_json)['vnetResourceGroup'] }}"
35+
azure_client_id: "{{ azure_credentials.resources[0]['data']['azure_client_id'] | b64decode }}"
36+
azure_client_secret: "{{ azure_credentials.resources[0]['data']['azure_client_secret'] | b64decode }}"
37+
azure_vnet: "{{ (azure_cloud_conf.resources[0]['data']['cloud.conf'] | from_json)['vnetName'] }}"
38+
azure_subnet: "{{ (azure_cloud_conf.resources[0]['data']['cloud.conf'] | from_json)['subnetName'] }}"
39+
coco_public_ip_name: "{{ resource_prefix }}-pip"
40+
coco_nat_gateway_name: "{{ resource_prefix }}-nat-gateway"
41+
no_log: true
42+
43+
- name: Create Public IP for NAT Gateway
44+
azure.azcollection.azure_rm_publicipaddress:
45+
subscription_id: "{{ azure_subscription_id }}"
46+
tenant: "{{ azure_tenant_id }}"
47+
client_id: "{{ azure_client_id }}"
48+
secret: "{{ azure_client_secret }}"
49+
resource_group: "{{ azure_resource_group }}"
50+
name: "{{ coco_public_ip_name }}"
51+
sku: "standard"
52+
allocation_method: "static"
53+
54+
- name: Retrieve Public IP for NAT Gateway
55+
azure.azcollection.azure_rm_publicipaddress_info:
56+
subscription_id: "{{ azure_subscription_id }}"
57+
tenant: "{{ azure_tenant_id }}"
58+
client_id: "{{ azure_client_id }}"
59+
secret: "{{ azure_client_secret }}"
60+
resource_group: "{{ azure_resource_group }}"
61+
name: "{{ coco_public_ip_name }}"
62+
register: coco_gw_public_ip
63+
64+
- name: Create NAT Gateway
65+
azure.azcollection.azure_rm_natgateway:
66+
subscription_id: "{{ azure_subscription_id }}"
67+
tenant: "{{ azure_tenant_id }}"
68+
client_id: "{{ azure_client_id }}"
69+
secret: "{{ azure_client_secret }}"
70+
resource_group: "{{ azure_resource_group }}"
71+
name: "{{ coco_nat_gateway_name }}"
72+
idle_timeout_in_minutes: 10
73+
sku:
74+
name: standard
75+
public_ip_addresses:
76+
- "{{ coco_gw_public_ip.publicipaddresses[0].id }}"
77+
register: coco_natgw
78+
79+
- name: Update the worker subnet to associate NAT gateway
80+
azure.azcollection.azure_rm_subnet:
81+
subscription_id: "{{ azure_subscription_id }}"
82+
tenant: "{{ azure_tenant_id }}"
83+
client_id: "{{ azure_client_id }}"
84+
secret: "{{ azure_client_secret }}"
85+
resource_group: "{{ azure_resource_group }}"
86+
name: "{{ azure_subnet }}"
87+
virtual_network_name: "{{ azure_vnet }}"
88+
nat_gateway: "{{ coco_nat_gateway_name }}"

ansible/azure-requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
azure-identity>=1.19.0
2+
azure-mgmt-core>=1.4.0
3+
azure-mgmt-managementgroups>=1.0.0
4+
azure-mgmt-network>=25.0.0
5+
azure-mgmt-resource>=23.0.0

ansible/init-data-gzipper.yaml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
- name: Gzip initdata
2+
become: false
3+
connection: local
4+
hosts: localhost
5+
gather_facts: false
6+
vars:
7+
kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}"
8+
cluster_platform: "{{ global.clusterPlatform | default('none') | lower }}"
9+
hub_domain: "{{ global.hubClusterDomain | default('none') | lower}}"
10+
image_security_policy: "{{ coco.imageSecurityPolicy | default('insecure') }}"
11+
template_src: "initdata-default.toml.tpl"
12+
tasks:
13+
- name: Create temporary working directory
14+
ansible.builtin.tempfile:
15+
state: directory
16+
suffix: initdata
17+
register: tmpdir
18+
- name: Read KBS TLS secret from Kubernetes
19+
kubernetes.core.k8s_info:
20+
kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}"
21+
api_version: v1
22+
kind: Secret
23+
name: kbs-tls-self-signed
24+
namespace: imperative
25+
register: kbs_secret_result
26+
27+
- name: Extract and decode certificate from secret
28+
ansible.builtin.set_fact:
29+
trustee_cert: "{{ kbs_secret_result.resources[0].data['tls.crt'] | b64decode }}"
30+
when: kbs_secret_result.resources | length > 0
31+
32+
- name: Fail if certificate not found
33+
ansible.builtin.fail:
34+
msg: "KBS TLS certificate not found in secret 'kbs-tls-self-signed' in namespace 'imperative'"
35+
when: kbs_secret_result.resources | length == 0
36+
37+
- name: Define temp file paths
38+
ansible.builtin.set_fact:
39+
rendered_path: "{{ tmpdir.path }}/rendered.toml"
40+
41+
- name: Render template to temp file
42+
ansible.builtin.template:
43+
src: "{{ template_src }}"
44+
dest: "{{ rendered_path }}"
45+
mode: "0600"
46+
47+
48+
- name: Gzip and base64 encode the rendered content
49+
ansible.builtin.shell: |
50+
cat "{{ rendered_path }}" | gzip | base64 -w0
51+
register: initdata_encoded
52+
changed_when: false
53+
54+
- name: Compute PCR8 hash from initdata
55+
ansible.builtin.shell: |
56+
hash=$(sha256sum "{{ rendered_path }}" | cut -d' ' -f1)
57+
initial_pcr=0000000000000000000000000000000000000000000000000000000000000000
58+
echo -n "$initial_pcr$hash" | python3 -c "import sys,hashlib; print(hashlib.sha256(bytes.fromhex(sys.stdin.read())).hexdigest())"
59+
register: pcr8_hash
60+
61+
- name: Create/update ConfigMap with gzipped+base64 content
62+
kubernetes.core.k8s:
63+
kubeconfig: "{{ kubeconfig | default(omit) }}"
64+
state: present
65+
definition:
66+
apiVersion: v1
67+
kind: ConfigMap
68+
metadata:
69+
name: "initdata"
70+
namespace: "imperative"
71+
data:
72+
INITDATA: "{{ initdata_encoded.stdout }}"
73+
PCR8_HASH: "{{ pcr8_hash.stdout }}"

ansible/initdata-default.toml.tpl

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# NOTE: PodVMs run in separate VMs outside the cluster network, so they cannot
2+
# resolve cluster-internal service DNS (*.svc.cluster.local). Therefore, we must
3+
# use the external KBS route even for same-cluster deployments.
4+
# For multi-cluster deployments, this also points to the trusted cluster's KBS.
5+
6+
algorithm = "sha256"
7+
version = "0.1.0"
8+
9+
[data]
10+
"aa.toml" = '''
11+
[token_configs]
12+
[token_configs.coco_as]
13+
url = "https://kbs.{{ hub_domain }}"
14+
15+
[token_configs.kbs]
16+
url = "https://kbs.{{ hub_domain }}"
17+
cert = """{{ trustee_cert }}"""
18+
'''
19+
20+
"cdh.toml" = '''
21+
socket = 'unix:///run/confidential-containers/cdh.sock'
22+
credentials = []
23+
24+
[kbc]
25+
name = "cc_kbc"
26+
url = "https://kbs.{{ hub_domain }}"
27+
kbs_cert = """{{ trustee_cert }}"""
28+
29+
[image]
30+
# Container image signature verification policy
31+
# Options: insecure, reject, signed (configured via coco.imageSecurityPolicy in values)
32+
image_security_policy_uri = "kbs:///default/security-policy/{{ image_security_policy }}"
33+
'''
34+
35+
"policy.rego" = '''
36+
package agent_policy
37+
38+
import future.keywords.in
39+
import future.keywords.if
40+
import future.keywords.every
41+
42+
default AddARPNeighborsRequest := true
43+
default AddSwapRequest := true
44+
default CloseStdinRequest := true
45+
default CopyFileRequest := true
46+
default CreateContainerRequest := true
47+
default CreateSandboxRequest := true
48+
default DestroySandboxRequest := true
49+
default GetMetricsRequest := true
50+
default GetOOMEventRequest := true
51+
default GuestDetailsRequest := true
52+
default ListInterfacesRequest := true
53+
default ListRoutesRequest := true
54+
default MemHotplugByProbeRequest := true
55+
default OnlineCPUMemRequest := true
56+
default PauseContainerRequest := true
57+
default PullImageRequest := true
58+
default ReadStreamRequest := true
59+
default RemoveContainerRequest := true
60+
default RemoveStaleVirtiofsShareMountsRequest := true
61+
default ReseedRandomDevRequest := true
62+
default ResumeContainerRequest := true
63+
default SetGuestDateTimeRequest := true
64+
default SignalProcessRequest := true
65+
default StartContainerRequest := true
66+
default StartTracingRequest := true
67+
default StatsContainerRequest := true
68+
default StopTracingRequest := true
69+
default TtyWinResizeRequest := true
70+
default UpdateContainerRequest := true
71+
default UpdateEphemeralMountsRequest := true
72+
default UpdateInterfaceRequest := true
73+
default UpdateRoutesRequest := true
74+
default WaitProcessRequest := true
75+
# FIXME: ExecProcessRequest and WriteStreamRequest are temporarily restricted
76+
# with a whitelist. This needs proper hardening before production use.
77+
default ExecProcessRequest := false
78+
default SetPolicyRequest := true
79+
default WriteStreamRequest := false
80+
81+
ExecProcessRequest if {
82+
input_command = concat(" ", input.process.Args)
83+
some allowed_command in policy_data.allowed_commands
84+
input_command == allowed_command
85+
}
86+
87+
policy_data := {
88+
"allowed_commands": [
89+
"curl http://127.0.0.1:8006/cdh/resource/default/attestation-status/status",
90+
"curl http://127.0.0.1:8006/cdh/resource/default/attestation-status/random"
91+
]
92+
}
93+
'''

ansible/install-deps.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
- name: Retrieve Credentials for AAP on OpenShift
2+
become: false
3+
connection: local
4+
hosts: localhost
5+
gather_facts: false
6+
tasks:
7+
- name: Ensure collection is installed
8+
community.general.ansible_galaxy_install:
9+
type: collection
10+
name: azure.azcollection
11+
- name: Ensure community.crypto collection is installed
12+
community.general.ansible_galaxy_install:
13+
type: collection
14+
name: community.crypto
15+
- name: Install Azure SDK
16+
ansible.builtin.pip:
17+
requirements: "~/.ansible/collections/ansible_collections/azure/azcollection/requirements.txt"
18+
extra_args: --user

overrides/values-Azure.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Azure platform-specific configuration
2+
3+
# CoCo confidential computing configuration for Azure
4+
global:
5+
coco:
6+
azure:
7+
defaultVMFlavour: "Standard_DC2eds_v5"
8+
VMFlavours: "Standard_DC2eds_v5,Standard_DC4eds_v5,Standard_DC8eds_v5,Standard_DC16eds_v5"

overrides/values-sandbox.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Override the default values for the sandboxed-containers chart
2+
# Configures External Secrets Operator integration for Azure SSH keys
3+
4+
# Secret store configuration for External Secrets Operator
5+
# Points to the ClusterSecretStore that knows how to connect to Vault
6+
secretStore:
7+
name: vault-backend
8+
kind: ClusterSecretStore

overrides/values-trustee.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Override the default values for the trustee chart
2+
# This lists the secret resources that are uploaded to your chosen ESO backend (default: Vault).
3+
# It does not contain the secrets themselves, only references to Vault paths.
4+
#
5+
# NOTE: When adding new CoCo workloads to coco.workloads, you must also add
6+
# corresponding spire-cert-{workload} and spire-key-{workload} entries here
7+
8+
# Secret store configuration for External Secrets Operator
9+
# Points to the ClusterSecretStore that knows how to connect to Vault
10+
secretStore:
11+
name: vault-backend
12+
kind: ClusterSecretStore
13+
14+
kbs:
15+
secretResources:
16+
- name: "attestation-status"
17+
key: "secret/data/hub/attestationStatus"
18+
- name: "passphrase"
19+
key: "secret/data/hub/passphrase"
20+
# SPIRE x509pop certificates per workload type
21+
- name: "spire-cert-qtodo"
22+
key: "secret/data/pushsecrets/spire-cert-qtodo"
23+
- name: "spire-key-qtodo"
24+
key: "secret/data/pushsecrets/spire-key-qtodo"

0 commit comments

Comments
 (0)