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

Quickstart: request a hardened image

The fastest path is: import this flake, wrap a derivation you already build with mkHardenedImage, nix build it, and then add the CI include to get signing, attestation, and the scan gate.

1. A minimal consumer flake

You need a derivation for whatever you ship. It can be anything — pkgs.hello stands in for your real service below. The public API lives at hardened-builds.lib.${system} and is Linux-only (x86_64-linux, aarch64-linux).

{
  description = "My service, packaged as a hardened image";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    hardened-builds.url = "git+https://gitlab.com/gitlab-org/software-supply-chain-security/hardened-builds?ref=main";
    hardened-builds.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs =
    { self, nixpkgs, hardened-builds }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
      hb = hardened-builds.lib.${system};

      # Your real package goes here (buildGoModule / buildRustPackage / …).
      myService = pkgs.hello;
    in
    {
      packages.${system}.my-service-image = hb.mkHardenedImage {
        name = "my-service";
        version = "0.1.0";
        package = myService;
        cmd = [ "/bin/hello" ];
      };
    };
}

Use the git+https URL as shown. Don’t use a gitlab: URL: Nix’s gitlab: fetcher resolves a project in a subgroup to the wrong project, even with the %2F encoding the Nix manual suggests (NixOS/nix#6435).

hardened-builds is currently a private GitLab project, so nix needs your GitLab credentials to fetch it. If you already clone from GitLab over SSH, this makes git fetch this one repository over SSH, without changing the URL in your flake.nix or flake.lock:

$ git config --global \
    url."ssh://git@gitlab.com/gitlab-org/software-supply-chain-security/hardened-builds".insteadOf \
    "https://gitlab.com/gitlab-org/software-supply-chain-security/hardened-builds"

In CI the slsa-l1 component handles this for you; see Fetching hardened-builds in CI.

2. Build it

$ nix build .#my-service-image

That gives you the OCI image. mkHardenedImage also attaches a CycloneDX SBOM by default; it hangs off passthru.sbom:

$ nix build .#my-service-image.passthru.sbom

That is everything the pure-Nix half produces: the image and its SBOM. It does not sign, attest, scan, or run an SLSA L3 build — those need this project’s signing key, Sigstore identity, and offline runners, which a Nix function you import cannot hold. See Signing & provenance and SLSA Level 3 builds.

3. Add the CI component

Drop this into your .gitlab-ci.yml. It builds .#my-service-image, generates its SBOM and scans it on your project’s own runners, with SLSA Build L1 provenance written by GitLab Runner. For signed provenance on the offline SLSA L3 tier, include /ci/components/hardened-artifact-slsa-l3.yml instead:

include:
  - project: "gitlab-org/software-supply-chain-security/hardened-builds"
    ref: main # pin to a release tag in real use
    file: "/ci/components/hardened-artifact-slsa-l1.yml"
    inputs:
      flake-target: "my-service-image"
      artifact-name: "my-service"

flake-target is your flake package output (.#my-service-image). The rest of the inputs (sbom, scan, fips, …) default sensibly; Integrate an external project walks through each one, both build tiers, and which job runs where.

Scaffold instead of hand-writing

If you’d rather start from a working skeleton:

$ nix flake init -t 'git+https://gitlab.com/gitlab-org/software-supply-chain-security/hardened-builds?ref=main#default'

That drops a flake.nix and a starter .gitlab-ci.yml already wired to the component. Adapt the package and the flake-target/artifact-name inputs.

Where to next