1
0
forked from brl/citadel

1 Commits

Author SHA1 Message Date
isa
5cf0cee2fc Implement TUF updates 2026-01-22 17:35:23 -05:00
16 changed files with 624 additions and 764 deletions

6
.gitignore vendored
View File

@@ -1,3 +1,9 @@
build/
bitbake-cookerdaemon.log
*~
# TUF update infrastructure generated files
tuf-repo/
*.key
*.key.enc
*.pub

View File

@@ -7,29 +7,35 @@ IMAGE_FSTYPES = "ext4"
IMAGE_OVERHEAD_FACTOR = "1.2"
inherit image
inherit citadel-signing
CITADEL_IMAGE_CHANNEL ??= "dev"
CITADEL_CHANNEL ??= "dev"
CITADEL_IMAGE_COMPRESS ??= "true"
do_citadel_mkimage() {
cat > ${B}/mkimage.conf << EOF
image-type = "${CITADEL_IMAGE_TYPE}"
channel = "${CITADEL_IMAGE_CHANNEL}"
version = ${CITADEL_IMAGE_VERSION}
channel = "${CITADEL_CHANNEL}"
version = "${CITADEL_IMAGE_VERSION}"
timestamp = "${DATETIME}"
source = "${IMGDEPLOYDIR}/${IMAGE_LINK_NAME}.ext4"
compress = ${CITADEL_IMAGE_COMPRESS}
EOF
ver=$(printf "%03d" ${CITADEL_IMAGE_VERSION})
if [ "${CITADEL_CHANNEL}" != "dev" ]; then
echo 'private-key-path = "${PRIVATE_KEY_PATH_ABS}"' >> ${B}/mkimage.conf
echo 'public-key-path = "${PUBLIC_KEY_PATH_ABS}"' >> ${B}/mkimage.conf
echo 'certificate-path = "${CERT_PATH_ABS}"' >> ${B}/mkimage.conf
fi
ver=${CITADEL_IMAGE_VERSION}
if [ "${CITADEL_IMAGE_TYPE}" = "kernel" ]; then
KERNEL_ID=$(generate_kernel_id)
echo "kernel-version = \"${CITADEL_KERNEL_VERSION}\"" >> ${B}/mkimage.conf
echo "kernel-id = \"${KERNEL_ID}\"" >> ${B}/mkimage.conf
fname="citadel-kernel-${CITADEL_KERNEL_VERSION}-${CITADEL_IMAGE_CHANNEL}-${ver}.img"
fname="citadel-kernel-${CITADEL_KERNEL_VERSION}-${CITADEL_CHANNEL}-${ver}.img"
else
fname="citadel-${CITADEL_IMAGE_TYPE}-${CITADEL_IMAGE_CHANNEL}-${ver}.img"
fname="citadel-${CITADEL_IMAGE_TYPE}-${CITADEL_CHANNEL}-${ver}.img"
fi
citadel-mkimage ${B}
mv ${B}/${fname} ${IMGDEPLOYDIR}
@@ -38,6 +44,7 @@ EOF
addtask do_citadel_mkimage after do_image_ext4 before do_image_complete
do_citadel_mkimage[cleandirs] = "${B}"
do_citadel_mkimage[vardepsexclude] = "DATETIME"
do_citadel_mkimage[vardeps] += "CITADEL_CHANNEL"
IMAGE_POSTPROCESS_COMMAND += " generate_shasum_buildhistory ;"

View File

@@ -0,0 +1,48 @@
# Logic for deriving Citadel signing key paths
# Used by citadel-image.bbclass and base-realmfs-image.bb
python () {
import os
import re
import bb
recipe_file = d.getVar('FILE')
if recipe_file is None:
bb.fatal("FILE variable is not set. This indicates a problem with the build environment.")
# Derive layerdir from recipe_file
# recipe_file is something like /path/to/meta-citadel/recipes-citadel/images/image.bb
# We need to get /path/to/meta-citadel
meta_citadel_index = recipe_file.find('meta-citadel')
if meta_citadel_index == -1:
bb.fatal("Could not find 'meta-citadel' in recipe file path.")
layerdir = os.path.abspath(recipe_file[:meta_citadel_index + len('meta-citadel')])
# Manually parse citadel-distro.conf to get CITADEL_CHANNEL
# This ensures we use the version defined in the distro config on disk
citadel_distro_conf_path = os.path.join(layerdir, 'conf', 'distro', 'citadel-distro.conf')
citadel_channel = None
try:
with open(citadel_distro_conf_path, 'r') as f:
for line in f:
match = re.match(r'CITADEL_CHANNEL\s*=\s*"(.*)"', line)
if match:
citadel_channel = match.group(1)
break
except FileNotFoundError:
bb.fatal(f"citadel-distro.conf not found at {citadel_distro_conf_path}")
if citadel_channel is None:
bb.fatal(f"CITADEL_CHANNEL not found in {citadel_distro_conf_path}. Please ensure it is set.")
# Use the same image signing key as all other images (unified key system)
private_key_path = os.path.join(layerdir, 'recipes-citadel', 'citadel-keys', 'files', citadel_channel + '_image.key')
public_key_path = os.path.join(layerdir, 'recipes-citadel', 'citadel-keys', 'files', citadel_channel + '_image.pub')
cert_path = os.path.join(layerdir, 'recipes-citadel', 'citadel-keys', 'files', citadel_channel + '_image.cert')
d.setVar('PRIVATE_KEY_PATH_ABS', private_key_path)
d.setVar('PUBLIC_KEY_PATH_ABS', public_key_path)
d.setVar('CERT_PATH_ABS', cert_path)
bb.note(f"Citadel image signing key: {private_key_path}")
}

View File

@@ -46,3 +46,15 @@ INHERIT += "buildhistory"
PREFERRED_RPROVIDER_libdevmapper-native = "libdevmapper-native"
require conf/distro/include/security_flags.inc
# --- Citadel Update Configuration ---
# Single source of truth for update client, channel, and component versions.
CITADEL_CLIENT = "public"
CITADEL_CHANNEL = "dev"
CITADEL_PUBLISHER = "Subgraph"
UPDATE_SERVER = "https://update.subgraph.com"
CITADEL_ROOTFS_VERSION = "0.1.0"
CITADEL_KERNEL_VERSION = "6.14.0"
CITADEL_EXTRA_VERSION = "0.1.0"
CITADEL_REALMFS_VERSION = "0.1.0"

View File

@@ -5,5 +5,7 @@
-A INPUT -m state --state ESTABLISHED -j ACCEPT
-A OUTPUT -p udp -m udp --sport 68 --dport 67 -j ACCEPT
-A OUTPUT -p udp -m owner --uid-owner systemd-timesync -j ACCEPT
-A OUTPUT -j LOG --log-uid --log-prefix 'iptables'
-A OUTPUT -p udp -m udp --dport 53 -m owner --uid-owner citadel-tool -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 443 -m owner --uid-owner citadel-tool -j ACCEPT
--A OUTPUT -j LOG --log-uid --log-prefix 'iptables'
COMMIT

View File

@@ -0,0 +1,98 @@
SUMMARY = "Installs the keys key for Citadel image verification and TUF updates"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
PV = "1.0"
NO_STAGING_AREA = "1"
# S = "${WORKDIR}"
python () {
import os
# 1. Setup paths and variables
thisdir = d.getVar('THISDIR')
channel = d.getVar('CITADEL_CHANNEL')
# 2. Define the list of absolutely required files
# (Matches your logic in do_install)
required_files = [
"files/root.json",
"files/root_image.pub",
]
# 3. Add channel-specific requirements
if channel != "dev":
required_files.append(f"files/{channel}_image.pub")
required_files.append(f"files/{channel}_image.cert")
required_files.append(f"files/{channel}.pub")
# 4. Check for existence immediately
missing_files = []
for f in required_files:
full_path = os.path.join(thisdir, f)
if not os.path.exists(full_path):
missing_files.append(f)
# 5. Fail hard if anything is missing
if missing_files:
bb.fatal(f"BUILD ABORTED: Missing critical Citadel keys in {thisdir}: {', '.join(missing_files)}")
}
SRC_URI = " \
file://root.json \
file://root_image.pub \
${@'file://' + d.getVar('CITADEL_CHANNEL') + '_image.pub' if d.getVar('CITADEL_CHANNEL') != 'dev' else ''} \
${@'file://' + d.getVar('CITADEL_CHANNEL') + '_image.cert' if d.getVar('CITADEL_CHANNEL') != 'dev' else ''} \
${@'file://' + d.getVar('CITADEL_CHANNEL') + '.pub' if d.getVar('CITADEL_CHANNEL') != 'dev' else ''} \
"
FILES:${PN} += "/usr/share/citadel/keys/"
do_install() {
if [ -f "${THISDIR}/files/dev_image.pub" ] || [ -f "${THISDIR}/files/dev_image.key" ]; then
bbfatal "dev_image.pub or dev_image.key should not exist. The dev channel must not have a pre-set key."
fi
install -d ${D}/usr/share/citadel/keys/
install -d ${D}/etc/citadel/
# 1. Install root.json for TUF updates
ROOT_JSON="${THISDIR}/files/root.json"
if [ -f "${ROOT_JSON}" ]; then
install -m 0644 "${ROOT_JSON}" ${D}/etc/citadel/root.json
else
bbfatal "root.json not found at ${ROOT_JSON}. TUF updates will not work."
fi
# 2. Install Root Image Public Key (Trust Anchor for all channel images)
ROOT_IMAGE_PUB="${THISDIR}/files/root_image.pub"
if [ -f "${ROOT_IMAGE_PUB}" ]; then
install -m 0644 "${ROOT_IMAGE_PUB}" ${D}/usr/share/citadel/keys/root_image.pub
else
bbfatal "root_image.pub not found at ${ROOT_IMAGE_PUB}. Image verification will fail."
fi
# 3. Install current channel keys
if [ "${CITADEL_CHANNEL}" != "dev" ]; then
# Image Verification Key
IMAGE_KEY_FILE="${THISDIR}/files/${CITADEL_CHANNEL}_image.pub"
if [ ! -f "${IMAGE_KEY_FILE}" ]; then
bbfatal "Public image key for channel '${CITADEL_CHANNEL}' not found at ${IMAGE_KEY_FILE}"
fi
install -m 0644 "${IMAGE_KEY_FILE}" ${D}/usr/share/citadel/keys/${CITADEL_CHANNEL}_image.pub
# Image Authorization Certificate
CERT_FILE="${THISDIR}/files/${CITADEL_CHANNEL}_image.cert"
if [ ! -f "${CERT_FILE}" ]; then
bbfatal "Authorization certificate for channel '${CITADEL_CHANNEL}' not found at ${CERT_FILE}"
fi
install -m 0644 "${CERT_FILE}" ${D}/usr/share/citadel/keys/${CITADEL_CHANNEL}_image.cert
# TUF Channel Delegation Key
TUF_KEY_FILE="${THISDIR}/files/${CITADEL_CHANNEL}.pub"
if [ ! -f "${TUF_KEY_FILE}" ]; then
bbfatal "TUF public key for channel '${CITADEL_CHANNEL}' not found at ${TUF_KEY_FILE}"
fi
install -m 0644 "${TUF_KEY_FILE}" ${D}/usr/share/citadel/keys/${CITADEL_CHANNEL}.pub
fi
}

View File

@@ -0,0 +1,84 @@
{
"signatures": [
{
"keyid": "9aa22bfb9ecc4197",
"sig": "ad454c2bf3581e1f7ebdb2dfd009fb1ff6f5f70495efeed2eb8fc0d22b3f8f10855eded4a3b45e8da0e5444cebd651a638065985505daa9f657ffe828af40103"
}
],
"signed": {
"_type": "root",
"spec_version": "1.0.0",
"version": 1,
"expires": "2027-01-15T21:39:40.274833840+00:00",
"keys": {
"9aa22bfb9ecc4197": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "f5e8ef289a4764e9a7610e27f19b56da90b3f6b6ae7ada3286f8a59c26f68fb2"
}
},
"26ad4609925ec523": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "b685eefea59bb2bb578152da92cfad860963bda45a97d48e6400425cd76f57fa"
}
},
"3571af7acd9ddf58": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "b01bbabdf9551200f26ab554c7737cdc7075d15fdcb65f1467872066412387db"
}
},
"18b1818444deba88": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "367a25f43c9c61a6a58896a373aeb1b0867b8ec538b0274a480e1e360ab2a9cd"
}
},
"38e1ec4c56c4245e": {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": "688a9b82ead1396256d893528f641525afeecbbfce73f0e262311f83c5d1b607"
}
}
},
"roles": {
"targets": {
"keyids": [
"3571af7acd9ddf58"
],
"threshold": 1
},
"root": {
"keyids": [
"9aa22bfb9ecc4197"
],
"threshold": 1
},
"snapshot": {
"keyids": [
"26ad4609925ec523"
],
"threshold": 1
},
"timestamp": {
"keyids": [
"18b1818444deba88"
],
"threshold": 1
},
"root_image": {
"keyids": [
"38e1ec4c56c4245e"
],
"threshold": 1
}
},
"consistent_snapshot": false
}
}

