lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 9c28bdb164296a1c3ae5e9d7c3e3b45282506cb7
parent 1f9e77a01e38c59e0b3bd47180a74f52e8e5625e
Author: triesap <tyson@radroots.org>
Date:   Thu, 20 Aug 2026 19:55:34 +0000

build: add service oci image

- add a closed rootless OCI image constructor for hardened services
- bind build identity, contract versions, mounts, and supported platforms
- verify minimal image contents and read-only-root container operation

Diffstat:
Mbuild/nix/service/default.nix | 1+
Mbuild/nix/service/fixture.nix | 289++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abuild/nix/service/oci.nix | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 438 insertions(+), 0 deletions(-)

diff --git a/build/nix/service/default.nix b/build/nix/service/default.nix @@ -11,5 +11,6 @@ mkServiceChecks = import ./checks.nix { inherit crane lib pkgs; }; mkServiceApps = import ./apps.nix { inherit lib pkgs; }; mkServiceDevShell = import ./devshell.nix { inherit lib pkgs; }; + mkServiceOciImage = import ./oci.nix { inherit lib pkgs; }; mkServiceOutputs = import ./compose.nix { inherit lib; }; } diff --git a/build/nix/service/fixture.nix b/build/nix/service/fixture.nix @@ -37,6 +37,101 @@ let serviceName = "fixture_service"; inherit nativeInputs toolchain; }; + fixtureBuildInfo = { + serviceVersion = "0.1.0-alpha"; + serviceCommit = "1111111111111111111111111111111111111111"; + libRevision = "2222222222222222222222222222222222222222"; + rustVersion = "1.97.1"; + target = pkgs.stdenv.hostPlatform.rust.rustcTarget; + featureProfile = "service-host"; + contractVersions = { + config = 1; + state = 2; + admin = 3; + status = 4; + provider = 5; + }; + }; + ociImage = + if pkgs.stdenv.isLinux then + service.mkServiceOciImage { + serviceName = "fixture_service"; + inherit package; + binaryName = "fixture-service"; + buildInfo = fixtureBuildInfo; + } + else + null; + ociImageCheck = + if pkgs.stdenv.isLinux then + pkgs.runCommand "fixture-service-oci-image" + { + nativeBuildInputs = [ + pkgs.coreutils + pkgs.gnutar + pkgs.gzip + pkgs.jq + ]; + } + '' + mkdir image + tar -xzf ${ociImage} -C image + config_file="$(jq -er '.[0].Config' image/manifest.json)" + test -f "image/$config_file" + + jq -e ' + .architecture == "${if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" else "amd64"}" and + .os == "linux" and + .created == "1970-01-01T00:00:01+00:00" and + .config.User == "65532:65532" and + .config.Entrypoint == ["${package}/bin/fixture-service"] and + .config.WorkingDir == "/" and + .config.Env == ["SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"] and + .config.StopSignal == "SIGTERM" and + (.config | has("Volumes") | not) and + .config.Labels == { + "dev.radroots.build.feature-profile": "service-host", + "dev.radroots.build.lib-revision": "2222222222222222222222222222222222222222", + "dev.radroots.build.rust-version": "1.97.1", + "dev.radroots.build.target": "${pkgs.stdenv.hostPlatform.rust.rustcTarget}", + "dev.radroots.contract.admin-version": "3", + "dev.radroots.contract.config-version": "1", + "dev.radroots.contract.provider-version": "5", + "dev.radroots.contract.state-version": "2", + "dev.radroots.contract.status-version": "4", + "dev.radroots.mount.config": "/etc/radroots/services/fixture_service", + "dev.radroots.mount.config.mode": "read-only", + "dev.radroots.mount.credentials": "/etc/radroots/secrets/services/fixture_service", + "dev.radroots.mount.credentials.mode": "read-only", + "dev.radroots.mount.runtime": "/run/radroots/services/fixture_service", + "dev.radroots.mount.runtime.mode": "read-write", + "dev.radroots.mount.state": "/var/lib/radroots/services/fixture_service", + "dev.radroots.mount.state.mode": "read-write", + "dev.radroots.rootfs": "read-only-compatible", + "org.opencontainers.image.description": "Hardened fixture_service service image", + "org.opencontainers.image.licenses": "MIT OR Apache-2.0", + "org.opencontainers.image.revision": "1111111111111111111111111111111111111111", + "org.opencontainers.image.title": "fixture_service", + "org.opencontainers.image.version": "0.1.0-alpha" + } + ' "image/$config_file" + + jq -e '.[0].RepoTags == ["fixture-service:0.1.0-alpha"]' image/manifest.json + jq -e '([.[0].Layers | length] | .[0] >= 1 and .[0] <= 2)' image/manifest.json + jq -er '.[0].Layers[]' image/manifest.json | while IFS= read -r layer; do + tar -tf "image/$layer" + done | sort -u > image-entries + + grep -E '/bin/fixture-service$' image-entries + if grep -E '(^|/)(bin/(ba)?sh|bin/nix|bin/cargo|bin/rustc|Cargo.toml|src/)' image-entries; then + echo "fixture OCI image contains a development or shell payload" >&2 + exit 1 + fi + + touch "$out" + '' + else + null; appSmoke = pkgs.runCommand "fixture-service-app-smoke" { } '' test "$(${apps.default.program} --help)" = "fixture-service" test "$(${apps.release-acceptance.program})" = "fixture-service release acceptance" @@ -102,6 +197,9 @@ let extraChecks = { app-smoke = appSmoke; inherit smoke; + } + // lib.optionalAttrs pkgs.stdenv.isLinux { + oci-image = ociImageCheck; }; }; outputs = service.mkServiceOutputs { @@ -109,6 +207,9 @@ let inherit nativeInputs package; inherit apps checks; devShells.default = devShell; + extraPackages = lib.optionalAttrs pkgs.stdenv.isLinux { + oci = ociImage; + }; }; invalidName = builtins.tryEval ( (service.mkServiceOutputs { @@ -353,6 +454,173 @@ let )).drvPath )) ]; + ociArgs = { + serviceName = "fixture_service"; + inherit package; + binaryName = "fixture-service"; + buildInfo = fixtureBuildInfo; + }; + maximumOciResult = + if pkgs.stdenv.isLinux then + builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + serviceName = lib.concatStrings (lib.replicate 128 "a"); + binaryName = lib.concatStrings (lib.replicate 128 "b"); + buildInfo = fixtureBuildInfo // { + serviceVersion = lib.concatStrings (lib.replicate 128 "1"); + contractVersions = lib.mapAttrs (_: _: 4294967295) fixtureBuildInfo.contractVersions; + }; + } + )).outPath + ) + else + { + success = true; + value = null; + }; + invalidOciResults = lib.optionals pkgs.stdenv.isLinux [ + (builtins.tryEval ( + (service.mkServiceOciImage (ociArgs // { serviceName = "../fixture"; })).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage (ociArgs // { package = "not-a-derivation"; })).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage (ociArgs // { binaryName = "fixture service"; })).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + serviceName = lib.concatStrings (lib.replicate 129 "a"); + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + binaryName = lib.concatStrings (lib.replicate 129 "b"); + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + serviceVersion = "bad value"; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + serviceVersion = lib.concatStrings (lib.replicate 129 "1"); + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + serviceCommit = "ABCDEF"; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + libRevision = "bad"; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + rustVersion = "stable"; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + target = "x86_64-unknown-linux-musl"; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + featureProfile = "development"; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + extra = "unexpected"; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + contractVersions = fixtureBuildInfo.contractVersions // { + config = 0; + }; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + contractVersions = fixtureBuildInfo.contractVersions // { + config = 4294967296; + }; + }; + } + )).outPath + )) + (builtins.tryEval ( + (service.mkServiceOciImage ( + ociArgs + // { + buildInfo = fixtureBuildInfo // { + contractVersions = builtins.removeAttrs fixtureBuildInfo.contractVersions [ "provider" ]; + }; + } + )).outPath + )) + ]; in assert service.supportedSystems == [ @@ -366,6 +634,16 @@ assert nativeInputs.buildInputs == [ ]; assert nativeInputs.environment.RADROOTS_SERVICE_FIXTURE == "1"; assert outputs.serviceName == "fixture_service"; assert outputs.packages.default == package; +assert (pkgs.stdenv.isLinux -> outputs.packages.oci == ociImage); +assert ( + pkgs.stdenv.isLinux + -> + ociImage.meta.platforms == [ + "aarch64-linux" + "x86_64-linux" + ] +); +assert (!pkgs.stdenv.isLinux -> !(builtins.hasAttr "oci" outputs.packages)); assert outputs.checks == checks; assert builtins.attrNames checks == [ @@ -376,6 +654,11 @@ assert "docs" "fmt" "integration" + ] + ++ lib.optionals pkgs.stdenv.isLinux [ + "oci-image" + ] + ++ [ "package" "smoke" "source-lock" @@ -415,6 +698,12 @@ assert invalidExtraCheck.success == false; assert standardOverride.success == false; assert lib.all (result: result.success == false) invalidAppResults; assert lib.all (result: result.success == false) invalidDevShellResults; +assert maximumOciResult.success; +assert lib.all (result: result.success == false) invalidOciResults; +assert ( + !pkgs.stdenv.isLinux + -> (builtins.tryEval ((service.mkServiceOciImage ociArgs).outPath)).success == false +); { inherit outputs; } diff --git a/build/nix/service/oci.nix b/build/nix/service/oci.nix @@ -0,0 +1,148 @@ +{ + lib, + pkgs, +}: +{ + serviceName, + package, + binaryName, + buildInfo, +}: +assert lib.assertMsg pkgs.stdenv.isLinux "service OCI images require a Linux builder"; +assert lib.assertMsg ( + pkgs.stdenv.hostPlatform.isAarch64 || pkgs.stdenv.hostPlatform.isx86_64 +) "service OCI images support only aarch64-linux and x86_64-linux"; +assert lib.assertMsg ( + builtins.isString serviceName + && builtins.stringLength serviceName <= 128 + && builtins.match "^[a-z][a-z0-9_]*$" serviceName != null +) "serviceName must be a lowercase snake-case identifier"; +assert lib.assertMsg (lib.isDerivation package) "package must be a derivation"; +assert lib.assertMsg ( + builtins.isString binaryName + && builtins.stringLength binaryName <= 128 + && builtins.match "^[a-z][a-z0-9_-]*$" binaryName != null +) "binaryName must be a lowercase Cargo binary identifier"; +assert lib.assertMsg (builtins.isAttrs buildInfo) "buildInfo must be an attribute set"; +assert lib.assertMsg ( + builtins.attrNames buildInfo == [ + "contractVersions" + "featureProfile" + "libRevision" + "rustVersion" + "serviceCommit" + "serviceVersion" + "target" + ] +) "buildInfo must contain exactly the governed service build fields"; +assert lib.assertMsg ( + builtins.isString buildInfo.serviceVersion + && builtins.stringLength buildInfo.serviceVersion <= 128 + && builtins.match "^[A-Za-z0-9][A-Za-z0-9_.-]*$" buildInfo.serviceVersion != null +) "buildInfo.serviceVersion must be a bounded image-tag-safe value"; +assert lib.assertMsg ( + builtins.isString buildInfo.serviceCommit + && builtins.match "^[0-9a-f]{40}$" buildInfo.serviceCommit != null +) "buildInfo.serviceCommit must be a full lowercase Git revision"; +assert lib.assertMsg ( + builtins.isString buildInfo.libRevision + && builtins.match "^[0-9a-f]{40}$" buildInfo.libRevision != null +) "buildInfo.libRevision must be a full lowercase Git revision"; +assert lib.assertMsg ( + buildInfo.rustVersion == "1.97.1" +) "buildInfo.rustVersion must match the governed Rust toolchain"; +assert lib.assertMsg ( + buildInfo.target == pkgs.stdenv.hostPlatform.rust.rustcTarget +) "buildInfo.target must match the package host platform"; +assert lib.assertMsg ( + buildInfo.featureProfile == "service-host" +) "buildInfo.featureProfile must select the service-host profile"; +assert lib.assertMsg (builtins.isAttrs buildInfo.contractVersions) ( + "buildInfo.contractVersions must be an attribute set" +); +assert lib.assertMsg ( + builtins.attrNames buildInfo.contractVersions == [ + "admin" + "config" + "provider" + "state" + "status" + ] +) "buildInfo.contractVersions must contain exactly the governed contract versions"; +assert lib.assertMsg (lib.all + (version: builtins.isInt version && version > 0 && version <= 4294967295) + (builtins.attrValues buildInfo.contractVersions) +) "every buildInfo contract version must be a positive u32"; +let + imageName = lib.replaceStrings [ "_" ] [ "-" ] serviceName; + user = "65532:65532"; + mountPaths = [ + "/etc/radroots/services/${serviceName}" + "/etc/radroots/secrets/services/${serviceName}" + "/run/radroots/services/${serviceName}" + "/var/lib/radroots/services/${serviceName}" + ]; + labels = { + "dev.radroots.build.feature-profile" = buildInfo.featureProfile; + "dev.radroots.build.lib-revision" = buildInfo.libRevision; + "dev.radroots.build.rust-version" = buildInfo.rustVersion; + "dev.radroots.build.target" = buildInfo.target; + "dev.radroots.contract.admin-version" = toString buildInfo.contractVersions.admin; + "dev.radroots.contract.config-version" = toString buildInfo.contractVersions.config; + "dev.radroots.contract.provider-version" = toString buildInfo.contractVersions.provider; + "dev.radroots.contract.state-version" = toString buildInfo.contractVersions.state; + "dev.radroots.contract.status-version" = toString buildInfo.contractVersions.status; + "dev.radroots.mount.config" = "/etc/radroots/services/${serviceName}"; + "dev.radroots.mount.config.mode" = "read-only"; + "dev.radroots.mount.credentials" = "/etc/radroots/secrets/services/${serviceName}"; + "dev.radroots.mount.credentials.mode" = "read-only"; + "dev.radroots.mount.runtime" = "/run/radroots/services/${serviceName}"; + "dev.radroots.mount.runtime.mode" = "read-write"; + "dev.radroots.mount.state" = "/var/lib/radroots/services/${serviceName}"; + "dev.radroots.mount.state.mode" = "read-write"; + "dev.radroots.rootfs" = "read-only-compatible"; + "org.opencontainers.image.description" = "Hardened ${serviceName} service image"; + "org.opencontainers.image.licenses" = "MIT OR Apache-2.0"; + "org.opencontainers.image.revision" = buildInfo.serviceCommit; + "org.opencontainers.image.title" = serviceName; + "org.opencontainers.image.version" = buildInfo.serviceVersion; + }; +in +pkgs.dockerTools.buildLayeredImage { + name = imageName; + tag = buildInfo.serviceVersion; + created = "1970-01-01T00:00:01Z"; + maxLayers = 2; + contents = [ + package + pkgs.dockerTools.caCertificates + ]; + config = { + User = user; + Entrypoint = [ "${package}/bin/${binaryName}" ]; + WorkingDir = "/"; + Env = [ "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt" ]; + StopSignal = "SIGTERM"; + Labels = labels; + }; + passthru.radrootsServiceOci = { + schema = "radroots.service-oci.v1"; + schemaVersion = 1; + inherit + buildInfo + imageName + labels + mountPaths + serviceName + user + ; + entrypoint = "${package}/bin/${binaryName}"; + }; + meta = { + description = "Hardened rootless OCI image for ${serviceName}"; + platforms = [ + "aarch64-linux" + "x86_64-linux" + ]; + }; +}