2014-11-09 21:51:10 +02:00
package main
import (
2015-11-16 05:08:30 +02:00
"crypto"
2016-06-20 13:17:39 +02:00
"encoding/base64"
2014-11-09 21:51:10 +02:00
"fmt"
2016-07-19 21:51:25 +02:00
"net/http"
2014-11-09 21:51:10 +02:00
"net/url"
2015-08-20 12:07:02 +02:00
"os"
2015-01-12 11:18:41 +02:00
"regexp"
2015-03-15 18:23:13 +02:00
"strings"
2015-01-19 18:10:37 +02:00
"time"
2015-03-30 21:48:30 +02:00
2015-11-16 05:08:30 +02:00
"github.com/18F/hmacauth"
2015-05-21 08:50:21 +02:00
"github.com/bitly/oauth2_proxy/providers"
2014-11-09 21:51:10 +02:00
)
// Configuration Options that can be set by Command Line Flag, or Config File
type Options struct {
2015-05-30 00:47:40 +02:00
ProxyPrefix string ` flag:"proxy-prefix" cfg:"proxy-prefix" `
2015-03-17 21:15:15 +02:00
HttpAddress string ` flag:"http-address" cfg:"http_address" `
2015-06-08 03:51:47 +02:00
HttpsAddress string ` flag:"https-address" cfg:"https_address" `
2015-11-09 01:47:44 +02:00
RedirectURL string ` flag:"redirect-url" cfg:"redirect_url" `
2015-05-21 08:50:21 +02: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" `
2015-06-08 03:51:47 +02:00
TLSCertFile string ` flag:"tls-cert" cfg:"tls_cert_file" `
TLSKeyFile string ` flag:"tls-key" cfg:"tls_key_file" `
2015-03-17 21:15:15 +02:00
2015-08-20 12:07:02 +02:00
AuthenticatedEmailsFile string ` flag:"authenticated-emails-file" cfg:"authenticated_emails_file" `
2015-11-09 10:28:34 +02:00
AzureTenant string ` flag:"azure-tenant" cfg:"azure_tenant" `
2015-08-20 12:07:02 +02:00
EmailDomains [ ] string ` flag:"email-domain" cfg:"email_domains" `
GitHubOrg string ` flag:"github-org" cfg:"github_org" `
GitHubTeam string ` flag:"github-team" cfg:"github_team" `
GoogleGroups [ ] string ` flag:"google-group" cfg:"google_group" `
GoogleAdminEmail string ` flag:"google-admin-email" cfg:"google_admin_email" `
GoogleServiceAccountJSON string ` flag:"google-service-account-json" cfg:"google_service_account_json" `
HtpasswdFile string ` flag:"htpasswd-file" cfg:"htpasswd_file" `
DisplayHtpasswdForm bool ` flag:"display-htpasswd-form" cfg:"display_htpasswd_form" `
CustomTemplatesDir string ` flag:"custom-templates-dir" cfg:"custom_templates_dir" `
2016-06-19 05:53:42 +02:00
Footer string ` flag:"footer" cfg:"footer" `
2015-03-17 21:15:15 +02:00
2015-06-08 05:52:28 +02:00
CookieName string ` flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME" `
CookieSecret string ` flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET" `
CookieDomain string ` flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN" `
CookieExpire time . Duration ` flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE" `
CookieRefresh time . Duration ` flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH" `
CookieSecure bool ` flag:"cookie-secure" cfg:"cookie_secure" `
CookieHttpOnly bool ` flag:"cookie-httponly" cfg:"cookie_httponly" `
2015-03-17 21:15:15 +02:00
2015-11-11 02:42:35 +02:00
Upstreams [ ] string ` flag:"upstream" cfg:"upstreams" `
SkipAuthRegex [ ] string ` flag:"skip-auth-regex" cfg:"skip_auth_regex" `
PassBasicAuth bool ` flag:"pass-basic-auth" cfg:"pass_basic_auth" `
BasicAuthPassword string ` flag:"basic-auth-password" cfg:"basic_auth_password" `
PassAccessToken bool ` flag:"pass-access-token" cfg:"pass_access_token" `
PassHostHeader bool ` flag:"pass-host-header" cfg:"pass_host_header" `
SkipProviderButton bool ` flag:"skip-provider-button" cfg:"skip_provider_button" `
2014-11-09 21:51:10 +02:00
2015-03-30 21:48:30 +02:00
// These options allow for other providers besides Google, with
// potential overrides.
2015-11-09 10:28:34 +02:00
Provider string ` flag:"provider" cfg:"provider" `
LoginURL string ` flag:"login-url" cfg:"login_url" `
RedeemURL string ` flag:"redeem-url" cfg:"redeem_url" `
ProfileURL string ` flag:"profile-url" cfg:"profile_url" `
ProtectedResource string ` flag:"resource" cfg:"resource" `
ValidateURL string ` flag:"validate-url" cfg:"validate_url" `
Scope string ` flag:"scope" cfg:"scope" `
ApprovalPrompt string ` flag:"approval-prompt" cfg:"approval_prompt" `
2015-03-30 21:48:30 +02:00
2015-03-19 22:37:16 +02:00
RequestLogging bool ` flag:"request-logging" cfg:"request_logging" `
2016-02-24 15:23:31 +02:00
SignatureKey string ` flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY" `
2015-11-16 05:08:30 +02:00
2014-11-09 21:51:10 +02:00
// internal values that are set after config validation
2015-11-09 01:47:44 +02:00
redirectURL * url . URL
proxyURLs [ ] * url . URL
2015-01-12 11:18:41 +02:00
CompiledRegex [ ] * regexp . Regexp
2015-03-30 21:48:30 +02:00
provider providers . Provider
2015-11-16 05:08:30 +02:00
signatureData * SignatureData
}
type SignatureData struct {
hash crypto . Hash
key string
2014-11-09 21:51:10 +02:00
}
func NewOptions ( ) * Options {
2014-11-10 05:21:46 +02:00
return & Options {
2015-05-30 00:47:40 +02:00
ProxyPrefix : "/oauth2" ,
2014-12-09 22:38:57 +02:00
HttpAddress : "127.0.0.1:4180" ,
2015-06-08 03:51:47 +02:00
HttpsAddress : ":443" ,
2014-12-09 22:38:57 +02:00
DisplayHtpasswdForm : true ,
2015-06-08 05:52:28 +02:00
CookieName : "_oauth2_proxy" ,
2015-03-18 05:13:45 +02:00
CookieSecure : true ,
2015-01-19 17:52:18 +02:00
CookieHttpOnly : true ,
2014-12-09 22:38:57 +02:00
CookieExpire : time . Duration ( 168 ) * time . Hour ,
2015-05-08 16:00:57 +02:00
CookieRefresh : time . Duration ( 0 ) ,
2015-03-17 21:15:15 +02:00
PassBasicAuth : true ,
2015-04-03 02:57:17 +02:00
PassAccessToken : false ,
2015-03-17 21:15:15 +02:00
PassHostHeader : true ,
2015-11-11 02:42:35 +02:00
SkipProviderButton : false ,
2015-07-26 01:27:49 +02:00
ApprovalPrompt : "force" ,
2015-03-19 22:37:16 +02:00
RequestLogging : true ,
2014-11-10 05:21:46 +02:00
}
2014-11-09 21:51:10 +02:00
}
2015-11-09 01:47:44 +02:00
func parseURL ( to_parse string , urltype string , msgs [ ] string ) ( * url . URL , [ ] string ) {
2015-03-30 21:48:30 +02:00
parsed , err := url . Parse ( to_parse )
if err != nil {
return nil , append ( msgs , fmt . Sprintf (
"error parsing %s-url=%q %s" , urltype , to_parse , err ) )
}
return parsed , msgs
}
2014-11-09 21:51:10 +02:00
func ( o * Options ) Validate ( ) error {
2015-03-15 18:23:13 +02:00
msgs := make ( [ ] string , 0 )
2014-11-09 21:51:10 +02:00
if len ( o . Upstreams ) < 1 {
2015-03-15 18:23:13 +02:00
msgs = append ( msgs , "missing setting: upstream" )
2014-11-09 21:51:10 +02:00
}
if o . CookieSecret == "" {
2015-03-15 18:23:13 +02:00
msgs = append ( msgs , "missing setting: cookie-secret" )
2014-11-09 21:51:10 +02:00
}
if o . ClientID == "" {
2015-03-15 18:23:13 +02:00
msgs = append ( msgs , "missing setting: client-id" )
2014-11-09 21:51:10 +02:00
}
if o . ClientSecret == "" {
2015-03-15 18:23:13 +02:00
msgs = append ( msgs , "missing setting: client-secret" )
2014-11-09 21:51:10 +02:00
}
2015-07-24 22:09:33 +02:00
if o . AuthenticatedEmailsFile == "" && len ( o . EmailDomains ) == 0 && o . HtpasswdFile == "" {
msgs = append ( msgs , "missing setting for email validation: email-domain or authenticated-emails-file required.\n use email-domain=* to authorize all email addresses" )
}
2014-11-09 21:51:10 +02:00
2015-11-09 01:47:44 +02:00
o . redirectURL , msgs = parseURL ( o . RedirectURL , "redirect" , msgs )
2014-11-09 21:51:10 +02:00
for _ , u := range o . Upstreams {
2015-11-09 01:47:44 +02:00
upstreamURL , err := url . Parse ( u )
2014-11-09 21:51:10 +02:00
if err != nil {
2015-03-15 18:23:13 +02:00
msgs = append ( msgs , fmt . Sprintf (
"error parsing upstream=%q %s" ,
2015-11-09 01:47:44 +02:00
upstreamURL , err ) )
2014-11-09 21:51:10 +02:00
}
2015-11-09 01:47:44 +02:00
if upstreamURL . Path == "" {
upstreamURL . Path = "/"
2014-11-09 21:51:10 +02:00
}
2015-11-09 01:47:44 +02:00
o . proxyURLs = append ( o . proxyURLs , upstreamURL )
2014-11-09 21:51:10 +02:00
}
2015-01-12 11:18:41 +02:00
for _ , u := range o . SkipAuthRegex {
CompiledRegex , err := regexp . Compile ( u )
if err != nil {
2015-03-15 18:23:13 +02:00
msgs = append ( msgs , fmt . Sprintf (
"error compiling regex=%q %s" , u , err ) )
2015-01-12 11:18:41 +02:00
}
o . CompiledRegex = append ( o . CompiledRegex , CompiledRegex )
}
2015-03-30 21:48:30 +02:00
msgs = parseProviderInfo ( o , msgs )
2015-01-12 11:18:41 +02:00
2015-05-09 23:31:13 +02:00
if o . PassAccessToken || ( o . CookieRefresh != time . Duration ( 0 ) ) {
2015-04-05 15:43:40 +02:00
valid_cookie_secret_size := false
for _ , i := range [ ] int { 16 , 24 , 32 } {
2016-06-20 13:17:39 +02:00
if len ( secretBytes ( o . CookieSecret ) ) == i {
2015-04-05 15:43:40 +02:00
valid_cookie_secret_size = true
}
}
2016-06-20 13:17:39 +02:00
var decoded bool
if string ( secretBytes ( o . CookieSecret ) ) != o . CookieSecret {
decoded = true
}
2015-04-05 15:43:40 +02:00
if valid_cookie_secret_size == false {
2016-06-20 13:17:39 +02:00
var suffix string
if decoded {
suffix = fmt . Sprintf ( " note: cookie secret was base64 decoded from %q" , o . CookieSecret )
}
2015-04-05 15:43:40 +02:00
msgs = append ( msgs , fmt . Sprintf (
"cookie_secret must be 16, 24, or 32 bytes " +
"to create an AES cipher when " +
2015-05-09 23:31:13 +02:00
"pass_access_token == true or " +
2016-06-20 13:17:39 +02:00
"cookie_refresh != 0, but is %d bytes.%s" ,
len ( secretBytes ( o . CookieSecret ) ) , suffix ) )
2015-04-05 15:43:40 +02:00
}
}
2015-05-09 23:16:19 +02: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 12:07:02 +02: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-16 05:08:30 +02:00
msgs = parseSignatureKey ( o , msgs )
2016-07-19 21:51:25 +02:00
msgs = validateCookieName ( o , msgs )
2015-11-16 05:08:30 +02:00
2015-03-15 18:23:13 +02:00
if len ( msgs ) != 0 {
return fmt . Errorf ( "Invalid configuration:\n %s" ,
strings . Join ( msgs , "\n " ) )
}
2014-11-09 21:51:10 +02:00
return nil
}
2015-03-30 21:48:30 +02:00
func parseProviderInfo ( o * Options , msgs [ ] string ) [ ] string {
2015-07-26 01:27:49 +02:00
p := & providers . ProviderData {
Scope : o . Scope ,
ClientID : o . ClientID ,
ClientSecret : o . ClientSecret ,
ApprovalPrompt : o . ApprovalPrompt ,
}
2015-11-09 01:47:44 +02: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 10:28:34 +02:00
p . ProtectedResource , msgs = parseURL ( o . ProtectedResource , "resource" , msgs )
2015-05-21 05:23:48 +02:00
2015-03-30 21:48:30 +02:00
o . provider = providers . New ( o . Provider , p )
2015-05-21 05:23:48 +02:00
switch p := o . provider . ( type ) {
2015-11-09 10:28:34 +02:00
case * providers . AzureProvider :
p . Configure ( o . AzureTenant )
2015-05-21 05:23:48 +02:00
case * providers . GitHubProvider :
p . SetOrgTeam ( o . GitHubOrg , o . GitHubTeam )
2015-08-20 12:07:02 +02: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 )
}
}
2015-05-21 05:23:48 +02:00
}
2015-03-30 21:48:30 +02:00
return msgs
}
2015-11-16 05:08:30 +02: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 ]
if hash , err := hmacauth . DigestNameToCryptoHash ( algorithm ) ; err != nil {
return append ( msgs , "unsupported signature hash algorithm: " +
o . SignatureKey )
} else {
o . signatureData = & SignatureData { hash , secretKey }
}
return msgs
}
2016-06-20 13:17:39 +02:00
2016-07-19 21:51:25 +02: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 13:17:39 +02: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 )
}