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
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,7 @@
./services/web-servers/nginx/default.nix
./services/web-servers/nginx/gitweb.nix
./services/web-servers/phpfpm/default.nix
./services/web-servers/pomerium.nix
./services/web-servers/unit/default.nix
./services/web-servers/shellinabox.nix
./services/web-servers/tomcat.nix
Expand Down
131 changes: 131 additions & 0 deletions nixos/modules/services/web-servers/pomerium.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{ config, lib, pkgs, ... }:

with lib;

let
format = pkgs.formats.yaml {};
in
{
options.services.pomerium = {
enable = mkEnableOption "the Pomerium authenticating reverse proxy";

configFile = mkOption {
type = with types; nullOr path;
default = null;
description = "Path to Pomerium config YAML. If set, overrides services.pomerium.settings.";
};

useACMEHost = mkOption {
type = with types; nullOr str;
default = null;
description = ''
If set, use a NixOS-generated ACME certificate with the specified name.

Note that this will require you to use a non-HTTP-based challenge, or
disable Pomerium's in-built HTTP redirect server by setting
http_redirect_addr to null and use a different HTTP server for serving
the challenge response.

If you're using an HTTP-based challenge, you should use the
Pomerium-native autocert option instead.
'';
};

settings = mkOption {
description = ''
The contents of Pomerium's config.yaml, in Nix expressions.

Specifying configFile will override this in its entirety.

See <link xlink:href="https://pomerium.io/reference/">the Pomerium
configuration reference</link> for more information about what to put
here.
'';
default = {};
type = format.type;
};

secretsFile = mkOption {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the use of DynamicUser in the systemd config, what would be the proper way to secure a secrets file on the local filesystem from other users?

type = with types; nullOr path;
default = null;
description = ''
Path to file containing secrets for Pomerium, in systemd
EnvironmentFile format. See the systemd.exec(5) man page.
'';
};
};

config = let
cfg = config.services.pomerium;
cfgFile = if cfg.configFile != null then cfg.configFile else (format.generate "pomerium.yaml" cfg.settings);
in mkIf cfg.enable ({
systemd.services.pomerium = {
description = "Pomerium authenticating reverse proxy";
wants = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target");
after = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target");
wantedBy = [ "multi-user.target" ];
environment = optionalAttrs (cfg.useACMEHost != null) {
CERTIFICATE_FILE = "fullchain.pem";
CERTIFICATE_KEY_FILE = "key.pem";
};
startLimitIntervalSec = 60;

serviceConfig = {
DynamicUser = true;
StateDirectory = [ "pomerium" ];
ExecStart = "${pkgs.pomerium}/bin/pomerium -config ${cfgFile}";

PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE
MemoryDenyWriteExecute = false; # breaks LuaJIT

NoNewPrivileges = true;
PrivateTmp = true;
PrivateDevices = true;
DevicePolicy = "closed";
ProtectSystem = "strict";
ProtectHome = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectKernelLogs = true;
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
LockPersonality = true;
SystemCallArchitectures = "native";

EnvironmentFile = cfg.secretsFile;
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];

WorkingDirectory = mkIf (cfg.useACMEHost != null) "$CREDENTIALS_DIRECTORY";
LoadCredential = optionals (cfg.useACMEHost != null) [
"fullchain.pem:/var/lib/acme/${cfg.useACMEHost}/fullchain.pem"
"key.pem:/var/lib/acme/${cfg.useACMEHost}/key.pem"
];
};
};

# postRun hooks on cert renew can't be used to restart Nginx since renewal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Says "Nginx" here ;)

# runs as the unprivileged acme user. sslTargets are added to wantedBy + before
# which allows the acme-finished-$cert.target to signify the successful updating
# of certs end-to-end.
systemd.services.pomerium-config-reload = mkIf (cfg.useACMEHost != null) {
# TODO(lukegb): figure out how to make config reloading work with credentials.

wantedBy = [ "acme-finished-${cfg.useACMEHost}.target" "multi-user.target" ];
# Before the finished targets, after the renew services.
before = [ "acme-finished-${cfg.useACMEHost}.target" ];
after = [ "acme-${cfg.useACMEHost}.service" ];
# Block reloading if not all certs exist yet.
unitConfig.ConditionPathExists = [ "${certs.${cfg.useACMEHost}.directory}/fullchain.pem" ];
serviceConfig = {
Type = "oneshot";
TimeoutSec = 60;
ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active pomerium.service";
ExecStart = "/run/current-system/systemd/bin/systemctl restart pomerium.service";
};
};
});
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ in
plikd = handleTest ./plikd.nix {};
plotinus = handleTest ./plotinus.nix {};
podman = handleTestOn ["x86_64-linux"] ./podman.nix {};
pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {};
postfix = handleTest ./postfix.nix {};
postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
postgis = handleTest ./postgis.nix {};
Expand Down
102 changes: 102 additions & 0 deletions nixos/tests/pomerium.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import ./make-test-python.nix ({ pkgs, ... }: {
name = "pomerium";
meta = with pkgs.stdenv.lib.maintainers; {
maintainers = [ lukegb ];
};

nodes = let base = myIP: { pkgs, lib, ... }: {
virtualisation.vlans = [ 1 ];
networking = {
dhcpcd.enable = false;
firewall.allowedTCPPorts = [ 80 443 ];
hosts = {
"192.168.1.1" = [ "pomerium" "pom-auth" ];
"192.168.1.2" = [ "backend" "dummy-oidc" ];
};
interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
{ address = myIP; prefixLength = 24; }
];
};
}; in {
pomerium = { pkgs, lib, ... }: {
imports = [ (base "192.168.1.1") ];
services.pomerium = {
enable = true;
settings = {
address = ":80";
insecure_server = true;
authenticate_service_url = "http://pom-auth";

idp_provider = "oidc";
idp_scopes = [ "oidc" ];
idp_client_id = "dummy";
idp_provider_url = "http://dummy-oidc";

policy = [{
from = "https://my.website";
to = "http://192.168.1.2";
allow_public_unauthenticated_access = true;
preserve_host_header = true;
} {
from = "https://login.required";
to = "http://192.168.1.2";
allowed_domains = [ "my.domain" ];
preserve_host_header = true;
}];
};
secretsFile = pkgs.writeText "pomerium-secrets" ''
# 12345678901234567890123456789012 in base64
COOKIE_SECRET=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=
IDP_CLIENT_SECRET=dummy
'';
};
};
backend = { pkgs, lib, ... }: {
imports = [ (base "192.168.1.2") ];
services.nginx.enable = true;
services.nginx.virtualHosts."my.website" = {
root = pkgs.runCommand "testdir" {} ''
mkdir "$out"
echo hello world > "$out/index.html"
'';
};
services.nginx.virtualHosts."dummy-oidc" = {
root = pkgs.runCommand "testdir" {} ''
mkdir -p "$out/.well-known"
cat <<EOF >"$out/.well-known/openid-configuration"
{
"issuer": "http://dummy-oidc",
"authorization_endpoint": "http://dummy-oidc/auth.txt",
"token_endpoint": "http://dummy-oidc/token",
"jwks_uri": "http://dummy-oidc/jwks.json",
"userinfo_endpoint": "http://dummy-oidc/userinfo",
"id_token_signing_alg_values_supported": ["RS256"]
}
EOF
echo hello I am login page >"$out/auth.txt"
'';
};
};
};

testScript = { ... }: ''
backend.wait_for_unit("nginx")
backend.wait_for_open_port(80)

pomerium.wait_for_unit("pomerium")
pomerium.wait_for_open_port(80)

with subtest("no authentication required"):
pomerium.succeed(
"curl --resolve my.website:80:127.0.0.1 http://my.website | grep -q 'hello world'"
)

with subtest("login required"):
pomerium.succeed(
"curl -I --resolve login.required:80:127.0.0.1 http://login.required | grep -q pom-auth"
)
pomerium.succeed(
"curl -L --resolve login.required:80:127.0.0.1 http://login.required | grep -q 'hello I am login page'"
)
'';
})
6 changes: 6 additions & 0 deletions pkgs/servers/http/envoy/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
, go
, ninja
, python3
, nixosTests
}:

