Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "flake-checker"
version = "0.2.14"
version = "0.2.15"
edition = "2024"

[workspace]
Expand Down
39 changes: 32 additions & 7 deletions src/flake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::collections::BTreeMap;

use crate::FlakeCheckerError;
use crate::issue::{Disallowed, Issue, IssueKind, NonUpstream, Outdated};
use crate::secure_packages::is_secure_packages_url;

use chrono::{Duration, Utc};
use parse_flake_lock::{FlakeLock, Node};
Expand Down Expand Up @@ -86,20 +87,26 @@ pub(crate) fn check_flake_lock(
let deps = nixpkgs_deps(flake_lock, &config.nixpkgs_keys)?;

for (name, node) in deps {
let (git_ref, last_modified, owner) = match node {
let (git_ref, last_modified, owner, secure_packages) = match node {
Node::Repo(repo) => (
repo.original.git_ref,
Some(repo.locked.last_modified),
Some(repo.original.owner),
false,
),
Node::Tarball(tarball) => (None, tarball.locked.last_modified, None),
_ => (None, None, None),
Node::Tarball(tarball) => {
let secure_packages = is_secure_packages_url(&tarball.original.url)
|| is_secure_packages_url(&tarball.locked.url);

(None, tarball.locked.last_modified, None, secure_packages)
}
_ => (None, None, None, false),
};

// Check if not explicitly supported
// Secure Packages flakes are exempt from the ref and owner checks, but not the age check
if let Some(git_ref) = git_ref {
// Check if not explicitly supported
if config.check_supported && !allowed_refs.contains(&git_ref) {
if config.check_supported && !secure_packages && !allowed_refs.contains(&git_ref) {
issues.push(Issue {
input: name.clone(),
kind: IssueKind::Disallowed(Disallowed {
Expand All @@ -125,7 +132,7 @@ pub(crate) fn check_flake_lock(

if let Some(owner) = owner {
// Check that the GitHub owner is NixOS
if config.check_owner && owner.to_lowercase() != "nixos" {
if config.check_owner && !secure_packages && owner.to_lowercase() != "nixos" {
issues.push(Issue {
input: name.clone(),
kind: IssueKind::NonUpstream(NonUpstream { owner }),
Expand Down Expand Up @@ -199,7 +206,7 @@ mod test {
let ref_statuses: BTreeMap<String, String> =
serde_json::from_str(include_str!("../ref-statuses.json")).unwrap();
let allowed_refs = supported_refs(ref_statuses);
for n in 0..=7 {
for n in 0..=8 {
let path = PathBuf::from(format!("tests/flake.clean.{n}.lock"));
let flake_lock = FlakeLock::new(&path).unwrap();
let config = FlakeCheckConfig {
Expand Down Expand Up @@ -255,6 +262,24 @@ mod test {
},
],
),
// Secure Packages is a FlakeHub-only product, so the GitHub form gets no exemption
(
"flake.dirty.2.lock",
vec![
Issue {
input: String::from("nixpkgs"),
kind: IssueKind::Disallowed(Disallowed {
reference: String::from("main"),
}),
},
Issue {
input: String::from("nixpkgs"),
kind: IssueKind::NonUpstream(NonUpstream {
owner: String::from("DeterminateSystems"),
}),
},
],
),
];

for (file, expected_issues) in cases {
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod condition;
mod error;
mod flake;
mod issue;
mod secure_packages;
mod summary;

#[cfg(feature = "ref-statuses")]
Expand Down
129 changes: 129 additions & 0 deletions src/secure_packages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//! Recognition of Determinate Secure Packages flakes as legitimate Nixpkgs inputs.
//!
//! These flakes are available only from FlakeHub, published under the `DeterminateSystems` org
//! with names beginning with `secure-packages` (`secure-packages-rolling`,
//! `secure-packages-26.05`, `secure-packages-rolling-fips`, `secure-packages-26.05-fips`, and so
//! on).

const DETERMINATE_ORG: &str = "determinatesystems";
const SECURE_PACKAGES_PREFIX: &str = "secure-packages";
const FLAKEHUB_HOSTS: &[&str] = &["flakehub.com", "api.flakehub.com"];

/// Whether a tarball URL points to a Determinate Secure Packages flake on FlakeHub. Handles both
/// the user-supplied form (`https://flakehub.com/f/{org}/{name}/{version}`) and the locked form
/// (`https://api.flakehub.com/f/pinned/{org}/{name}/{version}/{uuid}/source.tar.gz`).
pub(crate) fn is_secure_packages_url(url: &str) -> bool {
let Some((_scheme, rest)) = url.split_once("://") else {
return false;
};

let mut segments = rest.split('/');

let Some(host) = segments.next() else {
return false;
};

if !FLAKEHUB_HOSTS.contains(&host.to_lowercase().as_str()) {
return false;
}

if segments.next() != Some("f") {
return false;
}

// Locked FlakeHub URLs insert a `pinned` segment before the org
let owner = match segments.next() {
Some("pinned") => segments.next(),
owner => owner,
};

let (Some(owner), Some(name)) = (owner, segments.next()) else {
return false;
};

is_secure_packages_flake(owner, name)
}

fn is_secure_packages_flake(owner: &str, name: &str) -> bool {
owner.to_lowercase() == DETERMINATE_ORG && is_secure_packages_name(&name.to_lowercase())
}

fn is_secure_packages_name(name: &str) -> bool {
name == SECURE_PACKAGES_PREFIX || name.starts_with(&format!("{SECURE_PACKAGES_PREFIX}-"))
}

#[cfg(test)]
mod test {
use super::{is_secure_packages_flake, is_secure_packages_url};

#[test]
fn secure_packages_flakes() {
let cases: Vec<(&str, &str, bool)> = vec![
("DeterminateSystems", "secure-packages-rolling", true),
("DeterminateSystems", "secure-packages-rolling-fips", true),
("DeterminateSystems", "secure-packages-25.11", true),
("DeterminateSystems", "secure-packages-26.05", true),
("DeterminateSystems", "secure-packages-26.05-fips", true),
// Channels that don't exist yet still match the prefix
("DeterminateSystems", "secure-packages-26.11", true),
("DeterminateSystems", "secure-packages", true),
("determinatesystems", "secure-packages-rolling", true),
// Right name, wrong org
("NotDeterminateSystems", "secure-packages-rolling", false),
("someone-else", "secure-packages-rolling", false),
// Right org, wrong name
("DeterminateSystems", "nixpkgs", false),
("DeterminateSystems", "secure-packagesrolling", false),
("DeterminateSystems", "not-secure-packages-rolling", false),
];

for (owner, name, expected) in cases {
assert_eq!(
is_secure_packages_flake(owner, name),
expected,
"unexpected result for {owner}/{name}"
);
}
}

#[test]
fn secure_packages_urls() {
let cases: Vec<(&str, bool)> = vec![
(
"https://flakehub.com/f/DeterminateSystems/secure-packages-rolling/*",
true,
),
(
"https://flakehub.com/f/DeterminateSystems/secure-packages-26.05/0.1.tar.gz",
true,
),
(
"https://api.flakehub.com/f/pinned/DeterminateSystems/secure-packages-rolling/0.1.1%2Brev-6fb441f657e98f31024f19ca68b691c5b0c3fdd6/01978415-c996-7747-a25c-77ca3a4c5ed9/source.tar.gz",
true,
),
// Right flake, wrong host
(
"https://example.com/f/DeterminateSystems/secure-packages-rolling/*",
false,
),
// Right host, wrong flake
("https://flakehub.com/f/NixOS/nixpkgs/0.1", false),
(
"https://flakehub.com/f/DeterminateSystems/easy-template/0",
false,
),
// Not a FlakeHub URL at all
("https://some-server.com/flake.tar.gz", false),
("flakehub.com/f/DeterminateSystems/secure-packages", false),
("https://flakehub.com/f/DeterminateSystems", false),
];

for (url, expected) in cases {
assert_eq!(
is_secure_packages_url(url),
expected,
"unexpected result for {url}"
);
}
}
}
25 changes: 25 additions & 0 deletions tests/flake.clean.8.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1779041105,
"narHash": "sha256-nnGD2f8OlAZT2i5OfwikJsw+ifWfiA4d6A8BWlgOXV0=",
"rev": "10e6e3cb966f7cfcc789fe5eee7a85f3188ce08b",
"revCount": 858,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/secure-packages-rolling/0.1.858%2Brev-10e6e3cb966f7cfcc789fe5eee7a85f3188ce08b/019e3726-d9ea-7820-aece-59009301cab1/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/DeterminateSystems/secure-packages-rolling/*"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
27 changes: 27 additions & 0 deletions tests/flake.dirty.2.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1779041105,
"narHash": "sha256-nnGD2f8OlAZT2i5OfwikJsw+ifWfiA4d6A8BWlgOXV0=",
"owner": "DeterminateSystems",
"repo": "secure-packages-rolling-fips",
"rev": "10e6e3cb966f7cfcc789fe5eee7a85f3188ce08b",
"type": "github"
},
"original": {
"owner": "DeterminateSystems",
"ref": "main",
"repo": "secure-packages-rolling-fips",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
Loading