Custom initdata
Having such restrictive initdata policy is secure, but it’s a bit inconvinient in some use cases.
Suppose the developer wants to be able to debug or check some informations in a running CoCo pod. With the default restrictive policy, any exec command will be forbidden. On the other side, relaxing to allow exec is also not recommended, as it would allow anyone with cluster/namespace access to enter the pod and inspect data.
The solution is to use a custom initdata policy that only allows specific commands.
|
In general, using custom initdata is the preferred and safer way to define initdata, rather than having a generic single policy for all pods. In this workshop we started with using the generic one to simplify the user experience. |
PERSONA: Operational security expert
A recap on initdata
As we saw during the Trustee setup, initdata is composed of multiple sections, that define image policy signature, Trustee URL and certificates, and the agent policy. The latter is what interests us.
Let’s inspect what we defined as default initdata:
DEFAULT_INITDATA=$(oc get configmaps \
-n openshift-sandboxed-containers-operator \
peer-pods-cm -o jsonpath='{.data.INITDATA'} \
| base64 -d | gunzip)
echo "$DEFAULT_INITDATA" | tail -n 8
Notice how ExecProcessRequest is set to false.
Extending initdata
We will now extend it to allow only some commands:
-
curlto fetch the example secret pre-loaded into Trustee, to show how attestation can be also manually triggered -
A
uname -rcommand that simply shows the kernel version (notice how it differs from the worker node!)
Let’s extend the existing initdata by keeping everything before the exec policy block, then appending a new policy that allows only specific commands:
# Keep the initdata up to (but not including) the "# Disable exec" section
BEFORE_EXEC_POLICY=$(printf '%s\n' "$DEFAULT_INITDATA" | sed '/# Disable exec/,$d')
# New policy: disable exec by default, allow only listed commands
NEW_EXEC_POLICY=$(cat << 'POLICY_END'
# Disable exec
default ExecProcessRequest := false
ExecProcessRequest if {
input_command = concat(" ", input.process.Args)
some allowed_command in policy_data.allowed_commands
input_command == allowed_command
}
# Add allowed commands for exec
policy_data := {
"allowed_commands": [
"curl -s http://127.0.0.1:8006/cdh/resource/default/kbsres1/key1",
"uname -r"
]
}
'''
POLICY_END
)
CUSTOM_INITDATA="${BEFORE_EXEC_POLICY}
${NEW_EXEC_POLICY}"
echo "$CUSTOM_INITDATA"
In order to securely allow the user to manually perform some commands, we restrict the exec commands to only allow the secret fetching, and nothing else. allowed_commands defines the only commands allowed to be exec’ed into the CoCo pod.
What the above means is that the defined commands to curl kbsres1/key1 and uname -r will work, but any other command will fail. Even oc exec -it pods/your_pod — curl http://127.0.0.1:8006/cdh/resource/default/kbsres1/key2 will not work.
Let’s now translate it into base64 and calculate the PCR8 for the reference values.
echo "$CUSTOM_INITDATA" > custom-initdata.toml
CUSTOM_INITDATA_B64=$(cat custom-initdata.toml | gzip | base64 -w0)
echo ""
echo "$CUSTOM_INITDATA_B64"
initial_pcr=0000000000000000000000000000000000000000000000000000000000000000
hash=$(sha256sum custom-initdata.toml | cut -d' ' -f1)
PCR8_HASH_CI=$(echo -n "$initial_pcr$hash" | xxd -r -p | sha256sum | cut -d' ' -f1)
echo ""
echo "PCR 8 CUSTOM_INITDATA:" $PCR8_HASH_CI
rm custom-initdata.toml
Update the reference values
As we have a new initdata, we need to tell the Trustee that it can also accept such.
In order to do so, all we need to do is to add the new PCR8 to the reference values:
oc get configmap trusteeconfig-rvps-reference-values \
-n trustee-operator-system \
-o jsonpath='{.data.reference-values\.json}' \
| jq --arg p1 "$PCR8_HASH_CI" '
map(
if .name == "snp_pcr08" or .name == "tdx_pcr08"
then .value += [$p1]
else .
end
)
' \
| jq --indent 2 . \
| oc create configmap trusteeconfig-rvps-reference-values \
-n trustee-operator-system \
--from-file=reference-values.json=/dev/stdin \
--dry-run=client -o yaml \
| oc apply -f -
echo ""
oc get configmap trusteeconfig-rvps-reference-values \
-n trustee-operator-system \
-o jsonpath='{.data.reference-values\.json}'
The PCR8 should be added to the snp_pcr08 and tdx_pcr08 list.
And finally let’s apply the changes.
oc rollout restart deployment/trustee-deployment -n trustee-operator-system
Run the application
PERSONA: Application developer
Once that Trustee is set up, we can inject our custom initdata and see the pod running with a slightly relaxed but still secure policy.
To keep things as simple as possible, we will use the CoCo pod with default settings example.
cat > relaxed-fd.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
name: initdata-fraud-detection
namespace: default
annotations:
io.katacontainers.config.hypervisor.cc_init_data: "$CUSTOM_INITDATA_B64"
spec:
runtimeClassName: kata-remote
containers:
- name: fraud-detection
image: quay.io/confidential-devhub/signed/fraud-detection:latest
securityContext:
privileged: false
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1001
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
EOF
echo ""
cat relaxed-fd.yaml
echo ""
Note how the only difference in the podspec from the original example is the addition of metadata:annotations:io.katacontainers.config.hypervisor.cc_init_data.
Let’s run the pod.
oc apply -f relaxed-fd.yaml
Wait that the pod is created.
watch oc get pods/initdata-fraud-detection -n default
The pod is ready when the STATUS is in Running.
Verify that is possible to exec
Once the pod is ready, let’s try to exec into it.
Check that pod exec is disabled
oc exec -it pods/initdata-fraud-detection -n default -- bash
And notice how an error is returned:
error: Internal error occurred: error executing command in container: cannot enter container ba883405d94b151886307518daa04171716edcb410e8b4ff556a76ca951fbfa9, with err rpc error: code = PermissionDenied desc = "ExecProcessRequest is blocked by policy: ": unknown
Since this is one of the only commands allowed, let’s try to exec to get the example Trustee secret kbsres/key1 into the pod.
oc exec -it pods/initdata-fraud-detection -n default -- curl -s http://127.0.0.1:8006/cdh/resource/default/kbsres1/key1 && echo ""
And as expected, the secret is returned successfully.
[azure@bastion ~]# oc exec -it pods/initdata-fraud-detection -n default -- curl -s http://127.0.0.1:8006/cdh/resource/default/kbsres1/key1 && echo ""
resval1
Trying any other command in exec will fail.
[azure@bastion ~]# oc exec -it pods/initdata-fraud-detection -n default -- ls
error: Internal error occurred: error executing command in container: cannot enter container ba883405d94b151886307518daa04171716edcb410e8b4ff556a76ca951fbfa9, with err rpc error: code = PermissionDenied desc = "ExecProcessRequest is blocked by policy: ": unknown