Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The public hardened-artifact API (ADR-0039), generated from the doc-comments in lib/api.nix.

lib.api.mkHardenedImage

Derivation-first core (ADR-0039, FR-092..095): wrap an ALREADY-BUILT derivation in a hardened OCI image (via buildGitlabImage) and, by default, attach + build its CycloneDX SBOM (via lib/sbom). This is the headline “I have a derivation, give me a hardened image + SBOM” entry point, and the one mkSecureArtifact funnels into.

Inputs (attrset):

  • name (string, required) — image name, e.g. “my-service”
  • version (string, required) — image tag, e.g. “1.2.3”
  • package (derivation, required) — the built artifact to bundle
  • entrypoint / cmd (list|null) — OCI entrypoint/cmd (default null)
  • env (attrset) — extra env vars (default {})
  • extraDeps (list) / extraLabels (attrset) — extra runtime deps / labels
  • user / workDir / debug — passthrough to buildGitlabImage (debug = false ⇒ no shell in the image, FR-002)
  • fips (bool, default false) — build the image under the FIPS package set so it carries FIPS labels. TRUTHFULNESS CONTRACT (FR-095): the caller MUST have built package under the FIPS set; this flag does NOT convert a non-FIPS build into a FIPS one.
  • sbom (bool, default true) — attach + build a CycloneDX SBOM
  • sbomEcosystems (list|null) — override lib/sbom auto-detection (e.g. ruby/yarn lock paths that cannot be inferred from the derivation)

Returns the nix2container image derivation, with passthru: sbom (only when sbom = true), package, labels, fips.

Type

mkHardenedImage :: AttrSet -> Derivation

Example

lib.${system}.mkHardenedImage {
  name = "my-service";
  version = "1.0.0";
  package = self.packages.${system}.my-service;
  cmd = [ "/bin/my-service" ];
}

lib.api.mkSecureArtifact

Src-first umbrella (ADR-0039, FR-096..098): detect → build → mkHardenedImage, every stage overridable. Point it at a source tree; it detects the ecosystem (unless forced), builds a derivation with the matching emitter, and hands that to mkHardenedImage.

Inputs (attrset):

  • name, version (required)
  • src (derivation/path, required unless overrideDerivation) — the source
  • rev (string) — full commit SHA (used for Go ldflags etc.)
  • ecosystem (string|null) — force “go”|“python”|“ruby”|“rust”; null ⇒ detect from src (FR-097)
  • ecosystemArgs (attrset) — per-ecosystem knobs: go { vendorHash; goVersion?; goreleaser?; subPackages?; }, rust { cargoLock; outputHashes?; }, ruby { gemset; ruby?; gemdir?; }, python { lock; python?; overrides?; exclude?; format?; } for a ./poetry.lock, or { lock; python?; overrides?; sourcePreference?; } for a ./uv.lock
  • overrideDerivation (derivation|null) — bypass detect+build entirely and image THIS derivation (FR-097)
  • image (attrset) — extra args forwarded to mkHardenedImage
  • fips, sbom, sbomEcosystems — as in mkHardenedImage

Returns the mkHardenedImage result, with an additional passthru.ecosystem = the detected/forced IR (or { ecosystem = "custom"; } when overrideDerivation is used).

If src matches no known marker and neither ecosystem nor overrideDerivation is given, detect throws at eval time naming the markers searched and the escape hatches (FR-098) — no silent empty image.

Type

mkSecureArtifact :: AttrSet -> Derivation

Example

lib.${system}.mkSecureArtifact {
  name = "my-service";
  version = "1.0.0";
  src = ./.;
  rev = self.rev or "0000000000000000000000000000000000000000";
  ecosystemArgs = { vendorHash = "sha256-…"; };   # Go
}

lib.api.buildEcosystemDerivation

Internal dispatch from a detected/forced ecosystem to its emitter, producing the build derivation mkSecureArtifact then images. Exposed (rather than hidden in a let) so tests and docs can exercise a single ecosystem’s build-arg assembly directly.

Takes { name; version; src; rev; ecosystem; ecosystemArgs; buildPkgs; } where ecosystem is one of “go” | “rust” | “ruby” | “python” and buildPkgs is the plain or FIPS package set. Throws for an unsupported ecosystem.

Type

buildEcosystemDerivation :: AttrSet -> Derivation

Ecosystem detector (lib/ecosystem/detect.nix).

lib.detect.detect

Detect a source tree’s ecosystem by file-presence and return the normalized IR:

