1
0
Fork 0

protect vaultwarden with oauth2proxy

This commit is contained in:
ibizaman 2023-02-11 21:28:00 -08:00
parent a93a9cc7c5
commit 52af93898c
6 changed files with 333 additions and 23 deletions

View file

@ -23,6 +23,7 @@ let
mkPHPFPMService = callPackage ./php-fpm/unit.nix {inherit utils;};
mkKeycloakService = callPackage ./keycloak/unit.nix {inherit utils;};
mkOauth2Proxy = callPackage ./oauth2-proxy/unit.nix {inherit utils;};
mkKeycloakHaproxyService = callPackage ./keycloak-haproxy/unit.nix {inherit utils;};

View file

@ -7,6 +7,7 @@
, roles ? {}
, clients ? {}
, users ? {}
, groups ? []
}:
with builtins;
@ -17,7 +18,7 @@ let
iscomposite = (length v) > 0;
in {
name = k;
composite = if iscomposite then "true" else "false";
composite = if iscomposite then true else false;
} // optionalAttrs iscomposite {
composites = {
realm = v;
@ -26,18 +27,24 @@ let
mkClientRole =
let
roles = config:
if (hasAttr "roles" config)
then config.roles
else [];
roles = config: config.roles or [];
c = v:
{
name = v;
clientRole = "true";
clientRole = true;
};
in k: config: map c (roles config);
mkGroup = name: {
inherit name;
path = "/${name}";
attributes = {};
realmRoles = [];
clientRoles = {};
subGroups = [];
};
mkClient = k: config:
let
url = "https://${k}.${domain}";
@ -46,21 +53,134 @@ let
clientId = k;
rootUrl = url;
clientAuthenticatorType = "client-secret";
redirectUris = ["${url}/*"];
redirectUris = ["${url}/oauth2/callback"];
webOrigins = [url];
authorizationServicesEnabled = "true";
serviceAccountsEnabled = "true";
authorizationServicesEnabled = true;
serviceAccountsEnabled = true;
protocol = "openid-connect";
publicClient = "false";
publicClient = false;
protocolMappers = [
{
name = "Client ID";
protocol = "openid-connect";
protocolMapper = "oidc-usersessionmodel-note-mapper";
consentRequired = false;
config = {
"user.session.note" = "clientId";
"id.token.claim" = "true";
"access.token.claim" = "true";
"claim.name" = "clientId";
"jsonType.label" = "String";
};
}
{
name = "Client Host";
protocol = "openid-connect";
protocolMapper = "oidc-usersessionmodel-note-mapper";
consentRequired = false;
config = {
"user.session.note" = "clientHost";
"id.token.claim" = "true";
"access.token.claim" = "true";
"claim.name" = "clientHost";
"jsonType.label" = "String";
};
}
{
name = "Client IP Address";
protocol = "openid-connect";
protocolMapper = "oidc-usersessionmodel-note-mapper";
consentRequired = false;
config = {
"user.session.note" = "clientAddress";
"id.token.claim" = "true";
"access.token.claim" = "true";
"claim.name" = "clientAddress";
"jsonType.label" = "String";
};
}
{
name = "Audience";
protocol = "openid-connect";
protocolMapper = "oidc-audience-mapper";
config = {
"included.client.audience" = k;
"id.token.claim" = "false";
"access.token.claim" = "true";
"included.custom.audience" = k;
};
}
{
name = "Group";
protocol = "openid-connect";
protocolMapper = "oidc-group-membership-mapper";
config = {
"full.path" = "true";
"id.token.claim" = "true";
"access.token.claim" = "true";
"claim.name" = "groups";
"userinfo.token.claim" = "true";
};
}
];
authorizationSettings = {
policyEnforcementMode = "ENFORCING";
resources =
let
mkResource = name: uris: {
inherit name;
type = "urn:${k}:resources:${name}";
ownerManagedAccess = false;
inherit uris;
};
in
mapAttrsToList mkResource (config.resourcesUris or {});
policies =
let
mkPolicyRole = role: {
id = role;
required = true;
};
mkPolicy = name: roles: {
name = "${concatStringsSep "," roles} has access";
type = "role";
logic = "POSITIVE";
decisionStrategy = "UNANIMOUS";
config = {
roles = toJSON (map mkPolicyRole roles);
};
};
mkPermission = name: roles: resources: {
name = "${concatStringsSep "," roles} has access to ${concatStringsSep "," resources}";
type = "resource";
logic = "POSITIVE";
decisionStrategy = "UNANIMOUS";
config = {
resources = toJSON resources;
applyPolicies = toJSON (map (r: "${concatStringsSep "," roles} has access") roles);
};
};
in
(mapAttrsToList (name: {roles, ...}: mkPolicy name roles) (config.access or {}))
++ (mapAttrsToList (name: {roles, resources}: mkPermission name roles resources) (config.access or {}));
};
};
mkUser = k: config:
{
username = k;
enabled = "true";
enabled = true;
inherit (config) email firstName lastName realmRoles;
} // optionalAttrs (hasAttr "initialPassword" config && config.initialPassword) {
inherit (config) email firstName lastName;
} // optionalAttrs (config ? "groups") {
inherit (config) groups;
} // optionalAttrs (config ? "roles") {
realmRoles = config.roles;
} // optionalAttrs (config ? "initialPassword") {
credentials = [
{
type = "password";
@ -74,7 +194,7 @@ in
{
inherit realm;
id = realm;
enabled = "true";
enabled = true;
clients = mapAttrsToList mkClient clients;
@ -83,5 +203,7 @@ in
client = mapAttrs mkClientRole clients;
};
groups = map mkGroup groups;
users = mapAttrsToList mkUser users;
}

View file

@ -10,6 +10,7 @@
, postgresServiceName
, initialAdminUsername ? null
, keys
, listenPort ? 8080
, logLevel ? "INFO"
, metricsEnabled ? false
@ -38,6 +39,7 @@ in
inherit name;
inherit initialAdminUsername;
inherit hostname listenPort;
systemdUnitFile = "${name}.service";
@ -59,6 +61,9 @@ in
# HTTP
http-host=127.0.0.1
http-port=${builtins.toString listenPort}
# The file path to a server certificate or certificate chain in PEM format.
#https-certificate-file=''${kc.home.dir}conf/server.crt.pem

154
oauth2-proxy/unit.nix Normal file
View file

@ -0,0 +1,154 @@
{ stdenv
, pkgs
, utils
}:
{ name
, serviceName
, keycloakSubdomain ? "keycloak"
, domain
, realm
, allowed_roles ? []
, ingress
, egress
, metricsPort
, keys
, distribution
, KeycloakService
, KeycloakCliService
, debug ? true
}:
with builtins;
with pkgs.lib.lists;
with pkgs.lib.strings;
rec {
inherit name;
pkg =
{ KeycloakService
, KeycloakCliService
}:
let
formatted_allowed_roles = builtins.toJSON (concatStringsSep ", " allowed_roles);
config = pkgs.writeText "${serviceName}.cfg" (''
provider = "keycloak-oidc"
provider_display_name="Keycloak"
http_address = "${ingress}"
upstreams = [ "${concatStringsSep " " egress}" ]
metrics_address = "127.0.0.1:${toString metricsPort}"
client_id = "${serviceName}"
scope="openid"
redirect_url = "https://${serviceName}.${domain}/oauth2/callback"
oidc_issuer_url = "https://${keycloakSubdomain}.${domain}/realms/${realm}"
email_domains = [ "*" ]
allowed_roles = ${formatted_allowed_roles}
# skip_auth_routes = [ "^/api" ]
reverse_proxy = "true"
# trusted_ips = "@"
skip_provider_button = "true"
pass_authorization_header = true
pass_access_token = true
pass_user_headers = true
set_authorization_header = true
set_xauthrequest = true
'' + (if !debug then "" else ''
auth_logging = "true"
request_logging = "true"
''));
exec = pkgs.writeShellApplication {
name = "oauth2proxy-wrapper";
runtimeInputs = with pkgs; [curl coreutils];
text = ''
while ! curl --silent ${KeycloakService.hostname}:${builtins.toString KeycloakService.listenPort} > /dev/null; do
echo "Waiting for port ${builtins.toString KeycloakService.listenPort} to open..."
sleep 10
done
sleep 2
'';
};
oauth2-proxy =
let
version = "f93166229fe9b57f7d54fb0a9c42939f3f30340f";
src = pkgs.fetchFromGitHub {
owner = "ibizaman";
repo = "oauth2-proxy";
rev = version;
sha256 = "sha256-RI34N+YmUqAanuJOGUA+rUTS1TpUoy8rw6EFGeLh5L0=";
# sha256 = pkgs.lib.fakeSha256;
};
in
(pkgs.callPackage "${pkgs.path}/pkgs/tools/backup/kopia" {
buildGoModule = args: pkgs.buildGo118Module (args // {
vendorSha256 = "sha256-2WUd2RxeOal0lpp/TuGSyfP1ppvG/Vd3bgsSsNO8ejo=";
inherit src version;
});
});
oauth2proxyBin = "${oauth2-proxy}/bin/oauth2-proxy";
in utils.systemd.mkService rec {
name = "oauth2proxy-${serviceName}";
content = ''
[Unit]
Description=Oauth2 proxy for ${serviceName}
After=${KeycloakService.systemdUnitFile}
Wants=${KeycloakService.systemdUnitFile}
After=${utils.keyServiceDependencies keys}
Wants=${utils.keyServiceDependencies keys}
[Service]
ExecStartPre=${exec}/bin/oauth2proxy-wrapper
TimeoutStartSec=8m
ExecStart=${oauth2proxyBin} --config ${config}
DynamicUser=true
RuntimeDirectory=oauth2proxy-${serviceName}
${utils.keyEnvironmentFiles keys}
CapabilityBoundingSet=
AmbientCapabilities=
PrivateUsers=yes
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectHostname=yes
ProtectClock=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallArchitectures=native
[Install]
WantedBy=multi-user.target
'';
};
dependsOn = {
inherit KeycloakService KeycloakCliService;
};
type = "systemd-unit";
}

