ghostunnel.services.default: init
This commit is contained in:
parent
109a6a9d1e
commit
53f97deb26
@ -599,6 +599,7 @@ in
|
||||
gerrit = runTest ./gerrit.nix;
|
||||
geth = runTest ./geth.nix;
|
||||
ghostunnel = runTest ./ghostunnel.nix;
|
||||
ghostunnel-modular = runTest ./ghostunnel-modular.nix;
|
||||
gitdaemon = runTest ./gitdaemon.nix;
|
||||
gitea = handleTest ./gitea.nix { giteaPackage = pkgs.gitea; };
|
||||
github-runner = runTest ./github-runner.nix;
|
||||
|
||||
120
nixos/tests/ghostunnel-modular.nix
Normal file
120
nixos/tests/ghostunnel-modular.nix
Normal file
@ -0,0 +1,120 @@
|
||||
{ hostPkgs, lib, ... }:
|
||||
{
|
||||
_class = "nixosTest";
|
||||
name = "ghostunnel";
|
||||
nodes = {
|
||||
backend =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."backend".root = pkgs.runCommand "webroot" { } ''
|
||||
mkdir $out
|
||||
echo hi >$out/hi.txt
|
||||
'';
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
};
|
||||
service =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
system.services."ghostunnel-plain-old" = {
|
||||
imports = [ pkgs.ghostunnel.services.default ];
|
||||
ghostunnel = {
|
||||
listen = "0.0.0.0:443";
|
||||
cert = "/root/service-cert.pem";
|
||||
key = "/root/service-key.pem";
|
||||
disableAuthentication = true;
|
||||
target = "backend:80";
|
||||
unsafeTarget = true;
|
||||
};
|
||||
};
|
||||
system.services."ghostunnel-client-cert" = {
|
||||
imports = [ pkgs.ghostunnel.services.default ];
|
||||
ghostunnel = {
|
||||
listen = "0.0.0.0:1443";
|
||||
cert = "/root/service-cert.pem";
|
||||
key = "/root/service-key.pem";
|
||||
cacert = "/root/ca.pem";
|
||||
target = "backend:80";
|
||||
allowCN = [ "client" ];
|
||||
unsafeTarget = true;
|
||||
};
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
443
|
||||
1443
|
||||
];
|
||||
};
|
||||
client =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [
|
||||
pkgs.curl
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
|
||||
# prepare certificates
|
||||
|
||||
def cmd(command):
|
||||
print(f"+{command}")
|
||||
r = os.system(command)
|
||||
if r != 0:
|
||||
raise Exception(f"Command {command} failed with exit code {r}")
|
||||
|
||||
# Create CA
|
||||
cmd("${hostPkgs.openssl}/bin/openssl genrsa -out ca-key.pem 4096")
|
||||
cmd("${hostPkgs.openssl}/bin/openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -subj '/C=NL/ST=Zuid-Holland/L=The Hague/O=Stevige Balken en Planken B.V./OU=OpSec/CN=Certificate Authority' -out ca.pem")
|
||||
|
||||
# Create service
|
||||
cmd("${hostPkgs.openssl}/bin/openssl genrsa -out service-key.pem 4096")
|
||||
cmd("${hostPkgs.openssl}/bin/openssl req -subj '/CN=service' -sha256 -new -key service-key.pem -out service.csr")
|
||||
cmd("echo subjectAltName = DNS:service,IP:127.0.0.1 >> extfile.cnf")
|
||||
cmd("echo extendedKeyUsage = serverAuth >> extfile.cnf")
|
||||
cmd("${hostPkgs.openssl}/bin/openssl x509 -req -days 365 -sha256 -in service.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out service-cert.pem -extfile extfile.cnf")
|
||||
|
||||
# Create client
|
||||
cmd("${hostPkgs.openssl}/bin/openssl genrsa -out client-key.pem 4096")
|
||||
cmd("${hostPkgs.openssl}/bin/openssl req -subj '/CN=client' -new -key client-key.pem -out client.csr")
|
||||
cmd("echo extendedKeyUsage = clientAuth > extfile-client.cnf")
|
||||
cmd("${hostPkgs.openssl}/bin/openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile extfile-client.cnf")
|
||||
|
||||
cmd("ls -al")
|
||||
|
||||
start_all()
|
||||
|
||||
# Configuration
|
||||
service.copy_from_host("ca.pem", "/root/ca.pem")
|
||||
service.copy_from_host("service-cert.pem", "/root/service-cert.pem")
|
||||
service.copy_from_host("service-key.pem", "/root/service-key.pem")
|
||||
client.copy_from_host("ca.pem", "/root/ca.pem")
|
||||
client.copy_from_host("service-cert.pem", "/root/service-cert.pem")
|
||||
client.copy_from_host("client-cert.pem", "/root/client-cert.pem")
|
||||
client.copy_from_host("client-key.pem", "/root/client-key.pem")
|
||||
|
||||
backend.wait_for_unit("nginx.service")
|
||||
service.wait_for_unit("multi-user.target")
|
||||
service.wait_for_unit("multi-user.target")
|
||||
client.wait_for_unit("multi-user.target")
|
||||
|
||||
# Check assumptions before the real test
|
||||
client.succeed("bash -c 'diff <(curl -v --no-progress-meter http://backend/hi.txt) <(echo hi)'")
|
||||
|
||||
# Plain old simple TLS can connect, ignoring cert
|
||||
client.succeed("bash -c 'diff <(curl -v --no-progress-meter --insecure https://service/hi.txt) <(echo hi)'")
|
||||
|
||||
# Plain old simple TLS provides correct signature with its cert
|
||||
client.succeed("bash -c 'diff <(curl -v --no-progress-meter --cacert /root/ca.pem https://service/hi.txt) <(echo hi)'")
|
||||
|
||||
# Client can authenticate with certificate
|
||||
client.succeed("bash -c 'diff <(curl -v --no-progress-meter --cert /root/client-cert.pem --key /root/client-key.pem --cacert /root/ca.pem https://service:1443/hi.txt) <(echo hi)'")
|
||||
|
||||
# Client must authenticate with certificate
|
||||
client.fail("bash -c 'diff <(curl -v --no-progress-meter --cacert /root/ca.pem https://service:1443/hi.txt) <(echo hi)'")
|
||||
'';
|
||||
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
roberth
|
||||
];
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
fetchFromGitHub,
|
||||
lib,
|
||||
nixosTests,
|
||||
ghostunnel,
|
||||
apple-sdk_12,
|
||||
darwinMinVersionHook,
|
||||
}:
|
||||
@ -39,6 +40,11 @@ buildGoModule rec {
|
||||
podman = nixosTests.podman-tls-ghostunnel;
|
||||
};
|
||||
|
||||
passthru.services.default = {
|
||||
imports = [ ./service.nix ];
|
||||
ghostunnel.package = ghostunnel; # FIXME: finalAttrs.finalPackage
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "TLS proxy with mutual authentication support for securing non-TLS backend applications";
|
||||
homepage = "https://github.com/ghostunnel/ghostunnel#readme";
|
||||
|
||||
229
pkgs/by-name/gh/ghostunnel/service.nix
Normal file
229
pkgs/by-name/gh/ghostunnel/service.nix
Normal file
@ -0,0 +1,229 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
options,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib)
|
||||
concatStringsSep
|
||||
escapeShellArg
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
optional
|
||||
types
|
||||
;
|
||||
cfg = config.ghostunnel;
|
||||
|
||||
in
|
||||
{
|
||||
# https://nixos.org/manual/nixos/unstable/#modular-services
|
||||
_class = "service";
|
||||
options = {
|
||||
ghostunnel = {
|
||||
package = mkOption {
|
||||
description = "Package to use for ghostunnel";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
listen = mkOption {
|
||||
description = ''
|
||||
Address and port to listen on (can be HOST:PORT, unix:PATH).
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
target = mkOption {
|
||||
description = ''
|
||||
Address to forward connections to (can be HOST:PORT or unix:PATH).
|
||||
'';
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
keystore = mkOption {
|
||||
description = ''
|
||||
Path to keystore (combined PEM with cert/key, or PKCS12 keystore).
|
||||
|
||||
NB: storepass is not supported because it would expose credentials via `/proc/*/cmdline`.
|
||||
|
||||
Specify this or `cert` and `key`.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
cert = mkOption {
|
||||
description = ''
|
||||
Path to certificate (PEM with certificate chain).
|
||||
|
||||
Not required if `keystore` is set.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
key = mkOption {
|
||||
description = ''
|
||||
Path to certificate private key (PEM with private key).
|
||||
|
||||
Not required if `keystore` is set.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
cacert = mkOption {
|
||||
description = ''
|
||||
Path to CA bundle file (PEM/X509). Uses system trust store if `null`.
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
disableAuthentication = mkOption {
|
||||
description = ''
|
||||
Disable client authentication, no client certificate will be required.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
allowAll = mkOption {
|
||||
description = ''
|
||||
If true, allow all clients, do not check client cert subject.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
allowCN = mkOption {
|
||||
description = ''
|
||||
Allow client if common name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
allowOU = mkOption {
|
||||
description = ''
|
||||
Allow client if organizational unit name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
allowDNS = mkOption {
|
||||
description = ''
|
||||
Allow client if DNS subject alternative name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
allowURI = mkOption {
|
||||
description = ''
|
||||
Allow client if URI subject alternative name appears in the list.
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
extraArguments = mkOption {
|
||||
description = "Extra arguments to pass to `ghostunnel server` (shell syntax)";
|
||||
type = types.separatedString " ";
|
||||
default = "";
|
||||
};
|
||||
|
||||
unsafeTarget = mkOption {
|
||||
description = ''
|
||||
If set, does not limit target to localhost, 127.0.0.1, [::1], or UNIX sockets.
|
||||
|
||||
This is meant to protect against accidental unencrypted traffic on
|
||||
untrusted networks.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
assertions = [
|
||||
{
|
||||
message = ''
|
||||
At least one access control flag is required.
|
||||
Set at least one of:
|
||||
- ${options.ghostunnel.disableAuthentication}
|
||||
- ${options.ghostunnel.allowAll}
|
||||
- ${options.ghostunnel.allowCN}
|
||||
- ${options.ghostunnel.allowOU}
|
||||
- ${options.ghostunnel.allowDNS}
|
||||
- ${options.ghostunnel.allowURI}
|
||||
'';
|
||||
assertion =
|
||||
cfg.disableAuthentication
|
||||
|| cfg.allowAll
|
||||
|| cfg.allowCN != [ ]
|
||||
|| cfg.allowOU != [ ]
|
||||
|| cfg.allowDNS != [ ]
|
||||
|| cfg.allowURI != [ ];
|
||||
}
|
||||
];
|
||||
|
||||
ghostunnel = {
|
||||
# Clients should not be authenticated with the public root certificates
|
||||
# (afaict, it doesn't make sense), so we only provide that default when
|
||||
# client cert auth is disabled.
|
||||
cacert = mkIf cfg.disableAuthentication (mkDefault null);
|
||||
};
|
||||
|
||||
# TODO assertions
|
||||
|
||||
process = {
|
||||
executable = pkgs.writeScriptBin "run-ghostunnel" ''
|
||||
#!${pkgs.runtimeShell}
|
||||
exec ${lib.getExe cfg.package} ${
|
||||
concatStringsSep " " (
|
||||
optional (cfg.keystore != null) "--keystore=$CREDENTIALS_DIRECTORY/keystore"
|
||||
++ optional (cfg.cert != null) "--cert=$CREDENTIALS_DIRECTORY/cert"
|
||||
++ optional (cfg.key != null) "--key=$CREDENTIALS_DIRECTORY/key"
|
||||
++ optional (cfg.cacert != null) "--cacert=$CREDENTIALS_DIRECTORY/cacert"
|
||||
++ [
|
||||
"server"
|
||||
"--listen"
|
||||
cfg.listen
|
||||
"--target"
|
||||
cfg.target
|
||||
]
|
||||
++ optional cfg.allowAll "--allow-all"
|
||||
++ map (v: "--allow-cn=${escapeShellArg v}") cfg.allowCN
|
||||
++ map (v: "--allow-ou=${escapeShellArg v}") cfg.allowOU
|
||||
++ map (v: "--allow-dns=${escapeShellArg v}") cfg.allowDNS
|
||||
++ map (v: "--allow-uri=${escapeShellArg v}") cfg.allowURI
|
||||
++ optional cfg.disableAuthentication "--disable-authentication"
|
||||
++ optional cfg.unsafeTarget "--unsafe-target"
|
||||
++ [ cfg.extraArguments ]
|
||||
)
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
# refine the service
|
||||
systemd.service = {
|
||||
after = [ "network.target" ];
|
||||
wants = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Restart = "always";
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
DynamicUser = true;
|
||||
LoadCredential =
|
||||
optional (cfg.keystore != null) "keystore:${cfg.keystore}"
|
||||
++ optional (cfg.cert != null) "cert:${cfg.cert}"
|
||||
++ optional (cfg.key != null) "key:${cfg.key}"
|
||||
++ optional (cfg.cacert != null) "cacert:${cfg.cacert}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user