{
  ecosystem        = "go" | "rust" | "python" | "ruby";
  toolchainVersion = string | null;   # best-effort, from the marker
  primaryMarker    = string;          # e.g. "go.mod"
  buildConfigFile  = string | null;   # e.g. ".goreleaser.yml", relative
}

Throws (rather than returning a sentinel) when no marker is present, so a misconfigured mkSecureArtifact fails at eval time with an actionable message instead of silently producing an empty derivation.

Type

detect :: Path -> AttrSet

Example

detect ./my-go-service
# => { ecosystem = "go"; toolchainVersion = "1.22.3"; primaryMarker = "go.mod"; buildConfigFile = null; }

lib.detect.tryDetect

Like detect, but returns null instead of throwing when no ecosystem marker is present.

Use this for introspection — deciding whether a tree is buildable at all, or branching on the result — where a throw would be inconvenient. When you already know the tree has a marker, call detect directly so a missing one surfaces as an error rather than a silent null.

Type

tryDetect :: Path -> AttrSet | null

Example

tryDetect ./maybe-empty-dir   # => null
tryDetect ./my-go-service     # => { ecosystem = "go"; … }

lib.detect.markers

The marker table that drives detection, in priority order.

Each entry pairs an ecosystem with its primary marker file (the presence test) and the buildConfig files worth noting when that ecosystem wins. The first ecosystem whose primary marker exists takes precedence, so order only matters for a tree that carries several (say, a Go tool vendoring a small Ruby helper) — detection picks the top-level build, which is unambiguous for the gitlab-org projects this serves. Exposed for the docs site and tests.

Type

markers :: [ { ecosystem :: String; primary :: String; buildConfig :: [ String ]; } ]

Ruby / Bundler gemset enrichment (lib/ecosystem/ruby.nix).

lib.ruby.enrichGemset

Enrich a bundix gemset by swapping gems for their nixpkgs pre-built equivalents where that’s safe, so bundlerEnv fetches and builds fewer gems from rubygems.org.

For each gem in the gemset, this checks whether nixpkgs already ships it and, depending on matchMode, substitutes the entry’s source to point at the nixpkgs store path. The gem stays in the gemset either way, so bundlerEnv’s dependency-graph resolution is unaffected — only the fetch/build source changes.

matchMode controls how careful the substitution is:

  • "exact" (default) substitutes only when the nixpkgs version equals the gemset-pinned version. Bundler enforces lockfile versions strictly, so a mismatch (nixpkgs 7.2.3 vs a pinned 7.2.3.1) would break the build; exact matching can’t introduce that.
  • "name-only" substitutes whenever nixpkgs has a gem by that name and rewrites the entry’s version to the nixpkgs one. This reuses more pre-built closure but can pull in a different version, so reach for it when you want maximum reuse and the test suite covers the gems in play.

exclude is a list of gem names to never substitute, even when nixpkgs has them — the escape hatch for a name-only build where one gem’s nixpkgs version diverges from the lockfile in a way Bundler rejects.

Returns the enriched gemset plus introspection: gemset (the enriched map, the one to hand to bundlerEnv), enrichedGemset (the same value), the substituted nixpkgsGems, and stats (total, fromNixpkgs, fromBundlerEnv, matchMode).

Type

enrichGemset :: { gemset :: AttrSet; ruby :: Derivation; matchMode ? String; exclude ? [ String ]; } -> AttrSet

Example

enriched = rubyEcosystemLib.enrichGemset {
  inherit gemset ruby;
  matchMode = "exact";
};
gems = bundlerEnv { gemset = enriched.gemset; inherit ruby; };

CycloneDX SBOM helpers (lib/sbom).

lib.sbom.detect

Detect which language ecosystems a finished derivation vendors, from reliable build-system markers.

Reads goModules (set by buildGoModule) and cargoDeps (set by buildRustPackage / rustPlatform) on the built package and returns one entry per marker present. Both are safe to auto-detect because the emitters consume the finished output rather than rebuilding, so emitting Rust components for a package that only carries cargoDeps incidentally (nixpkgs ruby bundles YJIT crates) is correct, not a false positive. Ruby and yarn aren’t auto-detected here; their inventory lives in a gemset.nix or yarn.lock that isn’t discoverable from the built drv, so they need an explicit sbomEcosystems entry.

Type

detect :: Derivation -> [ { kind :: String; } ]

Example

detect goPackage   # => [ { kind = "go"; } ]

lib.sbom.ecosystemsFor

