From f92ddee906c1f9afe9d4f0ca2b44e614857270d1 Mon Sep 17 00:00:00 2001 From: Tuomo Tanskanen Date: Tue, 10 Dec 2024 14:33:19 +0200 Subject: [PATCH] add keylime-poc Make a Proof of Concept of Keylime is k8s cluster. This POC is needed as the concept of having Keylime Tenant/Verifier/Registrar outside k8s cluster, but Keylime Agent in k8s cluster and being accessed via Ingress/LoadBalancer IP, is something Keylime maintainers did not think originally as a use-case. This has several issues with the current design, and while there is a proposal/ study for changing from "pull model" to "push model", it is miles away and this POC tries to find out the minimal changes needed to make the current model work for this use case. Signed-off-by: Tuomo Tanskanen --- security/keylime-poc/Makefile | 28 ++ security/keylime-poc/README.md | 15 ++ .../keylime-poc/agent-with-swtpm/Dockerfile | 85 ++++++ .../agent-with-swtpm/dbus-policy.conf | 12 + .../keylime-poc/agent-with-swtpm/start.sh | 57 ++++ security/keylime-poc/compose/.gitignore | 1 + security/keylime-poc/compose/README.md | 21 ++ security/keylime-poc/compose/agent.conf | 87 ++++++ security/keylime-poc/compose/compose.yml | 70 +++++ security/keylime-poc/compose/logging.conf | 32 +++ security/keylime-poc/compose/registrar.conf | 120 +++++++++ security/keylime-poc/compose/tenant.conf | 130 +++++++++ security/keylime-poc/compose/tenant.sh | 13 + security/keylime-poc/compose/verifier.conf | 252 ++++++++++++++++++ security/keylime-poc/k8s/README.md | 217 +++++++++++++++ security/keylime-poc/k8s/clean.sh | 43 +++ security/keylime-poc/k8s/config.sh | 36 +++ security/keylime-poc/k8s/keylime-agent.yaml | 169 ++++++++++++ security/keylime-poc/k8s/run_docker.sh | 35 +++ security/keylime-poc/k8s/run_kind.sh | 44 +++ security/keylime-poc/k8s/run_tenant.sh | 24 ++ .../scripts/enable_ima_measurement.sh | 41 +++ security/keylime-poc/scripts/gen_allowlist.sh | 47 ++++ 23 files changed, 1579 insertions(+) create mode 100644 security/keylime-poc/Makefile create mode 100644 security/keylime-poc/README.md create mode 100644 security/keylime-poc/agent-with-swtpm/Dockerfile create mode 100644 security/keylime-poc/agent-with-swtpm/dbus-policy.conf create mode 100755 security/keylime-poc/agent-with-swtpm/start.sh create mode 100644 security/keylime-poc/compose/.gitignore create mode 100644 security/keylime-poc/compose/README.md create mode 100644 security/keylime-poc/compose/agent.conf create mode 100644 security/keylime-poc/compose/compose.yml create mode 100644 security/keylime-poc/compose/logging.conf create mode 100644 security/keylime-poc/compose/registrar.conf create mode 100644 security/keylime-poc/compose/tenant.conf create mode 100755 security/keylime-poc/compose/tenant.sh create mode 100644 security/keylime-poc/compose/verifier.conf create mode 100644 security/keylime-poc/k8s/README.md create mode 100755 security/keylime-poc/k8s/clean.sh create mode 100644 security/keylime-poc/k8s/config.sh create mode 100644 security/keylime-poc/k8s/keylime-agent.yaml create mode 100755 security/keylime-poc/k8s/run_docker.sh create mode 100755 security/keylime-poc/k8s/run_kind.sh create mode 100755 security/keylime-poc/k8s/run_tenant.sh create mode 100755 security/keylime-poc/scripts/enable_ima_measurement.sh create mode 100755 security/keylime-poc/scripts/gen_allowlist.sh diff --git a/security/keylime-poc/Makefile b/security/keylime-poc/Makefile new file mode 100644 index 00000000..0fe14743 --- /dev/null +++ b/security/keylime-poc/Makefile @@ -0,0 +1,28 @@ +# simple makefile to make life easy + +.PHONY: e2e run docker kind verify clean realclean +.PHONY: compose + +e2e: run verify + "e2e done!" + +run: docker kind + echo "Done!" + +docker: + cd k8s; ./run_docker.sh + +kind: + cd k8s; ./run_kind.sh + +verify: + #cd k8s; ./verify.sh + +clean: + cd k8s; ./clean.sh + +realclean: + cd k8s; ./clean.sh realclean + +compose: + sudo apt install -y docker-compose-plugin diff --git a/security/keylime-poc/README.md b/security/keylime-poc/README.md new file mode 100644 index 00000000..d602c48e --- /dev/null +++ b/security/keylime-poc/README.md @@ -0,0 +1,15 @@ +# Keylime POC + + +## Docker Compose + +First part of POC is deploying Keylime services in +[Docker Compose](compose/README.md). Setup is verified by a single Agent, backed +by software TPM, also in Docker. + +## Kubernetes + +Second part of POC is deploying [Agent in k8s](k8s/README.md), fronted by +Ingress/LoadBalancer, while the Verifier and Registrar sit in Docker Compose. +This simulates the use-case of using existing non-K8s Keylime installation to +measure nodes in K8s cluster. diff --git a/security/keylime-poc/agent-with-swtpm/Dockerfile b/security/keylime-poc/agent-with-swtpm/Dockerfile new file mode 100644 index 00000000..8a5e2616 --- /dev/null +++ b/security/keylime-poc/agent-with-swtpm/Dockerfile @@ -0,0 +1,85 @@ +############################################################################## +# keylime TPM 2.0 Dockerfile +# +# This file is for automatic test running of Keylime and rust-keylime. +# It is not recommended for use beyond testing scenarios. +############################################################################## + +FROM quay.io/fedora/fedora + +# environment variables +ARG BRANCH=master +ENV container docker +ENV HOME /root +ENV KEYLIME_HOME ${HOME}/keylime +ENV TPM_HOME ${HOME}/swtpm2 +COPY dbus-policy.conf /etc/dbus-1/system.d/ + +# Packaged dependencies +ENV PKGS_DEPS "automake \ +clang clang-devel \ +createrepo_c \ +czmq-devel \ +dbus \ +dbus-daemon \ +dbus-devel \ +dnf-plugins-core \ +efivar-devel \ +gcc \ +git \ +glib2-devel \ +glib2-static \ +gnulib \ +iproute \ +kmod \ +libarchive-devel \ +libselinux-python3 \ +libtool \ +libtpms \ +llvm llvm-devel \ +make \ +openssl-devel \ +pkg-config \ +procps \ +python3-cryptography \ +python3-dbus \ +python3-devel \ +python3-gpg \ +python3-pip \ +python3-requests \ +python3-setuptools \ +python3-sqlalchemy \ +python3-tornado \ +python3-virtualenv \ +python3-yaml \ +python3-zmq \ +redhat-rpm-config \ +rpm-build \ +rpm-sign \ +rust clippy cargo \ +swtpm \ +swtpm-tools \ +tpm2-abrmd \ +tpm2-tools \ +tpm2-tss \ +tpm2-tss-devel \ +uthash-devel \ +wget \ +which" + +ENV DEV_DEPS "strace openssl" + +RUN dnf makecache && \ + dnf -y install $PKGS_DEPS $DEV_DEPS && \ + dnf clean all && \ + rm -rf /var/cache/dnf/* + +RUN git clone https://github.com/keylime/rust-keylime \ + && cd rust-keylime \ + && make all \ + && make install \ + && cd .. \ + && rm -rf rust-keylime + +COPY start.sh / +CMD ["/start.sh"] diff --git a/security/keylime-poc/agent-with-swtpm/dbus-policy.conf b/security/keylime-poc/agent-with-swtpm/dbus-policy.conf new file mode 100644 index 00000000..c8b0b903 --- /dev/null +++ b/security/keylime-poc/agent-with-swtpm/dbus-policy.conf @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/security/keylime-poc/agent-with-swtpm/start.sh b/security/keylime-poc/agent-with-swtpm/start.sh new file mode 100755 index 00000000..e48e0f39 --- /dev/null +++ b/security/keylime-poc/agent-with-swtpm/start.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -eux + +KEYLIME=/var/lib/keylime +TPMDIR=/tmp/tpmdir + +# configure swtpm2 and start it +mkdir -p "${TPMDIR}" +chown tss:tss "${TPMDIR}" +chmod 750 "${TPMDIR}" + +swtpm_setup --tpm2 \ + --tpmstate "${TPMDIR}" \ + --createek --decryption --create-ek-cert \ + --create-platform-cert \ + --display || true +swtpm socket --tpm2 \ + --tpmstate dir="${TPMDIR}" \ + --flags startup-clear \ + --ctrl type=tcp,port=2322 \ + --server type=tcp,port=2321 \ + --daemon +sleep 2 + +# configure dbus for abmrd +sudo rm -rf /var/run/dbus +sudo mkdir /var/run/dbus +sudo dbus-daemon --system + +# run abmrd +tpm2-abrmd \ + --logger=stdout \ + --tcti=swtpm: \ + --allow-root \ + --flush-all \ + & +sleep 2 + +# prep image for running agent as non-root +useradd -s /sbin/nologin -g tss keylime || true + +chown keylime:tss "${KEYLIME}" "${KEYLIME}"/secure +chmod 770 "${KEYLIME}" "${KEYLIME}"/secure +cp "${KEYLIME}"/cv_ca/cacert.crt "${KEYLIME}"/secure/ +chown keylime:tss "${KEYLIME}"/secure/cacert.crt + +# make swtpm CA accessible to tenant to validate EK cert +# and verify it to be sure we have it right to avoid issues down the road +cat /var/lib/swtpm-localca/{issuercert,swtpm-localca-rootca-cert}.pem > "${KEYLIME}"/tpm_cert_store/swtpm_localca.pem +tpm2_getekcertificate > "${KEYLIME}"/ek.bin +openssl x509 -inform DER -in "${KEYLIME}"/ek.bin -out "${KEYLIME}"/ek.pem +openssl verify -CAfile "${KEYLIME}"/tpm_cert_store/swtpm_localca.pem "${KEYLIME}"/ek.pem +sleep 2 + +# run agent +keylime_agent diff --git a/security/keylime-poc/compose/.gitignore b/security/keylime-poc/compose/.gitignore new file mode 100644 index 00000000..92b72c46 --- /dev/null +++ b/security/keylime-poc/compose/.gitignore @@ -0,0 +1 @@ +allowlist.txt diff --git a/security/keylime-poc/compose/README.md b/security/keylime-poc/compose/README.md new file mode 100644 index 00000000..c41bb580 --- /dev/null +++ b/security/keylime-poc/compose/README.md @@ -0,0 +1,21 @@ +# Keylime in Docker Compose POC + + +Make a Proof of Concept of [Keylime in Docker Compose](compose/README.md). + +This will also work as part of [Keylime Agent in k8s](../k8s/README.md) POC, +as Keylime Verifier, Registrar and Tenant will be outside k8s, but Agent inside. +This creates interesting problems to be solved as Agent traffic will need to +flow via Ingress/LoadBalancer and it cannot be reached via IP. + +## Steps + +1. docker compose installed +1. Keylime images built locally via upstream + [build_locally.sh](https://github.com/keylime/keylime/blob/master/docker/release/build_locally.sh) + (we need unreleased fixes from `master`). +1. `docker compose up --build` to launch it +1. `./tenant.sh -c add` to verify stack works and agent gets added to verifier, + with EK certificate from software TPM. + +After this, feel free to play with `./tenant.sh`. diff --git a/security/keylime-poc/compose/agent.conf b/security/keylime-poc/compose/agent.conf new file mode 100644 index 00000000..db9ce8ed --- /dev/null +++ b/security/keylime-poc/compose/agent.conf @@ -0,0 +1,87 @@ +[agent] +# The configuration file version +version = "2.3" + +# The agent's UUID. +uuid = "c47b9ea2-2bc2-461b-957b-e77dbcf35e5e" +# uuid = "hash_ek" +# uuid = "generate" + +# The keylime working directory. The default value is /var/lib/keylime +keylime_dir = "/var/lib/keylime" + +# The size of the memory-backed tmpfs partition where Keylime stores crypto keys. +# Use syntax that the 'mount' command would accept as a size parameter for tmpfs. +# The default below sets it to 1 megabyte. +secure_size = "1m" +# run_as = "keylime:tss" + +# Enable mTLS communication between agent, verifier and tenant. +# Details on why setting it to "false" is generally considered insecure can be found +# on https://github.com/keylime/keylime/security/advisories/GHSA-2m39-75g9-ff5r +agent_enable_mtls = "true" + +# The name of the file containing the Keylime agent TLS server private key. +# This private key is used to serve the Keylime agent REST API +# A new private key is generated in case it is not found. +# If set as "default", the "server-private.pem" value is used. +# If a relative path is set, it will be considered relative from the keylime_dir. +# If an absolute path is set, it is used without change +server_key = "secure/agent.key" + +# The name of the file containing the X509 certificate used as the Keylime agent +# server TLS certificate. +# This certificate must be self signed. +# If set as "default", the "server-cert.crt" value is used +# If a relative path is set, it will be considered relative from the keylime_dir. +# If an absolute path is set, it is used without change. +server_cert = "secure/agent.crt" + +# The CA that signs the client certificates of the tenant and verifier. +# If set as "default" the "cv_ca/cacert.crt" value, relative from the +# keylime_dir is used. +# If a relative path is set, it will be considered relative from the keylime_dir. +# If an absolute path is set, it is used without change. +trusted_client_ca = "secure/cacert.crt" + +# The address and port of registrar server which agent communicate with +registrar_ip = "127.0.0.2" +registrar_port = 8890 + +# tbd +# The binding IP address and port for the agent server +ip = "0.0.0.0" +port = 9002 + +# Address and port where the verifier and tenant can connect to reach the agent. +# These keys are optional. +contact_ip = "127.0.0.3" +contact_port = 9002 + +# Use this option to state the existing TPM ownerpassword. +# This option should be set only when a password is set for the Endorsement +# Hierarchy (e.g. via "tpm2_changeauth -c e"). +# In order to use a hex value for the password, use the prefix "hex:" +# For example if tpm2_changeauth -c e "hex:00a1b2c3e4" has run, the config option +# would be 'tpm_ownerpassword = "hex:00a1b2c3e4"' +# If no password was set, keep the empty string "". +tpm_ownerpassword = "" +tpm_version = "2" + +# enc_keyname = "derived_tci_key" +# dec_payload_file = "decrypted_payload" +# extract_payload_zip = true +# enable_revocation_notifications = false +# revocation_actions_dir = "/usr/libexec/keylime" +# revocation_cert = "default" +# revocation_actions = "" +# payload_script = "autorun.sh" +# allow_payload_revocation_actions = true +# tpm_hash_alg = "sha256" +# tpm_encryption_alg = "rsa" +# tpm_signing_alg = "rsassa" +# ek_handle = "generate" +# enable_iak_idevid = false +# agent_data_path = "" +# ima_ml_path = "default" +# measuredboot_ml_path = "default" diff --git a/security/keylime-poc/compose/compose.yml b/security/keylime-poc/compose/compose.yml new file mode 100644 index 00000000..aadc20ed --- /dev/null +++ b/security/keylime-poc/compose/compose.yml @@ -0,0 +1,70 @@ +services: + keylime-verifier: + image: keylime_verifier:latest # locally built + hostname: keylime-verifier + volumes: + - ./verifier.conf:/etc/keylime/verifier.conf:ro + - ./logging.conf:/etc/keylime/logging.conf:ro + - secure_volume:/var/lib/keylime + ports: + - "8881:8881" + user: root + + keylime-registrar: + image: keylime_registrar:latest # locally built + hostname: keylime-registrar + depends_on: + - keylime-verifier + volumes: + - ./registrar.conf:/etc/keylime/registrar.conf:ro + - ./logging.conf:/etc/keylime/logging.conf:ro + - secure_volume:/var/lib/keylime + ports: + - "8891:8891" + - "8890:8890" + user: root + entrypoint: ["bash", "-c", "sleep 5; keylime_registrar"] + + keylime-tenant: + image: keylime_tenant:latest # locally built + hostname: keylime-tenant + network_mode: host + depends_on: + - keylime-verifier + - keylime-registrar + volumes: + - ./tenant.conf:/etc/keylime/tenant.conf:ro + - ./logging.conf:/etc/keylime/logging.conf:ro + - secure_volume:/var/lib/keylime + - ./allowlist.txt:/tmp/allowlist.txt:ro + user: root + entrypoint: ["bash", "-c", "tail -f /dev/null"] + + keylime-agent: + build: + # image: quay.io/keylime/keylime_agent:master + swtpm config + context: ../agent-with-swtpm + dockerfile: ../agent-with-swtpm/Dockerfile + hostname: keylime-agent + network_mode: host + user: root + depends_on: + - keylime-verifier + - keylime-registrar + environment: + - TPM2TOOLS_TCTI=tabrmd:bus_type=system + - TCTI=tabrmd:bus_type=system + - RUST_LOG=keylime_agent=debug + volumes: + - /sys/kernel/security:/sys/kernel/security:ro + - ./agent.conf:/etc/keylime/agent.conf:ro + - ./target/debug/:/rust-keylime + - secure_volume:/var/lib/keylime + - agent_volume:/var/lib/keylime/secure + +volumes: + secure_volume: + agent_volume: + driver_opts: + type: tmpfs + device: tmpfs diff --git a/security/keylime-poc/compose/logging.conf b/security/keylime-poc/compose/logging.conf new file mode 100644 index 00000000..9470c7f6 --- /dev/null +++ b/security/keylime-poc/compose/logging.conf @@ -0,0 +1,32 @@ +# Keylime logging configuration + +[logging] +version = 2.3 + +[loggers] +keys = root,keylime + +[handlers] +keys = consoleHandler + +[formatters] +keys = formatter + +[formatter_formatter] +format = %(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - %(message)s +datefmt = %Y-%m-%d %H:%M:%S + +[logger_root] +level = DEBUG +handlers = consoleHandler + +[handler_consoleHandler] +class = StreamHandler +level = DEBUG +formatter = formatter +args = (sys.stdout,) + +[logger_keylime] +level = DEBUG +qualname = keylime +handlers = diff --git a/security/keylime-poc/compose/registrar.conf b/security/keylime-poc/compose/registrar.conf new file mode 100644 index 00000000..37cbc1dd --- /dev/null +++ b/security/keylime-poc/compose/registrar.conf @@ -0,0 +1,120 @@ +[jenkins@bm08b-jenkins keylime_config]$ cat registrar.conf +# Keylime registrar configuration +[registrar] + +# The configuration file version number +version = 2.3 + +# The binding address and port for the registrar server +ip = "0.0.0.0" +port = 8890 +tls_port = 8891 + +# The 'tls_dir' option define the directory where the keys and certificates are +# stored. +# +# If set as 'generate', automatically generate a CA, keys, and certificates for +# the registrar server in the /var/lib/keylime/reg_ca directory, if not present. +# +# The 'server_key', 'server_cert', and 'trusted_client_ca' options should all be +# set with the 'default' keyword when 'generate' keyword is set for 'tls_dir'. +# +# If set as 'default', share the files with the verifier by using the +# 'var/lib/keylime/cv_ca' directory, which should contain the files indicated by +# the 'server_key', 'server_cert', and 'trusted_client_ca' options. +tls_dir = default + +# The name of the file containing the Keylime registrar server private key. +# The file should be stored in the directory set in the 'tls_dir' option. +# This private key is used to serve the Keylime registrar REST API +# +# If set as 'default', the 'server-private.pem' value is used. +server_key = default + +# Set the password used to decrypt the private key file. +# If 'tls_dir = generate', this password will also be used to protect the +# generated server private key. +# If left empty, the private key will not be encrypted. +server_key_password = + +# The name of the file containing the Keylime registrar server certificate. +# The file should be stored in the directory set in the 'tls_dir' option. +# +# If set as 'default', the 'server-cert.crt' value is used. +server_cert = default + +# The list of trusted client CA certificates. +# The files in the list should be stored in the directory set in the 'tls_dir' +# option. +# +# If set as 'default', the value is set as '[cacert.crt]' +trusted_client_ca = default + +# Database URL Configuration +# See this document https://keylime.readthedocs.io/en/latest/installation.html#database-support +# for instructions on using different database configurations. +# +# An example of database_url value for using sqlite: +# sqlite:////var/lib/keylime/reg_data.sqlite +# An example of database_url value for using mysql: +# mysql+pymysql://keylime:keylime@keylime_db:[port]/registrar?charset=utf8 +# +# If set as 'sqlite' keyword, will use the configuration set by the file located +# at "/var/lib/keylime/reg_data.sqlite". +database_url = sqlite + +# Limits for DB connection pool size in sqlalchemy +# (https://docs.sqlalchemy.org/en/14/core/pooling.html#api-documentation-available-pool-implementations) +database_pool_sz_ovfl = 5,10 + +# Whether to automatically update the DB schema using alembic +auto_migrate_db = True + +# Durable Attestation is currently marked as an experimental feature +# In order to enable Durable Attestation, an "adapter" for a Persistent data Store +# (time-series like database) needs to be specified. Some example adapters can be +# found under "da/examples" so, for instance +# "durable_attestation_import = keylime.da.examples.redis.py" +# could be used to interact with a Redis (Persistent data Store) +durable_attestation_import = + +# If an adapter for Durable Attestation was specified, then the URL for a Persistent Store +# needs to be specified here. A second optional URL could be specified, for a +# Rekor Transparency Log. A third additional URL could be specified, pointing to a +# Time Stamp Authority (TSA), compatible with RFC3161. Additionally, one might need to +# specify a path containing certificates required by the stores or TSA. Continuing with +# the above example, the following values could be assigned to the parameters: +# "persistent_store_url=redis://127.0.0.1:6379?db=10&password=/root/redis.auth&prefix=myda" +# "transparency_log_url=http://127.0.0.1:3000" +# "time_stamp_authority_url=http://127.0.0.1:2020" +# "time_stamp_authority_certs_path=~/mycerts/tsa_cert1.pem" +persistent_store_url = +transparency_log_url = +time_stamp_authority_url = +time_stamp_authority_certs_path = + +# If Durable Attestation was enabled, which requires a Persistent Store URL +# to be specified, the two following parameters control the format and enconding +# of the stored attestation artifacts (defaults "json" for format and "" for encoding) +persistent_store_format = json +persistent_store_encoding = + +# If Durable Attestation was enabled with a Transparency Log URL was specified, +# the digest algorithm for signatures is controlled by this parameter (default "sha256") +transparency_log_sign_algo = sha256 + +# If Durable Attestation was enabled with a Transparency Log URL was specified, +# a keylime administrator can specify some agent attributes (including attestation +# artifacts, such as quotes and logs) to be signed by the registrar. The use of "all" +# will result in the whole "package" (agent + artifacts) being signed and leaving it empty +# will mean no signing should be done. +signed_attributes = ek_tpm,aik_tpm,ekcert + +# What TPM-based identity is allowed to be used to register agents. +# The options "default" and "iak_idevid" will only allow registration with IAK and IDevID if python cryptography is version 38.0.0 or higher. +# The following options are accepted: +# "default": either an EK or IAK and IDevID may be used. In the case that cryptography version is <38.0.0 only EK will be used +# "ek_cert_or_iak_idevid": this is equivalent to default +# "ek_cert": only allow agents to use an EK to register +# "iak_idevid": only allow agents with an IAK and IDevID to register +tpm_identity = default diff --git a/security/keylime-poc/compose/tenant.conf b/security/keylime-poc/compose/tenant.conf new file mode 100644 index 00000000..1c4857f3 --- /dev/null +++ b/security/keylime-poc/compose/tenant.conf @@ -0,0 +1,130 @@ +# Keylime tenant configuration +[tenant] + +# The configuration file version number +version = 2.3 + +# The verifier IP address and port +verifier_ip = 127.0.0.1 +verifier_port = 8881 + +# The registrar IP address and port +registrar_ip = 127.0.0.2 +registrar_port = 8891 + +# The 'tls_dir' option define the directory where the keys and certificates are +# stored. +# +# If set as 'default', share the files with the verifier by using the +# 'var/lib/keylime/cv_ca', which should contain the files indicated by the +# 'client_key', 'client_cert', and 'trusted_server_ca' options. +tls_dir = default + +# Enable mTLS communication between agent, verifier and tenant. +# Details on why setting it to "False" is generally considered insecure can be found +# on https://github.com/keylime/keylime/security/advisories/GHSA-2m39-75g9-ff5r +enable_agent_mtls = True + +# The name of the file containing the Keylime tenant client private key. +# The file should be stored in the directory set in the 'tls_dir' option. +# This private key is used by the Keylime tenant to connect to the other +# services using TLS. +# +# If set as 'default', the 'client-private.pem' value is used. +client_key = default + +# Set the password used to encrypt the private key file. +# If client_key is set as 'default', should match the password set in the +# 'client_key_password' option in the verifier configuration file +client_key_password = + +# The name of the file containing the Keylime tenant client certificate. +# The file should be stored in the directory set in the 'tls_dir' option. +# This certificate is used by the Keylime tenant to connect to the other +# services using TLS. +# +# If set as 'default', the 'client-cert.crt' value is used. +client_cert = default + +# The list of trusted server CA certificates. +# The files in the list should be stored in the directory set in the 'tls_dir' +# option. +# +# If set as 'default', the value is set as '[cacert.crt]' +trusted_server_ca = default + +# Directory containing the EK CA certificates. +# The EK certificate provided by the agent will be validated against the CAs +# located in this directory. +tpm_cert_store = /var/lib/keylime/tpm_cert_store + +# Maximum size of the payload in bytes. The value should match the 'secure_size' +# option in the agent configuration +max_payload_size = 1048576 + +# List of hash algorithms used for PCRs +# Accepted values: sha512, sha384, sha256, sha1 +accept_tpm_hash_algs = ['sha512', 'sha384', 'sha256'] + +# List of encryption algorithms to use with the TPM +# Accepted values: ecc, rsa +accept_tpm_encryption_algs = ['ecc', 'rsa'] + +# List of signature algorithms to use +# Accepted values: rsassa, rsapss, ecdsa, ecdaa, ecschnorr +accept_tpm_signing_algs = ['ecschnorr', 'rsassa'] + +# Wether or not to use an exponantial backoff algorithm for retries. +exponential_backoff = True + +# Either how long to wait between failed attempts to communicate with the TPM +# in seconds, or the base for the exponential backoff algorithm if enabled via +# "exponential_backoff" option. +# Floating point values are accepted. +retry_interval = 2 + +# Integer number of retries to communicate with the TPM before giving up. +max_retries = 5 + +# Request timeout in seconds. +request_timeout = 60 + +# Tell the tenant whether to require an EK certificate from the TPM. +# If set to False the tenant will ignore EK certificates entirely. +# +# WARNING: SETTING THIS OPTION TO FALSE IS VERY DANGEROUS!!! +# +# If you disable this check, then you may not be talking to a real TPM. +# All the security guarantees of Keylime rely upon the security of the EK +# and the assumption that you are talking to a spec-compliant and honest TPM. + +# Some physical TPMs do not have EK certificates, so you may need to set +# this to "False" for some deployments. If you do set it to "False", you +# MUST use the 'ek_check_script' option below to specify a script that will +# check the provided EK against a allowlist for the environment that has +# been collected in a trustworthy way. For example, the cloud provider +# might provide a signed list of EK public key hashes. Then you could write +# an ek_check_script that checks the signature of the allowlist and then +# compares the hash of the given EK with the allowlist. +require_ek_cert = True + +# Optional script to execute to check the EK and/or EK certificate against a +# allowlist or any other additional EK processing you want to do. Runs in +# /var/lib/keylime. You call also specify an absolute path to the script. +# Script should return 0 if the EK or EK certificate are valid. Any other +# return value will invalidate the tenant quote check and prevent +# bootstrapping a key. +# +# The various keys are passed to the script via environment variables: +# EK - contains a PEM encoded version of the public EK +# EK_CERT - contains a DER encoded EK certificate if one is available. +# PROVKEYS - contains a json document containing EK, EKcert, and AIK from the +# provider. EK and AIK are in PEM format. The EKcert is in base64 encoded +# DER format. +# +# Set to blank to disable this check. See warning above if require_ek_cert +# is "False". +ek_check_script = + +# Path to file containing the measured boot reference state +mb_refstate = diff --git a/security/keylime-poc/compose/tenant.sh b/security/keylime-poc/compose/tenant.sh new file mode 100755 index 00000000..7dece8fc --- /dev/null +++ b/security/keylime-poc/compose/tenant.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Run docker-compose up -d first to have infra in place + +set -eu + +# test with args "-c add" as that triggers basically the whole keylime/tpm chain +docker exec \ + -it \ + --user root \ + compose-keylime-tenant-1 \ + keylime_tenant \ + --uuid c47b9ea2-2bc2-461b-957b-e77dbcf35e5e \ + "$@" diff --git a/security/keylime-poc/compose/verifier.conf b/security/keylime-poc/compose/verifier.conf new file mode 100644 index 00000000..d6daba7f --- /dev/null +++ b/security/keylime-poc/compose/verifier.conf @@ -0,0 +1,252 @@ +# Keylime verifier configuration +[verifier] + +# The configuration file version number +version = 2.3 + +# Unique identifier for the each verifier instances. +uuid = default + +# The binding address and port for the verifier server +ip = "0.0.0.0" +port = 8881 + +# The address and port of registrar server that the verifier communicates with +registrar_ip = "127.0.0.2" +registrar_port = 8891 + +# Enable mTLS communication between agent, verifier and tenant. +# Details on why setting it to "False" is generally considered insecure can be found +# on https://github.com/keylime/keylime/security/advisories/GHSA-2m39-75g9-ff5r +enable_agent_mtls = True + +# The 'tls_dir' option define the directory where the keys and certificates are +# stored. +# +# If set as 'generate', automatically generate a CA, keys, and certificates for +# the client and the server in the /var/lib/keylime/cv_ca directory, if not +# present. +# +# The 'server_key', 'server_cert', 'client_key', 'client_cert', +# 'trusted_client_ca', and 'trusted_server_ca' options should all be set with +# the 'default' keyword when 'generate' keyword is set for 'tls_dir'. +# +# If set as 'default', the 'var/lib/keylime/cv_ca' directory is used, which +# should contain the files indicated by the 'server_key', 'server_cert', +# 'client_key', 'client_cert', 'trusted_client_ca', and 'trusted_server_ca' +# options. +tls_dir = generate + +# The name of the file containing the Keylime verifier server private key. +# The file should be stored in the directory set in the 'tls_dir' option. +# This private key is used to serve the Keylime verifier REST API +# +# If set as 'default', the 'server-private.pem' value is used. +server_key = default + +# Set the password used to decrypt the server private key file. +# If 'tls_dir = generate', this password will also be used to protect the +# generated server private key. +# If left empty, the private key will not be encrypted. +server_key_password = + +# The name of the file containing the Keylime verifier server certificate. +# The file should be stored in the directory set in the 'tls_dir' option. +# +# If set as 'default', the 'server-cert.crt' value is used. +server_cert = default + +# The list of trusted client CA certificates. +# The files in the list should be stored in the directory set in the 'tls_dir' +# option. +# +# If set as 'default', the value is set as '[cacert.crt]' +trusted_client_ca = default + +# The name of the file containing the Keylime verifier client private key. +# The file should be stored in the directory set in the 'tls_dir' option. +# This private key is used by the Keylime verifier to connect to the other +# services using TLS. +# +# If set as 'default', the 'client-private.pem' value is used. +client_key = default + +# Set the password used to decrypt the client private key file. +# If 'tls_dir = generate', this password will also be used to protect the +# generated client private key. +# If left empty, the private key will not be encrypted. +client_key_password = + +# The name of the file containing the Keylime verifier client certificate. +# The file should be stored in the directory set in the 'tls_dir' option. +# This certificate is used by the Keylime verifier to connect to the other +# services using TLS. +# +# If set as 'default', the 'client-cert.crt' value is used. +client_cert = default + +# The list of trusted server CA certificates. +# The files in the list should be stored in the directory set in the 'tls_dir' +# option. +# +# If set as 'default', the value is set as '[cacert.crt]' +trusted_server_ca = default + +# Database URL Configuration +# See this document https://keylime.readthedocs.io/en/latest/installation.html#database-support +# for instructions on using different database configurations. +# +# An example of database_url value for using sqlite: +# sqlite:////var/lib/keylime/cv_data.sqlite +# An example of database_url value for using mysql: +# mysql+pymysql://keylime:keylime@keylime_db:[port]/verifier?charset=utf8 +# +# If set as 'sqlite' keyword, will use the configuration set by the file located +# at "/var/lib/keylime/cv_data.sqlite". +database_url = sqlite + +# Limits for DB connection pool size in sqlalchemy +# (https://docs.sqlalchemy.org/en/14/core/pooling.html#api-documentation-available-pool-implementations) +database_pool_sz_ovfl = 5,10 + +# Whether to automatically update the DB schema using alembic +auto_migrate_db = True + +# The number of worker processes to use for the cloud verifier. +# Set to "0" to create one worker per processor. +num_workers = 2 + +# Wether or not to use an exponantial backoff algorithm for retries. +exponential_backoff = True + +# Either how long to wait between failed attempts to connect to a cloud agent +# in seconds, or the base for the exponential backoff algorithm. +# Floating point values accepted here. +retry_interval = 2 + +# Number of retries to connect to an agent before giving up. Must be an integer. +max_retries = 5 + +# Time between integrity measurement checks, in seconds. If set to "0", checks +# will done as fast as possible. Floating point values accepted here. +quote_interval = 2 + +# The verifier limits the size of upload payloads (allowlists) which defaults to +# 100MB (104857600 bytes). This setting can be raised (or lowered) based on the +# size of the actual payloads +max_upload_size = 104857600 + +# Timeout in seconds for requests made to agents +request_timeout = 60.0 + +# The name of the boot attestation policy to use in comparing a measured boot event log +# with a measured boot reference state. +# A policy is a Python object that `isinstance` of `keylime.elchecking.policies.Policy` +# and was registered by calling `keylime.elchecking.policies.register`. +# The keylime agent extracts the measured boot event log. +# The verifier client specifies the measured boot reference state to use; +# this is specified independently for each agent. +# Depending on the policy, the same reference state may be usable with multiple agents. +# The `accept-all` policy ignores the reference state and approves every log. +measured_boot_policy_name = accept-all + +# This is a list of Python modules to dynamically load, for example to register +# additional boot attestation policies. +# Empty strings in the list are ignored. +# A module here may be relative, in which case it is interpreted +# relative to the keylime.elchecking package. +# The default value for this config item is the empty list. +measured_boot_imports = [] + +# This is used to manage the number of times measure boot attestation +# is done. In other words, it controls the number of times the call +# to the measure boot policy engine is made to evaluate the boot log +# against the policy specified. +# Here are its possible values and number of bootlog evaluations. +# once (default) : Bootlog evaluation will be done for only one time. +# always : Bootlog evaluation will always be done (i.e. for unlimited times). +measured_boot_evaluate = once + +# Severity labels for revocation events strictly ordered from least severity to +# highest severtiy. +severity_labels = ["info", "notice", "warning", "error", "critical", "alert", "emergency"] + +# Severity policy that matches different event_ids to the severity label. +# The rules are evaluated from the beginning of the list and the first match is +# used. The event_id can also be a regex. Default policy assigns the highest +# severity to all events. +severity_policy = [{"event_id": ".*", "severity_label" : "emergency"}] + +# If files are already opened when IMA tries to measure them this causes +# a time of measure, time of use (ToMToU) error entry. +# By default we ignore those entries and only print a warning. +# Set to False to treat ToMToU entries as errors. +ignore_tomtou_errors = False + +# Durable Attestation is currently marked as an experimental feature +# In order to enable Durable Attestation, an "adapter" for a Persistent data Store +# (time-series like database) needs to be specified. Some example adapters can be +# found under "da/examples" so, for instance +# "durable_attestation_import = keylime.da.examples.redis.py" +# could be used to interact with a Redis (Persistent data Store) +durable_attestation_import = + +# If an adapter for Durable Attestation was specified, then the URL for a Persistent Store +# needs to be specified here. A second optional URL could be specified, for a +# Rekor Transparency Log. A third additional URL could be specified, pointing to a +# Time Stamp Authority (TSA), compatible with RFC3161. Additionally, one might need to +# specify a path containing certificates required by the stores or TSA. Continuing with +# the above example, the following values could be assigned to the parameters: +# "persistent_store_url=redis://127.0.0.1:6379?db=10&password=/root/redis.auth&prefix=myda" +# "transparency_log_url=http://127.0.0.1:3000" +# "time_stamp_authority_url=http://127.0.0.1:2020" +# "time_stamp_authority_certs_path=~/mycerts/tsa_cert1.pem" +persistent_store_url = +transparency_log_url = +time_stamp_authority_url = +time_stamp_authority_certs_path = + +# If Durable Attestation was enabled, which requires a Persistent Store URL +# to be specified, the two following parameters control the format and enconding +# of the stored attestation artifacts (defaults "json" for format and "" for encoding) +persistent_store_format = json +persistent_store_encoding = + +# If Durable Attestation was enabled with a Transparency Log URL was specified, +# the digest algorithm for signatures is controlled by this parameter (default "sha256") +transparency_log_sign_algo = sha256 + +# If Durable Attestation was enabled with a Transparency Log URL was specified, +# a keylime administrator can specify some agent attributes (including attestation +# artifacts, such as quotes and logs) to be signed by the verifier. The use of "all" +# will result in the whole "package" (agent + artifacts) being signed and leaving it empty +# will mean no signing should be done. +signed_attributes = + +# Require that allowlists are signed with a key passed via the tenant tool +require_allow_list_signatures = False + +[revocations] + +# List of revocation notification methods to enable. +# +# Available methods are: +# +# "agent": Deliver notification directly to the agent via the REST +# protocol. +# +# "zeromq": Enable the ZeroMQ based revocation notification method; +# zmq_ip and zmq_port options must be set. Currently this only works if you are +# using keylime-CA. +# +# "webhook": Send notification via webhook. The endpoint URL must be +# configured with 'webhook_url' option. This can be used to notify other +# systems that do not have a Keylime agent running. +enabled_revocation_notifications = ['agent'] + +# The binding address and port of the revocation notifier service via ZeroMQ. +zmq_ip = 127.0.0.1 +zmq_port = 8992 + +# Webhook url for revocation notifications. +webhook_url = diff --git a/security/keylime-poc/k8s/README.md b/security/keylime-poc/k8s/README.md new file mode 100644 index 00000000..6fa57999 --- /dev/null +++ b/security/keylime-poc/k8s/README.md @@ -0,0 +1,217 @@ +# Keylime POC + + +WIP: NOTHING IS EXPECTED WORK HERE YET! + +## Kubernetes + +Make a Proof of Concept of Keylime in k8s. + +This POC is needed as the concept of having Keylime Tenant/Verifier/Registrar +outside K8s cluster, but Keylime Agent in K8s cluster and being accessed via +Ingress/LoadBalancer IP, is something Keylime maintainers did not think +originally as a use-case. This has several issues with the current design, and +while there is a proposal/ study for changing from "pull model" to "push +model", it is miles away and this POC tries to find out the minimal changes +needed to make the current model work for this use case. + +## POC requirements + +Since Keylime is about using TPM and being secure, we have some requirements: + +1. A machine/VM where we can setup a K8s cluster, and the pods can have access + to TPM. +1. We need the TPM public certificates for said machine, EK CA etc. +1. Machine where we can run Keylime services/commands, Registrar, Verifier and + Tenant. Preferably a second machine, so we can't "cheat". +1. There needs to be connectivity between these. Preferably different networks, + again we don't want to "cheat". +1. Agents have unique UUID that are pre-set, generated or tied to TPM EK. + There should be more than one machine in Cluster, so we have multiple Agents. + +For test/reproducibility purposes, we must consider some shortcuts: + +1. Use of single laptop, where the separation is happening either via + - VM (does it expose TPM properly) + - Containers (eventually all services, directly on Docker or in K8s cluster + will run in the same container runtime) +1. Using TPM simulation software (might be good for reproducibility due + different manufacturers having different certs etc) +1. Using only one machine in K8s cluster, and hence only one Agent (this may + lead of incorrect configs as there is only one Agent as is always "correct" + target) + +We have some availability for infra: + +1. Xerces (OpenStack) does not expose TPM to the VMs, so it is ruled out +1. BML (Bare Metal Lab) has real machines that certainly would work, but those + are not for development (jump hosts) or run test payloads (actual servers). + Also access to those is not readily available to persons outside the ESJ + team. + +## POC setup + +As we're talking about POC, it is good to start with MVP, which here is: + +1. Use laptop as hardware for TPM access +1. Use containers to make it reproducible +1. Make all hardware related things configurable (EK certs, networks, ...) + +This leads to test setup looking like this. + +### Registrar in Docker + +1. Make Docker networking such it can be accessed by the K8s services (use + host network if needed) +1. Run Keylime Registrar image in local Docker +1. Run Keylime Verifier image in local Docker +1. Run Keylime Tenant CLI in local Docker + +### Agent in K8s + +1. Run K8s cluster with Kind +1. Install Keylime Agent in Kind +1. Expose the Agent with LoadBalancer IP (if Kind supports that?) +1. Make Ingress rule for a fake hostname (uuid0-31.uuid32-64.cluster.local) + +## POC plan + +With POC setup in place, we aim to achieve following steps. + +1. Agent registration to Registrar must work. + + - Connectivity from Agent -> Registrar + - Agent must be configured with valid data (for later stages) + - `contact_ip` + - ... + - See Keylime Rust Agent + [contact_ip issue](https://github.com/keylime/rust-keylime/issues/848) + +1. Tenant will add Agent to Verifier with data from Registrar/command line. + + - With use of Tenant CLI command, we add Agent to Verifier to be polled + - Verifier will query Registrar about Agent details, when it is supplied + with Agent UUID. This needs Tenant -> Verifier -> Registrar connectivity + and configuration to work. + +1. Verifier will query Agent for data + + - Verifier now has all needed data to poll Agent. This is the tricky part as + it will use data input by Agent to Registrar, but needs to also travel + through LoadBalancer IP and Ingress rules to the correct Agent. Data must + only go to the Agent with matching UUID. + - NOTE: In the POC setup we have only one Agent, leading to the risk of + sending all Agent queries to this single Agent, allowing "cheating" in + K8s configurations as the sole Agent is the only target and UUID based rules + might "cheat". + +1. Agent sends data back + + - Agent gets data from TPM and sends it to Verifier. + - Agent EK certs and Verifier EK certs need to match at this point. + +1. ... + +1. Profit! + + There might be some additional steps, but this is how far we've come right now. + Stay tuned! + +## POC steps + +Actual steps to take to achieve [POC plan](#poc-plan). For this, a +[Makefile](./makefile) and [helper scripts](./scripts/) are implemented in this +repository. + +1. `make e2e` to run e2e, ie. `run` + `verify` +1. `make run` to just setup everything, ie. `docker` + `kind` +1. `make docker` to setup Verifier/Registrar correctly +1. `make kind` to setup Agent in K8s cluster +1. `make verify` to have Tenant sign up the Agent and trigger the verification +1. `make clean` to clean everything up +1. `make realclean` to clean everything up + remove any temporary files + images + +Whole POC e2e can be executed with `make e2e` and cleaned away with +`make realclean`. + +For anything else, use `docker` and `kubectl` commands for digging into details, +and when everything is running, use [scripts/run_tenant.sh](./scripts/run_tenant.sh) +to issue Tenant commands. + +### Verifier and Registrar installation + +Running `make docker` sets up Verifier first, which generates certificates for +mTLS in the shared directory `/tmp/keylime/cv_ca`, which needs to be mounted as +`/var/lib/keylime/cv_ca` in all containers. + +See [scripts/run_docker.sh](./scripts/run_docker.sh) for the code. + +### Agent installation + +Running `make kind` sets up Kind cluster, then applies K8s manifests from `k8s` +subdirectory to run Keylime Agent as DaemonSet. + +For nitty gritty details of the K8s installation of the agent, see +[manifest generation details](#manifest-generation). + +See [scripts/run_kind.sh](./scripts/run_kind.sh) for the code. + +#### Manifest generation + +This K8s installation is created based on the +[attestation-operator](https://github.com/keylime/attestation-operator) templates +via following process (no need to repeat, resulting files are stored in this +POC): + +1. Clone attestation-operator repo +1. Run `make helm-keylime` +1. Run `kind create cluster` to have something to deploy into +1. Run `make helm-keylime-deploy` run deploy all components from the Helm chart +1. Run `kubectl -n keylime get daemonset hhkl-keylime-agent -o yaml` to get a + manifest for Agent. +1. Add some adaptation, especially around mTLS and cert mounts. Certs generated + by the Docker container must be made secrets etc + +### Verification with Tenant + +tbd + +## Issues found during POC + +1. [Agent contact_ip issue](https://github.com/keylime/rust-keylime/issues/848) + +## POC shortcomings + +As mentioned in [POC requirements](#poc-requirements) section, this POC for +sake of simplicity has its limits. Following items could be implemented for +more completeness, if time is not an issue. + +1. Two or mode nodes +1. Two or more machines +1. Adding TPM simulation (or removal of it) +1. tbc + +## Extras + +Some extra more or less useful notes related to this POC. + +### Ubuntu 24.04 tools + +Per [Keylime documentation on TPM2 tools](https://keylime-docs.readthedocs.io/en/latest/installation.html#tpm-2-0-support) +we need to install some tools manually. + +- `tpm2-tools` and `tpm2-tss-engine-tools` need to be installed (at least for + convenience) + - `sudo apt install tpm2-tools tpm2-tss-engine-tools` + +## References + +Collection of referenced docs, issues etc. + +1. [Keylime documentation](https://keylime-docs.readthedocs.io/en/latest/) +1. [Attestation Operator](https://github.com/keylime/attestation-operator) + aka Keylime in K8s +1. [Push model proxy doc](https://github.com/keylime/attestation-operator/blob/main/docs/push-model-proxy.md) +1. [Agent contact_ip issue](https://github.com/keylime/rust-keylime/issues/848) +1. [Slack discussion](https://cloud-native.slack.com/archives/C01ARE2QUTZ/p1727792733885549) +1. [RedHat's Keylime docs](https://docs.redhat.com/de/documentation/red_hat_enterprise_linux/9/html/security_hardening/assembly_ensuring-system-integrity-with-keylime_security-hardening#configuring-keylime-agent_assembly_ensuring-system-integrity-with-keylime) diff --git a/security/keylime-poc/k8s/clean.sh b/security/keylime-poc/k8s/clean.sh new file mode 100755 index 00000000..ee5acc06 --- /dev/null +++ b/security/keylime-poc/k8s/clean.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091 + +# a clean up script for everything we've done + +set -eu + +# shall we do deep clean or not +REALCLEAN="${1:-}" + +. config.sh + +remove_docker_instances() +{ + docker rm -f "${KEYLIME_VERIFIER_NAME}" || true + docker rm -f "${KEYLIME_REGISTRAR_NAME}" || true +} + +remove_kind_cluster() +{ + kind delete cluster --name="${KIND_NAME}" || true +} + +remove_temporary_files() +{ + sudo rm -rf "${KEYLIME_TMP_DIR:?}" +} + +remove_docker_images() +{ + for image in "${KEYLIME_IMAGES[@]}"; do + docker image rm -f "${image}" + done +} + +# main +remove_docker_instances +remove_kind_cluster +remove_temporary_files + +if [[ -n "${REALCLEAN}" ]]; then + remove_docker_images +fi diff --git a/security/keylime-poc/k8s/config.sh b/security/keylime-poc/k8s/config.sh new file mode 100644 index 00000000..a471eca7 --- /dev/null +++ b/security/keylime-poc/k8s/config.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +# This holds the shared configuration options for Keylime scripts + +# container runtime +CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" + +# registry and image details +REGISTRY=quay.io/keylime +TAG=latest + +KEYLIME_TENANT_IMAGE="${REGISTRY}/keylime_tenant:${TAG}" +KEYLIME_REGISTRAR_IMAGE="${REGISTRY}/keylime_registrar:${TAG}" +KEYLIME_VERIFIER_IMAGE="${REGISTRY}/keylime_verifier:${TAG}" +KEYLIME_AGENT_IMAGE="${REGISTRY}/keylime_agent:${TAG}" + +declare -a KEYLIME_IMAGES=( + "${KEYLIME_TENANT_IMAGE}" + "${KEYLIME_REGISTRAR_IMAGE}" + "${KEYLIME_VERIFIER_IMAGE}" + "${KEYLIME_AGENT_IMAGE}" +) + +# docker instance names +KEYLIME_VERIFIER_NAME="keylime-verifier" +KEYLIME_REGISTRAR_NAME="keylime-registrar" +KEYLIME_TENANT_NAME="keylime-tenant" + +# shared directory name +KEYLIME_TMP_DIR="/tmp/keylime" +KEYLIME_INTERNAL_TLS_DIR="/var/lib/keylime/cv_ca" +KEYLIME_EXTERNAL_TLS_DIR="${KEYLIME_TMP_DIR}/cv_ca" + +# kind setup +KIND_NAME="keylime" diff --git a/security/keylime-poc/k8s/keylime-agent.yaml b/security/keylime-poc/k8s/keylime-agent.yaml new file mode 100644 index 00000000..309f3f47 --- /dev/null +++ b/security/keylime-poc/k8s/keylime-agent.yaml @@ -0,0 +1,169 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + annotations: + deprecated.daemonset.template.generation: "1" + meta.helm.sh/release-name: hhkl + meta.helm.sh/release-namespace: keylime + creationTimestamp: "2024-10-24T11:42:03Z" + generation: 1 + labels: + app.kubernetes.io/instance: hhkl + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: keylime-agent + app.kubernetes.io/version: latest + helm.sh/chart: keylime-agent-0.1.0 + name: hhkl-keylime-agent + namespace: keylime + resourceVersion: "3324" + uid: b271fc2c-af4d-4626-a981-2bda2bcf7fb2 +spec: + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/instance: hhkl + app.kubernetes.io/name: keylime-agent + template: + metadata: + creationTimestamp: null + labels: + app.kubernetes.io/instance: hhkl + app.kubernetes.io/name: keylime-agent + spec: + containers: + - command: + - /bin/keylime_agent + env: + - name: KEYLIME_AGENT_CONTACT_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: KEYLIME_AGENT_SERVER_KEY + value: /etc/keylime/agent/server/certs/server-private.pem + - name: KEYLIME_AGENT_SERVER_CERT + value: /etc/keylime/agent/server/certs/server-cert.crt + - name: KEYLIME_AGENT_RUN_AS + - name: KEYLIME_AGENT_AGENT_DATA_PATH + value: /var/lib/keylime-persistent/agent_data.json + - name: RUST_LOG + value: keylime_agent=info + envFrom: + - configMapRef: + name: hhkl-keylime-config + image: quay.io/keylime/keylime_agent:latest + imagePullPolicy: IfNotPresent + name: keylime-agent + ports: + - containerPort: 9002 + name: agent + protocol: TCP + resources: {} + securityContext: + privileged: true + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /etc/keylime/agent/server/certs + name: certs + readOnly: true + - mountPath: /var/lib/keylime/cv_ca/ + name: cvca-certs + readOnly: true + - mountPath: /var/lib/keylime-persistent + name: persistent + - mountPath: /var/lib/keylime/secure + name: secure + readOnly: true + - mountPath: /tmp + name: tmpfs + - mountPath: /sys/kernel/security + name: securityfs + readOnly: true + dnsPolicy: ClusterFirst + initContainers: + - command: + - /bin/bash + - -c + - | + # fail if any of the commands fail + set -e + + # copy the CA directory where we'll generate the certs from + cp -Rv /keylime/cv_ca /tmp/ + cd /tmp/cv_ca + + # now generate a new cert for the agent + # we need to do this on every start as the pod IP changes which is being used as the connection address + keylime_ca -d /tmp/cv_ca --command create --name "$POD_IP" + + # copy them to the expected destinations + cp -v /tmp/cv_ca/${POD_IP}-private.pem /certs/server-private.pem + cp -v /tmp/cv_ca/${POD_IP}-cert.crt /certs/server-cert.crt + env: + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + envFrom: + - configMapRef: + name: hhkl-keylime-config + - secretRef: + name: hhkl-keylime-ca-password + image: quay.io/keylime/keylime_tenant:latest + imagePullPolicy: IfNotPresent + name: keylime-agent-init + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /certs + name: certs + - mountPath: /keylime/cv_ca/ + name: cvca-certs + readOnly: true + - mountPath: /tmp + name: tmpfs + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: hhkl-keylime-agent + serviceAccountName: hhkl-keylime-agent + terminationGracePeriodSeconds: 30 + volumes: + - hostPath: + path: /sys/kernel/security + type: Directory + name: securityfs + - hostPath: + path: /var/lib/keylime + type: DirectoryOrCreate + name: persistent + - emptyDir: + medium: Memory + sizeLimit: 10Mi + name: secure + - emptyDir: + medium: Memory + sizeLimit: 10Mi + name: certs + - name: cvca-certs + secret: + defaultMode: 420 + secretName: hhkl-keylime-certs + - emptyDir: {} + name: tmpfs + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate +status: + currentNumberScheduled: 1 + desiredNumberScheduled: 1 + numberMisscheduled: 0 + numberReady: 0 + numberUnavailable: 1 + observedGeneration: 1 + updatedNumberScheduled: 1 diff --git a/security/keylime-poc/k8s/run_docker.sh b/security/keylime-poc/k8s/run_docker.sh new file mode 100755 index 00000000..3d569e94 --- /dev/null +++ b/security/keylime-poc/k8s/run_docker.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091 + +# This script runs Keylime Verifier and Registrar as Docker containers. +# It is complimented by the "run_tenant.sh" that runs Keylime Tenant in +# container, sharing the config witht this script. + +set -eux + +. config.sh + +run_verifier() +{ + # we need to supply cv_ca directory for sharing mtls certs + docker run \ + -d \ + -v "${KEYLIME_EXTERNAL_TLS_DIR}":"${KEYLIME_INTERNAL_TLS_DIR}":rw \ + --name "${KEYLIME_VERIFIER_NAME}" \ + "${KEYLIME_VERIFIER_IMAGE}" +} + +run_registrar() +{ + # we need to supply cv_ca directory for sharing mtls certs + docker run \ + -d \ + -v "${KEYLIME_EXTERNAL_TLS_DIR}":"${KEYLIME_INTERNAL_TLS_DIR}":rw \ + --name "${KEYLIME_REGISTRAR_NAME}" \ + "${KEYLIME_REGISTRAR_IMAGE}" +} + + +# main +run_verifier +run_registrar diff --git a/security/keylime-poc/k8s/run_kind.sh b/security/keylime-poc/k8s/run_kind.sh new file mode 100755 index 00000000..a28328be --- /dev/null +++ b/security/keylime-poc/k8s/run_kind.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091 + +# This script runs Kind cluster and sets up Keylime Agent in the cluster. +# Keylime Agent runs as DaemonSet, and is then fronted by LoadBalancer +# service and Ingress rules. +# +# In first step, we run it without Ingress. It should be noted that this +# cheats in multiple ways: +# +# 1. It uses an IP address (not hostname) +# 2. It avoids Ingress rules for UUID based call routing + +set -eux + +. config.sh + +check_tools() +{ + declare -a tools=( + kind + kubectl + ) + + for tool in "${tools[@]}"; do + command -v "${tool}" &>/dev/null || { echo "error: ${tool} is not in PATH"; exit 1; } + done +} + +launch_kind_cluster() +{ + kind create cluster --name="${KIND_NAME}" +} + +run_agent() +{ + echo TBD +} + + +# main +check_tools +launch_kind_cluster +run_agent diff --git a/security/keylime-poc/k8s/run_tenant.sh b/security/keylime-poc/k8s/run_tenant.sh new file mode 100755 index 00000000..d4ddaff8 --- /dev/null +++ b/security/keylime-poc/k8s/run_tenant.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091 + +# This script run Keylime Tenant in Docker container, allowing non-intrusive +# way of executing the Tenant CLI commands while sharing the configuration +# with the other Keylime components. + +set -eux + +. config.sh + +run_tenant() +{ + # we need to supply cv_ca directory for sharing mtls certs + docker run -it --rm \ + -v "${KEYLIME_EXTERNAL_TLS_DIR}":"${KEYLIME_INTERNAL_TLS_DIR}":rw \ + --name "${KEYLIME_TENANT_NAME}" \ + "${KEYLIME_TENANT_IMAGE}" \ + "$@" +} + + +# main +run_tenant "$@" diff --git a/security/keylime-poc/scripts/enable_ima_measurement.sh b/security/keylime-poc/scripts/enable_ima_measurement.sh new file mode 100755 index 00000000..845bee86 --- /dev/null +++ b/security/keylime-poc/scripts/enable_ima_measurement.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# This is enabling IMA measurement temporarily +# Need to set up grub/boottime parameters for permanent measurements +# If it doesn't work, grub config for safe startup: +# GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX ima=on integrity_audit=1 lsm=integrity,ima" + +# policy is auditing: change "audit" to "measure" + +# check requirements (tpm2-tools) +set -e +command -v tpm2_startup &>/dev/null + +# don't fail +set -x +set +e + +# Enable IMA measurement +echo "1" | sudo tee /sys/kernel/security/ima/policy_update +mkdir -p /etc/ima +sudo tee /etc/ima/ima-policy << 'EOF' +# Default IMA policy +# Don't measure files opened with read-only permissions +dont_measure obj_type=file mask=MAY_READ +# Measure all executed files +audit func=BPRM_CHECK mask=MAY_EXEC +# Measure files mmap()ed for execute +audit func=FILE_MMAP mask=MAY_EXEC +# Measure files opened for write or append +audit func=FILE_CHECK mask=MAY_WRITE uid=0 +EOF + +# load the ima policy +sudo cat /etc/ima/ima-policy | sudo tee /sys/kernel/security/ima/policy + +# Configure TPM PRC - this needs +# setup tpm2-tools to access the tpmserver in docker +export TPM2TOOLS_TCTI="mssim:host=localhost,port=2321" +tpm2_startup -c + +# PCR 10 will store IMA measurements +tpm2_pcrextend 10:sha256=0000000000000000000000000000000000000000000000000000000000000000 diff --git a/security/keylime-poc/scripts/gen_allowlist.sh b/security/keylime-poc/scripts/gen_allowlist.sh new file mode 100755 index 00000000..a5506106 --- /dev/null +++ b/security/keylime-poc/scripts/gen_allowlist.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Script to generate file hashes for allowlist +# redirect to target file "allowlist.txt" + +set -eu + +cat <