View File

@@ -0,0 +1 @@
688a9b82ead1396256d893528f641525afeecbbfce73f0e262311f83c5d1b607

View File

@@ -6,33 +6,39 @@ DEPENDS = "citadel-tools-native cryptsetup-native"
PACKAGE_ARCH = "${MACHINE_ARCH}"
inherit deploy
inherit citadel-signing
require citadel-image.inc
REALMFS_DIR = "${TOPDIR}/realmfs"
CITADEL_IMAGE_VERSION = "1"
do_realmfs_mkimage() {
cat > ${B}/mkimage.conf << EOF
image-type = "realmfs"
channel = "${CITADEL_IMAGE_CHANNEL}"
version = 1
channel = "${CITADEL_CHANNEL}"
version = "${CITADEL_REALMFS_VERSION}"
timestamp = "${DATETIME}"
source = "${REALMFS_DIR}/citadel-realmfs.ext4"
realmfs-name = "base"
compress = true
private-key-path = "${PRIVATE_KEY_PATH_ABS}"
public-key-path = "${PUBLIC_KEY_PATH_ABS}"
certificate-path = "${CERT_PATH_ABS}"
EOF
citadel-mkimage ${B}
}
addtask do_realmfs_mkimage after do_configure before do_build
do_realmfs_mkimage[vardepsexclude] = "DATETIME"
do_realmfs_mkimage[vardeps] += "CITADEL_CHANNEL"
do_realmfs_mkimage[cleandirs] = "${B}"
do_deploy() {
ver=$(printf "%03d" ${CITADEL_IMAGE_VERSION})
fname="citadel-realmfs-${CITADEL_IMAGE_CHANNEL}-${ver}.img"
ver=${CITADEL_REALMFS_VERSION}
fname="citadel-realmfs-${CITADEL_CHANNEL}-${ver}.img"
install -m 644 -T ${B}/${fname} ${DEPLOYDIR}/base-realmfs.img
}
addtask do_deploy after do_realmfs_mkimage before do_build
do_deploy[vardeps] += "CITADEL_CHANNEL"
do_fetch[noexec] = "1"
do_unpack[noexec] = "1"