Resolve the ecosystem list for a package: an explicit passthru.sbomEcosystems if the package sets one, otherwise the auto-detected list from detect.

An explicit list wins outright, so a package can both add path-bearing ecosystems (ruby’s gemset, yarn’s lockfiles) and suppress a false-positive auto-detection in the same declaration.

Type

ecosystemsFor :: Derivation -> [ AttrSet ]

Example

ecosystemsFor rails
# => [ { kind = "ruby"; gemset = ./gemset.nix; } { kind = "yarn"; yarnLock = [ ./yarn.lock ]; } ]

lib.sbom.attach

Attach an aggregated vendored SBOM to a package as its bombonVendoredSbom passthru, covering every detected or declared ecosystem.

bombon’s transformer reads every *.cdx.json under that derivation’s $out, so combining ecosystems is just collecting their files into one directory (prefixed by kind so a package with more than one ecosystem doesn’t get identically-named files clobbered on copy). Meant for stdenv packages (rails, workhorse); it uses overrideAttrs to add the passthru, keeping pkg a real derivation, which matters because buildBom hands attached packages to bombon as extraPaths that go through exportReferencesGraph and must be derivations. Since the emitters consume pkg rather than rebuilding it, the package’s own output hash is unchanged. Passing a package with no ecosystems returns it untouched; passing a non-derivation throws rather than silently yielding a plain attrset that fails far from the cause.

Type

attach :: Derivation -> Derivation

Example

attach workhorse   # => workhorse with passthru.bombonVendoredSbom

lib.sbom.buildBom

Build the ecosystem-complete CycloneDX SBOM derivation for a package.

Folds every vendored ecosystem SBOM into the Nix-closure SBOM so gem, npm, Go module, and Rust crate components surface for CVE matching. For our images, the bundled app package and its runtime deps are surfaced as gitlabSbomPackages; a nix2container image references those as copied store paths rather than derivation inputs, so bombon’s dependency walk sees bare paths and can’t read their bombonVendoredSbom. This attaches each surfaced package (safe, since the emitters consume rather than rebuild) and adds the ones that gain a vendored SBOM to extraPaths, entering them into the graph as real derivations the transformer reads from. Without it an image BOM would list only the Nix closure. extraPaths, includeBuildtimeDependencies, and excludes pass straight through to bombon.

Type

buildBom :: Derivation -> { extraPaths :: [ Derivation ]; includeBuildtimeDependencies :: Bool; excludes :: [ ... ]; } -> Derivation

Example

buildBom gitlabImage { }   # => the image's CycloneDX SBOM derivation

OCI image layer construction (lib/oci-layers.nix).

lib.ociLayers.appMaxLayers

The layer budget for the one heavy application group in an image.

Set to 80, which leaves about 45 layers of headroom under the ~125-layer OCI ceiling for the base, /etc, and extra groups (one layer each) plus any future group, even across the largest closures. Only the application group uses this split; every other group stays a single layer.

Type

appMaxLayers :: Int

lib.ociLayers.foldImageLayers

Build an image’s layers from an ordered list of group specs, deduplicating shared store paths across the groups.

Each spec is an attrset passed to nix2container.buildLayer ({ deps ? []; copyToRoot ? null; maxLayers ? … }). The specs fold in order, and each built layer excludes paths already present in the layers before it, so a store path lands in exactly one layer. A spec that omits maxLayers gets maxLayers = 1 (the whole group in one layer); only a group that sets a higher maxLayers (typically the application group, via appMaxLayers) is popularity-split. That keeps the total layer count across all groups well under the OCI ceiling.

Type

foldImageLayers :: [ AttrSet ] -> [ Layer ]

Example

foldImageLayers [
  { deps = [ glibc cacert ]; }          # base, one layer
  { deps = [ app ]; maxLayers = appMaxLayers; }  # split for cache reuse
  { copyToRoot = etcFiles; }            # /etc at the image root
]

GitLab release version registry (lib/gitlab-versions.nix).

lib.gitlabVersions.releases

The latest patch of each maintained GitLab minor, keyed by MAJOR.MINOR.

Tracks the release train’s active window (N, N-1, N-2). Each key holds the full component set for that series (gitlab-rails, gitaly, workhorse, and the rest) with real harvested source and vendor hashes, so a build pinned to a minor is fully reproducible. Refresh with the gitlab-versions Rust CLI, which realigns every release to CNG and re-harvests hashes; lib/gitlab-release.nix resolves a selector against this set.

Type

