diff --git a/src/agent/Makefile b/src/agent/Makefile index e0e76a9cd2..e62a894aae 100644 --- a/src/agent/Makefile +++ b/src/agent/Makefile @@ -159,7 +159,7 @@ vendor: #TARGET test: run cargo tests test: $(GENERATED_FILES) - @RUST_LIB_BACKTRACE=0 cargo test --all --target $(TRIPLE) $(EXTRA_RUSTFEATURES) -- --nocapture + @RUST_LIB_BACKTRACE=0 RUST_BACKTRACE=1 cargo test --all --target $(TRIPLE) $(EXTRA_RUSTFEATURES) -- --nocapture ##TARGET check: run test check: $(GENERATED_FILES) standard_rust_check diff --git a/src/agent/README.md b/src/agent/README.md index fbe4452240..4309676642 100644 --- a/src/agent/README.md +++ b/src/agent/README.md @@ -131,6 +131,8 @@ The kata agent has the ability to configure agent options in guest kernel comman | `agent.cdh_api_timeout` | Confidential Data Hub (CDH) API timeout | Allow to configure CDH API timeout(seconds) | integer | `50` | | `agent.https_proxy` | HTTPS proxy | Allow to configure `https_proxy` in the guest | string | `""` | | `agent.image_registry_auth` | Image registry credential URI | The URI to where image-rs can find the credentials for pulling images from private registries e.g. `file:///root/.docker/config.json` to read from a file in the guest image, or `kbs:///default/credentials/test` to get the file from the KBS| string | `""` | +| `agent.enable_signature_verification` | Image security policy flag | Whether enable image security policy enforcement. If `true`, the resource indexed by URI `agent.image_policy_file` will be got to work as image pulling policy. | string | `""` | +| `agent.image_policy_file` | Image security policy URI | The URI to where image-rs Typical policy URIs are like `file:///etc/image.json` to read from a file in the guest image, or `kbs:///default/security-policy/test` to get the file from the KBS| string | `""` | | `agent.log` | Log level | Allow the agent log level to be changed (produces more or less output) | string | `"info"` | | `agent.log_vport` | Log port | Allow to specify the `vsock` port to read logs | integer | `0` | | `agent.no_proxy` | NO proxy | Allow to configure `no_proxy` in the guest | string | `""` | diff --git a/src/agent/src/config.rs b/src/agent/src/config.rs index 9ced9fc5a3..3f721afbb9 100644 --- a/src/agent/src/config.rs +++ b/src/agent/src/config.rs @@ -34,6 +34,12 @@ const GUEST_COMPONENTS_PROCS_OPTION: &str = "agent.guest_components_procs"; const IMAGE_REGISTRY_AUTH_OPTION: &str = "agent.image_registry_auth"; const SECURE_STORAGE_INTEGRITY_OPTION: &str = "agent.secure_storage_integrity"; +#[cfg(feature = "guest-pull")] +const ENABLE_SIGNATURE_VERIFICATION: &str = "agent.enable_signature_verification"; + +#[cfg(feature = "guest-pull")] +const IMAGE_POLICY_FILE: &str = "agent.image_policy_file"; + // Configure the proxy settings for HTTPS requests in the guest, // to solve the problem of not being able to access the specified image in some cases. const HTTPS_PROXY: &str = "agent.https_proxy"; @@ -115,6 +121,10 @@ pub struct AgentConfig { #[cfg(feature = "guest-pull")] pub image_registry_auth: String, pub secure_storage_integrity: bool, + #[cfg(feature = "guest-pull")] + pub enable_signature_verification: bool, + #[cfg(feature = "guest-pull")] + pub image_policy_file: String, } #[derive(Debug, Deserialize)] @@ -138,6 +148,10 @@ pub struct AgentConfigBuilder { #[cfg(feature = "guest-pull")] pub image_registry_auth: Option, pub secure_storage_integrity: Option, + #[cfg(feature = "guest-pull")] + pub enable_signature_verification: Option, + #[cfg(feature = "guest-pull")] + pub image_policy_file: Option, } macro_rules! config_override { @@ -207,6 +221,10 @@ impl Default for AgentConfig { #[cfg(feature = "guest-pull")] image_registry_auth: String::from(""), secure_storage_integrity: false, + #[cfg(feature = "guest-pull")] + enable_signature_verification: false, + #[cfg(feature = "guest-pull")] + image_policy_file: String::from(""), } } } @@ -246,8 +264,17 @@ impl FromStr for AgentConfig { ); config_override!(agent_config_builder, agent_config, guest_components_procs); #[cfg(feature = "guest-pull")] - config_override!(agent_config_builder, agent_config, image_registry_auth); + { + config_override!(agent_config_builder, agent_config, image_registry_auth); + config_override!( + agent_config_builder, + agent_config, + enable_signature_verification + ); + config_override!(agent_config_builder, agent_config, image_policy_file); + } config_override!(agent_config_builder, agent_config, secure_storage_integrity); + Ok(agent_config) } } @@ -372,12 +399,26 @@ impl AgentConfig { get_guest_components_procs_value ); #[cfg(feature = "guest-pull")] - parse_cmdline_param!( - param, - IMAGE_REGISTRY_AUTH_OPTION, - config.image_registry_auth, - get_string_value - ); + { + parse_cmdline_param!( + param, + IMAGE_REGISTRY_AUTH_OPTION, + config.image_registry_auth, + get_string_value + ); + parse_cmdline_param!( + param, + ENABLE_SIGNATURE_VERIFICATION, + config.enable_signature_verification, + get_bool_value + ); + parse_cmdline_param!( + param, + IMAGE_POLICY_FILE, + config.image_policy_file, + get_string_value + ); + } parse_cmdline_param!( param, SECURE_STORAGE_INTEGRITY_OPTION, @@ -585,6 +626,11 @@ mod tests { assert!(!config.dev_mode); assert_eq!(config.log_level, DEFAULT_LOG_LEVEL); assert_eq!(config.hotplug_timeout, DEFAULT_HOTPLUG_TIMEOUT); + #[cfg(feature = "guest-pull")] + { + assert!(!config.enable_signature_verification); + assert_eq!(config.image_policy_file, ""); + } } #[test] @@ -612,6 +658,10 @@ mod tests { #[cfg(feature = "guest-pull")] image_registry_auth: &'a str, secure_storage_integrity: bool, + #[cfg(feature = "guest-pull")] + enable_signature_verification: bool, + #[cfg(feature = "guest-pull")] + image_policy_file: &'a str, } impl Default for TestData<'_> { @@ -634,6 +684,10 @@ mod tests { #[cfg(feature = "guest-pull")] image_registry_auth: "", secure_storage_integrity: false, + #[cfg(feature = "guest-pull")] + enable_signature_verification: false, + #[cfg(feature = "guest-pull")] + image_policy_file: "", } } } @@ -1102,6 +1156,24 @@ mod tests { secure_storage_integrity: false, ..Default::default() }, + #[cfg(feature = "guest-pull")] + TestData { + contents: "agent.enable_signature_verification=true", + enable_signature_verification: true, + ..Default::default() + }, + #[cfg(feature = "guest-pull")] + TestData { + contents: "agent.image_policy_file=kbs:///default/image-policy/test", + image_policy_file: "kbs:///default/image-policy/test", + ..Default::default() + }, + #[cfg(feature = "guest-pull")] + TestData { + contents: "agent.image_policy_file=file:///etc/image-policy.json", + image_policy_file: "file:///etc/image-policy.json", + ..Default::default() + }, ]; let dir = tempdir().expect("failed to create tmpdir"); @@ -1162,13 +1234,20 @@ mod tests { msg ); #[cfg(feature = "guest-pull")] - assert_eq!(d.image_registry_auth, config.image_registry_auth, "{}", msg); + { + assert_eq!(d.image_registry_auth, config.image_registry_auth, "{}", msg); + assert_eq!( + d.enable_signature_verification, config.enable_signature_verification, + "{}", + msg + ); + assert_eq!(d.image_policy_file, config.image_policy_file, "{}", msg); + } assert_eq!( d.secure_storage_integrity, config.secure_storage_integrity, "{}", msg ); - for v in vars_to_unset { env::remove_var(v); } diff --git a/src/agent/src/image.rs b/src/agent/src/image.rs index 0cc0d209c5..5168a24f88 100644 --- a/src/agent/src/image.rs +++ b/src/agent/src/image.rs @@ -57,13 +57,27 @@ impl ImageService { pub fn new() -> Self { let mut image_client = ImageClient::new(PathBuf::from(KATA_IMAGE_WORK_DIR)); #[cfg(feature = "guest-pull")] - if !AGENT_CONFIG.image_registry_auth.is_empty() { - let registry_auth = &AGENT_CONFIG.image_registry_auth; - debug!(sl(), "Set registry auth file {:?}", registry_auth); - image_client.config.file_paths.auth_file = registry_auth.clone(); - image_client.config.auth = true; - } + { + if !AGENT_CONFIG.image_registry_auth.is_empty() { + let registry_auth = &AGENT_CONFIG.image_registry_auth; + debug!(sl(), "Set registry auth file {:?}", registry_auth); + image_client.config.file_paths.auth_file = registry_auth.clone(); + image_client.config.auth = true; + } + let enable_signature_verification = &AGENT_CONFIG.enable_signature_verification; + debug!( + sl(), + "Enable image signature verification: {:?}", enable_signature_verification + ); + image_client.config.security_validate = *enable_signature_verification; + + if !AGENT_CONFIG.image_policy_file.is_empty() { + let image_policy_file = &AGENT_CONFIG.image_policy_file; + debug!(sl(), "Use imagepolicy file {:?}", image_policy_file); + image_client.config.file_paths.policy_path = image_policy_file.clone(); + } + } Self { image_client } } diff --git a/tests/integration/kubernetes/confidential_common.sh b/tests/integration/kubernetes/confidential_common.sh index bdd97c480d..5e5fb019ae 100644 --- a/tests/integration/kubernetes/confidential_common.sh +++ b/tests/integration/kubernetes/confidential_common.sh @@ -8,6 +8,8 @@ source "${BATS_TEST_DIRNAME}/tests_common.sh" source "${BATS_TEST_DIRNAME}/../../common.bash" +load "${BATS_TEST_DIRNAME}/confidential_kbs.sh" + SUPPORTED_TEE_HYPERVISORS=("qemu-sev" "qemu-snp" "qemu-tdx" "qemu-se") SUPPORTED_NON_TEE_HYPERVISORS=("qemu-coco-dev") @@ -97,7 +99,7 @@ function cleanup_loop_device(){ local loop_file="${1:-/tmp/trusted-image-storage.img}" # Find all loop devices associated with $loop_file local existed_devices=$(sudo losetup -j $loop_file | awk -F'[: ]' '{print $1}') - + if [ -n "$existed_devices" ]; then # Iterate over each found loop device and detach it for d in $existed_devices; do @@ -106,4 +108,63 @@ function cleanup_loop_device(){ fi sudo rm -f "$loop_file" >/dev/null 2>&1 || true -} \ No newline at end of file +} + +# This function creates pod yaml. Parameters +# - $1: image reference +# - $2: image policy file. If given, `enable_signature_verification` will be set to true +# - $3: image registry auth. +# - $4: guest components procs parameter +# - $5: guest components rest api parameter +# - $6: node +function create_coco_pod_yaml() { + image=$1 + image_policy=${2:-} + image_registry_auth=${3:-} + guest_components_procs=${4:-} + guest_components_rest_api=${5:-} + node=${6:-} + + local CC_KBS_ADDR + export CC_KBS_ADDR=$(kbs_k8s_svc_http_addr) + + kernel_params_annotation="io.katacontainers.config.hypervisor.kernel_params" + kernel_params_value="" + + if [ -n "$image_policy" ]; then + kernel_params_value+=" agent.image_policy_file=${image_policy}" + kernel_params_value+=" agent.enable_signature_verification=true" + fi + + if [ -n "$image_registry_auth" ]; then + kernel_params_value+=" agent.image_registry_auth=${image_registry_auth}" + fi + + if [ -n "$guest_components_procs" ]; then + kernel_params_value+=" agent.guest_components_procs=${guest_components_procs}" + fi + + if [ -n "$guest_components_rest_api" ]; then + kernel_params_value+=" agent.guest_components_rest_api=${guest_components_rest_api}" + fi + + kernel_params_value+=" agent.aa_kbc_params=cc_kbc::${CC_KBS_ADDR}" + + # Note: this is not local as we use it in the caller test + kata_pod="$(new_pod_config "$image" "kata-${KATA_HYPERVISOR}")" + set_container_command "${kata_pod}" "0" "sleep" "30" + + # Set annotations + set_metadata_annotation "${kata_pod}" \ + "io.containerd.cri.runtime-handler" \ + "kata-${KATA_HYPERVISOR}" + set_metadata_annotation "${kata_pod}" \ + "${kernel_params_annotation}" \ + "${kernel_params_value}" + + add_allow_all_policy_to_yaml "${kata_pod}" + + if [ -n "$node" ]; then + set_node "${kata_pod}" "$node" + fi +} diff --git a/tests/integration/kubernetes/k8s-confidential-attestation.bats b/tests/integration/kubernetes/k8s-confidential-attestation.bats index cc27b2193a..7d57db2770 100644 --- a/tests/integration/kubernetes/k8s-confidential-attestation.bats +++ b/tests/integration/kubernetes/k8s-confidential-attestation.bats @@ -7,7 +7,6 @@ load "${BATS_TEST_DIRNAME}/lib.sh" load "${BATS_TEST_DIRNAME}/confidential_common.sh" -load "${BATS_TEST_DIRNAME}/confidential_kbs.sh" export KBS="${KBS:-false}" export test_key="aatest" diff --git a/tests/integration/kubernetes/k8s-guest-pull-image-authenticated.bats b/tests/integration/kubernetes/k8s-guest-pull-image-authenticated.bats index c819be7fd4..cf9ab5a469 100644 --- a/tests/integration/kubernetes/k8s-guest-pull-image-authenticated.bats +++ b/tests/integration/kubernetes/k8s-guest-pull-image-authenticated.bats @@ -6,7 +6,6 @@ load "${BATS_TEST_DIRNAME}/lib.sh" load "${BATS_TEST_DIRNAME}/confidential_common.sh" -load "${BATS_TEST_DIRNAME}/confidential_kbs.sh" export KBS="${KBS:-false}" @@ -63,72 +62,44 @@ function setup_kbs_credentials() { kbs_set_resource "default" "credentials" "test" "${auth_json}" } -function create_pod_yaml_with_private_image() { - image=$1 - auth_path_set=${2:-true} - - # Note: this is not local as we use it in the caller test - kata_pod_with_private_image="$(new_pod_config "$image" "kata-${KATA_HYPERVISOR}")" - set_node "${kata_pod_with_private_image}" "$node" - set_container_command "${kata_pod_with_private_image}" "0" "sleep" "30" - - local CC_KBS_ADDR - export CC_KBS_ADDR=$(kbs_k8s_svc_http_addr) - kernel_params_annotation="io.katacontainers.config.hypervisor.kernel_params" - kernel_params_value="agent.guest_components_rest_api=resource" - - if [[ $auth_path_set == true ]]; then - kernel_params_value+=" agent.aa_kbc_params=cc_kbc::${CC_KBS_ADDR}" - kernel_params_value+=" agent.image_registry_auth=kbs:///default/credentials/test" - fi - set_metadata_annotation "${kata_pod_with_private_image}" \ - "${kernel_params_annotation}" \ - "${kernel_params_value}" - - # Set annotation to pull image in guest - set_metadata_annotation "${kata_pod_with_private_image}" \ - "io.containerd.cri.runtime-handler" \ - "kata-${KATA_HYPERVISOR}" - - add_allow_all_policy_to_yaml "${kata_pod_with_private_image}" - - yq -i ".spec.imagePullSecrets[0].name = \"cococred\"" "${kata_pod_with_private_image}" -} - @test "Test that creating a container from an authenticated image, with correct credentials works" { setup_kbs_credentials "${AUTHENTICATED_IMAGE}" ${AUTHENTICATED_IMAGE_USER} ${AUTHENTICATED_IMAGE_PASSWORD} - create_pod_yaml_with_private_image "${AUTHENTICATED_IMAGE}" + create_coco_pod_yaml "${AUTHENTICATED_IMAGE}" "" "kbs:///default/credentials/test" "" "resource" "$node" + yq -i ".spec.imagePullSecrets[0].name = \"cococred\"" "${kata_pod}" # For debug sake - echo "Pod ${kata_pod_with_private_image}: $(cat ${kata_pod_with_private_image})" + echo "Pod ${kata_pod}: $(cat ${kata_pod})" - k8s_create_pod "${kata_pod_with_private_image}" + k8s_create_pod "${kata_pod}" echo "Kata pod test-e2e from authenticated image is running" } @test "Test that creating a container from an authenticated image, with incorrect credentials fails" { setup_kbs_credentials "${AUTHENTICATED_IMAGE}" ${AUTHENTICATED_IMAGE_USER} "junk" - create_pod_yaml_with_private_image "${AUTHENTICATED_IMAGE}" + + create_coco_pod_yaml "${AUTHENTICATED_IMAGE}" "" "kbs:///default/credentials/test" "" "resource" "$node" + yq -i ".spec.imagePullSecrets[0].name = \"cococred\"" "${kata_pod}" # For debug sake - echo "Pod ${kata_pod_with_private_image}: $(cat ${kata_pod_with_private_image})" + echo "Pod ${kata_pod}: $(cat ${kata_pod})" - assert_pod_fail "${kata_pod_with_private_image}" + assert_pod_fail "${kata_pod}" assert_logs_contain "${node}" kata "${node_start_time}" "failed to pull manifest Not authorized" } @test "Test that creating a container from an authenticated image, with no credentials fails" { # Create pod config, but don't add agent.image_registry_auth annotation - create_pod_yaml_with_private_image "${AUTHENTICATED_IMAGE}" false + create_coco_pod_yaml "${AUTHENTICATED_IMAGE}" "" "" "" "resource" "$node" + yq -i ".spec.imagePullSecrets[0].name = \"cococred\"" "${kata_pod}" # For debug sake - echo "Pod ${kata_pod_with_private_image}: $(cat ${kata_pod_with_private_image})" + echo "Pod ${kata_pod}: $(cat ${kata_pod})" - assert_pod_fail "${kata_pod_with_private_image}" + assert_pod_fail "${kata_pod}" assert_logs_contain "${node}" kata "${node_start_time}" "failed to pull manifest Not authorized" } diff --git a/tests/integration/kubernetes/k8s-guest-pull-image-encrypted.bats b/tests/integration/kubernetes/k8s-guest-pull-image-encrypted.bats index 1a87198245..0753af5170 100644 --- a/tests/integration/kubernetes/k8s-guest-pull-image-encrypted.bats +++ b/tests/integration/kubernetes/k8s-guest-pull-image-encrypted.bats @@ -6,7 +6,6 @@ load "${BATS_TEST_DIRNAME}/lib.sh" load "${BATS_TEST_DIRNAME}/confidential_common.sh" -load "${BATS_TEST_DIRNAME}/confidential_kbs.sh" export KBS="${KBS:-false}" @@ -39,32 +38,6 @@ function setup_kbs_decryption_key() { kbs_set_resource_base64 "default" "key" "${decryption_key_id}" "${decryption_key}" } -function create_pod_yaml_with_encrypted_image() { - image=$1 - - # Note: this is not local as we use it in the caller test - kata_pod_with_encrypted_image="$(new_pod_config "$image" "kata-${KATA_HYPERVISOR}")" - set_node "${kata_pod_with_encrypted_image}" "$node" - set_container_command "${kata_pod_with_encrypted_image}" "0" "sleep" "30" - - local CC_KBS_ADDR - export CC_KBS_ADDR=$(kbs_k8s_svc_http_addr) - kernel_params_annotation="io.katacontainers.config.hypervisor.kernel_params" - kernel_params_value+=" agent.guest_components_procs=confidential-data-hub" - kernel_params_value+=" agent.aa_kbc_params=cc_kbc::${CC_KBS_ADDR}" - - set_metadata_annotation "${kata_pod_with_encrypted_image}" \ - "${kernel_params_annotation}" \ - "${kernel_params_value}" - - # Set annotation to pull image in guest - set_metadata_annotation "${kata_pod_with_encrypted_image}" \ - "io.containerd.cri.runtime-handler" \ - "kata-${KATA_HYPERVISOR}" - - add_allow_all_policy_to_yaml "${kata_pod_with_encrypted_image}" -} - @test "Test that creating a container from an encrypted image, with no decryption key fails" { # TODO - there is now delete KBS resource to ensure there is no key, so we need to keep @@ -73,12 +46,12 @@ function create_pod_yaml_with_encrypted_image() { # policy, so for TEE tests we'd stay remaining with reject all, which could cause other # subsequent tests to fail - create_pod_yaml_with_encrypted_image "${ENCRYPTED_IMAGE}" + create_coco_pod_yaml "${ENCRYPTED_IMAGE}" "" "" "confidential-data-hub" "" "$node" # For debug sake - echo "Pod ${kata_pod_with_encrypted_image}: $(cat ${kata_pod_with_encrypted_image})" + echo "Pod ${kata_pod}: $(cat ${kata_pod})" - assert_pod_fail "${kata_pod_with_encrypted_image}" + assert_pod_fail "${kata_pod}" assert_logs_contain "${node}" kata "${node_start_time}" 'failed to get decrypt key' assert_logs_contain "${node}" kata "${node_start_time}" 'no suitable key found for decrypting layer key' } @@ -88,12 +61,12 @@ function create_pod_yaml_with_encrypted_image() { setup_kbs_decryption_key "${DECRYPTION_KEY}" "${DECRYPTION_KEY_ID}" - create_pod_yaml_with_encrypted_image "${ENCRYPTED_IMAGE}" + create_coco_pod_yaml "${ENCRYPTED_IMAGE}" "" "" "confidential-data-hub" "" "$node" # For debug sake - echo "Pod ${kata_pod_with_encrypted_image}: $(cat ${kata_pod_with_encrypted_image})" + echo "Pod ${kata_pod}: $(cat ${kata_pod})" - k8s_create_pod "${kata_pod_with_encrypted_image}" + k8s_create_pod "${kata_pod}" echo "Kata pod test-e2e from encrypted image is running" } @@ -101,12 +74,12 @@ function create_pod_yaml_with_encrypted_image() { setup_kbs_decryption_key "anVua19rZXk=" "${DECRYPTION_KEY_ID}" - create_pod_yaml_with_encrypted_image "${ENCRYPTED_IMAGE}" + create_coco_pod_yaml "${ENCRYPTED_IMAGE}" "" "" "confidential-data-hub" "" "$node" # For debug sake - echo "Pod ${kata_pod_with_encrypted_image}: $(cat ${kata_pod_with_encrypted_image})" + echo "Pod ${kata_pod}: $(cat ${kata_pod})" - assert_pod_fail "${kata_pod_with_encrypted_image}" + assert_pod_fail "${kata_pod}" assert_logs_contain "${node}" kata "${node_start_time}" 'failed to get decrypt key' assert_logs_contain "${node}" kata "${node_start_time}" 'no suitable key found for decrypting layer key' } diff --git a/tests/integration/kubernetes/k8s-guest-pull-image-signature.bats b/tests/integration/kubernetes/k8s-guest-pull-image-signature.bats new file mode 100644 index 0000000000..4866ae84a0 --- /dev/null +++ b/tests/integration/kubernetes/k8s-guest-pull-image-signature.bats @@ -0,0 +1,156 @@ +#!/usr/bin/env bats +# Copyright (c) 2024 IBM Corporation +# Copyright (c) 2024 Alibaba Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +load "${BATS_TEST_DIRNAME}/lib.sh" +load "${BATS_TEST_DIRNAME}/confidential_common.sh" + +export KBS="${KBS:-false}" + +setup() { + if ! is_confidential_runtime_class; then + skip "Test not supported for ${KATA_HYPERVISOR}." + fi + + [ "${SNAPSHOTTER:-}" = "nydus" ] || skip "None snapshotter was found but this test requires one" + + tag_suffix="" + if [ "$(uname -m)" != "x86_64" ]; then + tag_suffix="-$(uname -m)" + fi + + setup_common + UNSIGNED_UNPROTECTED_REGISTRY_IMAGE="quay.io/prometheus/busybox:latest" + UNSIGNED_PROTECTED_REGISTRY_IMAGE="ghcr.io/confidential-containers/test-container-image-rs:unsigned${tag_suffix}" + COSIGN_SIGNED_PROTECTED_REGISTRY_IMAGE="ghcr.io/confidential-containers/test-container-image-rs:cosign-signed${tag_suffix}" + COSIGNED_SIGNED_PROTECTED_REGISTRY_WRONG_KEY_IMAGE="ghcr.io/confidential-containers/test-container-image-rs:cosign-signed-key2${tag_suffix}" + SECURITY_POLICY_KBS_URI="kbs:///default/security-policy/test" +} + +function setup_kbs_image_policy() { + if [ "${KBS}" = "false" ]; then + skip "Test skipped as KBS not setup" + fi + + default_policy="${1:-insecureAcceptAnything}" + policy_json=$(cat << EOF +{ + "default": [ + { + "type": "${default_policy}" + } + ], + "transports": { + "docker": { + "ghcr.io/confidential-containers/test-container-image-rs": [ + { + "type": "sigstoreSigned", + "keyPath": "kbs:///default/cosign-public-key/test" + } + ], + "quay.io/prometheus": [ + { + "type": "insecureAcceptAnything" + } + ] + } + } +} +EOF + ) + + # This public key is corresponding to a private key that was generated to test signed images in image-rs CI. + # TODO: Update the CI to generate a signed image together with verification. See issue #9360 + public_key=$(curl -sSL "https://raw.githubusercontent.com/confidential-containers/guest-components/075b9a9ee77227d9d92b6f3649ef69de5e72d204/image-rs/test_data/signature/cosign/cosign1.pub") + + if ! is_confidential_hardware; then + kbs_set_allow_all_resources + fi + + kbs_set_resource "default" "security-policy" "test" "${policy_json}" + kbs_set_resource "default" "cosign-public-key" "test" "${public_key}" +} + +@test "Create a pod from an unsigned image, on an insecureAcceptAnything registry works" { + # We want to set the default policy to be reject to rule out false positives + setup_kbs_image_policy "reject" + + create_coco_pod_yaml "${UNSIGNED_UNPROTECTED_REGISTRY_IMAGE}" "${SECURITY_POLICY_KBS_URI}" "" "" "resource" "$node" + + # For debug sake + echo "Pod ${kata_pod}: $(cat ${kata_pod})" + + k8s_create_pod "${kata_pod}" + echo "Kata pod test-e2e from image security policy is running" +} + +@test "Create a pod from an unsigned image, on a 'restricted registry' is rejected" { + # We want to leave the default policy to be insecureAcceptAnything to rule out false negatives + setup_kbs_image_policy + + create_coco_pod_yaml "${UNSIGNED_PROTECTED_REGISTRY_IMAGE}" "${SECURITY_POLICY_KBS_URI}" "" "" "resource" "$node" + + # For debug sake + echo "Pod ${kata_pod}: $(cat ${kata_pod})" + + assert_pod_fail "${kata_pod}" + assert_logs_contain "${node}" kata "${node_start_time}" "Security validate failed: Validate image failed: Cannot pull manifest" +} + +@test "Create a pod from a signed image, on a 'restricted registry' is successful" { + # We want to set the default policy to be reject to rule out false positives + setup_kbs_image_policy "reject" + + create_coco_pod_yaml "${COSIGN_SIGNED_PROTECTED_REGISTRY_IMAGE}" "${SECURITY_POLICY_KBS_URI}" "" "" "resource" "$node" + + # For debug sake + echo "Pod ${kata_pod}: $(cat ${kata_pod})" + + k8s_create_pod "${kata_pod}" + echo "Kata pod test-e2e from image security policy is running" +} + +@test "Create a pod from a signed image, on a 'restricted registry', but with the wrong key is rejected" { + # We want to leave the default policy to be insecureAcceptAnything to rule out false negatives + setup_kbs_image_policy + + create_coco_pod_yaml "${COSIGNED_SIGNED_PROTECTED_REGISTRY_WRONG_KEY_IMAGE}" "${SECURITY_POLICY_KBS_URI}" "" "" "resource" "$node" + + # For debug sake + echo "Pod ${kata_pod}: $(cat ${kata_pod})" + + assert_pod_fail "${kata_pod}" + assert_logs_contain "${node}" kata "${node_start_time}" "Security validate failed: Validate image failed: \[PublicKeyVerifier" +} + +@test "Create a pod from an unsigned image, on a 'restricted registry' works if policy files isn't set" { + # We want to set the default policy to be reject to rule out false positives + setup_kbs_image_policy "reject" + + create_coco_pod_yaml "${UNSIGNED_PROTECTED_REGISTRY_IMAGE}" "" "" "" "resource" "$node" + + # For debug sake + echo "Pod ${kata_pod}: $(cat ${kata_pod})" + + k8s_create_pod "${kata_pod}" + echo "Kata pod test-e2e from image security policy is running" +} + +teardown() { + if ! is_confidential_runtime_class; then + skip "Test not supported for ${KATA_HYPERVISOR}." + fi + + [ "${SNAPSHOTTER:-}" = "nydus" ] || skip "None snapshotter was found but this test requires one" + + kubectl describe pods + k8s_delete_all_pods_if_any_exists || true + + if [[ -n "${node_start_time:-}" && -z "$BATS_TEST_COMPLETED" ]]; then + echo "DEBUG: system logs of node '$node' since test start time ($node_start_time)" + print_node_journal "$node" "kata" --since "$node_start_time" || true + fi +} diff --git a/tests/integration/kubernetes/k8s-guest-pull-image.bats b/tests/integration/kubernetes/k8s-guest-pull-image.bats index 4d7dfe1926..2de58bd36e 100644 --- a/tests/integration/kubernetes/k8s-guest-pull-image.bats +++ b/tests/integration/kubernetes/k8s-guest-pull-image.bats @@ -7,7 +7,6 @@ load "${BATS_TEST_DIRNAME}/lib.sh" load "${BATS_TEST_DIRNAME}/confidential_common.sh" -load "${BATS_TEST_DIRNAME}/confidential_kbs.sh" setup() { if ! is_confidential_runtime_class; then @@ -63,12 +62,12 @@ setup() { } @test "Test we cannot pull an image that exceeds the memory limit inside the guest" { - # The image pulled in the guest will be downloaded and unpacked in the `/run/kata-containers/image` directory. - # However, by default, systemd allocates 50% of the available physical RAM to the `/run` directory using a `tmpfs` filesystem. - # It means that if we run a kata container with the default configuration (where the default memory assigned for a VM is 2048 MiB), - # `/run` would be allocated around 1024 MiB. Consequently, we can only pull images up to 1024 MiB in the guest. - # However, the unpacked size of image "ghcr.io/confidential-containers/test-container:rust-1.79.0" is 1.41GB. - # It will fail to run the pod with pulling the image in the memory in the guest by default. + # The image pulled in the guest will be downloaded and unpacked in the `/run/kata-containers/image` directory. + # However, by default, systemd allocates 50% of the available physical RAM to the `/run` directory using a `tmpfs` filesystem. + # It means that if we run a kata container with the default configuration (where the default memory assigned for a VM is 2048 MiB), + # `/run` would be allocated around 1024 MiB. Consequently, we can only pull images up to 1024 MiB in the guest. + # However, the unpacked size of image "ghcr.io/confidential-containers/test-container:rust-1.79.0" is 1.41GB. + # It will fail to run the pod with pulling the image in the memory in the guest by default. pod_config="$(new_pod_config "$image_pulled_time_less_than_default_time" "kata-${KATA_HYPERVISOR}")" set_node "$pod_config" "$node" @@ -90,13 +89,13 @@ setup() { } @test "Test we can pull an image inside the guest using trusted storage" { - # The image pulled in the guest will be downloaded and unpacked in the `/run/kata-containers/image` directory. + # The image pulled in the guest will be downloaded and unpacked in the `/run/kata-containers/image` directory. # The tests will use `cryptsetup` to encrypt a block device and mount it at `/run/kata-containers/image`. - + if [ "${KATA_HYPERVISOR}" = "qemu-coco-dev" ]; then skip "skip this specific one due to issue https://github.com/kata-containers/kata-containers/issues/10133" fi - + storage_config=$(mktemp "${BATS_FILE_TMPDIR}/$(basename "${storage_config_template}").XXX") local_device=$(create_loop_device) LOCAL_DEVICE="$local_device" NODE_NAME="$node" envsubst < "$storage_config_template" > "$storage_config" @@ -142,7 +141,7 @@ setup() { # For debug sake echo "Trusted storage $storage_config file:" cat $storage_config - + # Create persistent volume and persistent volume claim kubectl create -f $storage_config @@ -187,7 +186,7 @@ setup() { # For debug sake echo "Trusted storage $storage_config file:" cat $storage_config - + # Create persistent volume and persistent volume claim kubectl create -f $storage_config diff --git a/tests/integration/kubernetes/k8s-sealed-secret.bats b/tests/integration/kubernetes/k8s-sealed-secret.bats index 36ac18b030..b36ff41bd5 100644 --- a/tests/integration/kubernetes/k8s-sealed-secret.bats +++ b/tests/integration/kubernetes/k8s-sealed-secret.bats @@ -9,7 +9,6 @@ load "${BATS_TEST_DIRNAME}/lib.sh" load "${BATS_TEST_DIRNAME}/confidential_common.sh" -load "${BATS_TEST_DIRNAME}/confidential_kbs.sh" export KBS="${KBS:-false}" export KATA_HYPERVISOR="${KATA_HYPERVISOR:-qemu}" diff --git a/tests/integration/kubernetes/run_kubernetes_tests.sh b/tests/integration/kubernetes/run_kubernetes_tests.sh index 51a12dbe30..67753849d1 100755 --- a/tests/integration/kubernetes/run_kubernetes_tests.sh +++ b/tests/integration/kubernetes/run_kubernetes_tests.sh @@ -28,6 +28,7 @@ else "k8s-guest-pull-image-encrypted.bats" \ "k8s-guest-pull-image.bats" \ "k8s-guest-pull-image-authenticated.bats" \ + "k8s-guest-pull-image-signature.bats" \ "k8s-confidential-attestation.bats" \ "k8s-confidential.bats" \ "k8s-sealed-secret.bats" \ diff --git a/tests/integration/kubernetes/tests_common.sh b/tests/integration/kubernetes/tests_common.sh index 86780711dc..c552f5bf2c 100644 --- a/tests/integration/kubernetes/tests_common.sh +++ b/tests/integration/kubernetes/tests_common.sh @@ -230,8 +230,8 @@ delete_tmp_policy_settings_dir() { auto_generate_policy() { declare -r settings_dir="$1" declare -r yaml_file="$2" - declare -r config_map_yaml_file="$3" - declare -r additional_flags="$4" + declare -r config_map_yaml_file="${3:-""}" + declare -r additional_flags="${4:-""}" auto_generate_policy_enabled || return 0 local genpolicy_command="RUST_LOG=info /opt/kata/bin/genpolicy -u -y ${yaml_file}"