Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vulnix Dashboard #63

Merged
merged 13 commits into from
Sep 8, 2021
19 changes: 18 additions & 1 deletion flake.lock

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

4 changes: 4 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
url = "github:input-output-hk/nomad/release-1.1.3";
flake = false;
};
vulnix = {
url = "github:flyingcircusio/vulnix";
flake = false;
};

nix.url = "github:NixOS/nix";
};
Expand Down
194 changes: 194 additions & 0 deletions modules/vulnix.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
{ config, pkgs, lib, ... }:

let
cfg = config.services.vulnix;
whitelistFormat = pkgs.formats.toml {};
in {
options.services.vulnix = with lib; {
enable = mkEnableOption "Vulnix scan";

package = mkOption {
type = types.package;
default = pkgs.vulnix;
defaultText = "pkgs.vulnix";
description = "The Vulnix distribution to use.";
};

scanRequisites = mkEnableOption "scan of transitive closures" // {
default = true;
};

scanSystem = mkEnableOption "scan of the current system" // {
default = true;
};

scanGcRoots = mkEnableOption "scan of all active GC roots";

scanNomadJobs = {
enable = mkEnableOption "scan of all active Nomad jobs";

namespaces = mkOption {
type = with types; listOf str;
default =
let nss = builtins.attrNames config.services.nomad.namespaces; in
nss ++ lib.optional (nss == []) "*";
description = "Nomad namespaces to scan jobs in.";
};

sshKey = mkOption {
type = types.path;
description = "The SSH key to use for private Git repos.";
};

netrcFile = mkOption {
type = types.path;
description = "The netrc file to use for private Git repos.";
};
};

whitelists = mkOption {
type = types.listOf whitelistFormat.type;
default = [];
description = "Whitelists to respect.";
};

paths = mkOption {
type = with types; listOf str;
default = [];
description = "Paths to scan.";
};

extraOpts = mkOption {
type = with types; listOf str;
default = [];
description = ''
Extra options to pass to Vulnix. See the README:
<link xlink:href="https://github.com/flyingcircusio/vulnix/blob/master/README.rst"/>
or <command>vulnix --help</command> for more information.
'';
};

sink = mkOption {
type = types.path;
description = ''
Program that processes the result of each scan. It receives the vulnix output on stdin.
When receiving the result of nomad job scans the environment variables
<envar>NOMAD_JOB_NAMESPACE</envar>, <envar>NOMAD_JOB_ID</envar>,
<envar>NOMAD_JOB_TASKGROUP_NAME</envar>, and <envar>NOMAD_JOB_TASK_NAME</envar> are set.
'';
};
};

config.systemd = let
inherit (config.cluster) domain;
in lib.mkIf cfg.enable {
services.vulnix = {
description = "Vulnix scan";

serviceConfig = {
Type = "oneshot";
DynamicUser = true;
CacheDirectory = "vulnix";
StateDirectory = "vulnix";
} // (with cfg.scanNomadJobs; lib.optionalAttrs enable {
Type = "simple";
Restart = "on-failure";
LoadCredential = [
(assert config.services.vault-agent-core.enable; "vault-token:/run/keys/vault-token")
"ssh:${sshKey}"
"netrc:${netrcFile}"
];
});

startLimitIntervalSec = 20;
startLimitBurst = 10;

environment = lib.mkIf cfg.scanNomadJobs.enable {
VAULT_ADDR = "https://vault.${domain}";
NOMAD_ADDR = "https://nomad.${domain}";
VAULT_CACERT = "/etc/ssl/certs/${domain}-full.pem";
};

path = with pkgs; [ cfg.package vault-bin curl jq nixFlakes gitMinimal ];

script = ''
set -o pipefail