releases :: AttrSet   # { "19.2" = { gitlab-rails = { … }; … }; … }

lib.gitlabVersions.shared

Components pinned once and shared across every GitLab release.

These don’t follow the GitLab release train (the auth services iam / glaz / sts, orbit-knowledge-graph, glab-cli, ai-assist, and friends), so they live outside the per-minor releases set and get merged in by the resolver. Pin values here and every release picks them up.

Type

shared :: AttrSet   # { iam = { … }; glaz = { … }; … }

lib.gitlabVersions.latest

The default GitLab minor when a caller selects no release.

A MAJOR.MINOR key into releases (the newest tracked series). The resolver falls back to this when handed null.

Type

latest :: String   # e.g. "19.2"

GitLab release resolution helpers (lib/gitlab-release.nix).

lib.gitlabRelease.toMinorKey

Normalise a user-supplied release selector to a registry key (MAJOR.MINOR).

Accepts null (resolves to the registry’s latest tracked release), a bare "19.1", a full patch like "19.1.3" (reduced to its minor series), and tolerates a leading v or trailing -ee. It only trims to the minor; the patch component isn’t validated against the pin, so "19.1.0" resolves to whatever 19.1 patch is tracked. Callers needing an exact-patch guarantee should compare against patchVersion themselves.

Type

toMinorKey :: Registry -> (String | null) -> String

Example

toMinorKey registry "v19.1.3-ee"   # => "19.1"
toMinorKey registry null           # => registry.latest

lib.gitlabRelease.releaseKeys

List every tracked release key (MAJOR.MINOR) in the registry.

Order isn’t guaranteed, so sort if you need one; the flake uses this to enumerate the per-release alias outputs.

Type

releaseKeys :: Registry -> [ String ]

Example

releaseKeys registry   # => [ "19.2" "19.1" "19.0" ]

lib.gitlabRelease.patchVersion

The exact patch version string for a resolved release, taken from that series’ gitlab-rails pin.

Used to build alias suffixes; resolves the selector through toMinorKey first, so a null or partial selector works the same way resolve does.

Type

patchVersion :: Registry -> (String | null) -> String

Example

patchVersion registry "19.1"   # => "19.1.3"

lib.gitlabRelease.resolve

Resolve a release selector to the flat component attrset every package derivation consumes (gitlabVersions.<component>).

Picks the release for the selector’s minor key, then merges the shared, release-independent components under the release-specific ones (the two sets are disjoint by construction, so it’s really a union). Throws with the list of known keys when the selector names a release that isn’t tracked.

Type

resolve :: Registry -> (String | null) -> AttrSet

Example

(resolve registry "19.1").gitaly.version   # => "19.1.3"

FIPS toolchain and mode configuration (lib/fips.nix).

lib.fips.modes

BoringSSL FIPS mode definitions, keyed by mode name.

Each mode fixes a BoringSSL branch, the exact commit and its sha256, the NIST CMVP certificate number, and whether it’s validated. legacy is the fully validated Certificate #5104; update-stream tracks a real pending CMVP submission (FedRAMP FR-R7); main is development-only and unpinned. These pins must track the certificate, never “latest”.

Type

modes :: AttrSet   # { legacy = { branch; commit; sha256; certificateNumber; validated; systems; sbomSystems; }; … }

lib.fips.toolchainVersions

The exact build-toolchain versions NIST CMVP Certificate #5104 requires.

Any deviation invalidates FIPS certification, so this is a permanent requirement rather than a floor; fipsAssertToolchainVersions enforces it at build time. Covers clang, go, cmake, and ninja.

Type

toolchainVersions :: { clang :: String; go :: String; cmake :: String; ninja :: String; }

lib.fips.integrityHashes

Expected FIPS integrity hash checksums (from bcm.o), per architecture.

amd64 holds the BORINGSSL_bcm_text_hash a validated build must reproduce; arm64 is null because ARM64 isn’t validated.

Type

integrityHashes :: { amd64 :: String; arm64 :: String | null; }

lib.fips.getArch

Map a Nix system string to the FIPS architecture label.

Returns “amd64”, “arm64”, “amd64-darwin”, “arm64-darwin”, or “unknown” for anything unrecognised. This is the label used in image tags and provenance, distinct from whether the arch is actually validated (see isFipsValidated).

Type

getArch :: String -> String

Example

getArch "aarch64-linux"   # => "arm64"

lib.fips.isFipsValidated

Whether a Nix system is FIPS 140-3 validated.

