2020-05-26 20:56:10 +02:00
package options
import (
"fmt"
"net/url"
"strconv"
2020-08-31 15:05:52 +02:00
"strings"
2020-05-26 20:56:10 +02:00
"time"
2020-09-29 18:44:42 +02:00
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
2020-07-12 17:47:25 +02:00
"github.com/spf13/pflag"
2020-05-26 20:56:10 +02:00
)
type LegacyOptions struct {
// Legacy options related to upstream servers
2020-07-12 17:47:25 +02:00
LegacyUpstreams LegacyUpstreams ` cfg:",squash" `
2020-05-26 20:56:10 +02:00
2020-07-29 21:08:46 +02:00
// Legacy options for injecting request/response headers
LegacyHeaders LegacyHeaders ` cfg:",squash" `
2021-02-14 20:47:15 +02:00
// Legacy options for the server address and TLS
LegacyServer LegacyServer ` cfg:",squash" `
2020-05-26 20:56:10 +02:00
Options Options ` cfg:",squash" `
}
func NewLegacyOptions ( ) * LegacyOptions {
return & LegacyOptions {
2020-07-12 17:47:25 +02:00
LegacyUpstreams : LegacyUpstreams {
PassHostHeader : true ,
ProxyWebSockets : true ,
2020-11-19 12:35:04 +02:00
FlushInterval : DefaultUpstreamFlushInterval ,
2020-07-12 17:47:25 +02:00
} ,
2020-05-26 20:56:10 +02:00
2020-07-29 21:08:46 +02:00
LegacyHeaders : LegacyHeaders {
2020-11-07 22:33:37 +02:00
PassBasicAuth : true ,
PassUserHeaders : true ,
SkipAuthStripHeaders : true ,
2020-07-29 21:08:46 +02:00
} ,
2021-02-14 20:47:15 +02:00
LegacyServer : LegacyServer {
HTTPAddress : "127.0.0.1:4180" ,
HTTPSAddress : ":443" ,
} ,
2020-05-26 20:56:10 +02:00
Options : * NewOptions ( ) ,
}
}
2020-11-09 22:17:43 +02:00
func NewLegacyFlagSet ( ) * pflag . FlagSet {
flagSet := NewFlagSet ( )
flagSet . AddFlagSet ( legacyUpstreamsFlagSet ( ) )
flagSet . AddFlagSet ( legacyHeadersFlagSet ( ) )
2021-02-14 20:47:15 +02:00
flagSet . AddFlagSet ( legacyServerFlagset ( ) )
2020-11-09 22:17:43 +02:00
return flagSet
}
2020-05-26 20:56:10 +02:00
func ( l * LegacyOptions ) ToOptions ( ) ( * Options , error ) {
2020-07-12 17:47:25 +02:00
upstreams , err := l . LegacyUpstreams . convert ( )
2020-05-26 20:56:10 +02:00
if err != nil {
return nil , fmt . Errorf ( "error converting upstreams: %v" , err )
}
l . Options . UpstreamServers = upstreams
2020-07-29 21:08:46 +02:00
l . Options . InjectRequestHeaders , l . Options . InjectResponseHeaders = l . LegacyHeaders . convert ( )
2021-02-14 20:47:15 +02:00
l . Options . Server , l . Options . MetricsServer = l . LegacyServer . convert ( )
2020-05-26 20:56:10 +02:00
return & l . Options , nil
}
2020-07-12 17:47:25 +02:00
type LegacyUpstreams struct {
FlushInterval time . Duration ` flag:"flush-interval" cfg:"flush_interval" `
PassHostHeader bool ` flag:"pass-host-header" cfg:"pass_host_header" `
ProxyWebSockets bool ` flag:"proxy-websockets" cfg:"proxy_websockets" `
SSLUpstreamInsecureSkipVerify bool ` flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify" `
Upstreams [ ] string ` flag:"upstream" cfg:"upstreams" `
}
func legacyUpstreamsFlagSet ( ) * pflag . FlagSet {
flagSet := pflag . NewFlagSet ( "upstreams" , pflag . ExitOnError )
2020-11-19 12:35:04 +02:00
flagSet . Duration ( "flush-interval" , DefaultUpstreamFlushInterval , "period between response flushing when streaming responses" )
2020-07-12 17:47:25 +02:00
flagSet . Bool ( "pass-host-header" , true , "pass the request Host Header to upstream" )
flagSet . Bool ( "proxy-websockets" , true , "enables WebSocket proxying" )
flagSet . Bool ( "ssl-upstream-insecure-skip-verify" , false , "skip validation of certificates presented when using HTTPS upstreams" )
flagSet . StringSlice ( "upstream" , [ ] string { } , "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path" )
return flagSet
}
func ( l * LegacyUpstreams ) convert ( ) ( Upstreams , error ) {
2020-05-26 20:56:10 +02:00
upstreams := Upstreams { }
2020-07-12 17:47:25 +02:00
for _ , upstreamString := range l . Upstreams {
2020-05-26 20:56:10 +02:00
u , err := url . Parse ( upstreamString )
if err != nil {
return nil , fmt . Errorf ( "could not parse upstream %q: %v" , upstreamString , err )
}
if u . Path == "" {
u . Path = "/"
}
2020-11-12 01:02:00 +02:00
flushInterval := Duration ( l . FlushInterval )
2020-05-26 20:56:10 +02:00
upstream := Upstream {
ID : u . Path ,
Path : u . Path ,
URI : upstreamString ,
2020-07-12 17:47:25 +02:00
InsecureSkipTLSVerify : l . SSLUpstreamInsecureSkipVerify ,
2020-07-19 15:00:52 +02:00
PassHostHeader : & l . PassHostHeader ,
ProxyWebSockets : & l . ProxyWebSockets ,
2020-11-12 01:02:00 +02:00
FlushInterval : & flushInterval ,
2020-05-26 20:56:10 +02:00
}
switch u . Scheme {
case "file" :
if u . Fragment != "" {
upstream . ID = u . Fragment
upstream . Path = u . Fragment
2020-08-31 15:05:52 +02:00
// Trim the fragment from the end of the URI
upstream . URI = strings . SplitN ( upstreamString , "#" , 2 ) [ 0 ]
2020-05-26 20:56:10 +02:00
}
case "static" :
responseCode , err := strconv . Atoi ( u . Host )
if err != nil {
2020-08-10 12:44:08 +02:00
logger . Errorf ( "unable to convert %q to int, use default \"200\"" , u . Host )
2020-05-26 20:56:10 +02:00
responseCode = 200
}
upstream . Static = true
upstream . StaticCode = & responseCode
2020-08-31 12:22:10 +02:00
// This is not allowed to be empty and must be unique
2020-05-26 20:56:10 +02:00
upstream . ID = upstreamString
2020-08-31 12:22:10 +02:00
// We only support the root path in the legacy config
upstream . Path = "/"
2020-05-26 20:56:10 +02:00
// Force defaults compatible with static responses
upstream . URI = ""
upstream . InsecureSkipTLSVerify = false
2020-07-19 15:00:52 +02:00
upstream . PassHostHeader = nil
upstream . ProxyWebSockets = nil
2020-08-31 12:22:10 +02:00
upstream . FlushInterval = nil
2020-05-26 20:56:10 +02:00
}
upstreams = append ( upstreams , upstream )
}
return upstreams , nil
}
2020-07-29 21:08:46 +02:00
type LegacyHeaders struct {
PassBasicAuth bool ` flag:"pass-basic-auth" cfg:"pass_basic_auth" `
PassAccessToken bool ` flag:"pass-access-token" cfg:"pass_access_token" `
PassUserHeaders bool ` flag:"pass-user-headers" cfg:"pass_user_headers" `
PassAuthorization bool ` flag:"pass-authorization-header" cfg:"pass_authorization_header" `
SetBasicAuth bool ` flag:"set-basic-auth" cfg:"set_basic_auth" `
SetXAuthRequest bool ` flag:"set-xauthrequest" cfg:"set_xauthrequest" `
SetAuthorization bool ` flag:"set-authorization-header" cfg:"set_authorization_header" `
PreferEmailToUser bool ` flag:"prefer-email-to-user" cfg:"prefer_email_to_user" `
BasicAuthPassword string ` flag:"basic-auth-password" cfg:"basic_auth_password" `
SkipAuthStripHeaders bool ` flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers" `
}
func legacyHeadersFlagSet ( ) * pflag . FlagSet {
flagSet := pflag . NewFlagSet ( "headers" , pflag . ExitOnError )
flagSet . Bool ( "pass-basic-auth" , true , "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream" )
flagSet . Bool ( "pass-access-token" , false , "pass OAuth access_token to upstream via X-Forwarded-Access-Token header" )
flagSet . Bool ( "pass-user-headers" , true , "pass X-Forwarded-User and X-Forwarded-Email information to upstream" )
flagSet . Bool ( "pass-authorization-header" , false , "pass the Authorization Header to upstream" )
flagSet . Bool ( "set-basic-auth" , false , "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)" )
flagSet . Bool ( "set-xauthrequest" , false , "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)" )
flagSet . Bool ( "set-authorization-header" , false , "set Authorization response headers (useful in Nginx auth_request mode)" )
flagSet . Bool ( "prefer-email-to-user" , false , "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers" )
flagSet . String ( "basic-auth-password" , "" , "the password to set when passing the HTTP Basic Auth header" )
2020-11-07 21:19:30 +02:00
flagSet . Bool ( "skip-auth-strip-headers" , true , "strips X-Forwarded-* style authentication headers & Authorization header if they would be set by oauth2-proxy" )
2020-07-29 21:08:46 +02:00
return flagSet
}
// convert takes the legacy request/response headers and converts them to
// the new format for InjectRequestHeaders and InjectResponseHeaders
func ( l * LegacyHeaders ) convert ( ) ( [ ] Header , [ ] Header ) {
return l . getRequestHeaders ( ) , l . getResponseHeaders ( )
}
func ( l * LegacyHeaders ) getRequestHeaders ( ) [ ] Header {
requestHeaders := [ ] Header { }
if l . PassBasicAuth && l . BasicAuthPassword != "" {
requestHeaders = append ( requestHeaders , getBasicAuthHeader ( l . PreferEmailToUser , l . BasicAuthPassword ) )
}
// In the old implementation, PassUserHeaders is a subset of PassBasicAuth
if l . PassBasicAuth || l . PassUserHeaders {
requestHeaders = append ( requestHeaders , getPassUserHeaders ( l . PreferEmailToUser ) ... )
requestHeaders = append ( requestHeaders , getPreferredUsernameHeader ( ) )
}
if l . PassAccessToken {
requestHeaders = append ( requestHeaders , getPassAccessTokenHeader ( ) )
}
if l . PassAuthorization {
requestHeaders = append ( requestHeaders , getAuthorizationHeader ( ) )
}
for i := range requestHeaders {
requestHeaders [ i ] . PreserveRequestValue = ! l . SkipAuthStripHeaders
}
return requestHeaders
}
func ( l * LegacyHeaders ) getResponseHeaders ( ) [ ] Header {
responseHeaders := [ ] Header { }
if l . SetXAuthRequest {
2020-11-07 22:34:27 +02:00
responseHeaders = append ( responseHeaders , getXAuthRequestHeaders ( ) ... )
if l . PassAccessToken {
responseHeaders = append ( responseHeaders , getXAuthRequestAccessTokenHeader ( ) )
}
2020-07-29 21:08:46 +02:00
}
if l . SetBasicAuth {
responseHeaders = append ( responseHeaders , getBasicAuthHeader ( l . PreferEmailToUser , l . BasicAuthPassword ) )
}
if l . SetAuthorization {
responseHeaders = append ( responseHeaders , getAuthorizationHeader ( ) )
}
return responseHeaders
}
func getBasicAuthHeader ( preferEmailToUser bool , basicAuthPassword string ) Header {
claim := "user"
if preferEmailToUser {
claim = "email"
}
return Header {
Name : "Authorization" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
2020-11-19 22:06:43 +02:00
Claim : claim ,
Prefix : "Basic " ,
2020-07-29 21:08:46 +02:00
BasicAuthPassword : & SecretSource {
2020-11-19 21:58:50 +02:00
Value : [ ] byte ( basicAuthPassword ) ,
2020-07-29 21:08:46 +02:00
} ,
} ,
} ,
} ,
}
}
func getPassUserHeaders ( preferEmailToUser bool ) [ ] Header {
headers := [ ] Header {
{
Name : "X-Forwarded-Groups" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "groups" ,
} ,
} ,
} ,
} ,
}
if preferEmailToUser {
return append ( headers ,
Header {
Name : "X-Forwarded-User" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
)
}
return append ( headers ,
Header {
Name : "X-Forwarded-User" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
Header {
Name : "X-Forwarded-Email" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
)
}
func getPassAccessTokenHeader ( ) Header {
return Header {
Name : "X-Forwarded-Access-Token" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "access_token" ,
} ,
} ,
} ,
}
}
func getAuthorizationHeader ( ) Header {
return Header {
Name : "Authorization" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "id_token" ,
Prefix : "Bearer " ,
} ,
} ,
} ,
}
}
func getPreferredUsernameHeader ( ) Header {
return Header {
Name : "X-Forwarded-Preferred-Username" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "preferred_username" ,
} ,
} ,
} ,
}
}
2020-11-07 22:34:27 +02:00
func getXAuthRequestHeaders ( ) [ ] Header {
2020-07-29 21:08:46 +02:00
headers := [ ] Header {
{
Name : "X-Auth-Request-User" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "user" ,
} ,
} ,
} ,
} ,
{
Name : "X-Auth-Request-Email" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "email" ,
} ,
} ,
} ,
} ,
{
2020-11-07 22:34:27 +02:00
Name : "X-Auth-Request-Preferred-Username" ,
2020-07-29 21:08:46 +02:00
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
2020-11-07 22:34:27 +02:00
Claim : "preferred_username" ,
2020-07-29 21:08:46 +02:00
} ,
} ,
} ,
} ,
2020-11-07 22:34:27 +02:00
{
Name : "X-Auth-Request-Groups" ,
2020-07-29 21:08:46 +02:00
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
2020-11-07 22:34:27 +02:00
Claim : "groups" ,
2020-07-29 21:08:46 +02:00
} ,
} ,
} ,
2020-11-07 22:34:27 +02:00
} ,
2020-07-29 21:08:46 +02:00
}
return headers
}
2020-11-07 22:34:27 +02:00
func getXAuthRequestAccessTokenHeader ( ) Header {
return Header {
Name : "X-Auth-Request-Access-Token" ,
Values : [ ] HeaderValue {
{
ClaimSource : & ClaimSource {
Claim : "access_token" ,
} ,
} ,
} ,
}
}
2021-02-14 20:47:15 +02:00
type LegacyServer struct {
2021-02-19 13:56:20 +02:00
MetricsAddress string ` flag:"metrics-address" cfg:"metrics_address" `
MetricsSecureAddress string ` flag:"metrics-secure-address" cfg:"metrics_address" `
MetricsTLSCertFile string ` flag:"metrics-tls-cert-file" cfg:"tls_cert_file" `
MetricsTLSKeyFile string ` flag:"metrics-tls-key-file" cfg:"tls_key_file" `
HTTPAddress string ` flag:"http-address" cfg:"http_address" `
HTTPSAddress string ` flag:"https-address" cfg:"https_address" `
TLSCertFile string ` flag:"tls-cert-file" cfg:"tls_cert_file" `
TLSKeyFile string ` flag:"tls-key-file" cfg:"tls_key_file" `
2021-02-14 20:47:15 +02:00
}
func legacyServerFlagset ( ) * pflag . FlagSet {
flagSet := pflag . NewFlagSet ( "server" , pflag . ExitOnError )
flagSet . String ( "metrics-address" , "" , "the address /metrics will be served on (e.g. \":9100\")" )
2021-02-19 13:56:20 +02:00
flagSet . String ( "metrics-secure-address" , "" , "the address /metrics will be served on for HTTPS clients (e.g. \":9100\")" )
flagSet . String ( "metrics-tls-cert-file" , "" , "path to certificate file for secure metrics server" )
flagSet . String ( "metrics-tls-key-file" , "" , "path to private key file for secure metrics server" )
2021-02-14 20:47:15 +02:00
flagSet . String ( "http-address" , "127.0.0.1:4180" , "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients" )
flagSet . String ( "https-address" , ":443" , "<addr>:<port> to listen on for HTTPS clients" )
flagSet . String ( "tls-cert-file" , "" , "path to certificate file" )
flagSet . String ( "tls-key-file" , "" , "path to private key file" )
return flagSet
}
func ( l LegacyServer ) convert ( ) ( Server , Server ) {
appServer := Server {
BindAddress : l . HTTPAddress ,
SecureBindAddress : l . HTTPSAddress ,
}
if l . TLSKeyFile != "" || l . TLSCertFile != "" {
appServer . TLS = & TLS {
Key : & SecretSource {
FromFile : l . TLSKeyFile ,
} ,
Cert : & SecretSource {
FromFile : l . TLSCertFile ,
} ,
}
// Preserve backwards compatibility, only run one server
appServer . BindAddress = ""
} else {
// Disable the HTTPS server if there's no certificates.
// This preserves backwards compatibility.
appServer . SecureBindAddress = ""
}
metricsServer := Server {
2021-02-19 13:56:20 +02:00
BindAddress : l . MetricsAddress ,
SecureBindAddress : l . MetricsSecureAddress ,
}
if l . MetricsTLSKeyFile != "" || l . MetricsTLSCertFile != "" {
metricsServer . TLS = & TLS {
Key : & SecretSource {
FromFile : l . MetricsTLSKeyFile ,
} ,
Cert : & SecretSource {
FromFile : l . MetricsTLSCertFile ,
} ,
}
2021-02-14 20:47:15 +02:00
}
return appServer , metricsServer
}