ccop is a command-line tool that enables security-sensitive Kubernetes operations within a Confidential Containers (CoCo) deployment. The current release supports kubectl exec and kubectl cp. It wraps exec command arguments in a Hybrid Public Key Encryption (HPKE) Auth-mode envelope using a user's private key and a public key provisioned to the workload at startup. This cryptographically binds the user's identity to the exec request and locks command decryption and verification to the workload with the corresponding credential.
ccop cp is built on ccop exec: it streams tar archives through authenticated ccop exec sessions, so file transfers get the same protection as commands.
Additionally, per-session stream keys are derived from the same HPKE context and are used to encrypt all stdin/stdout/stderr traffic end-to-end, so neither the host OS, the container runtime, nor the Kubernetes control plane can observe or tamper with operational data.
- The Kubernetes cluster has a Kata Containers VM root runtime supporting cc-op, e.g.,
kata-qemu-snp-ccop.[!IMPORTANT] A key requirement is that the runtime's root image is compiled with both
AGENT_POLICY=yesandCC_OP=yesoptions enabled. - Trustee is deployed running a Key Broker Service implementing the
credgenPlugin. This plugin dynamically generates cryptographic credentials for confidential VMs.
- Build ccop tool
- Generate key pairs: one for the user
- Retrieve tool, change directory, and build
$ git clone git@github.com:IBM/ccop.git $ cd ccop $ go build
- Create an EC public and private key pair
$ openssl ecparam -name prime256v1 -genkey -noout -out key.priv $ openssl ec -in key.priv -pubout -out key.pub - Write a certificate configuration
$ tee > openssl.cnf <<EOF # Edit generic configuration [ req ] distinguished_name = dn x509_extensions = v3_req prompt = no [ dn ] C = US ST = NY L = New York City O = Development CN = John Doe #emailAddress = johndoe@foobar.com [ v3_req ] basicConstraints = CA:FALSE keyUsage = critical, digitalSignature subjectAltName = @alt_names [ alt_names ] DNS.1 = John Doe email.1 = johndoe@foobar.com EOF - Build a self-signed certificate of the public key
$ openssl req -new -key key.priv -out cert.csr -config openssl.cnf $ openssl x509 -req -in cert.csr -signkey key.priv -out cert.crt -days 365 \ -extensions v3_req -extfile openssl.cnf
-
Save the user certificate in shell variable
$ CERTDATA=$(cat cert.crt) -
Set shell variables to KBS URL and admin bearer token file
$ export KBS_URL=<KBS_SERVER_URL> $ export KBS_ADMIN_TOKEN=/path/to/admin-token -
Set shell variables to the pod name and namespace used in the pod configuration deployed below (this example uses
pod-busybox-execanddefault):$ export PODNAME=pod-busybox-exec $ export NAMESPACE=default -
Create initdata with an agent policy that blocks all exec operations and a ccop config file (
ccop.toml) containing the user's public certificate (auth_cert). The embedded config file enables cc-op for the workload.[!NOTE] The initdata must also specify the pod's
nameandns(namespace). The credential generator plugin (credgen) uses them to verify that a credential request comes from the pod that owns them. They must match the pod'smetadata.nameand namespace.$ tee > initdata.toml <<EOF version = "0.1.0" algorithm = "sha256" [data] "policy.rego" = ''' package agent_policy default AddARPNeighborsRequest := true default AddSwapRequest := true default CloseStdinRequest := true default CopyFileRequest := true default CreateContainerRequest := true default CreateSandboxRequest := true default DestroySandboxRequest := true # Block exec by default default ExecProcessRequest := false default GetMetricsRequest := true default GetOOMEventRequest := true default GuestDetailsRequest := true default ListInterfacesRequest := true default ListRoutesRequest := true default MemHotplugByProbeRequest := true default OnlineCPUMemRequest := true default PauseContainerRequest := true default PullImageRequest := true default ReadStreamRequest := true default RemoveContainerRequest := true default RemoveStaleVirtiofsShareMountsRequest := true default ReseedRandomDevRequest := true default ResumeContainerRequest := true default SetGuestDateTimeRequest := true default SetPolicyRequest := true default SignalProcessRequest := true default StartContainerRequest := true default StartTracingRequest := true default StatsContainerRequest := true default StopTracingRequest := true default TtyWinResizeRequest := true default UpdateContainerRequest := true default UpdateEphemeralMountsRequest := true default UpdateInterfaceRequest := true default UpdateRoutesRequest := true default WaitProcessRequest := true default WriteStreamRequest := true ''' "ccop.toml" = ''' auth_cert =""" ${CERTDATA} """ ''' "aa.toml" = ''' [token_configs] [token_configs.kbs] url = "${KBS_URL}" ''' "cdh.toml" = ''' [kbc] name = "cc_kbc" url = "${KBS_URL}" ''' "name" = "${PODNAME}" "ns" = "${NAMESPACE}" EOF -
Embed base64 encoding of initdata in pod configuration
$ INITDATA=$(cat initdata.toml | gzip | base64 -w0) $ tee > pod-busybox-exec.yaml <<EOF apiVersion: v1 kind: Pod metadata: name: ${PODNAME} namespace: ${NAMESPACE} annotations: io.containerd.cri.runtime-handler: kata-qemu-snp-ccop io.katacontainers.config.hypervisor.cc_init_data: ${INITDATA} spec: runtimeClassName: kata-qemu-snp-ccop containers: - name: busybox image: quay.io/prometheus/busybox:latest imagePullPolicy: Always command: ["sleep", "infinity"] restartPolicy: Never EOF -
Deploy workload
$ kubectl apply -f pod-busybox-exec.yaml -
Display running pod(s)
$ kubectl get pods NAME READY STATUS RESTARTS AGE pod-busybox-exec 1/1 Running 0 16s ...Note: the create pod sandbox operation fails if the pod is unable to retrieve its credential from Trustee. This also happens if the
name/nsin the initdata don't match the pod's actual name and namespace. -
Execute the original
kubectl exec; it fails$ kubectl exec pod-busybox-exec -- ls -C / error: Internal error occurred: Internal error occurred: error executing command in container: failed to exec in container: failed to start exec "3fdc43d613a7096822eea349691ccb938c7290ccb40267291c05e9a009d15478": cannot enter container 6631f6b1c127d85eb49440e2ddfa75a3eb499264ff7d1c1209daca1f3bc29f11, with err rpc error: code = Internal desc = Error: missing arguments: --payload Stack backtrace: 0: ... -
Fetch the pod's public key certificate from Trustee (KBS) and save in
./receiver.crt$ ccop fetch --url "${KBS_URL}" --admin-token-file "${KBS_ADMIN_TOKEN}" --namespace "${NAMESPACE}" \ --pod "${PODNAME}" secret_name=ccop_p256 secret_type=cert kind=self_signed_ca \ algorithm=ec_p256 --out ./receiver.crt -
Run
ccop exec, specifying--privkeyfor the user's private key; and--pubkeycertfor the pod's public certificate; it successfully executes$ ccop exec pod-busybox-exec --privkey ./key.priv --pubkeycert ./receiver.crt -- ls -C / bin dev etc home lib proc root sys tmp usr var
ccop cp copies files and directories to and from a pod, using the same keys and pod certificate as ccop exec. The pod above and its ./receiver.crt can be reused.
Important
ccop cp requires the sh and tar binaries in the container image; it fails if they are not present.
-
Copy a local file into the pod
$ echo "hello" > hello.txt $ ccop cp hello.txt pod-busybox-exec:/tmp --privkey ./key.priv --pubkeycert ./receiver.crt -
Verify the file arrived
$ ccop exec pod-busybox-exec --privkey ./key.priv --pubkeycert ./receiver.crt -- cat /tmp/hello.txt hello
For other copy examples, e.g., copying from a pod or copying directories, run ccop cp -h.
The ccop config command manages and displays the ccop environment. It always requires an option (--admin-token-file, -c, or -l ).
$ ccop config -c
-
Creates the
~/.ccop/directory in the user home directory, containing aconfig.tomlfile and acachesub-directory, containing artifacts from running ccop confidential workloads, e.g., the pod'sreceiver.crt. -
Add paths to the user EC key pair in
config.toml. This avoids specifying--privkeyon every command:$ vim ~/.ccop/config.toml UserPublicKeyCert = "/path/to/user/publickeycert" UserPrivateKey = "/path/to/user/privatekey"
$ ccop config pod-busybox-exec --admin-token-file "${KBS_ADMIN_TOKEN}"
- Extracts KBS's URL from the running pod's initdata.
- Retrieves artifacts, including the public key certificate associated with sandbox environment ...
- This avoids specifying
--pubkeycerton every command.
With both the key paths and the certificate cached, all key flags can be omitted:
$ ccop exec pod-busybox-exec -- ls -C /
bin dev etc home lib proc root sys tmp usr var
$ ccop cp hello.txt pod-busybox-exec:/tmp
$ ccop config -l
=== Pod: default/busybox ===
=== Pod: default/pod-busybox-exec ===
algorithm: sha256
digest: 2iu8vLDEocN72nfOKvfA6fA3BBhgtC7KcdxKZsDcYNc=
ccop.toml: defined
ccop.toml.auth_cert: set
cdh.toml: defined
cdh.toml.kbs: cc_kbc
cdh.toml.url: http://1.2.3.4:8080
=== Pod: default/busybox-noccop-nexec ===
algorithm: sha256
digest: 0zH/DXa2cy0mD/nsSgHTcXToHn0vhkWhHDeXkrPFVRc=
initdata ccop.toml: not found
...
$ ccop config -l pod-busybox-exec
pod-busybox-exec is ccop enabled
Checks whether a pod's initdata contains a ccop.toml with an auth_cert.
- Support for other command-specific options:
-f,-q, and--pod-running-timeout - Support for overriding any command inherited option
- Support for
attachandlogsis planned for follow-on release(s)