Only x86_64-linux is covered by Certificate #5104, so this is true there and false everywhere else. Use it to gate FIPS-only build paths.

Type

isFipsValidated :: String -> Bool

Example

isFipsValidated "x86_64-linux"   # => true

lib.fips.generateConstantsJson

Write the FIPS constants to a JSON file for non-Nix tools (Python, shell).

Keeps a single source of truth: it emits the toolchain versions and per-mode branch, commit, certificate number, and validated flag, omitting the Nix-specific sha256. Takes writeText explicitly so the pure config file stays free of a pkgs dependency.

Type

generateConstantsJson :: { writeText :: ... } -> Derivation

Example

generateConstantsJson { inherit (pkgs) writeText; }

lib.fips.getShortCommit

The first 12 characters of a mode’s pinned commit.

Used as the short-commit suffix in image tags, SBOM and provenance version strings, and SPDX document namespaces; centralised here so every lateral builder derives it the same way.

Type

getShortCommit :: { commit :: String; … } -> String

Example

getShortCommit fipsLib.modes.legacy   # => "85897d07196b"

lib.fips.getVersionString

The {mode}-{shortCommit} version string shared across all four lateral builders.

Used identically as the image tag, the SBOM and provenance version, and the SPDX versionInfo. Centralised so a change to the format updates every builder’s output together; otherwise one build’s version strings would stop matching across its own SBOM, provenance, and tag.

Type

getVersionString :: String -> { commit :: String; … } -> String

Example

getVersionString "legacy" fipsLib.modes.legacy   # => "legacy-85897d07196b"

lib.fips.rustFips

Rust FIPS configuration for the aws-lc-rs / AWS-LC module (ADR-0028).

Separate from the BoringSSL module the Go services use: aws-lc-rs with the fips feature builds AWS-LC from a pinned commit and branch, which carry their own NIST CMVP certificates (dynamic #5298, static #5314). Holds the pinned commit, branch, certificate numbers, module name, cargo feature flag, the build-time Go minimum, and the validated architecture scope.

Type

rustFips :: AttrSet

lib.fips.getModeConfig

Look up the configuration for a named FIPS mode.

Returns the mode’s entry from modes, or throws when the name isn’t one of legacy, update-stream, or main; throwing keeps a typo from silently selecting a non-compliant build.

Type

getModeConfig :: String -> AttrSet

Example

(getModeConfig "legacy").certificateNumber   # => "5104"

lib.fips.buildToolchain

Build the FIPS toolchain package set with every tool pinned to its certificate-required version.

Assembles Clang 17.0.6 (from the llvm-of-yore backport) plus Go 1.22.3, CMake 3.29.3, and Ninja 1.12.1 overridden to the exact sources Certificate #5104 requires, alongside the ordinary build tools. Takes llvm-of-yore and pkgs explicitly so this config file keeps no implicit pkgs dependency. Returns an attrset of the individual tools plus an allPackages list ready for environment.systemPackages.

Type

buildToolchain :: { llvm-of-yore :: AttrSet; pkgs :: AttrSet; } -> AttrSet

Example

(fipsLib.buildToolchain { inherit llvm-of-yore pkgs; }).go   # => go 1.22.3

lib.fips.getBoringSSLSource

Fetch the BoringSSL source pinned for a given FIPS mode.

Resolves the mode’s commit and sha256 from modes and fetches from google/boringssl; throws for an unknown mode or one with no pinned hash (main). The commit is the FIPS-validated source for the certificate, so it must track the certificate rather than a moving branch. Takes fetchFromGitHub explicitly to stay pkgs-free.

Type

getBoringSSLSource :: { mode :: String; fetchFromGitHub :: ...; } -> Derivation

Example

getBoringSSLSource { mode = "legacy"; inherit (pkgs) fetchFromGitHub; }

lib.fips.fipsAssertToolchainVersions

Assert that a toolchain matches the FIPS-required versions, failing the build on any mismatch.

Reads the version of each supplied clang, go, cmake, and ninja and compares against toolchainVersions. On a match it returns an empty attrset you can ignore; on a mismatch it throws with the offending tools and the expected versions, so a non-compliant toolchain fails loudly rather than producing a silently uncertified binary. Wire it into a derivation to gate the build.

Type

fipsAssertToolchainVersions :: { clang :: Derivation; go :: Derivation; cmake :: Derivation; ninja :: Derivation; } -> AttrSet

Example

fipsAssertToolchainVersions { inherit clang go cmake ninja; }   # => { } or throws