View File

@@ -1,9 +1,7 @@
CITADEL_IMAGE_CHANNEL = "dev"
CITADEL_IMAGE_VERSION_rootfs = "1"
CITADEL_IMAGE_VERSION_extra = "1"
CITADEL_IMAGE_VERSION_kernel = "1"
CITADEL_IMAGE_VERSION_rootfs = "${CITADEL_ROOTFS_VERSION}"
CITADEL_IMAGE_VERSION_extra = "${CITADEL_EXTRA_VERSION}"
CITADEL_IMAGE_VERSION_kernel = "${CITADEL_KERNEL_VERSION}"
CITADEL_KERNEL_VERSION = "6.14.0"

View File

@@ -95,13 +95,13 @@ EOF
}
install_resource_image() {
version=$(printf "%03d" ${2})
version=${2}
if [ "${1}" = "kernel" ]; then
src_fname="citadel-kernel-${CITADEL_KERNEL_VERSION}-${CITADEL_IMAGE_CHANNEL}-${version}.img"
src_fname="citadel-kernel-${CITADEL_KERNEL_VERSION}-${CITADEL_CHANNEL}-${version}.img"
dst_fname="citadel-kernel-${CITADEL_KERNEL_VERSION}.img"
else
src_fname="citadel-${1}-${CITADEL_IMAGE_CHANNEL}-${version}.img"
src_fname="citadel-${1}-${CITADEL_CHANNEL}-${version}.img"
dst_fname="citadel-${1}.img"
fi

View File

@@ -9,6 +9,7 @@ ROOTFS_POSTPROCESS_COMMAND += "set_disable_root_password; symlink_lib64; setup_v
IMAGE_INSTALL += "\
packagegroup-citadel-base \
packagegroup-citadel \
citadel-keys \
"
CITADEL_IMAGE_VERSION = "${CITADEL_IMAGE_VERSION_rootfs}"
@@ -54,7 +55,7 @@ setup_var() {
}
append_os_release() {
echo "CITADEL_CHANNEL=\"${CITADEL_IMAGE_CHANNEL}\"" >> ${IMAGE_ROOTFS}/etc/os-release
echo "CITADEL_CHANNEL=\"${CITADEL_CHANNEL}\"" >> ${IMAGE_ROOTFS}/etc/os-release
echo "CITADEL_ROOTFS_VERSION=\"${CITADEL_IMAGE_VERSION_rootfs}\"" >> ${IMAGE_ROOTFS}/etc/os-release
}

View File

@@ -3,7 +3,7 @@ HOMEPAGE = "http://github.com/subgraph/citadel"
LICENSE = "CLOSED"
LIC_FILES_CHKSUM=""
inherit cargo cargo-update-recipe-crates systemd gsettings pkgconfig
inherit cargo cargo-update-recipe-crates systemd gsettings pkgconfig useradd
# DONUT USE CARGO BITBAKE ANYMORE!
#
@@ -14,7 +14,7 @@ require citadel-tools-crates.inc
#
# Update this when changes are pushed to github
#
SRCREV = "3a3d5c3b9b02728753d4f659073168c5d3f3664e"
SRCREV = "054a32bf75169024526c49df3b83f88811ba3fe3"
# get git repo owner from citadel to find the correct citadel-tools repo path
python () {
@@ -54,11 +54,15 @@ FILES:${PN} = "\
${bindir}/citadel-image \
${bindir}/citadel-realmfs \
${bindir}/citadel-update \
${bindir}/citadel-fetch \
${systemd_system_unitdir} \
${sysconfdir}/dbus-1/system.d \
${datadir}/applications \
"
USERADD_PACKAGES = "${PN}"
USERADD_PARAM:${PN} = "-m -u 700 -s /bin/nologin citadel-tool"
SYSTEMD_SERVICE:${PN} = "citadel-current-watcher.path citadel-realmsd.service citadel-boot-automount.service"
TARGET_BIN = "${B}/target/${CARGO_TARGET_SUBDIR}"
@@ -89,6 +93,8 @@ do_install() {
# /usr/libexec/citadel-tool
install -m 755 ${TARGET_BIN}/citadel-tool ${D}${libexecdir}
# Change ownership of the main tool executable for citadel-fetch
chown 700 ${D}${libexecdir}/citadel-tool
# citadel-realms as /usr/bin/realms
install -m 755 -T ${TARGET_BIN}/citadel-realms ${D}${bindir}/realms
@@ -108,6 +114,7 @@ do_install() {
ln ${D}${libexecdir}/citadel-tool ${D}${bindir}/citadel-mkimage
ln ${D}${libexecdir}/citadel-tool ${D}${bindir}/citadel-realmfs
ln ${D}${libexecdir}/citadel-tool ${D}${bindir}/citadel-update
ln ${D}${libexecdir}/citadel-tool ${D}${bindir}/citadel-fetch
}
#

View File

@@ -40,6 +40,7 @@ do_install() {
install -d ${D}${sysconfdir}
install -m 644 ${UNPACKDIR}/initrd-release ${D}${sysconfdir}
echo "CITADEL_CHANNEL=\"${CITADEL_CHANNEL}\"" >> ${D}${sysconfdir}/initrd-release
install -m 644 ${UNPACKDIR}/crypttab ${D}${sysconfdir}
install -d ${D}${sysconfdir}/udev/rules.d
install -m 644 ${UNPACKDIR}/11-dm.rules ${D}${sysconfdir}/udev/rules.d

View File

@@ -5,6 +5,7 @@ NO_RECOMMENDATIONS = "1"
PACKAGE_INSTALL = "\
citadel-initramfs \
citadel-tools-boot \
citadel-keys \
cryptsetup \
lvm2 \
xz \