let
Expand Down Expand Up @@ -110,6 +111,11 @@ buildBazelPackage rec {
"--cxxopt=-Wno-uninitialized"
];

passthru.tests = {
# No tests for Envoy itself (yet), but it's tested as a core component of Pomerium.
inherit (nixosTests) pomerium;
};

meta = with lib; {
homepage = "https://envoyproxy.io";
description = "Cloud-native edge and service proxy";
Expand Down
80 changes: 80 additions & 0 deletions pkgs/servers/http/pomerium/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{ buildGoModule
, fetchFromGitHub
, lib
, envoy
, zip
, nixosTests
}:

let
inherit (lib) concatStringsSep mapAttrsToList;
in
buildGoModule rec {
pname = "pomerium";
version = "0.13.3";
src = fetchFromGitHub {
owner = "pomerium";
repo = "pomerium";
rev = "v${version}";
hash = "sha256-g0w1aIHvf2rJANvGWHeUxdnyCDsvy/PQ9Kp8nDdT/0w=";
};

vendorSha256 = "sha256-grihU85OcGyf9/KKrv87xZonX5r+Z1oHQTf84Ya61fg=";
subPackages = [
"cmd/pomerium"
"cmd/pomerium-cli"
];

buildFlagsArray = let
# Set a variety of useful meta variables for stamping the build with.
setVars = {
Version = "v${version}";
BuildMeta = "nixpkgs";
ProjectName = "pomerium";
ProjectURL = "github.com/pomerium/pomerium";
};
varFlags = concatStringsSep " " (mapAttrsToList (name: value: "-X github.com/pomerium/pomerium/internal/version.${name}=${value}") setVars);
in [
"-ldflags=${varFlags}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing -s -w.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem to be set in pkgs/development/go-modules/generic/default.nix as a default, so it's not like we're overriding it off, I think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably does not work for every package. Should be set anyway when specifying ldflags.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe does not work with all packages. Should be set anyway when specifying ldflags.

];

nativeBuildInputs = [
zip
];

# Pomerium expects to have envoy append to it in a zip.
# We use a store-only (-0) zip, so that the Nix scanner can find any store references we had in the envoy binary.
postBuild = ''
# Append Envoy
pushd $NIX_BUILD_TOP
mkdir -p envoy
cd envoy
cp ${envoy}/bin/envoy envoy
zip -0 envoy.zip envoy
popd

mv $GOPATH/bin/pomerium $GOPATH/bin/pomerium.old
cat $GOPATH/bin/pomerium.old $NIX_BUILD_TOP/envoy/envoy.zip >$GOPATH/bin/pomerium
zip --adjust-sfx $GOPATH/bin/pomerium
'';

# We also need to set dontStrip to avoid having the envoy ZIP stripped off the end.
dontStrip = true;

installPhase = ''
install -Dm0755 $GOPATH/bin/pomerium $out/bin/pomerium
install -Dm0755 $GOPATH/bin/pomerium-cli $out/bin/pomerium-cli
'';

passthru.tests = {
inherit (nixosTests) pomerium;
};

meta = with lib; {
homepage = "https://pomerium.io";
description = "Authenticating reverse proxy";
license = licenses.asl20;
maintainers = with maintainers; [ lukegb ];
platforms = [ "x86_64-linux" ]; # Envoy derivation is x86_64-linux only.
};
}
2 changes: 2 additions & 0 deletions pkgs/top-level/all-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18511,6 +18511,8 @@ in
};
pflogsumm = callPackage ../servers/mail/postfix/pflogsumm.nix { };

pomerium = callPackage ../servers/http/pomerium { };

postgrey = callPackage ../servers/mail/postgrey { };

pshs = callPackage ../servers/http/pshs { };
Expand Down