at 18.09-beta 16 kB view raw
1# NixOS module for oauth2_proxy. 2 3{ config, lib, pkgs, ... }: 4 5with lib; 6let 7 cfg = config.services.oauth2_proxy; 8 9 # oauth2_proxy provides many options that are only relevant if you are using 10 # a certain provider. This set maps from provider name to a function that 11 # takes the configuration and returns a string that can be inserted into the 12 # command-line to launch oauth2_proxy. 13 providerSpecificOptions = { 14 azure = cfg: { 15 azure.tenant = cfg.azure.tenant; 16 resource = cfg.azure.resource; 17 }; 18 19 github = cfg: { github = { 20 inherit (cfg.github) org team; 21 }; }; 22 23 google = cfg: { google = with cfg.google; optionalAttrs (groups != []) { 24 admin-email = adminEmail; 25 service-account = serviceAccountJSON; 26 group = groups; 27 }; }; 28 }; 29 30 authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses; 31 32 getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: {}) cfg; 33 34 allConfig = with cfg; { 35 inherit (cfg) provider scope upstream; 36 approval-prompt = approvalPrompt; 37 basic-auth-password = basicAuthPassword; 38 client-id = clientID; 39 client-secret = clientSecret; 40 custom-templates-dir = customTemplatesDir; 41 email-domain = email.domains; 42 http-address = httpAddress; 43 login-url = loginURL; 44 pass-access-token = passAccessToken; 45 pass-basic-auth = passBasicAuth; 46 pass-host-header = passHostHeader; 47 proxy-prefix = proxyPrefix; 48 profile-url = profileURL; 49 redeem-url = redeemURL; 50 redirect-url = redirectURL; 51 request-logging = requestLogging; 52 skip-auth-regex = skipAuthRegexes; 53 signature-key = signatureKey; 54 validate-url = validateURL; 55 htpasswd-file = htpasswd.file; 56 cookie = { 57 inherit (cookie) domain secure expire name secret refresh; 58 httponly = cookie.httpOnly; 59 }; 60 set-xauthrequest = setXauthrequest; 61 } // lib.optionalAttrs (!isNull cfg.email.addresses) { 62 authenticated-emails-file = authenticatedEmailsFile; 63 } // lib.optionalAttrs (cfg.passBasicAuth) { 64 basic-auth-password = cfg.basicAuthPassword; 65 } // lib.optionalAttrs (!isNull cfg.htpasswd.file) { 66 display-htpasswd-file = cfg.htpasswd.displayForm; 67 } // lib.optionalAttrs tls.enable { 68 tls-cert = tls.certificate; 69 tls-key = tls.key; 70 https-address = tls.httpsAddress; 71 } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig; 72 73 mapConfig = key: attr: 74 if (!isNull attr && attr != []) then ( 75 if isDerivation attr then mapConfig key (toString attr) else 76 if (builtins.typeOf attr) == "set" then concatStringsSep " " 77 (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else 78 if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else 79 if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else 80 if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else 81 "--${key}=${toString attr}") 82 else ""; 83 84 configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig); 85in 86{ 87 options.services.oauth2_proxy = { 88 enable = mkEnableOption "oauth2_proxy"; 89 90 package = mkOption { 91 type = types.package; 92 default = pkgs.oauth2_proxy; 93 defaultText = "pkgs.oauth2_proxy"; 94 description = '' 95 The package that provides oauth2_proxy. 96 ''; 97 }; 98 99 ############################################## 100 # PROVIDER configuration 101 provider = mkOption { 102 type = types.enum [ 103 "google" 104 "github" 105 "azure" 106 "gitlab" 107 "linkedin" 108 "myusa" 109 ]; 110 default = "google"; 111 description = '' 112 OAuth provider. 113 ''; 114 }; 115 116 approvalPrompt = mkOption { 117 type = types.enum ["force" "auto"]; 118 default = "force"; 119 description = '' 120 OAuth approval_prompt. 121 ''; 122 }; 123 124 clientID = mkOption { 125 type = types.nullOr types.str; 126 description = '' 127 The OAuth Client ID. 128 ''; 129 example = "123456.apps.googleusercontent.com"; 130 }; 131 132 clientSecret = mkOption { 133 type = types.nullOr types.str; 134 description = '' 135 The OAuth Client Secret. 136 ''; 137 }; 138 139 skipAuthRegexes = mkOption { 140 type = types.listOf types.str; 141 default = []; 142 description = '' 143 Skip authentication for requests matching any of these regular 144 expressions. 145 ''; 146 }; 147 148 # XXX: Not clear whether these two options are mutually exclusive or not. 149 email = { 150 domains = mkOption { 151 type = types.listOf types.str; 152 default = []; 153 description = '' 154 Authenticate emails with the specified domains. Use 155 <literal>*</literal> to authenticate any email. 156 ''; 157 }; 158 159 addresses = mkOption { 160 type = types.nullOr types.lines; 161 default = null; 162 description = '' 163 Line-separated email addresses that are allowed to authenticate. 164 ''; 165 }; 166 }; 167 168 loginURL = mkOption { 169 type = types.nullOr types.str; 170 default = null; 171 description = '' 172 Authentication endpoint. 173 174 You only need to set this if you are using a self-hosted provider (e.g. 175 Github Enterprise). If you're using a publicly hosted provider 176 (e.g github.com), then the default works. 177 ''; 178 example = "https://provider.example.com/oauth/authorize"; 179 }; 180 181 redeemURL = mkOption { 182 type = types.nullOr types.str; 183 default = null; 184 description = '' 185 Token redemption endpoint. 186 187 You only need to set this if you are using a self-hosted provider (e.g. 188 Github Enterprise). If you're using a publicly hosted provider 189 (e.g github.com), then the default works. 190 ''; 191 example = "https://provider.example.com/oauth/token"; 192 }; 193 194 validateURL = mkOption { 195 type = types.nullOr types.str; 196 default = null; 197 description = '' 198 Access token validation endpoint. 199 200 You only need to set this if you are using a self-hosted provider (e.g. 201 Github Enterprise). If you're using a publicly hosted provider 202 (e.g github.com), then the default works. 203 ''; 204 example = "https://provider.example.com/user/emails"; 205 }; 206 207 redirectURL = mkOption { 208 # XXX: jml suspects this is always necessary, but the command-line 209 # doesn't require it so making it optional. 210 type = types.nullOr types.str; 211 default = null; 212 description = '' 213 The OAuth2 redirect URL. 214 ''; 215 example = "https://internalapp.yourcompany.com/oauth2/callback"; 216 }; 217 218 azure = { 219 tenant = mkOption { 220 type = types.str; 221 default = "common"; 222 description = '' 223 Go to a tenant-specific or common (tenant-independent) endpoint. 224 ''; 225 }; 226 227 resource = mkOption { 228 type = types.str; 229 description = '' 230 The resource that is protected. 231 ''; 232 }; 233 }; 234 235 google = { 236 adminEmail = mkOption { 237 type = types.str; 238 description = '' 239 The Google Admin to impersonate for API calls. 240 241 Only users with access to the Admin APIs can access the Admin SDK 242 Directory API, thus the service account needs to impersonate one of 243 those users to access the Admin SDK Directory API. 244 245 See <link xlink:href="https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account" />. 246 ''; 247 }; 248 249 groups = mkOption { 250 type = types.listOf types.str; 251 default = []; 252 description = '' 253 Restrict logins to members of these Google groups. 254 ''; 255 }; 256 257 serviceAccountJSON = mkOption { 258 type = types.path; 259 description = '' 260 The path to the service account JSON credentials. 261 ''; 262 }; 263 }; 264 265 github = { 266 org = mkOption { 267 type = types.nullOr types.str; 268 default = null; 269 description = '' 270 Restrict logins to members of this organisation. 271 ''; 272 }; 273 274 team = mkOption { 275 type = types.nullOr types.str; 276 default = null; 277 description = '' 278 Restrict logins to members of this team. 279 ''; 280 }; 281 }; 282 283 284 #################################################### 285 # UPSTREAM Configuration 286 upstream = mkOption { 287 type = with types; coercedTo string (x: [x]) (listOf string); 288 default = []; 289 description = '' 290 The http url(s) of the upstream endpoint or <literal>file://</literal> 291 paths for static files. Routing is based on the path. 292 ''; 293 }; 294 295 passAccessToken = mkOption { 296 type = types.bool; 297 default = false; 298 description = '' 299 Pass OAuth access_token to upstream via X-Forwarded-Access-Token header. 300 ''; 301 }; 302 303 passBasicAuth = mkOption { 304 type = types.bool; 305 default = true; 306 description = '' 307 Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream. 308 ''; 309 }; 310 311 basicAuthPassword = mkOption { 312 type = types.nullOr types.str; 313 default = null; 314 description = '' 315 The password to set when passing the HTTP Basic Auth header. 316 ''; 317 }; 318 319 passHostHeader = mkOption { 320 type = types.bool; 321 default = true; 322 description = '' 323 Pass the request Host Header to upstream. 324 ''; 325 }; 326 327 signatureKey = mkOption { 328 type = types.nullOr types.str; 329 default = null; 330 description = '' 331 GAP-Signature request signature key. 332 ''; 333 example = "sha1:secret0"; 334 }; 335 336 cookie = { 337 domain = mkOption { 338 type = types.nullOr types.str; 339 default = null; 340 description = '' 341 An optional cookie domain to force cookies to. 342 ''; 343 example = ".yourcompany.com"; 344 }; 345 346 expire = mkOption { 347 type = types.str; 348 default = "168h0m0s"; 349 description = '' 350 Expire timeframe for cookie. 351 ''; 352 }; 353 354 httpOnly = mkOption { 355 type = types.bool; 356 default = true; 357 description = '' 358 Set HttpOnly cookie flag. 359 ''; 360 }; 361 362 name = mkOption { 363 type = types.str; 364 default = "_oauth2_proxy"; 365 description = '' 366 The name of the cookie that the oauth_proxy creates. 367 ''; 368 }; 369 370 refresh = mkOption { 371 # XXX: Unclear what the behavior is when this is not specified. 372 type = types.nullOr types.str; 373 default = null; 374 description = '' 375 Refresh the cookie after this duration; 0 to disable. 376 ''; 377 example = "168h0m0s"; 378 }; 379 380 secret = mkOption { 381 type = types.nullOr types.str; 382 description = '' 383 The seed string for secure cookies. 384 ''; 385 }; 386 387 secure = mkOption { 388 type = types.bool; 389 default = true; 390 description = '' 391 Set secure (HTTPS) cookie flag. 392 ''; 393 }; 394 }; 395 396 #################################################### 397 # OAUTH2 PROXY configuration 398 399 httpAddress = mkOption { 400 type = types.str; 401 default = "http://127.0.0.1:4180"; 402 description = '' 403 HTTPS listening address. This module does not expose the port by 404 default. If you want this URL to be accessible to other machines, please 405 add the port to <literal>networking.firewall.allowedTCPPorts</literal>. 406 ''; 407 }; 408 409 htpasswd = { 410 file = mkOption { 411 type = types.nullOr types.path; 412 default = null; 413 description = '' 414 Additionally authenticate against a htpasswd file. Entries must be 415 created with <literal>htpasswd -s</literal> for SHA encryption. 416 ''; 417 }; 418 419 displayForm = mkOption { 420 type = types.bool; 421 default = true; 422 description = '' 423 Display username / password login form if an htpasswd file is provided. 424 ''; 425 }; 426 }; 427 428 customTemplatesDir = mkOption { 429 type = types.nullOr types.path; 430 default = null; 431 description = '' 432 Path to custom HTML templates. 433 ''; 434 }; 435 436 proxyPrefix = mkOption { 437 type = types.str; 438 default = "/oauth2"; 439 description = '' 440 The url root path that this proxy should be nested under. 441 ''; 442 }; 443 444 tls = { 445 enable = mkOption { 446 type = types.bool; 447 default = false; 448 description = '' 449 Whether to serve over TLS. 450 ''; 451 }; 452 453 certificate = mkOption { 454 type = types.path; 455 description = '' 456 Path to certificate file. 457 ''; 458 }; 459 460 key = mkOption { 461 type = types.path; 462 description = '' 463 Path to private key file. 464 ''; 465 }; 466 467 httpsAddress = mkOption { 468 type = types.str; 469 default = ":443"; 470 description = '' 471 <literal>addr:port</literal> to listen on for HTTPS clients. 472 473 Remember to add <literal>port</literal> to 474 <literal>allowedTCPPorts</literal> if you want other machines to be 475 able to connect to it. 476 ''; 477 }; 478 }; 479 480 requestLogging = mkOption { 481 type = types.bool; 482 default = true; 483 description = '' 484 Log requests to stdout. 485 ''; 486 }; 487 488 #################################################### 489 # UNKNOWN 490 491 # XXX: Is this mandatory? Is it part of another group? Is it part of the provider specification? 492 scope = mkOption { 493 # XXX: jml suspects this is always necessary, but the command-line 494 # doesn't require it so making it optional. 495 type = types.nullOr types.str; 496 default = null; 497 description = '' 498 OAuth scope specification. 499 ''; 500 }; 501 502 profileURL = mkOption { 503 type = types.nullOr types.str; 504 default = null; 505 description = '' 506 Profile access endpoint. 507 ''; 508 }; 509 510 setXauthrequest = mkOption { 511 type = types.nullOr types.bool; 512 default = false; 513 description = '' 514 Set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode). Setting this to 'null' means using the upstream default (false). 515 ''; 516 }; 517 518 extraConfig = mkOption { 519 default = {}; 520 description = '' 521 Extra config to pass to oauth2_proxy. 522 ''; 523 }; 524 525 keyFile = mkOption { 526 type = types.nullOr types.string; 527 default = null; 528 description = '' 529 oauth2_proxy allows passing sensitive configuration via environment variables. 530 Make a file that contains lines like 531 OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com 532 and specify the path here. 533 ''; 534 example = "/run/keys/oauth2_proxy"; 535 }; 536 537 }; 538 539 config = mkIf cfg.enable { 540 541 services.oauth2_proxy = mkIf (!isNull cfg.keyFile) { 542 clientID = mkDefault null; 543 clientSecret = mkDefault null; 544 cookie.secret = mkDefault null; 545 }; 546 547 users.users.oauth2_proxy = { 548 description = "OAuth2 Proxy"; 549 }; 550 551 systemd.services.oauth2_proxy = { 552 description = "OAuth2 Proxy"; 553 path = [ cfg.package ]; 554 wantedBy = [ "multi-user.target" ]; 555 after = [ "network.target" ]; 556 557 serviceConfig = { 558 User = "oauth2_proxy"; 559 Restart = "always"; 560 ExecStart = "${cfg.package.bin}/bin/oauth2_proxy ${configString}"; 561 EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile; 562 }; 563 }; 564 565 }; 566}