2014-11-09 14:51:10 -05:00
package main
import (
2017-05-09 11:20:35 -07:00
"context"
2015-11-15 22:08:30 -05:00
"crypto"
2017-03-29 10:57:07 -04:00
"crypto/tls"
2016-06-20 07:17:39 -04:00
"encoding/base64"
2014-11-09 14:51:10 -05:00
"fmt"
2019-03-20 15:15:47 -07:00
"io/ioutil"
2016-07-19 20:51:25 +01:00
"net/http"
2014-11-09 14:51:10 -05:00
"net/url"
2015-08-20 03:07:02 -07:00
"os"
2015-01-12 14:48:41 +05:30
"regexp"
2015-03-15 12:23:13 -04:00
"strings"
2015-01-19 16:10:37 +00:00
"time"
2015-03-30 15:48:30 -04:00
2017-05-09 11:20:35 -07:00
oidc "github.com/coreos/go-oidc"
2019-03-20 06:44:51 -07:00
"github.com/dgrijalva/jwt-go"
2017-09-12 18:59:00 -04:00
"github.com/mbland/hmacauth"
2019-05-05 19:13:10 +01:00
"github.com/pusher/oauth2_proxy/pkg/apis/options"
2019-05-07 14:27:09 +01:00
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
2019-05-24 17:06:48 +01:00
"github.com/pusher/oauth2_proxy/pkg/encryption"
2019-06-15 11:33:29 +02:00
"github.com/pusher/oauth2_proxy/pkg/logger"
2019-05-07 14:27:09 +01:00
"github.com/pusher/oauth2_proxy/pkg/sessions"
2018-11-29 14:26:41 +00:00
"github.com/pusher/oauth2_proxy/providers"
2019-04-12 09:26:44 -07:00
"gopkg.in/natefinch/lumberjack.v2"
2014-11-09 14:51:10 -05:00
)
2018-11-29 14:26:41 +00:00
// Options holds Configuration Options that can be set by Command Line Flag,
// or Config File
2014-11-09 14:51:10 -05:00
type Options struct {
2019-06-09 23:47:18 +02:00
ProxyPrefix string ` flag:"proxy-prefix" cfg:"proxy_prefix" env:"OAUTH2_PROXY_PROXY_PREFIX" `
2019-06-03 13:51:59 +12:00
PingPath string ` flag:"ping-path" cfg:"ping-path" env:"OAUTH2_PROXY_PING_PATH" `
2019-03-20 06:44:51 -07:00
ProxyWebSockets bool ` flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS" `
HTTPAddress string ` flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS" `
HTTPSAddress string ` flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS" `
RedirectURL string ` flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL" `
2019-03-08 09:15:21 +01:00
ClientID string ` flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID" `
ClientSecret string ` flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET" `
2019-06-21 15:44:06 +01:00
TLSCertFile string ` flag:"tls-cert-file" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE" `
TLSKeyFile string ` flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE" `
2015-03-17 15:15:15 -04:00
2019-03-20 06:44:51 -07:00
AuthenticatedEmailsFile string ` flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE" `
AzureTenant string ` flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT" `
EmailDomains [ ] string ` flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS" `
2018-01-18 10:20:50 +00:00
WhitelistDomains [ ] string ` flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS" `
2019-03-20 06:44:51 -07:00
GitHubOrg string ` flag:"github-org" cfg:"github_org" env:"OAUTH2_PROXY_GITHUB_ORG" `
GitHubTeam string ` flag:"github-team" cfg:"github_team" env:"OAUTH2_PROXY_GITHUB_TEAM" `
GoogleGroups [ ] string ` flag:"google-group" cfg:"google_group" env:"OAUTH2_PROXY_GOOGLE_GROUPS" `
GoogleAdminEmail string ` flag:"google-admin-email" cfg:"google_admin_email" env:"OAUTH2_PROXY_GOOGLE_ADMIN_EMAIL" `
GoogleServiceAccountJSON string ` flag:"google-service-account-json" cfg:"google_service_account_json" env:"OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON" `
HtpasswdFile string ` flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE" `
DisplayHtpasswdForm bool ` flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM" `
CustomTemplatesDir string ` flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR" `
2019-06-19 15:24:25 +01:00
Banner string ` flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER" `
2019-03-20 06:44:51 -07:00
Footer string ` flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER" `
2015-03-17 15:15:15 -04:00
2019-05-05 19:13:10 +01:00
// Embed CookieOptions
options . CookieOptions
2019-03-20 06:44:51 -07:00
2019-05-07 14:24:12 +01:00
// Embed SessionOptions
options . SessionOptions
2019-03-20 06:44:51 -07:00
Upstreams [ ] string ` flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS" `
SkipAuthRegex [ ] string ` flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX" `
2019-05-01 09:18:54 -07:00
SkipJwtBearerTokens bool ` flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS" `
ExtraJwtIssuers [ ] string ` flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS" `
2019-03-20 06:44:51 -07:00
PassBasicAuth bool ` flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH" `
BasicAuthPassword string ` flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD" `
PassAccessToken bool ` flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN" `
PassHostHeader bool ` flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER" `
SkipProviderButton bool ` flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON" `
PassUserHeaders bool ` flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS" `
SSLInsecureSkipVerify bool ` flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY" `
SetXAuthRequest bool ` flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST" `
SetAuthorization bool ` flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER" `
PassAuthorization bool ` flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER" `
SkipAuthPreflight bool ` flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT" `
FlushInterval time . Duration ` flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL" `
2014-11-09 14:51:10 -05:00
2015-03-30 15:48:30 -04:00
// These options allow for other providers besides Google, with
// potential overrides.
2019-07-11 15:18:36 +01:00
Provider string ` flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER" `
OIDCIssuerURL string ` flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL" `
InsecureOIDCAllowUnverifiedEmail bool ` flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL" `
2019-06-09 23:47:18 +02:00
SkipOIDCDiscovery bool ` flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY" `
OIDCJwksURL string ` flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_PROXY_OIDC_JWKS_URL" `
2019-07-11 15:18:36 +01:00
LoginURL string ` flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL" `
RedeemURL string ` flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL" `
ProfileURL string ` flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL" `
ProtectedResource string ` flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE" `
ValidateURL string ` flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL" `
Scope string ` flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE" `
ApprovalPrompt string ` flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT" `
2019-03-20 06:44:51 -07:00
2019-02-10 08:37:45 -08:00
// Configuration values for logging
2019-06-09 23:47:18 +02:00
LoggingFilename string ` flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_PROXY_LOGGING_FILENAME" `
LoggingMaxSize int ` flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE" `
LoggingMaxAge int ` flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE" `
LoggingMaxBackups int ` flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS" `
LoggingLocalTime bool ` flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME" `
LoggingCompress bool ` flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS" `
StandardLogging bool ` flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING" `
StandardLoggingFormat string ` flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT" `
RequestLogging bool ` flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING" `
RequestLoggingFormat string ` flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT" `
2019-06-24 19:53:43 +12:00
ExcludeLoggingPaths string ` flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS" `
2019-06-03 13:51:59 +12:00
SilencePingLogging bool ` flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING" `
2019-06-09 23:47:18 +02:00
AuthLogging bool ` flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING" `
AuthLoggingFormat string ` flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT" `
2019-06-02 14:36:54 +12:00
SignatureKey string ` flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY" `
AcrValues string ` flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES" `
JWTKey string ` flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY" `
JWTKeyFile string ` flag:"jwt-key-file" cfg:"jwt_key_file" env:"OAUTH2_PROXY_JWT_KEY_FILE" `
PubJWKURL string ` flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL" `
GCPHealthChecks bool ` flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS" `
2015-11-15 22:08:30 -05:00
2014-11-09 14:51:10 -05:00
// internal values that are set after config validation
2019-01-17 12:49:14 -08:00
redirectURL * url . URL
proxyURLs [ ] * url . URL
CompiledRegex [ ] * regexp . Regexp
provider providers . Provider
sessionStore sessionsapi . SessionStore
signatureData * SignatureData
oidcVerifier * oidc . IDTokenVerifier
jwtBearerVerifiers [ ] * oidc . IDTokenVerifier
2015-11-15 22:08:30 -05:00
}
2018-12-20 09:30:42 +00:00
// SignatureData holds hmacauth signature hash and key
2015-11-15 22:08:30 -05:00
type SignatureData struct {
hash crypto . Hash
key string
2014-11-09 14:51:10 -05:00
}
2018-12-20 09:30:42 +00:00
// NewOptions constructs a new Options with defaulted values
2014-11-09 14:51:10 -05:00
func NewOptions ( ) * Options {
2014-11-09 22:21:46 -05:00
return & Options {
2019-05-05 19:13:10 +01:00
ProxyPrefix : "/oauth2" ,
2019-06-03 13:51:59 +12:00
PingPath : "/ping" ,
2019-05-05 19:13:10 +01:00
ProxyWebSockets : true ,
HTTPAddress : "127.0.0.1:4180" ,
HTTPSAddress : ":443" ,
DisplayHtpasswdForm : true ,
CookieOptions : options . CookieOptions {
CookieName : "_oauth2_proxy" ,
CookieSecure : true ,
CookieHTTPOnly : true ,
CookieExpire : time . Duration ( 168 ) * time . Hour ,
CookieRefresh : time . Duration ( 0 ) ,
} ,
2019-05-07 14:27:09 +01:00
SessionOptions : options . SessionOptions {
Type : "cookie" ,
} ,
2019-07-11 15:18:36 +01:00
SetXAuthRequest : false ,
SkipAuthPreflight : false ,
PassBasicAuth : true ,
PassUserHeaders : true ,
PassAccessToken : false ,
PassHostHeader : true ,
SetAuthorization : false ,
PassAuthorization : false ,
ApprovalPrompt : "force" ,
InsecureOIDCAllowUnverifiedEmail : false ,
SkipOIDCDiscovery : false ,
LoggingFilename : "" ,
LoggingMaxSize : 100 ,
LoggingMaxAge : 7 ,
LoggingMaxBackups : 0 ,
LoggingLocalTime : true ,
LoggingCompress : false ,
2019-06-22 09:39:46 +12:00
ExcludeLoggingPaths : "" ,
2019-05-31 20:11:28 +12:00
SilencePingLogging : false ,
2019-07-11 15:18:36 +01:00
StandardLogging : true ,
StandardLoggingFormat : logger . DefaultStandardLoggingFormat ,
RequestLogging : true ,
RequestLoggingFormat : logger . DefaultRequestLoggingFormat ,
AuthLogging : true ,
AuthLoggingFormat : logger . DefaultAuthLoggingFormat ,
2014-11-09 22:21:46 -05:00
}
2014-11-09 14:51:10 -05:00
}
2019-06-05 16:09:29 -07:00
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
type jwtIssuer struct {
2019-05-01 10:00:54 -07:00
issuerURI string
audience string
}
2018-11-29 14:26:41 +00:00
func parseURL ( toParse string , urltype string , msgs [ ] string ) ( * url . URL , [ ] string ) {
parsed , err := url . Parse ( toParse )
2015-03-30 15:48:30 -04:00
if err != nil {
return nil , append ( msgs , fmt . Sprintf (
2018-11-29 14:26:41 +00:00
"error parsing %s-url=%q %s" , urltype , toParse , err ) )
2015-03-30 15:48:30 -04:00
}
return parsed , msgs
}
2018-12-20 09:30:42 +00:00
// Validate checks that required options are set and validates those that they
// are of the correct format
2014-11-09 14:51:10 -05:00
func ( o * Options ) Validate ( ) error {
2017-05-09 11:20:35 -07:00
if o . SSLInsecureSkipVerify {
// TODO: Accept a certificate bundle.
insecureTransport := & http . Transport {
TLSClientConfig : & tls . Config { InsecureSkipVerify : true } ,
}
http . DefaultClient = & http . Client { Transport : insecureTransport }
}
2015-03-15 12:23:13 -04:00
msgs := make ( [ ] string , 0 )
2014-11-09 14:51:10 -05:00
if o . CookieSecret == "" {
2015-03-15 12:23:13 -04:00
msgs = append ( msgs , "missing setting: cookie-secret" )
2014-11-09 14:51:10 -05:00
}
if o . ClientID == "" {
2015-03-15 12:23:13 -04:00
msgs = append ( msgs , "missing setting: client-id" )
2014-11-09 14:51:10 -05:00
}
2019-03-20 06:44:51 -07:00
// login.gov uses a signed JWT to authenticate, not a client-secret
if o . ClientSecret == "" && o . Provider != "login.gov" {
2015-03-15 12:23:13 -04:00
msgs = append ( msgs , "missing setting: client-secret" )
2014-11-09 14:51:10 -05:00
}
2015-07-24 16:09:33 -04:00
if o . AuthenticatedEmailsFile == "" && len ( o . EmailDomains ) == 0 && o . HtpasswdFile == "" {
2017-08-05 12:54:31 -04:00
msgs = append ( msgs , "missing setting for email validation: email-domain or authenticated-emails-file required." +
"\n use email-domain=* to authorize all email addresses" )
2015-07-24 16:09:33 -04:00
}
2014-11-09 14:51:10 -05:00
2017-05-09 11:20:35 -07:00
if o . OIDCIssuerURL != "" {
2019-03-04 14:54:22 +01:00
ctx := context . Background ( )
// Construct a manual IDTokenVerifier from issuer URL & JWKS URI
// instead of metadata discovery if we enable -skip-oidc-discovery.
// In this case we need to make sure the required endpoints for
// the provider are configured.
if o . SkipOIDCDiscovery {
if o . LoginURL == "" {
msgs = append ( msgs , "missing setting: login-url" )
}
if o . RedeemURL == "" {
msgs = append ( msgs , "missing setting: redeem-url" )
}
if o . OIDCJwksURL == "" {
msgs = append ( msgs , "missing setting: oidc-jwks-url" )
}
keySet := oidc . NewRemoteKeySet ( ctx , o . OIDCJwksURL )
o . oidcVerifier = oidc . NewVerifier ( o . OIDCIssuerURL , keySet , & oidc . Config {
ClientID : o . ClientID ,
} )
} else {
// Configure discoverable provider data.
provider , err := oidc . NewProvider ( ctx , o . OIDCIssuerURL )
if err != nil {
return err
}
o . oidcVerifier = provider . Verifier ( & oidc . Config {
ClientID : o . ClientID ,
} )
o . LoginURL = provider . Endpoint ( ) . AuthURL
o . RedeemURL = provider . Endpoint ( ) . TokenURL
2017-05-09 11:20:35 -07:00
}
if o . Scope == "" {
o . Scope = "openid email profile"
}
}
2019-01-17 12:49:14 -08:00
if o . SkipJwtBearerTokens {
// If we are using an oidc provider, go ahead and add that provider to the list
if o . oidcVerifier != nil {
o . jwtBearerVerifiers = append ( o . jwtBearerVerifiers , o . oidcVerifier )
}
// Configure extra issuers
if len ( o . ExtraJwtIssuers ) > 0 {
2019-06-05 16:09:29 -07:00
var jwtIssuers [ ] jwtIssuer
2019-05-01 10:00:54 -07:00
jwtIssuers , msgs = parseJwtIssuers ( o . ExtraJwtIssuers , msgs )
for _ , jwtIssuer := range jwtIssuers {
verifier , err := newVerifierFromJwtIssuer ( jwtIssuer )
2019-01-17 12:49:14 -08:00
if err != nil {
2019-05-01 10:00:54 -07:00
msgs = append ( msgs , fmt . Sprintf ( "error building verifiers: %s" , err ) )
2019-01-17 12:49:14 -08:00
}
o . jwtBearerVerifiers = append ( o . jwtBearerVerifiers , verifier )
}
}
}
2015-11-09 00:47:44 +01:00
o . redirectURL , msgs = parseURL ( o . RedirectURL , "redirect" , msgs )
2014-11-09 14:51:10 -05:00
for _ , u := range o . Upstreams {
2015-11-09 00:47:44 +01:00
upstreamURL , err := url . Parse ( u )
2014-11-09 14:51:10 -05:00
if err != nil {
2017-08-05 12:48:36 -04:00
msgs = append ( msgs , fmt . Sprintf ( "error parsing upstream: %s" , err ) )
} else {
if upstreamURL . Path == "" {
upstreamURL . Path = "/"
}
o . proxyURLs = append ( o . proxyURLs , upstreamURL )
2014-11-09 14:51:10 -05:00
}
}
2015-01-12 14:48:41 +05:30
for _ , u := range o . SkipAuthRegex {
CompiledRegex , err := regexp . Compile ( u )
if err != nil {
2017-05-05 15:47:40 -04:00
msgs = append ( msgs , fmt . Sprintf ( "error compiling regex=%q %s" , u , err ) )
continue
2015-01-12 14:48:41 +05:30
}
o . CompiledRegex = append ( o . CompiledRegex , CompiledRegex )
}
2015-03-30 15:48:30 -04:00
msgs = parseProviderInfo ( o , msgs )
2015-01-12 14:48:41 +05:30
2019-05-24 17:06:48 +01:00
var cipher * encryption . Cipher
2019-05-15 16:56:05 +01:00
if o . PassAccessToken || o . SetAuthorization || o . PassAuthorization || ( o . CookieRefresh != time . Duration ( 0 ) ) {
2018-11-29 14:26:41 +00:00
validCookieSecretSize := false
2015-04-05 09:43:40 -04:00
for _ , i := range [ ] int { 16 , 24 , 32 } {
2016-06-20 07:17:39 -04:00
if len ( secretBytes ( o . CookieSecret ) ) == i {
2018-11-29 14:26:41 +00:00
validCookieSecretSize = true
2015-04-05 09:43:40 -04:00
}
}
2016-06-20 07:17:39 -04:00
var decoded bool
if string ( secretBytes ( o . CookieSecret ) ) != o . CookieSecret {
decoded = true
}
2018-11-29 14:26:41 +00:00
if validCookieSecretSize == false {
2016-06-20 07:17:39 -04:00
var suffix string
if decoded {
suffix = fmt . Sprintf ( " note: cookie secret was base64 decoded from %q" , o . CookieSecret )
}
2015-04-05 09:43:40 -04:00
msgs = append ( msgs , fmt . Sprintf (
"cookie_secret must be 16, 24, or 32 bytes " +
"to create an AES cipher when " +
2015-05-09 17:31:13 -04:00
"pass_access_token == true or " +
2016-06-20 07:17:39 -04:00
"cookie_refresh != 0, but is %d bytes.%s" ,
len ( secretBytes ( o . CookieSecret ) ) , suffix ) )
2019-05-07 14:27:09 +01:00
} else {
2019-05-15 16:56:05 +01:00
var err error
2019-05-24 17:06:48 +01:00
cipher , err = encryption . NewCipher ( secretBytes ( o . CookieSecret ) )
2019-05-15 16:56:05 +01:00
if err != nil {
msgs = append ( msgs , fmt . Sprintf ( "cookie-secret error: %v" , err ) )
}
2015-04-05 09:43:40 -04:00
}
}
2019-05-15 16:56:05 +01:00
o . SessionOptions . Cipher = cipher
2019-05-07 14:27:09 +01:00
sessionStore , err := sessions . NewSessionStore ( & o . SessionOptions , & o . CookieOptions )
if err != nil {
msgs = append ( msgs , fmt . Sprintf ( "error initialising session storage: %v" , err ) )
} else {
o . sessionStore = sessionStore
}
2015-05-09 17:16:19 -04:00
if o . CookieRefresh >= o . CookieExpire {
msgs = append ( msgs , fmt . Sprintf (
"cookie_refresh (%s) must be less than " +
"cookie_expire (%s)" ,
o . CookieRefresh . String ( ) ,
o . CookieExpire . String ( ) ) )
}
2015-08-20 03:07:02 -07:00
if len ( o . GoogleGroups ) > 0 || o . GoogleAdminEmail != "" || o . GoogleServiceAccountJSON != "" {
if len ( o . GoogleGroups ) < 1 {
msgs = append ( msgs , "missing setting: google-group" )
}
if o . GoogleAdminEmail == "" {
msgs = append ( msgs , "missing setting: google-admin-email" )
}
if o . GoogleServiceAccountJSON == "" {
msgs = append ( msgs , "missing setting: google-service-account-json" )
}
}
2015-11-15 22:08:30 -05:00
msgs = parseSignatureKey ( o , msgs )
2016-07-19 20:51:25 +01:00
msgs = validateCookieName ( o , msgs )
2019-02-10 08:37:45 -08:00
msgs = setupLogger ( o , msgs )
2015-11-15 22:08:30 -05:00
2015-03-15 12:23:13 -04:00
if len ( msgs ) != 0 {
return fmt . Errorf ( "Invalid configuration:\n %s" ,
strings . Join ( msgs , "\n " ) )
}
2014-11-09 14:51:10 -05:00
return nil
}
2015-03-30 15:48:30 -04:00
func parseProviderInfo ( o * Options , msgs [ ] string ) [ ] string {
2015-07-25 16:27:49 -07:00
p := & providers . ProviderData {
Scope : o . Scope ,
ClientID : o . ClientID ,
ClientSecret : o . ClientSecret ,
ApprovalPrompt : o . ApprovalPrompt ,
}
2015-11-09 00:47:44 +01:00
p . LoginURL , msgs = parseURL ( o . LoginURL , "login" , msgs )
p . RedeemURL , msgs = parseURL ( o . RedeemURL , "redeem" , msgs )
p . ProfileURL , msgs = parseURL ( o . ProfileURL , "profile" , msgs )
p . ValidateURL , msgs = parseURL ( o . ValidateURL , "validate" , msgs )
2015-11-09 09:28:34 +01:00
p . ProtectedResource , msgs = parseURL ( o . ProtectedResource , "resource" , msgs )
2015-05-20 23:23:48 -04:00
2015-03-30 15:48:30 -04:00
o . provider = providers . New ( o . Provider , p )
2015-05-20 23:23:48 -04:00
switch p := o . provider . ( type ) {
2015-11-09 09:28:34 +01:00
case * providers . AzureProvider :
p . Configure ( o . AzureTenant )
2015-05-20 23:23:48 -04:00
case * providers . GitHubProvider :
p . SetOrgTeam ( o . GitHubOrg , o . GitHubTeam )
2015-08-20 03:07:02 -07:00
case * providers . GoogleProvider :
if o . GoogleServiceAccountJSON != "" {
file , err := os . Open ( o . GoogleServiceAccountJSON )
if err != nil {
msgs = append ( msgs , "invalid Google credentials file: " + o . GoogleServiceAccountJSON )
} else {
p . SetGroupRestriction ( o . GoogleGroups , o . GoogleAdminEmail , file )
}
}
2017-05-09 11:20:35 -07:00
case * providers . OIDCProvider :
2019-07-11 15:18:36 +01:00
p . AllowUnverifiedEmail = o . InsecureOIDCAllowUnverifiedEmail
2017-05-09 11:20:35 -07:00
if o . oidcVerifier == nil {
msgs = append ( msgs , "oidc provider requires an oidc issuer URL" )
} else {
p . Verifier = o . oidcVerifier
}
2019-03-20 06:44:51 -07:00
case * providers . LoginGovProvider :
p . AcrValues = o . AcrValues
p . PubJWKURL , msgs = parseURL ( o . PubJWKURL , "pubjwk" , msgs )
2019-03-20 15:15:47 -07:00
// JWT key can be supplied via env variable or file in the filesystem, but not both.
switch {
case o . JWTKey != "" && o . JWTKeyFile != "" :
msgs = append ( msgs , "cannot set both jwt-key and jwt-key-file options" )
case o . JWTKey == "" && o . JWTKeyFile == "" :
2019-03-20 06:44:51 -07:00
msgs = append ( msgs , "login.gov provider requires a private key for signing JWTs" )
2019-03-20 15:15:47 -07:00
case o . JWTKey != "" :
// The JWT Key is in the commandline argument
2019-03-20 06:44:51 -07:00
signKey , err := jwt . ParseRSAPrivateKeyFromPEM ( [ ] byte ( o . JWTKey ) )
if err != nil {
msgs = append ( msgs , "could not parse RSA Private Key PEM" )
} else {
p . JWTKey = signKey
}
2019-03-20 15:15:47 -07:00
case o . JWTKeyFile != "" :
// The JWT key is in the filesystem
keyData , err := ioutil . ReadFile ( o . JWTKeyFile )
if err != nil {
msgs = append ( msgs , "could not read key file: " + o . JWTKeyFile )
}
signKey , err := jwt . ParseRSAPrivateKeyFromPEM ( keyData )
if err != nil {
msgs = append ( msgs , "could not parse private key from PEM file:" + o . JWTKeyFile )
} else {
p . JWTKey = signKey
}
2019-03-20 06:44:51 -07:00
}
2015-05-20 23:23:48 -04:00
}
2015-03-30 15:48:30 -04:00
return msgs
}
2015-11-15 22:08:30 -05:00
func parseSignatureKey ( o * Options , msgs [ ] string ) [ ] string {
if o . SignatureKey == "" {
return msgs
}
components := strings . Split ( o . SignatureKey , ":" )
if len ( components ) != 2 {
return append ( msgs , "invalid signature hash:key spec: " +
o . SignatureKey )
}
algorithm , secretKey := components [ 0 ] , components [ 1 ]
2018-11-29 14:26:41 +00:00
var hash crypto . Hash
var err error
if hash , err = hmacauth . DigestNameToCryptoHash ( algorithm ) ; err != nil {
2015-11-15 22:08:30 -05:00
return append ( msgs , "unsupported signature hash algorithm: " +
o . SignatureKey )
}
2019-06-23 20:41:23 +01:00
o . signatureData = & SignatureData { hash : hash , key : secretKey }
2015-11-15 22:08:30 -05:00
return msgs
}
2016-06-20 07:17:39 -04:00
2019-05-01 10:00:54 -07:00
// parseJwtIssuers takes in an array of strings in the form of issuer=audience
2019-06-05 16:09:29 -07:00
// and parses to an array of jwtIssuer structs.
func parseJwtIssuers ( issuers [ ] string , msgs [ ] string ) ( [ ] jwtIssuer , [ ] string ) {
var parsedIssuers [ ] jwtIssuer
2019-05-01 10:00:54 -07:00
for _ , jwtVerifier := range issuers {
2019-01-17 12:49:14 -08:00
components := strings . Split ( jwtVerifier , "=" )
if len ( components ) < 2 {
2019-05-01 10:00:54 -07:00
msgs = append ( msgs , fmt . Sprintf ( "invalid jwt verifier uri=audience spec: %s" , jwtVerifier ) )
continue
2019-01-17 12:49:14 -08:00
}
uri , audience := components [ 0 ] , strings . Join ( components [ 1 : ] , "=" )
2019-06-05 16:09:29 -07:00
parsedIssuers = append ( parsedIssuers , jwtIssuer { issuerURI : uri , audience : audience } )
2019-01-17 12:49:14 -08:00
}
2019-05-01 10:00:54 -07:00
return parsedIssuers , msgs
}
2019-06-05 16:09:29 -07:00
// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
2019-05-01 10:00:54 -07:00
// a verifier for that issuer.
2019-06-05 16:09:29 -07:00
func newVerifierFromJwtIssuer ( jwtIssuer jwtIssuer ) ( * oidc . IDTokenVerifier , error ) {
2019-05-01 10:00:54 -07:00
config := & oidc . Config {
ClientID : jwtIssuer . audience ,
}
// Try as an OpenID Connect Provider first
var verifier * oidc . IDTokenVerifier
provider , err := oidc . NewProvider ( context . Background ( ) , jwtIssuer . issuerURI )
if err != nil {
// Try as JWKS URI
jwksURI := strings . TrimSuffix ( jwtIssuer . issuerURI , "/" ) + "/.well-known/jwks.json"
_ , err := http . NewRequest ( "GET" , jwksURI , nil )
if err != nil {
return nil , err
}
verifier = oidc . NewVerifier ( jwtIssuer . issuerURI , oidc . NewRemoteKeySet ( context . Background ( ) , jwksURI ) , config )
} else {
verifier = provider . Verifier ( config )
}
return verifier , nil
2019-01-17 12:49:14 -08:00
}
2016-07-19 20:51:25 +01:00
func validateCookieName ( o * Options , msgs [ ] string ) [ ] string {
cookie := & http . Cookie { Name : o . CookieName }
if cookie . String ( ) == "" {
return append ( msgs , fmt . Sprintf ( "invalid cookie name: %q" , o . CookieName ) )
}
return msgs
}
2016-06-20 07:17:39 -04:00
func addPadding ( secret string ) string {
padding := len ( secret ) % 4
switch padding {
case 1 :
return secret + "==="
case 2 :
return secret + "=="
case 3 :
return secret + "="
default :
return secret
}
}
// secretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary
func secretBytes ( secret string ) [ ] byte {
b , err := base64 . URLEncoding . DecodeString ( addPadding ( secret ) )
if err == nil {
return [ ] byte ( addPadding ( string ( b ) ) )
}
return [ ] byte ( secret )
}
2019-02-10 08:37:45 -08:00
func setupLogger ( o * Options , msgs [ ] string ) [ ] string {
// Setup the log file
if len ( o . LoggingFilename ) > 0 {
// Validate that the file/dir can be written
file , err := os . OpenFile ( o . LoggingFilename , os . O_WRONLY | os . O_CREATE , 0666 )
if err != nil {
if os . IsPermission ( err ) {
return append ( msgs , "unable to write to log file: " + o . LoggingFilename )
}
}
file . Close ( )
logger . Printf ( "Redirecting logging to file: %s" , o . LoggingFilename )
logWriter := & lumberjack . Logger {
Filename : o . LoggingFilename ,
MaxSize : o . LoggingMaxSize , // megabytes
MaxAge : o . LoggingMaxAge , // days
MaxBackups : o . LoggingMaxBackups ,
LocalTime : o . LoggingLocalTime ,
Compress : o . LoggingCompress ,
}
logger . SetOutput ( logWriter )
}
// Supply a sanity warning to the logger if all logging is disabled
if ! o . StandardLogging && ! o . AuthLogging && ! o . RequestLogging {
logger . Print ( "Warning: Logging disabled. No further logs will be shown." )
}
// Pass configuration values to the standard logger
logger . SetStandardEnabled ( o . StandardLogging )
logger . SetAuthEnabled ( o . AuthLogging )
logger . SetReqEnabled ( o . RequestLogging )
logger . SetStandardTemplate ( o . StandardLoggingFormat )
logger . SetAuthTemplate ( o . AuthLoggingFormat )
logger . SetReqTemplate ( o . RequestLoggingFormat )
2019-06-22 09:39:46 +12:00
excludePaths := make ( [ ] string , 0 )
excludePaths = append ( excludePaths , strings . Split ( o . ExcludeLoggingPaths , "," ) ... )
2019-06-03 13:51:59 +12:00
if o . SilencePingLogging {
2019-06-22 09:39:46 +12:00
excludePaths = append ( excludePaths , o . PingPath )
2019-06-03 13:51:59 +12:00
}
2019-06-22 09:39:46 +12:00
logger . SetExcludePaths ( excludePaths )
2019-02-10 08:37:45 -08:00
if ! o . LoggingLocalTime {
logger . SetFlags ( logger . Flags ( ) | logger . LUTC )
}
return msgs
}