Skip to content
Draft
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
10 changes: 7 additions & 3 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@
inherit system;
},
lib ? import "${sources.nixpkgs}/lib",
name ? "web-security-tracker",
}:
rec {
inherit pkgs;
inherit pkgs name;
inherit (pkgs) python3;
localPythonPackages = import ./pkgs { inherit pkgs python3; };

# For exports.
overlays = [ overlay ];
# TODO: `callPackage` the derivation here instead of splicing through
# overlays, it's needlessly hard to follow
package = pkgs.web-security-tracker;
module = import ./nix/web-security-tracker.nix;
module = ./nix/web-security-tracker.nix;
dev-container = import ./infra/container.nix;
dev-setup = import ./nix/dev-setup.nix;

Expand Down Expand Up @@ -137,6 +140,7 @@ rec {
GH_ISSUES_REPO = "sectracker-testing";
GH_SECURITY_TEAM = "setracker-testing-security";
GH_COMMITTERS_TEAM = "sectracker-testing-committers";
STATIC_ROOT = "${toString ./src/website/static}";
};
};

Expand Down Expand Up @@ -166,5 +170,5 @@ rec {
'';
};

tests = pkgs.callPackage ./nix/tests.nix { };
tests = pkgs.callPackage ./nix/tests.nix { application = name; };
}
2 changes: 1 addition & 1 deletion infra/configuration.nix
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ in
values = [ "unix_timestamp" ];
};
};
connections = [ "postgres://postgres@/web-security-tracker?host=/run/postgresql" ];
connections = [ "postgres://postgres@/${application}?host=/run/postgresql" ];
interval = "1h";
};
};
Expand Down
17 changes: 9 additions & 8 deletions infra/container.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
...
}:
let
application = "web-security-tracker";
sectracker = import ../. { };
secretsPath = "/etc/secrets";
secretsGuestPath = "/mnt/secrets";
secretsHostPath = toString ../.credentials;
cfg = config.containers.nix-security-tracker;
cfg = config.containers.${application};
in
{
/**
Expand All @@ -18,17 +19,17 @@ in

The container can be managed at runtime with [`nixos-container`](https://nixos.org/manual/nixos/unstable/#sec-imperative-containers).
*/
users.users.web-security-tracker = {
users.users.${application} = {
isSystemUser = true;
group = "web-security-tracker";
group = application;
};
users.groups.web-security-tracker = { };
systemd.services."container@nix-security-tracker" = {
users.groups.${application} = { };
systemd.services."container@${application}" = {
serviceConfig = {
TimeoutStartSec = lib.mkForce "15m";
};
};
containers.nix-security-tracker = {
containers.${application} = {
autoStart = true;
privateNetwork = true;
# local address range that is unlikely to collide with something else
Expand Down Expand Up @@ -56,7 +57,7 @@ in
# which almost certainly won't be the same as the user under which the service runs
boot.postBootCommands = ''
cp -r ${secretsGuestPath}/* ${secretsPath}
chown web-security-tracker ${secretsPath}
chown ${application} ${secretsPath}
'';
networking.firewall.allowedTCPPorts = map (forward: forward.containerPort) (
lib.filter (forward: forward.protocol == "tcp") cfg.forwardPorts
Expand All @@ -67,7 +68,7 @@ in
imports = [
sectracker.module
];
services.web-security-tracker = {
services.${application} = {
enable = true;
domain = "sectracker.local";
production = false;
Expand Down
2 changes: 1 addition & 1 deletion infra/sectracker.nix
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ in
80
443
];
services.web-security-tracker = {
services.${sectracker.name} = {
enable = true;
production = true;
domain = "tracker.security.nixos.org";
Expand Down
8 changes: 5 additions & 3 deletions nix/tests.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{ lib, pkgs }:
{
lib,
pkgs,
application,
}:
let
# TODO: specify project/service name globally
application = "web-security-tracker";
defaults = {
documentation.enable = lib.mkDefault false;

Expand Down
85 changes: 49 additions & 36 deletions nix/web-security-tracker.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ let
recursiveUpdate
optionalString
;
# TODO: make it somehow configurable from the outside... modular services anyone?
application = "web-security-tracker";
inherit (pkgs) writeScriptBin writeShellApplication stdenv;
cfg = config.services.web-security-tracker;
cfg = config.services.${application};
pythonFmt = pkgs.formats.pythonVars { };

settingsFile = pythonFmt.generate "wst-settings.py" cfg.settings;
Expand Down Expand Up @@ -50,15 +52,15 @@ let

text = ''
sudo="exec"
if [[ "$USER" != "web-security-tracker" ]]; then
sudo='exec /run/wrappers/bin/sudo -u web-security-tracker --preserve-env --preserve-env=PYTHONPATH'
if [[ "$USER" != "${application}" ]]; then
sudo='exec /run/wrappers/bin/sudo -u ${application} --preserve-env --preserve-env=PYTHONPATH'
fi
export PYTHONPATH=${toString cfg.package.pythonPath}
$sudo ${cfg.package}/bin/manage.py "$@"
'';
};
credentials = mapAttrsToList (name: secretPath: "${name}:${secretPath}") cfg.secrets;
databaseUrl = "postgres:///web-security-tracker";
databaseUrl = "postgres:///${application}";

environment = {
DATABASE_URL = databaseUrl;
Expand All @@ -75,9 +77,9 @@ let
--collect \
--service-type=exec \
--unit "wst-manage.service" \
--property "User=web-security-tracker" \
--property "Group=web-security-tracker" \
--property "WorkingDirectory=/var/lib/web-security-tracker" \
--property "User=${application}" \
--property "Group=${application}" \
--property "WorkingDirectory=${cfg.stateDir}/${application}" \
${concatStringsSep "\n" (map (cred: "--property 'LoadCredential=${cred}' \\") credentials)}
--property "Environment=${
toString (lib.mapAttrsToList (name: value: "${name}=${value}") environment)
Expand All @@ -86,9 +88,10 @@ let
'';
in
{
options.services.web-security-tracker = {
options.services.${application} = {
enable = mkEnableOption "web security tracker for Nixpkgs and similar monorepo";

# TODO: `callPackage` the derivation here instead of splicing through overlays, it's needlessly hard to follow
package = mkPackageOption pkgs "web-security-tracker" { };
production = mkOption {
type = types.bool;
Expand All @@ -108,9 +111,21 @@ in
type = types.nullOr types.str;
default = null;
};
env = mkOption {
stateDir = mkOption {
description = "directory for keeping file system state";
type = types.path;
default = "/var/lib";
};
env = mkOption rec {
description = ''
Django configuration via environment variables, see `settings.py` for options.
'';
type = types.attrsOf types.anything;
default = { };
default = {
STATIC_ROOT = "${cfg.stateDir}/${application}/static/";
};
# only override defaults with explicit values
apply = lib.recursiveUpdate default;
};
settings = mkOption {
type = types.attrsOf types.anything;
Expand Down Expand Up @@ -151,20 +166,18 @@ in
environment.systemPackages = [ wstExternalManageScript ];
services = {
# TODO(@fricklerhandwerk): move all configuration over to pydantic-settings
web-security-tracker.settings = {
STATIC_ROOT = mkDefault "/var/lib/web-security-tracker/static";
DEBUG = mkDefault false;
${application}.settings = {
ALLOWED_HOSTS = mkDefault [
(with cfg; if production then domain else "*")
"localhost"
"127.0.0.1"
"[::1]"
];
CSRF_TRUSTED_ORIGINS = mkDefault [ "https://${cfg.domain}" ];
EVALUATION_GC_ROOTS_DIRECTORY = mkDefault "/var/lib/web-security-tracker/gc-roots";
EVALUATION_LOGS_DIRECTORY = mkDefault "/var/log/web-security-tracker/evaluation";
LOCAL_NIXPKGS_CHECKOUT = mkDefault "/var/lib/web-security-tracker/nixpkgs-repo";
CVE_CACHE_DIR = mkDefault "/var/lib/web-security-tracker/cve-cache";
EVALUATION_GC_ROOTS_DIRECTORY = mkDefault "${cfg.stateDir}/${application}/gc-roots";
EVALUATION_LOGS_DIRECTORY = mkDefault "${cfg.stateDir}/${application}/evaluation";
LOCAL_NIXPKGS_CHECKOUT = mkDefault "${cfg.stateDir}/${application}/nixpkgs-repo";
CVE_CACHE_DIR = mkDefault "${cfg.stateDir}/${application}/cve-cache";
ACCOUNT_DEFAULT_HTTP_PROTOCOL = mkDefault (with cfg; if production then "https" else "http");
};

Expand All @@ -174,7 +187,7 @@ in
{
locations = {
"/".proxyPass = "http://localhost:${toString cfg.wsgi-port}";
"/static/".alias = "/var/lib/web-security-tracker/static/";
"/static/".alias = cfg.env.STATIC_ROOT;
};
}
// lib.optionalAttrs cfg.production {
Expand All @@ -187,19 +200,19 @@ in
postgresql = {
ensureUsers = [
{
name = "web-security-tracker";
name = application;
ensureDBOwnership = true;
}
];
ensureDatabases = [ "web-security-tracker" ];
ensureDatabases = [ application ];
};
};

users.users.web-security-tracker = {
users.users.${application} = {
isSystemUser = true;
group = "web-security-tracker";
group = application;
};
users.groups.web-security-tracker = { };
users.groups.${application} = { };

systemd.services =
let
Expand All @@ -210,18 +223,18 @@ in
pkgs.nix-eval-jobs
];
serviceConfig = {
User = "web-security-tracker";
WorkingDirectory = "/var/lib/web-security-tracker";
StateDirectory = "web-security-tracker";
RuntimeDirectory = "web-security-tracker";
LogsDirectory = "web-security-tracker";
User = application;
WorkingDirectory = "${cfg.stateDir}/${application}";
StateDirectory = application;
RuntimeDirectory = application;
LogsDirectory = application;
LoadCredential = credentials;
};
inherit environment;
};
in
mapAttrs (_: recursiveUpdate defaults) {
web-security-tracker-server = {
"${application}-server" = {
description = "A web security tracker ASGI server";
after = [
"network.target"
Expand All @@ -235,7 +248,7 @@ in
};
preStart = ''
# Auto-migrate on first run or if the package has changed
versionFile="/var/lib/web-security-tracker/package-version"
versionFile="${cfg.stateDir}/${application}/package-version"
if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
wst-manage migrate --no-input
wst-manage collectstatic --no-input --clear
Expand All @@ -256,12 +269,12 @@ in
'';
};

web-security-tracker-worker = {
"${application}-worker" = {
description = "Web security tracker - background job processor";
after = [
"network.target"
"postgresql.service"
"web-security-tracker-server.service"
"${application}-server.service"
];
requires = [ "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
Expand All @@ -274,13 +287,13 @@ in
'';
};

web-security-tracker-fetch-all-channels = {
"${application}-fetch-all-channels" = {
description = "Web security tracker - refresh all channels and start nixpkgs evaluation";

after = [
"network.target"
"postgresql.service"
"web-security-tracker-server.service"
"${application}-server.service"
];
requires = [ "postgresql.service" ];

Expand All @@ -294,12 +307,12 @@ in
startAt = "*-*-* 04:00:00";
};

web-security-tracker-delta = {
"${application}-delta" = {
description = "Web security tracker catch up with CVEs";
after = [
"network.target"
"postgresql.service"
"web-security-tracker-server.service"
"${application}-server.service"
];
requires = [ "postgresql.service" ];
serviceConfig.Type = "oneshot";
Expand Down
5 changes: 5 additions & 0 deletions src/website/tracker/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class Settings(BaseSettings):
class DjangoSettings(BaseModel):
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG: bool = False
STATIC_ROOT: Path = Field(
description="""
Writeable directory for compilimg static files, such as stylesheets, when running `manage collectstatic`.
"""
)
SYNC_GITHUB_STATE_AT_STARTUP: bool = Field(
description="""
Connect to GitHub when the service is started and update
Expand Down
Loading