View file

@ -180,7 +180,6 @@ rec {
keycloakCliConfig = {
clients = {
ttrss = {
roles = ["uma_protection"];
};
};
};

View file

@ -4,7 +4,6 @@
}:
{ serviceName ? "Vaultwarden"
, subdomain ? "vaultwarden"
, domain ? ""
, ingress ? 18005
, signupsAllowed ? false
, signupsVerify ? true
@ -25,12 +24,15 @@
, sso ? {}
, distribution ? {}
, KeycloakService ? null
, KeycloakCliService ? null
}:
let
mkVaultwardenWeb = pkgs.callPackage ./web.nix {inherit utils;};
ssoIngress = if sso != {} then ingress else null;
serviceIngress = if sso != {} then ingress+1 else ingress;
metricsPort = if sso != {} then ingress+2 else ingress+1;
smtpConfig = smtp;
in
@ -174,18 +176,43 @@ rec {
};
};
oauth2Proxy = {
name = "${serviceName}Oauth2Proxy";
serviceName = subdomain;
inherit domain;
cookieSecret = "${serviceName}_oauth2proxy_cookiesecret";
clientSecret = "${serviceName}_oauth2proxy_clientsecret";
oauth2Proxy =
let
name = "${serviceName}Oauth2Proxy";
in customPkgs.mkOauth2Proxy {
inherit name;
serviceName = subdomain;
domain = utils.getDomain distribution name;
ingress = "127.0.0.1:${toString ssoIngress}";
egress = [ "http://127.0.0.1:${toString serviceIngress}" ];
realm = sso.realm;
allowed_roles = [ "user" "/admin|admin" ];
inherit metricsPort;
keys = {
cookieSecret = "${serviceName}_oauth2proxy_cookiesecret";
clientSecret = "${serviceName}_oauth2proxy_clientsecret";
};
inherit distribution KeycloakService KeycloakCliService;
};
keycloakCliConfig = {
clients = {
vaultwarden = {
roles = ["uma_protection"];
resourcesUris = {
adminPath = ["/admin/*"];
userPath = ["/*"];
};
access = {
admin = {
roles = [ "admin" ];
resources = [ "adminPath" ];
};
user = {
roles = [ "user" ];
resources = [ "userPath" ];
};
};
};
};
};
@ -210,11 +237,13 @@ rec {
${db.name} = db;
${web.name} = web;
${service.name} = service;
${oauth2Proxy.name} = oauth2Proxy;
};
distribute = on: {
${db.name} = on;
${web.name} = on;
${service.name} = on;
${oauth2Proxy.name} = on;
};
}