function scan {
vulnix ${lib.cli.toGNUCommandLineShell {} (with cfg; {
json = true;
requisites = scanRequisites;
no-requisites = !scanRequisites;
whitelist = map (lib.flip lib.pipe [
(whitelistFormat.generate "vulnix-whitelist.toml")
(drv: "${drv}")
]) whitelists;
})} \
--cache-dir $CACHE_DIRECTORY \
${lib.concatStringsSep " " cfg.extraOpts} "$@" \
|| case $? in
# XXX adapt this after action on https://github.com/flyingcircusio/vulnix/issues/79
0 ) ;; # no vulnerabilities found
1 ) ;; # only whitelisted vulnerabilities found
2 ) ;; # vulnerabilities found
* ) exit $? ;; # unexpected
esac
}

scan ${lib.cli.toGNUCommandLineShell {} (with cfg; {
system = scanSystem;
gc-roots = scanGcRoots;
})} \
-- ${lib.escapeShellArgs cfg.paths} \
| ${cfg.sink}
'' + lib.optionalString cfg.scanNomadJobs.enable ''
export VAULT_TOKEN=$(< $CREDENTIALS_DIRECTORY/vault-token)
NOMAD_TOKEN=$(vault read -field secret_id nomad/creds/admin)
sleep 5s # let nomad token be propagated to come into effect

[[ -f $STATE_DIRECTORY/index ]] || {
printf '%d' 0 > $STATE_DIRECTORY/index
}

# TODO If the NOMAD_TOKEN expires the service would probably exit uncleanly and restart. Make it a clean restart.

function stream {
<<< X-Nomad-Token:"$NOMAD_TOKEN" \
curl -H @- \
--no-progress-meter \
--cacert /etc/ssl/certs/${domain}-ca.pem \
-NG "$NOMAD_ADDR"/v1/event/stream \
--data-urlencode namespace="$1" \
--data-urlencode topic=Job \
--data-urlencode index=$(< $STATE_DIRECTORY/index) \
| jq --unbuffered -rc 'select(length > 0) | {"index": .Index} as $out | .Events[] | select(.Type == "EvaluationUpdated").Payload.Job | $out * {"namespace": .Namespace, "job": .ID} as $out | .TaskGroups[] | $out * {"taskgroup": .Name} as $out | .Tasks[] | $out * {"task": .Name, "flake": .Config.flake}' \
| while read -r job; do
<<< "$job" jq -rc .flake \
| XDG_CACHE_HOME=$CACHE_DIRECTORY \
GIT_SSH_COMMAND="${pkgs.openssh}/bin/ssh -i $CREDENTIALS_DIRECTORY/ssh" \
xargs -L 1 \
nix --netrc-file $CREDENTIALS_DIRECTORY/netrc show-derivation \
| jq --unbuffered -r keys[] \
| while read -r drv; do
scan -- "$drv" \
| NOMAD_JOB_NAMESPACE=$(<<< "$job" jq -rj .namespace) \
NOMAD_JOB_ID=$(<<< "$job" jq -rj .job) \
NOMAD_JOB_TASKGROUP_NAME=$(<<< "$job" jq -rj .taskgroup) \
NOMAD_JOB_TASK_NAME=$(<<< "$job" jq -rj .task) \
${cfg.sink}
done
<<< "$job" jq -r .index > $STATE_DIRECTORY/index
done
}
${lib.concatMapStringsSep "\n" (ns:
"stream ${lib.escapeShellArg ns} &"
) cfg.scanNomadJobs.namespaces}
wait

exit 1
'';

wantedBy = [ "multi-user.target" ];
};
};
}
5 changes: 5 additions & 0 deletions overlay.nix
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ in final: prev: {

oauth2-proxy = final.callPackage ./pkgs/oauth2_proxy.nix { };

vulnix = import (inputs.vulnix) {
inherit nixpkgs;
pkgs = import nixpkgs { inherit (final) system; };
};

mkRequired = constituents:
let
build-version = final.writeText "version.json" (toJSON {
Expand Down
1 change: 1 addition & 0 deletions profiles/monitoring.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ in {
ingress.enable = true;
ingress-config.enable = true;
minio.enable = true;
vulnix.enable = true;

vault-agent-core = {
enable = true;
Expand Down
Loading