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