mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-02-03 13:21:51 +02:00
Multiple providers in alpha config (#947)
* Initial commit of multiple provider logic: 1. Created new provider options. 2. Created legacy provider options and conversion options. 3. Added Providers to alpha Options. 4. Started Validation migration of multiple providers 5. Tests. * fixed lint issues * additional lint fixes * Nits and alterations based on CR: manliy splitting large providers validation function and adding comments to provider options * fixed typo * removed weird : file * small CR changes * Removed GoogleGroups validation due to new allowed-groups (including tests). Added line in CHANGELOG * Update pkg/apis/options/providers.go Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Update pkg/apis/options/providers.go Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> * Update pkg/apis/options/providers.go Co-authored-by: Nick Meves <nick.meves@greenhouse.io> * Initial commit of multiple provider logic: 1. Created new provider options. 2. Created legacy provider options and conversion options. 3. Added Providers to alpha Options. 4. Started Validation migration of multiple providers 5. Tests. * fixed lint issues * additional lint fixes * Nits and alterations based on CR: manliy splitting large providers validation function and adding comments to provider options * small CR changes * auto generates alpha_config.md * rebase (mainly service alpha options related conflicts) * removed : * Nits and alterations based on CR: manliy splitting large providers validation function and adding comments to provider options * small CR changes * Removed GoogleGroups validation due to new allowed-groups (including tests). Added line in CHANGELOG * "cntd. rebase" * ran make generate again * last conflicts * removed duplicate client id validation * 1. Removed provider prefixes 2. altered optionsWithNilProvider logic 3. altered default provider logic 4. moved change in CHANELOG to 7.0.0 * fixed TestGoogleGroupOptions test * ran make generate * moved CHANGLOG line to 7.1.1 * moved changelog comment to 7.1.2 (additional rebase) Co-authored-by: Yana Segal <yana.segal@nielsen.com> Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk> Co-authored-by: Nick Meves <nick.meves@greenhouse.io>
This commit is contained in:
parent
9d20b4e0e2
commit
42475c28f7
@ -8,6 +8,8 @@
|
||||
|
||||
## Changes since v7.1.2
|
||||
|
||||
- [#947](https://github.com/oauth2-proxy/oauth2-proxy/pull/947) Multiple provider ingestion and validation in alpha options (first stage: [#926](https://github.com/oauth2-proxy/oauth2-proxy/issues/926)) (@yanasega)
|
||||
|
||||
# V7.1.2
|
||||
|
||||
## Release Highlights
|
||||
|
@ -1,10 +1,5 @@
|
||||
http_address="0.0.0.0:4180"
|
||||
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
||||
provider="oidc"
|
||||
email_domains="example.com"
|
||||
oidc_issuer_url="http://dex.localhost:4190/dex"
|
||||
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
||||
client_id="oauth2-proxy"
|
||||
cookie_secure="false"
|
||||
|
||||
redirect_url="http://localhost:4180/oauth2/callback"
|
||||
|
@ -15,3 +15,9 @@ injectRequestHeaders:
|
||||
- name: X-Forwarded-Preferred-Username
|
||||
values:
|
||||
- claim: preferred_username
|
||||
providers:
|
||||
- provider: oidc
|
||||
clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
|
||||
clientID: oauth2-proxy
|
||||
oidcConfig:
|
||||
oidcIssuerURL: http://dex.localhost:4190/dex
|
||||
|
@ -119,6 +119,28 @@ They may change between releases without notice.
|
||||
| `injectResponseHeaders` | _[[]Header](#header)_ | InjectResponseHeaders is used to configure headers that should be added<br/>to responses from the proxy.<br/>This is typically used when using the proxy as an external authentication<br/>provider in conjunction with another proxy such as NGINX and its<br/>auth_request module.<br/>Headers may source values from either the authenticated user's session<br/>or from a static secret value. |
|
||||
| `server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.<br/>You may choose to run both HTTP and HTTPS servers simultaneously.<br/>This can be done by setting the BindAddress and the SecureBindAddress simultaneously.<br/>To use the secure server you must configure a TLS certificate and key. |
|
||||
| `metricsServer` | _[Server](#server)_ | MetricsServer is used to configure the HTTP(S) server for metrics.<br/>You may choose to run both HTTP and HTTPS servers simultaneously.<br/>This can be done by setting the BindAddress and the SecureBindAddress simultaneously.<br/>To use the secure server you must configure a TLS certificate and key. |
|
||||
| `providers` | _[Providers](#providers)_ | Providers is used to configure multiple providers. |
|
||||
|
||||
### AzureOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `tenant` | _string_ | Tenant directs to a tenant-specific or common (tenant-independent) endpoint<br/>Default value is 'commmon' |
|
||||
|
||||
### BitbucketOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `team` | _string_ | Team sets restrict logins to members of this team |
|
||||
| `repository` | _string_ | Repository sets restrict logins to user with access to this repository |
|
||||
|
||||
### ClaimSource
|
||||
|
||||
@ -143,6 +165,43 @@ each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45
|
||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
|
||||
|
||||
### GitHubOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `org` | _string_ | Org sets restrict logins to members of this organisation |
|
||||
| `team` | _string_ | Team sets restrict logins to members of this team |
|
||||
| `repo` | _string_ | Repo sets restrict logins to collaborators of this repository |
|
||||
| `token` | _string_ | Token is the token to use when verifying repository collaborators<br/>it must have push access to the repository |
|
||||
| `users` | _[]string_ | Users allows users with these usernames to login<br/>even if they do not belong to the specified org and team or collaborators |
|
||||
|
||||
### GitLabOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `group` | _[]string_ | Group sets restrict logins to members of this group |
|
||||
| `projects` | _[]string_ | Projects restricts logins to members of any of these projects |
|
||||
|
||||
### GoogleOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `group` | _[]string_ | Groups sets restrict logins to members of this google group |
|
||||
| `adminEmail` | _string_ | AdminEmail is the google admin to impersonate for api calls |
|
||||
| `serviceAccountJson` | _string_ | ServiceAccountJSON is the path to the service account json credentials |
|
||||
|
||||
### Header
|
||||
|
||||
(**Appears on:** [AlphaOptions](#alphaoptions))
|
||||
@ -172,6 +231,88 @@ make up the header value
|
||||
| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the<br/>claim if it is non-empty. |
|
||||
| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.<br/>Note the value of claim will become the basic auth username and the<br/>basicAuthPassword will be used as the password value. |
|
||||
|
||||
### KeycloakOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `groups` | _[]string_ | Group enables to restrict login to members of indicated group |
|
||||
|
||||
### LoginGovOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `jwtKey` | _string_ | JWTKey is a private key in PEM format used to sign JWT, |
|
||||
| `jwtKeyFile` | _string_ | JWTKeyFile is a path to the private key file in PEM format used to sign the JWT |
|
||||
| `pubjwkURL` | _string_ | PubJWKURL is the JWK pubkey access endpoint |
|
||||
|
||||
### OIDCOptions
|
||||
|
||||
(**Appears on:** [Provider](#provider))
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `issuerURL` | _string_ | IssuerURL is the OpenID Connect issuer URL<br/>eg: https://accounts.google.com |
|
||||
| `insecureAllowUnverifiedEmail` | _bool_ | InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified<br/>default set to 'false' |
|
||||
| `insecureSkipIssuerVerification` | _bool_ | InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL<br/>default set to 'false' |
|
||||
| `skipDiscovery` | _bool_ | SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints<br/>default set to 'false' |
|
||||
| `jwksURL` | _string_ | JwksURL is the OpenID Connect JWKS URL<br/>eg: https://www.googleapis.com/oauth2/v3/certs |
|
||||
| `emailClaim` | _string_ | EmailClaim indicates which claim contains the user email,<br/>default set to 'email' |
|
||||
| `groupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups<br/>default set to 'groups' |
|
||||
| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID<br/>default set to 'email' |
|
||||
|
||||
### Provider
|
||||
|
||||
(**Appears on:** [Providers](#providers))
|
||||
|
||||
Provider holds all configuration for a single provider
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientID` | _string_ | ClientID is the OAuth Client ID that is defined in the provider<br/>This value is required for all providers. |
|
||||
| `clientSecret` | _string_ | ClientSecret is the OAuth Client Secret that is defined in the provider<br/>This value is required for all providers. |
|
||||
| `clientSecretFile` | _string_ | ClientSecretFile is the name of the file<br/>containing the OAuth Client Secret, it will be used if ClientSecret is not set. |
|
||||
| `keycloakConfig` | _[KeycloakOptions](#keycloakoptions)_ | KeycloakConfig holds all configurations for Keycloak provider. |
|
||||
| `azureConfig` | _[AzureOptions](#azureoptions)_ | AzureConfig holds all configurations for Azure provider. |
|
||||
| `bitbucketConfig` | _[BitbucketOptions](#bitbucketoptions)_ | BitbucketConfig holds all configurations for Bitbucket provider. |
|
||||
| `githubConfig` | _[GitHubOptions](#githuboptions)_ | GitHubConfig holds all configurations for GitHubC provider. |
|
||||
| `gitlabConfig` | _[GitLabOptions](#gitlaboptions)_ | GitLabConfig holds all configurations for GitLab provider. |
|
||||
| `googleConfig` | _[GoogleOptions](#googleoptions)_ | GoogleConfig holds all configurations for Google provider. |
|
||||
| `oidcConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider<br/>or providers utilize OIDC configurations. |
|
||||
| `loginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. |
|
||||
| `id` | _string_ | ID should be a unique identifier for the provider.<br/>This value is required for all providers. |
|
||||
| `provider` | _string_ | Type is the OAuth provider<br/>must be set from the supported providers group,<br/>otherwise 'Google' is set as default |
|
||||
| `name` | _string_ | Name is the providers display name<br/>if set, it will be shown to the users in the login page. |
|
||||
| `caFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.<br/>If not specified, the default Go trust sources are used instead |
|
||||
| `loginURL` | _string_ | LoginURL is the authentication endpoint |
|
||||
| `redeemURL` | _string_ | RedeemURL is the token redemption endpoint |
|
||||
| `profileURL` | _string_ | ProfileURL is the profile access endpoint |
|
||||
| `resource` | _string_ | ProtectedResource is the resource that is protected (Azure AD only) |
|
||||
| `validateURL` | _string_ | ValidateURL is the access token validation endpoint |
|
||||
| `scope` | _string_ | Scope is the OAuth scope specification |
|
||||
| `prompt` | _string_ | Prompt is OIDC prompt |
|
||||
| `approvalPrompt` | _string_ | ApprovalPrompt is the OAuth approval_prompt<br/>default is set to 'force' |
|
||||
| `allowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group |
|
||||
| `acrValues` | _string_ | AcrValues is a string of acr values |
|
||||
|
||||
### Providers
|
||||
|
||||
#### ([[]Provider](#provider) alias)
|
||||
|
||||
(**Appears on:** [AlphaOptions](#alphaoptions))
|
||||
|
||||
Providers is a collection of definitions for providers.
|
||||
|
||||
|
||||
### SecretSource
|
||||
|
||||
(**Appears on:** [ClaimSource](#claimsource), [HeaderValue](#headervalue), [TLS](#tls))
|
||||
|
1
go.sum
1
go.sum
@ -398,6 +398,7 @@ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
|
42
main_test.go
42
main_test.go
@ -19,6 +19,8 @@ http_address="127.0.0.1:4180"
|
||||
upstreams="http://httpbin"
|
||||
set_basic_auth="true"
|
||||
basic_auth_password="super-secret-password"
|
||||
client_id="oauth2-proxy"
|
||||
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
||||
`
|
||||
|
||||
const testAlphaConfig = `
|
||||
@ -57,15 +59,23 @@ injectResponseHeaders:
|
||||
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
|
||||
server:
|
||||
bindAddress: "127.0.0.1:4180"
|
||||
providers:
|
||||
- provider: google
|
||||
ID: google=oauth2-proxy
|
||||
clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
|
||||
clientID: oauth2-proxy
|
||||
approvalPrompt: force
|
||||
azureConfig:
|
||||
tenant: common
|
||||
oidcConfig:
|
||||
groupsClaim: groups
|
||||
emailClaim: email
|
||||
userIDClaim: email
|
||||
`
|
||||
|
||||
const testCoreConfig = `
|
||||
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
||||
provider="oidc"
|
||||
email_domains="example.com"
|
||||
oidc_issuer_url="http://dex.localhost:4190/dex"
|
||||
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
||||
client_id="oauth2-proxy"
|
||||
cookie_secure="false"
|
||||
|
||||
redirect_url="http://localhost:4180/oauth2/callback"
|
||||
@ -85,11 +95,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
opts.Cookie.Secret = "OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
||||
opts.ProviderType = "oidc"
|
||||
opts.EmailDomains = []string{"example.com"}
|
||||
opts.OIDCIssuerURL = "http://dex.localhost:4190/dex"
|
||||
opts.ClientSecret = "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
||||
opts.ClientID = "oauth2-proxy"
|
||||
opts.Cookie.Secure = false
|
||||
opts.RawRedirectURL = "http://localhost:4180/oauth2/callback"
|
||||
|
||||
@ -121,6 +127,24 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
||||
|
||||
opts.InjectRequestHeaders = append([]options.Header{authHeader}, opts.InjectRequestHeaders...)
|
||||
opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader)
|
||||
|
||||
opts.Providers = options.Providers{
|
||||
{
|
||||
ID: "google=oauth2-proxy",
|
||||
Type: "google",
|
||||
ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
|
||||
ClientID: "oauth2-proxy",
|
||||
AzureConfig: options.AzureOptions{
|
||||
Tenant: "common",
|
||||
},
|
||||
OIDCConfig: options.OIDCOptions{
|
||||
GroupsClaim: "groups",
|
||||
EmailClaim: "email",
|
||||
UserIDClaim: "email",
|
||||
},
|
||||
ApprovalPrompt: "force",
|
||||
},
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
@ -204,7 +228,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
||||
configContent: testCoreConfig,
|
||||
alphaConfigContent: testAlphaConfig + ":",
|
||||
expectedOptions: func() *options.Options { return nil },
|
||||
expectedErr: errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 36: did not find expected key"),
|
||||
expectedErr: errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 48: did not find expected key"),
|
||||
}),
|
||||
Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
|
||||
configContent: testCoreConfig + "unknown_field=\"something\"",
|
||||
|
@ -122,7 +122,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
||||
Footer: opts.Templates.Footer,
|
||||
Version: VERSION,
|
||||
Debug: opts.Templates.Debug,
|
||||
ProviderName: buildProviderName(opts.GetProvider(), opts.ProviderName),
|
||||
ProviderName: buildProviderName(opts.GetProvider(), opts.Providers[0].Name),
|
||||
SignInMessage: buildSignInMessage(opts),
|
||||
DisplayLoginForm: basicAuthValidator != nil && opts.Templates.DisplayLoginForm,
|
||||
})
|
||||
@ -136,7 +136,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
||||
}
|
||||
|
||||
if opts.SkipJwtBearerTokens {
|
||||
logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.OIDCIssuerURL)
|
||||
logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.Providers[0].OIDCConfig.IssuerURL)
|
||||
for _, issuer := range opts.ExtraJwtIssuers {
|
||||
logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer)
|
||||
}
|
||||
@ -146,7 +146,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
||||
redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
|
||||
}
|
||||
|
||||
logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.GetProvider().Data().ProviderName, opts.ClientID)
|
||||
logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.GetProvider().Data().ProviderName, opts.Providers[0].ClientID)
|
||||
refresh := "disabled"
|
||||
if opts.Cookie.Refresh != time.Duration(0) {
|
||||
refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh)
|
||||
|
@ -981,7 +981,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
|
||||
ProviderData: &providers.ProviderData{},
|
||||
ValidToken: opts.providerValidateCookieResponse,
|
||||
}
|
||||
pcTest.proxy.provider.(*TestProvider).SetAllowedGroups(pcTest.opts.AllowedGroups)
|
||||
pcTest.proxy.provider.(*TestProvider).SetAllowedGroups(pcTest.opts.Providers[0].AllowedGroups)
|
||||
|
||||
pcTest.rw = httptest.NewRecorder()
|
||||
pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
|
||||
@ -1322,7 +1322,7 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
pcTest.opts.AllowedGroups = []string{"oauth_groups"}
|
||||
pcTest.opts.Providers[0].AllowedGroups = []string{"oauth_groups"}
|
||||
err := validation.Validate(pcTest.opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@ -2292,8 +2292,9 @@ func Test_noCacheHeaders(t *testing.T) {
|
||||
func baseTestOptions() *options.Options {
|
||||
opts := options.NewOptions()
|
||||
opts.Cookie.Secret = rawCookieSecret
|
||||
opts.ClientID = clientID
|
||||
opts.ClientSecret = clientSecret
|
||||
opts.Providers[0].ID = "providerID"
|
||||
opts.Providers[0].ClientID = clientID
|
||||
opts.Providers[0].ClientSecret = clientSecret
|
||||
opts.EmailDomains = []string{"*"}
|
||||
|
||||
// Default injected headers for legacy configuration
|
||||
@ -2786,7 +2787,7 @@ func TestProxyAllowedGroups(t *testing.T) {
|
||||
t.Cleanup(upstreamServer.Close)
|
||||
|
||||
test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
|
||||
opts.AllowedGroups = tt.allowedGroups
|
||||
opts.Providers[0].AllowedGroups = tt.allowedGroups
|
||||
opts.UpstreamServers = options.Upstreams{
|
||||
{
|
||||
ID: upstreamServer.URL,
|
||||
@ -2915,7 +2916,7 @@ func TestAuthOnlyAllowedGroups(t *testing.T) {
|
||||
}
|
||||
|
||||
test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
|
||||
opts.AllowedGroups = tc.allowedGroups
|
||||
opts.Providers[0].AllowedGroups = tc.allowedGroups
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -40,6 +40,9 @@ type AlphaOptions struct {
|
||||
// This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
|
||||
// To use the secure server you must configure a TLS certificate and key.
|
||||
MetricsServer Server `json:"metricsServer,omitempty"`
|
||||
|
||||
// Providers is used to configure multiple providers.
|
||||
Providers Providers `json:"providers,omitempty"`
|
||||
}
|
||||
|
||||
// MergeInto replaces alpha options in the Options struct with the values
|
||||
@ -50,6 +53,8 @@ func (a *AlphaOptions) MergeInto(opts *Options) {
|
||||
opts.InjectResponseHeaders = a.InjectResponseHeaders
|
||||
opts.Server = a.Server
|
||||
opts.MetricsServer = a.MetricsServer
|
||||
opts.Providers = a.Providers
|
||||
|
||||
}
|
||||
|
||||
// ExtractFrom populates the fields in the AlphaOptions with the values from
|
||||
@ -60,4 +65,5 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) {
|
||||
a.InjectResponseHeaders = opts.InjectResponseHeaders
|
||||
a.Server = opts.Server
|
||||
a.MetricsServer = opts.MetricsServer
|
||||
a.Providers = opts.Providers
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@ -21,6 +22,9 @@ type LegacyOptions struct {
|
||||
// Legacy options for the server address and TLS
|
||||
LegacyServer LegacyServer `cfg:",squash"`
|
||||
|
||||
// Legacy options for single provider
|
||||
LegacyProvider LegacyProvider `cfg:",squash"`
|
||||
|
||||
Options Options `cfg:",squash"`
|
||||
}
|
||||
|
||||
@ -43,6 +47,15 @@ func NewLegacyOptions() *LegacyOptions {
|
||||
HTTPSAddress: ":443",
|
||||
},
|
||||
|
||||
LegacyProvider: LegacyProvider{
|
||||
ProviderType: "google",
|
||||
AzureTenant: "common",
|
||||
ApprovalPrompt: "force",
|
||||
UserIDClaim: "email",
|
||||
OIDCEmailClaim: "email",
|
||||
OIDCGroupsClaim: "groups",
|
||||
},
|
||||
|
||||
Options: *NewOptions(),
|
||||
}
|
||||
}
|
||||
@ -53,6 +66,7 @@ func NewLegacyFlagSet() *pflag.FlagSet {
|
||||
flagSet.AddFlagSet(legacyUpstreamsFlagSet())
|
||||
flagSet.AddFlagSet(legacyHeadersFlagSet())
|
||||
flagSet.AddFlagSet(legacyServerFlagset())
|
||||
flagSet.AddFlagSet(legacyProviderFlagSet())
|
||||
|
||||
return flagSet
|
||||
}
|
||||
@ -65,10 +79,17 @@ func (l *LegacyOptions) ToOptions() (*Options, error) {
|
||||
l.Options.UpstreamServers = upstreams
|
||||
|
||||
l.Options.InjectRequestHeaders, l.Options.InjectResponseHeaders = l.LegacyHeaders.convert()
|
||||
|
||||
l.Options.Server, l.Options.MetricsServer = l.LegacyServer.convert()
|
||||
|
||||
l.Options.LegacyPreferEmailToUser = l.LegacyHeaders.PreferEmailToUser
|
||||
|
||||
providers, err := l.LegacyProvider.convert()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting provider: %v", err)
|
||||
}
|
||||
l.Options.Providers = providers
|
||||
|
||||
return &l.Options, nil
|
||||
}
|
||||
|
||||
@ -443,6 +464,106 @@ func legacyServerFlagset() *pflag.FlagSet {
|
||||
return flagSet
|
||||
}
|
||||
|
||||
type LegacyProvider struct {
|
||||
ClientID string `flag:"client-id" cfg:"client_id"`
|
||||
ClientSecret string `flag:"client-secret" cfg:"client_secret"`
|
||||
ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file"`
|
||||
|
||||
KeycloakGroups []string `flag:"keycloak-group" cfg:"keycloak_groups"`
|
||||
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
|
||||
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team"`
|
||||
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository"`
|
||||
GitHubOrg string `flag:"github-org" cfg:"github_org"`
|
||||
GitHubTeam string `flag:"github-team" cfg:"github_team"`
|
||||
GitHubRepo string `flag:"github-repo" cfg:"github_repo"`
|
||||
GitHubToken string `flag:"github-token" cfg:"github_token"`
|
||||
GitHubUsers []string `flag:"github-user" cfg:"github_users"`
|
||||
GitLabGroup []string `flag:"gitlab-group" cfg:"gitlab_groups"`
|
||||
GitLabProjects []string `flag:"gitlab-project" cfg:"gitlab_projects"`
|
||||
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"`
|
||||
|
||||
// These options allow for other providers besides Google, with
|
||||
// potential overrides.
|
||||
ProviderType string `flag:"provider" cfg:"provider"`
|
||||
ProviderName string `flag:"provider-display-name" cfg:"provider_display_name"`
|
||||
ProviderCAFiles []string `flag:"provider-ca-file" cfg:"provider_ca_files"`
|
||||
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
|
||||
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email"`
|
||||
InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"`
|
||||
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery"`
|
||||
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"`
|
||||
OIDCEmailClaim string `flag:"oidc-email-claim" cfg:"oidc_email_claim"`
|
||||
OIDCGroupsClaim string `flag:"oidc-groups-claim" cfg:"oidc_groups_claim"`
|
||||
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"`
|
||||
Prompt string `flag:"prompt" cfg:"prompt"`
|
||||
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
|
||||
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
|
||||
AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
|
||||
|
||||
AcrValues string `flag:"acr-values" cfg:"acr_values"`
|
||||
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
|
||||
JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file"`
|
||||
PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url"`
|
||||
}
|
||||
|
||||
func legacyProviderFlagSet() *pflag.FlagSet {
|
||||
flagSet := pflag.NewFlagSet("provider", pflag.ExitOnError)
|
||||
|
||||
flagSet.StringSlice("keycloak-group", []string{}, "restrict logins to members of these groups (may be given multiple times)")
|
||||
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
|
||||
flagSet.String("bitbucket-team", "", "restrict logins to members of this team")
|
||||
flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository")
|
||||
flagSet.String("github-org", "", "restrict logins to members of this organisation")
|
||||
flagSet.String("github-team", "", "restrict logins to members of this team")
|
||||
flagSet.String("github-repo", "", "restrict logins to collaborators of this repository")
|
||||
flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)")
|
||||
flagSet.StringSlice("github-user", []string{}, "allow users with these usernames to login even if they do not belong to the specified org and team or collaborators (may be given multiple times)")
|
||||
flagSet.StringSlice("gitlab-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
||||
flagSet.StringSlice("gitlab-project", []string{}, "restrict logins to members of this project (may be given multiple times) (eg `group/project=accesslevel`). Access level should be a value matching Gitlab access levels (see https://docs.gitlab.com/ee/api/members.html#valid-access-levels), defaulted to 20 if absent")
|
||||
flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).")
|
||||
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
|
||||
flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
|
||||
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
|
||||
flagSet.String("client-secret", "", "the OAuth Client Secret")
|
||||
flagSet.String("client-secret-file", "", "the file with OAuth Client Secret")
|
||||
|
||||
flagSet.String("provider", "google", "OAuth provider")
|
||||
flagSet.String("provider-display-name", "", "Provider display name")
|
||||
flagSet.StringSlice("provider-ca-file", []string{}, "One or more paths to CA certificates that should be used when connecting to the provider. If not specified, the default Go trust sources are used instead.")
|
||||
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
|
||||
flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
|
||||
flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL")
|
||||
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
|
||||
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
|
||||
flagSet.String("oidc-groups-claim", providers.OIDCGroupsClaim, "which OIDC claim contains the user groups")
|
||||
flagSet.String("oidc-email-claim", providers.OIDCEmailClaim, "which OIDC claim contains the user's email")
|
||||
flagSet.String("login-url", "", "Authentication endpoint")
|
||||
flagSet.String("redeem-url", "", "Token redemption endpoint")
|
||||
flagSet.String("profile-url", "", "Profile access endpoint")
|
||||
flagSet.String("resource", "", "The resource that is protected (Azure AD only)")
|
||||
flagSet.String("validate-url", "", "Access token validation endpoint")
|
||||
flagSet.String("scope", "", "OAuth scope specification")
|
||||
flagSet.String("prompt", "", "OIDC prompt")
|
||||
flagSet.String("approval-prompt", "force", "OAuth approval_prompt")
|
||||
|
||||
flagSet.String("acr-values", "", "acr values string: optional")
|
||||
flagSet.String("jwt-key", "", "private key in PEM format used to sign JWT, so that you can say something like -jwt-key=\"${OAUTH2_PROXY_JWT_KEY}\": required by login.gov")
|
||||
flagSet.String("jwt-key-file", "", "path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov")
|
||||
flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov")
|
||||
|
||||
flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
|
||||
flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
||||
|
||||
return flagSet
|
||||
}
|
||||
|
||||
func (l LegacyServer) convert() (Server, Server) {
|
||||
appServer := Server{
|
||||
BindAddress: l.HTTPAddress,
|
||||
@ -482,3 +603,91 @@ func (l LegacyServer) convert() (Server, Server) {
|
||||
|
||||
return appServer, metricsServer
|
||||
}
|
||||
|
||||
func (l *LegacyProvider) convert() (Providers, error) {
|
||||
providers := Providers{}
|
||||
|
||||
provider := Provider{
|
||||
ClientID: l.ClientID,
|
||||
ClientSecret: l.ClientSecret,
|
||||
ClientSecretFile: l.ClientSecretFile,
|
||||
Type: l.ProviderType,
|
||||
CAFiles: l.ProviderCAFiles,
|
||||
LoginURL: l.LoginURL,
|
||||
RedeemURL: l.RedeemURL,
|
||||
ProfileURL: l.ProfileURL,
|
||||
ProtectedResource: l.ProtectedResource,
|
||||
ValidateURL: l.ValidateURL,
|
||||
Scope: l.Scope,
|
||||
Prompt: l.Prompt,
|
||||
ApprovalPrompt: l.ApprovalPrompt,
|
||||
AllowedGroups: l.AllowedGroups,
|
||||
AcrValues: l.AcrValues,
|
||||
}
|
||||
|
||||
// This part is out of the switch section for all providers that support OIDC
|
||||
provider.OIDCConfig = OIDCOptions{
|
||||
IssuerURL: l.OIDCIssuerURL,
|
||||
InsecureAllowUnverifiedEmail: l.InsecureOIDCAllowUnverifiedEmail,
|
||||
InsecureSkipIssuerVerification: l.InsecureOIDCSkipIssuerVerification,
|
||||
SkipDiscovery: l.SkipOIDCDiscovery,
|
||||
JwksURL: l.OIDCJwksURL,
|
||||
UserIDClaim: l.UserIDClaim,
|
||||
EmailClaim: l.OIDCEmailClaim,
|
||||
GroupsClaim: l.OIDCGroupsClaim,
|
||||
}
|
||||
|
||||
// This part is out of the switch section because azure has a default tenant
|
||||
// that needs to be added from legacy options
|
||||
provider.AzureConfig = AzureOptions{
|
||||
Tenant: l.AzureTenant,
|
||||
}
|
||||
|
||||
switch provider.Type {
|
||||
case "github":
|
||||
provider.GitHubConfig = GitHubOptions{
|
||||
Org: l.GitHubOrg,
|
||||
Team: l.GitHubTeam,
|
||||
Repo: l.GitHubRepo,
|
||||
Token: l.GitHubToken,
|
||||
Users: l.GitHubUsers,
|
||||
}
|
||||
case "keycloak":
|
||||
provider.KeycloakConfig = KeycloakOptions{
|
||||
Groups: l.KeycloakGroups,
|
||||
}
|
||||
case "gitlab":
|
||||
provider.GitLabConfig = GitLabOptions{
|
||||
Group: l.GitLabGroup,
|
||||
Projects: l.GitLabProjects,
|
||||
}
|
||||
case "login.gov":
|
||||
provider.LoginGovConfig = LoginGovOptions{
|
||||
JWTKey: l.JWTKey,
|
||||
JWTKeyFile: l.JWTKeyFile,
|
||||
PubJWKURL: l.PubJWKURL,
|
||||
}
|
||||
case "bitbucket":
|
||||
provider.BitbucketConfig = BitbucketOptions{
|
||||
Team: l.BitbucketTeam,
|
||||
Repository: l.BitbucketRepository,
|
||||
}
|
||||
case "google":
|
||||
provider.GoogleConfig = GoogleOptions{
|
||||
Groups: l.GoogleGroups,
|
||||
AdminEmail: l.GoogleAdminEmail,
|
||||
ServiceAccountJSON: l.GoogleServiceAccountJSON,
|
||||
}
|
||||
}
|
||||
|
||||
if l.ProviderName != "" {
|
||||
provider.ID = l.ProviderName
|
||||
provider.Name = l.ProviderName
|
||||
} else {
|
||||
provider.ID = l.ProviderType + "=" + l.ClientID
|
||||
}
|
||||
|
||||
providers = append(providers, provider)
|
||||
|
||||
return providers, nil
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ var _ = Describe("Legacy Options", func() {
|
||||
legacyOpts.LegacyUpstreams.ProxyWebSockets = true
|
||||
legacyOpts.LegacyUpstreams.SSLUpstreamInsecureSkipVerify = true
|
||||
legacyOpts.LegacyUpstreams.Upstreams = []string{"http://foo.bar/baz", "file:///var/lib/website#/bar", "static://204"}
|
||||
legacyOpts.LegacyProvider.ClientID = "oauth-proxy"
|
||||
|
||||
truth := true
|
||||
staticCode := 204
|
||||
@ -110,6 +111,9 @@ var _ = Describe("Legacy Options", func() {
|
||||
BindAddress: "127.0.0.1:4180",
|
||||
}
|
||||
|
||||
opts.Providers[0].ClientID = "oauth-proxy"
|
||||
opts.Providers[0].ID = "google=oauth-proxy"
|
||||
|
||||
converted, err := legacyOpts.ToOptions()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(converted).To(Equal(opts))
|
||||
@ -196,9 +200,9 @@ var _ = Describe("Legacy Options", func() {
|
||||
invalidHTTPErrMsg := "could not parse upstream \":foo\": parse \":foo\": missing protocol scheme"
|
||||
|
||||
DescribeTable("convertLegacyUpstreams",
|
||||
func(o *convertUpstreamsTableInput) {
|
||||
func(in *convertUpstreamsTableInput) {
|
||||
legacyUpstreams := LegacyUpstreams{
|
||||
Upstreams: o.upstreamStrings,
|
||||
Upstreams: in.upstreamStrings,
|
||||
SSLUpstreamInsecureSkipVerify: skipVerify,
|
||||
PassHostHeader: passHostHeader,
|
||||
ProxyWebSockets: proxyWebSockets,
|
||||
@ -207,14 +211,14 @@ var _ = Describe("Legacy Options", func() {
|
||||
|
||||
upstreams, err := legacyUpstreams.convert()
|
||||
|
||||
if o.errMsg != "" {
|
||||
if in.errMsg != "" {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal(o.errMsg))
|
||||
Expect(err.Error()).To(Equal(in.errMsg))
|
||||
} else {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
Expect(upstreams).To(ConsistOf(o.expectedUpstreams))
|
||||
Expect(upstreams).To(ConsistOf(in.expectedUpstreams))
|
||||
},
|
||||
Entry("with no upstreams", &convertUpstreamsTableInput{
|
||||
upstreamStrings: []string{},
|
||||
@ -850,6 +854,87 @@ var _ = Describe("Legacy Options", func() {
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
Context("Legacy Providers", func() {
|
||||
type convertProvidersTableInput struct {
|
||||
legacyProvider LegacyProvider
|
||||
expectedProviders Providers
|
||||
errMsg string
|
||||
}
|
||||
|
||||
// Non defaults for these options
|
||||
clientID := "abcd"
|
||||
|
||||
defaultProvider := Provider{
|
||||
ID: "google=" + clientID,
|
||||
ClientID: clientID,
|
||||
Type: "google",
|
||||
}
|
||||
defaultLegacyProvider := LegacyProvider{
|
||||
ClientID: clientID,
|
||||
ProviderType: "google",
|
||||
}
|
||||
|
||||
displayNameProvider := Provider{
|
||||
ID: "displayName",
|
||||
Name: "displayName",
|
||||
ClientID: clientID,
|
||||
Type: "google",
|
||||
}
|
||||
|
||||
displayNameLegacyProvider := LegacyProvider{
|
||||
ClientID: clientID,
|
||||
ProviderName: "displayName",
|
||||
ProviderType: "google",
|
||||
}
|
||||
|
||||
internalConfigProvider := Provider{
|
||||
ID: "google=" + clientID,
|
||||
ClientID: clientID,
|
||||
Type: "google",
|
||||
GoogleConfig: GoogleOptions{
|
||||
AdminEmail: "email@email.com",
|
||||
ServiceAccountJSON: "test.json",
|
||||
Groups: []string{"1", "2"},
|
||||
},
|
||||
}
|
||||
|
||||
internalConfigLegacyProvider := LegacyProvider{
|
||||
ClientID: clientID,
|
||||
ProviderType: "google",
|
||||
GoogleAdminEmail: "email@email.com",
|
||||
GoogleServiceAccountJSON: "test.json",
|
||||
GoogleGroups: []string{"1", "2"},
|
||||
}
|
||||
DescribeTable("convertLegacyProviders",
|
||||
func(in *convertProvidersTableInput) {
|
||||
providers, err := in.legacyProvider.convert()
|
||||
|
||||
if in.errMsg != "" {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal(in.errMsg))
|
||||
} else {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
Expect(providers).To(ConsistOf(in.expectedProviders))
|
||||
},
|
||||
Entry("with default provider", &convertProvidersTableInput{
|
||||
legacyProvider: defaultLegacyProvider,
|
||||
expectedProviders: Providers{defaultProvider},
|
||||
errMsg: "",
|
||||
}),
|
||||
Entry("with provider display name", &convertProvidersTableInput{
|
||||
legacyProvider: displayNameLegacyProvider,
|
||||
expectedProviders: Providers{displayNameProvider},
|
||||
errMsg: "",
|
||||
}),
|
||||
Entry("with internal provider config", &convertProvidersTableInput{
|
||||
legacyProvider: internalConfigLegacyProvider,
|
||||
expectedProviders: Providers{internalConfigProvider},
|
||||
errMsg: "",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -14,6 +14,49 @@ import (
|
||||
)
|
||||
|
||||
var _ = Describe("Load", func() {
|
||||
optionsWithNilProvider := NewOptions()
|
||||
optionsWithNilProvider.Providers = nil
|
||||
|
||||
legacyOptionsWithNilProvider := &LegacyOptions{
|
||||
LegacyUpstreams: LegacyUpstreams{
|
||||
PassHostHeader: true,
|
||||
ProxyWebSockets: true,
|
||||
FlushInterval: DefaultUpstreamFlushInterval,
|
||||
},
|
||||
|
||||
LegacyHeaders: LegacyHeaders{
|
||||
PassBasicAuth: true,
|
||||
PassUserHeaders: true,
|
||||
SkipAuthStripHeaders: true,
|
||||
},
|
||||
|
||||
LegacyServer: LegacyServer{
|
||||
HTTPAddress: "127.0.0.1:4180",
|
||||
HTTPSAddress: ":443",
|
||||
},
|
||||
|
||||
LegacyProvider: LegacyProvider{
|
||||
ProviderType: "google",
|
||||
AzureTenant: "common",
|
||||
ApprovalPrompt: "force",
|
||||
UserIDClaim: "email",
|
||||
OIDCEmailClaim: "email",
|
||||
OIDCGroupsClaim: "groups",
|
||||
},
|
||||
|
||||
Options: Options{
|
||||
ProxyPrefix: "/oauth2",
|
||||
PingPath: "/ping",
|
||||
RealClientIPHeader: "X-Real-IP",
|
||||
ForceHTTPS: false,
|
||||
Cookie: cookieDefaults(),
|
||||
Session: sessionOptionsDefaults(),
|
||||
Templates: templatesDefaults(),
|
||||
SkipAuthPreflight: false,
|
||||
Logging: loggingDefaults(),
|
||||
},
|
||||
}
|
||||
|
||||
Context("with a testOptions structure", func() {
|
||||
type TestOptionSubStruct struct {
|
||||
StringSliceOption []string `flag:"string-slice-option" cfg:"string_slice_option"`
|
||||
@ -294,12 +337,12 @@ var _ = Describe("Load", func() {
|
||||
Entry("with an empty Options struct, should return default values", &testOptionsTableInput{
|
||||
flagSet: NewFlagSet,
|
||||
input: &Options{},
|
||||
expectedOutput: NewOptions(),
|
||||
expectedOutput: optionsWithNilProvider,
|
||||
}),
|
||||
Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{
|
||||
flagSet: NewLegacyFlagSet,
|
||||
input: &LegacyOptions{},
|
||||
expectedOutput: NewLegacyOptions(),
|
||||
expectedOutput: legacyOptionsWithNilProvider,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
@ -27,27 +27,10 @@ type Options struct {
|
||||
TrustedIPs []string `flag:"trusted-ip" cfg:"trusted_ips"`
|
||||
ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
|
||||
RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
|
||||
ClientID string `flag:"client-id" cfg:"client_id"`
|
||||
ClientSecret string `flag:"client-secret" cfg:"client_secret"`
|
||||
ClientSecretFile string `flag:"client-secret-file" cfg:"client_secret_file"`
|
||||
|
||||
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
|
||||
KeycloakGroups []string `flag:"keycloak-group" cfg:"keycloak_groups"`
|
||||
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant"`
|
||||
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team"`
|
||||
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository"`
|
||||
EmailDomains []string `flag:"email-domain" cfg:"email_domains"`
|
||||
WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains"`
|
||||
GitHubOrg string `flag:"github-org" cfg:"github_org"`
|
||||
GitHubTeam string `flag:"github-team" cfg:"github_team"`
|
||||
GitHubRepo string `flag:"github-repo" cfg:"github_repo"`
|
||||
GitHubToken string `flag:"github-token" cfg:"github_token"`
|
||||
GitHubUsers []string `flag:"github-user" cfg:"github_users"`
|
||||
GitLabGroup []string `flag:"gitlab-group" cfg:"gitlab_groups"`
|
||||
GitlabProjects []string `flag:"gitlab-project" cfg:"gitlab_projects"`
|
||||
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"`
|
||||
HtpasswdUserGroups []string `flag:"htpasswd-user-group" cfg:"htpasswd_user_groups"`
|
||||
|
||||
@ -66,6 +49,8 @@ type Options struct {
|
||||
Server Server `cfg:",internal"`
|
||||
MetricsServer Server `cfg:",internal"`
|
||||
|
||||
Providers Providers `cfg:",internal"`
|
||||
|
||||
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
|
||||
SkipAuthRoutes []string `flag:"skip-auth-route" cfg:"skip_auth_routes"`
|
||||
SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
|
||||
@ -74,34 +59,7 @@ type Options struct {
|
||||
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
|
||||
SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
|
||||
|
||||
// These options allow for other providers besides Google, with
|
||||
// potential overrides.
|
||||
ProviderType string `flag:"provider" cfg:"provider"`
|
||||
ProviderName string `flag:"provider-display-name" cfg:"provider_display_name"`
|
||||
ProviderCAFiles []string `flag:"provider-ca-file" cfg:"provider_ca_files"`
|
||||
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
|
||||
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email"`
|
||||
InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"`
|
||||
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery"`
|
||||
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"`
|
||||
OIDCEmailClaim string `flag:"oidc-email-claim" cfg:"oidc_email_claim"`
|
||||
OIDCGroupsClaim string `flag:"oidc-groups-claim" cfg:"oidc_groups_claim"`
|
||||
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"`
|
||||
Prompt string `flag:"prompt" cfg:"prompt"`
|
||||
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
|
||||
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
|
||||
AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
|
||||
|
||||
SignatureKey string `flag:"signature-key" cfg:"signature_key"`
|
||||
AcrValues string `flag:"acr-values" cfg:"acr_values"`
|
||||
JWTKey string `flag:"jwt-key" cfg:"jwt_key"`
|
||||
JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file"`
|
||||
PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url"`
|
||||
GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks"`
|
||||
|
||||
// This is used for backwards compatibility for basic auth users
|
||||
@ -136,23 +94,15 @@ func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClie
|
||||
func NewOptions() *Options {
|
||||
return &Options{
|
||||
ProxyPrefix: "/oauth2",
|
||||
ProviderType: "google",
|
||||
Providers: providerDefaults(),
|
||||
PingPath: "/ping",
|
||||
RealClientIPHeader: "X-Real-IP",
|
||||
ForceHTTPS: false,
|
||||
Cookie: cookieDefaults(),
|
||||
Session: sessionOptionsDefaults(),
|
||||
Templates: templatesDefaults(),
|
||||
AzureTenant: "common",
|
||||
SkipAuthPreflight: false,
|
||||
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
|
||||
ApprovalPrompt: "force",
|
||||
InsecureOIDCAllowUnverifiedEmail: false,
|
||||
SkipOIDCDiscovery: false,
|
||||
Logging: loggingDefaults(),
|
||||
UserIDClaim: providers.OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
|
||||
OIDCEmailClaim: providers.OIDCEmailClaim,
|
||||
OIDCGroupsClaim: providers.OIDCGroupsClaim,
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,23 +125,6 @@ func NewFlagSet() *pflag.FlagSet {
|
||||
|
||||
flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
|
||||
flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
|
||||
flagSet.StringSlice("keycloak-group", []string{}, "restrict logins to members of these groups (may be given multiple times)")
|
||||
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
|
||||
flagSet.String("bitbucket-team", "", "restrict logins to members of this team")
|
||||
flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository")
|
||||
flagSet.String("github-org", "", "restrict logins to members of this organisation")
|
||||
flagSet.String("github-team", "", "restrict logins to members of this team")
|
||||
flagSet.String("github-repo", "", "restrict logins to collaborators of this repository")
|
||||
flagSet.String("github-token", "", "the token to use when verifying repository collaborators (must have push access to the repository)")
|
||||
flagSet.StringSlice("github-user", []string{}, "allow users with these usernames to login even if they do not belong to the specified org and team or collaborators (may be given multiple times)")
|
||||
flagSet.StringSlice("gitlab-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
||||
flagSet.StringSlice("gitlab-project", []string{}, "restrict logins to members of this project (may be given multiple times) (eg `group/project=accesslevel`). Access level should be a value matching Gitlab access levels (see https://docs.gitlab.com/ee/api/members.html#valid-access-levels), defaulted to 20 if absent")
|
||||
flagSet.StringSlice("google-group", []string{}, "restrict logins to members of this google group (may be given multiple times).")
|
||||
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
|
||||
flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
|
||||
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
|
||||
flagSet.String("client-secret", "", "the OAuth Client Secret")
|
||||
flagSet.String("client-secret-file", "", "the file with OAuth Client Secret")
|
||||
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
|
||||
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -B\" for bcrypt encryption")
|
||||
flagSet.StringSlice("htpasswd-user-group", []string{}, "the groups to be set on sessions for htpasswd users (may be given multiple times)")
|
||||
@ -211,35 +144,9 @@ func NewFlagSet() *pflag.FlagSet {
|
||||
flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature")
|
||||
flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster")
|
||||
|
||||
flagSet.String("provider", "google", "OAuth provider")
|
||||
flagSet.String("provider-display-name", "", "Provider display name")
|
||||
flagSet.StringSlice("provider-ca-file", []string{}, "One or more paths to CA certificates that should be used when connecting to the provider. If not specified, the default Go trust sources are used instead.")
|
||||
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
|
||||
flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
|
||||
flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL")
|
||||
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
|
||||
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
|
||||
flagSet.String("oidc-groups-claim", providers.OIDCGroupsClaim, "which OIDC claim contains the user groups")
|
||||
flagSet.String("oidc-email-claim", providers.OIDCEmailClaim, "which OIDC claim contains the user's email")
|
||||
flagSet.String("login-url", "", "Authentication endpoint")
|
||||
flagSet.String("redeem-url", "", "Token redemption endpoint")
|
||||
flagSet.String("profile-url", "", "Profile access endpoint")
|
||||
flagSet.String("resource", "", "The resource that is protected (Azure AD only)")
|
||||
flagSet.String("validate-url", "", "Access token validation endpoint")
|
||||
flagSet.String("scope", "", "OAuth scope specification")
|
||||
flagSet.String("prompt", "", "OIDC prompt")
|
||||
flagSet.String("approval-prompt", "force", "OAuth approval_prompt")
|
||||
|
||||
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
|
||||
flagSet.String("acr-values", "", "acr values string: optional")
|
||||
flagSet.String("jwt-key", "", "private key in PEM format used to sign JWT, so that you can say something like -jwt-key=\"${OAUTH2_PROXY_JWT_KEY}\": required by login.gov")
|
||||
flagSet.String("jwt-key-file", "", "path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov")
|
||||
flagSet.String("pubjwk-url", "", "JWK pubkey access endpoint: required by login.gov")
|
||||
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")
|
||||
|
||||
flagSet.String("user-id-claim", providers.OIDCEmailClaim, "(DEPRECATED for `oidc-email-claim`) which claim contains the user ID")
|
||||
flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
|
||||
|
||||
flagSet.AddFlagSet(cookieFlagSet())
|
||||
flagSet.AddFlagSet(loggingFlagSet())
|
||||
flagSet.AddFlagSet(templatesFlagSet())
|
||||
|
180
pkg/apis/options/providers.go
Normal file
180
pkg/apis/options/providers.go
Normal file
@ -0,0 +1,180 @@
|
||||
package options
|
||||
|
||||
import "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
|
||||
|
||||
// Providers is a collection of definitions for providers.
|
||||
type Providers []Provider
|
||||
|
||||
// Provider holds all configuration for a single provider
|
||||
type Provider struct {
|
||||
// ClientID is the OAuth Client ID that is defined in the provider
|
||||
// This value is required for all providers.
|
||||
ClientID string `json:"clientID,omitempty"`
|
||||
// ClientSecret is the OAuth Client Secret that is defined in the provider
|
||||
// This value is required for all providers.
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
// ClientSecretFile is the name of the file
|
||||
// containing the OAuth Client Secret, it will be used if ClientSecret is not set.
|
||||
ClientSecretFile string `json:"clientSecretFile,omitempty"`
|
||||
|
||||
// KeycloakConfig holds all configurations for Keycloak provider.
|
||||
KeycloakConfig KeycloakOptions `json:"keycloakConfig,omitempty"`
|
||||
// AzureConfig holds all configurations for Azure provider.
|
||||
AzureConfig AzureOptions `json:"azureConfig,omitempty"`
|
||||
// BitbucketConfig holds all configurations for Bitbucket provider.
|
||||
BitbucketConfig BitbucketOptions `json:"bitbucketConfig,omitempty"`
|
||||
// GitHubConfig holds all configurations for GitHubC provider.
|
||||
GitHubConfig GitHubOptions `json:"githubConfig,omitempty"`
|
||||
// GitLabConfig holds all configurations for GitLab provider.
|
||||
GitLabConfig GitLabOptions `json:"gitlabConfig,omitempty"`
|
||||
// GoogleConfig holds all configurations for Google provider.
|
||||
GoogleConfig GoogleOptions `json:"googleConfig,omitempty"`
|
||||
// OIDCConfig holds all configurations for OIDC provider
|
||||
// or providers utilize OIDC configurations.
|
||||
OIDCConfig OIDCOptions `json:"oidcConfig,omitempty"`
|
||||
// LoginGovConfig holds all configurations for LoginGov provider.
|
||||
LoginGovConfig LoginGovOptions `json:"loginGovConfig,omitempty"`
|
||||
|
||||
// ID should be a unique identifier for the provider.
|
||||
// This value is required for all providers.
|
||||
ID string `json:"id,omitempty"`
|
||||
// Type is the OAuth provider
|
||||
// must be set from the supported providers group,
|
||||
// otherwise 'Google' is set as default
|
||||
Type string `json:"provider,omitempty"`
|
||||
// Name is the providers display name
|
||||
// if set, it will be shown to the users in the login page.
|
||||
Name string `json:"name,omitempty"`
|
||||
// CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
|
||||
// If not specified, the default Go trust sources are used instead
|
||||
CAFiles []string `json:"caFiles,omitempty"`
|
||||
|
||||
// LoginURL is the authentication endpoint
|
||||
LoginURL string `json:"loginURL,omitempty"`
|
||||
// RedeemURL is the token redemption endpoint
|
||||
RedeemURL string `json:"redeemURL,omitempty"`
|
||||
// ProfileURL is the profile access endpoint
|
||||
ProfileURL string `json:"profileURL,omitempty"`
|
||||
// ProtectedResource is the resource that is protected (Azure AD only)
|
||||
ProtectedResource string `json:"resource,omitempty"`
|
||||
// ValidateURL is the access token validation endpoint
|
||||
ValidateURL string `json:"validateURL,omitempty"`
|
||||
// Scope is the OAuth scope specification
|
||||
Scope string `json:"scope,omitempty"`
|
||||
// Prompt is OIDC prompt
|
||||
Prompt string `json:"prompt,omitempty"`
|
||||
// ApprovalPrompt is the OAuth approval_prompt
|
||||
// default is set to 'force'
|
||||
ApprovalPrompt string `json:"approvalPrompt,omitempty"`
|
||||
// AllowedGroups is a list of restrict logins to members of this group
|
||||
AllowedGroups []string `json:"allowedGroups,omitempty"`
|
||||
|
||||
// AcrValues is a string of acr values
|
||||
AcrValues string `json:"acrValues,omitempty"`
|
||||
}
|
||||
|
||||
type KeycloakOptions struct {
|
||||
// Group enables to restrict login to members of indicated group
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
type AzureOptions struct {
|
||||
// Tenant directs to a tenant-specific or common (tenant-independent) endpoint
|
||||
// Default value is 'commmon'
|
||||
Tenant string `json:"tenant,omitempty"`
|
||||
}
|
||||
|
||||
type BitbucketOptions struct {
|
||||
// Team sets restrict logins to members of this team
|
||||
Team string `json:"team,omitempty"`
|
||||
// Repository sets restrict logins to user with access to this repository
|
||||
Repository string `json:"repository,omitempty"`
|
||||
}
|
||||
|
||||
type GitHubOptions struct {
|
||||
// Org sets restrict logins to members of this organisation
|
||||
Org string `json:"org,omitempty"`
|
||||
// Team sets restrict logins to members of this team
|
||||
Team string `json:"team,omitempty"`
|
||||
// Repo sets restrict logins to collaborators of this repository
|
||||
Repo string `json:"repo,omitempty"`
|
||||
// Token is the token to use when verifying repository collaborators
|
||||
// it must have push access to the repository
|
||||
Token string `json:"token,omitempty"`
|
||||
// Users allows users with these usernames to login
|
||||
// even if they do not belong to the specified org and team or collaborators
|
||||
Users []string `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
type GitLabOptions struct {
|
||||
// Group sets restrict logins to members of this group
|
||||
Group []string `json:"group,omitempty"`
|
||||
// Projects restricts logins to members of any of these projects
|
||||
Projects []string `json:"projects,omitempty"`
|
||||
}
|
||||
|
||||
type GoogleOptions struct {
|
||||
// Groups sets restrict logins to members of this google group
|
||||
Groups []string `json:"group,omitempty"`
|
||||
// AdminEmail is the google admin to impersonate for api calls
|
||||
AdminEmail string `json:"adminEmail,omitempty"`
|
||||
// ServiceAccountJSON is the path to the service account json credentials
|
||||
ServiceAccountJSON string `json:"serviceAccountJson,omitempty"`
|
||||
}
|
||||
|
||||
type OIDCOptions struct {
|
||||
// IssuerURL is the OpenID Connect issuer URL
|
||||
// eg: https://accounts.google.com
|
||||
IssuerURL string `json:"issuerURL,omitempty"`
|
||||
// InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
|
||||
// default set to 'false'
|
||||
InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"`
|
||||
// InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
|
||||
// default set to 'false'
|
||||
InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification,omitempty"`
|
||||
// SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
|
||||
// default set to 'false'
|
||||
SkipDiscovery bool `json:"skipDiscovery,omitempty"`
|
||||
// JwksURL is the OpenID Connect JWKS URL
|
||||
// eg: https://www.googleapis.com/oauth2/v3/certs
|
||||
JwksURL string `json:"jwksURL,omitempty"`
|
||||
// EmailClaim indicates which claim contains the user email,
|
||||
// default set to 'email'
|
||||
EmailClaim string `json:"emailClaim,omitempty"`
|
||||
// GroupsClaim indicates which claim contains the user groups
|
||||
// default set to 'groups'
|
||||
GroupsClaim string `json:"groupsClaim,omitempty"`
|
||||
// UserIDClaim indicates which claim contains the user ID
|
||||
// default set to 'email'
|
||||
UserIDClaim string `json:"userIDClaim,omitempty"`
|
||||
}
|
||||
|
||||
type LoginGovOptions struct {
|
||||
// JWTKey is a private key in PEM format used to sign JWT,
|
||||
JWTKey string `json:"jwtKey,omitempty"`
|
||||
// JWTKeyFile is a path to the private key file in PEM format used to sign the JWT
|
||||
JWTKeyFile string `json:"jwtKeyFile,omitempty"`
|
||||
// PubJWKURL is the JWK pubkey access endpoint
|
||||
PubJWKURL string `json:"pubjwkURL,omitempty"`
|
||||
}
|
||||
|
||||
func providerDefaults() Providers {
|
||||
providers := Providers{
|
||||
{
|
||||
Type: "google",
|
||||
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
|
||||
ApprovalPrompt: "force",
|
||||
AzureConfig: AzureOptions{
|
||||
Tenant: "common",
|
||||
},
|
||||
OIDCConfig: OIDCOptions{
|
||||
InsecureAllowUnverifiedEmail: false,
|
||||
SkipDiscovery: false,
|
||||
UserIDClaim: providers.OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
|
||||
EmailClaim: providers.OIDCEmailClaim,
|
||||
GroupsClaim: providers.OIDCGroupsClaim,
|
||||
},
|
||||
},
|
||||
}
|
||||
return providers
|
||||
}
|
@ -29,6 +29,7 @@ func Validate(o *options.Options) error {
|
||||
msgs = append(msgs, validateRedisSessionStore(o)...)
|
||||
msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
|
||||
msgs = append(msgs, prefixValues("injectResponseHeaders: ", validateHeaders(o.InjectResponseHeaders)...)...)
|
||||
msgs = append(msgs, validateProviders(o)...)
|
||||
msgs = configureLogger(o.Logging, msgs)
|
||||
msgs = parseSignatureKey(o, msgs)
|
||||
|
||||
@ -39,8 +40,8 @@ func Validate(o *options.Options) error {
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
http.DefaultClient = &http.Client{Transport: insecureTransport}
|
||||
} else if len(o.ProviderCAFiles) > 0 {
|
||||
pool, err := util.GetCertPool(o.ProviderCAFiles)
|
||||
} else if len(o.Providers[0].CAFiles) > 0 {
|
||||
pool, err := util.GetCertPool(o.Providers[0].CAFiles)
|
||||
if err == nil {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
@ -54,38 +55,23 @@ func Validate(o *options.Options) error {
|
||||
}
|
||||
}
|
||||
|
||||
if o.ClientID == "" {
|
||||
msgs = append(msgs, "missing setting: client-id")
|
||||
}
|
||||
// login.gov uses a signed JWT to authenticate, not a client-secret
|
||||
if o.ProviderType != "login.gov" {
|
||||
if o.ClientSecret == "" && o.ClientSecretFile == "" {
|
||||
msgs = append(msgs, "missing setting: client-secret or client-secret-file")
|
||||
}
|
||||
if o.ClientSecret == "" && o.ClientSecretFile != "" {
|
||||
_, err := ioutil.ReadFile(o.ClientSecretFile)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not read client secret file: "+o.ClientSecretFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
if o.OIDCIssuerURL != "" {
|
||||
if o.Providers[0].OIDCConfig.IssuerURL != "" {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if o.InsecureOIDCSkipIssuerVerification && !o.SkipOIDCDiscovery {
|
||||
if o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification && !o.Providers[0].OIDCConfig.SkipDiscovery {
|
||||
// go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
|
||||
// (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
|
||||
// we get the URLs, we'll just use the non-discovery path.
|
||||
|
||||
logger.Printf("Performing OIDC Discovery...")
|
||||
|
||||
requestURL := strings.TrimSuffix(o.OIDCIssuerURL, "/") + "/.well-known/openid-configuration"
|
||||
requestURL := strings.TrimSuffix(o.Providers[0].OIDCConfig.IssuerURL, "/") + "/.well-known/openid-configuration"
|
||||
body, err := requests.New(requestURL).
|
||||
WithContext(ctx).
|
||||
Do().
|
||||
@ -96,23 +82,23 @@ func Validate(o *options.Options) error {
|
||||
// Prefer manually configured URLs. It's a bit unclear
|
||||
// why you'd be doing discovery and also providing the URLs
|
||||
// explicitly though...
|
||||
if o.LoginURL == "" {
|
||||
o.LoginURL = body.Get("authorization_endpoint").MustString()
|
||||
if o.Providers[0].LoginURL == "" {
|
||||
o.Providers[0].LoginURL = body.Get("authorization_endpoint").MustString()
|
||||
}
|
||||
|
||||
if o.RedeemURL == "" {
|
||||
o.RedeemURL = body.Get("token_endpoint").MustString()
|
||||
if o.Providers[0].RedeemURL == "" {
|
||||
o.Providers[0].RedeemURL = body.Get("token_endpoint").MustString()
|
||||
}
|
||||
|
||||
if o.OIDCJwksURL == "" {
|
||||
o.OIDCJwksURL = body.Get("jwks_uri").MustString()
|
||||
if o.Providers[0].OIDCConfig.JwksURL == "" {
|
||||
o.Providers[0].OIDCConfig.JwksURL = body.Get("jwks_uri").MustString()
|
||||
}
|
||||
|
||||
if o.ProfileURL == "" {
|
||||
o.ProfileURL = body.Get("userinfo_endpoint").MustString()
|
||||
if o.Providers[0].ProfileURL == "" {
|
||||
o.Providers[0].ProfileURL = body.Get("userinfo_endpoint").MustString()
|
||||
}
|
||||
|
||||
o.SkipOIDCDiscovery = true
|
||||
o.Providers[0].OIDCConfig.SkipDiscovery = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,42 +106,45 @@ func Validate(o *options.Options) error {
|
||||
// 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 == "" {
|
||||
if o.Providers[0].OIDCConfig.SkipDiscovery {
|
||||
if o.Providers[0].LoginURL == "" {
|
||||
msgs = append(msgs, "missing setting: login-url")
|
||||
}
|
||||
if o.RedeemURL == "" {
|
||||
if o.Providers[0].RedeemURL == "" {
|
||||
msgs = append(msgs, "missing setting: redeem-url")
|
||||
}
|
||||
if o.OIDCJwksURL == "" {
|
||||
if o.Providers[0].OIDCConfig.JwksURL == "" {
|
||||
msgs = append(msgs, "missing setting: oidc-jwks-url")
|
||||
}
|
||||
keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
|
||||
o.SetOIDCVerifier(oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
|
||||
ClientID: o.ClientID,
|
||||
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
||||
keySet := oidc.NewRemoteKeySet(ctx, o.Providers[0].OIDCConfig.JwksURL)
|
||||
o.SetOIDCVerifier(oidc.NewVerifier(o.Providers[0].OIDCConfig.IssuerURL, keySet, &oidc.Config{
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
|
||||
}))
|
||||
} else {
|
||||
// Configure discoverable provider data.
|
||||
provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
|
||||
provider, err := oidc.NewProvider(ctx, o.Providers[0].OIDCConfig.IssuerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
|
||||
ClientID: o.ClientID,
|
||||
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
|
||||
}))
|
||||
|
||||
o.LoginURL = provider.Endpoint().AuthURL
|
||||
o.RedeemURL = provider.Endpoint().TokenURL
|
||||
o.Providers[0].LoginURL = provider.Endpoint().AuthURL
|
||||
o.Providers[0].RedeemURL = provider.Endpoint().TokenURL
|
||||
}
|
||||
if o.Scope == "" {
|
||||
o.Scope = "openid email profile"
|
||||
if o.Providers[0].Scope == "" {
|
||||
o.Providers[0].Scope = "openid email profile"
|
||||
|
||||
if len(o.AllowedGroups) > 0 {
|
||||
o.Scope += " groups"
|
||||
if len(o.Providers[0].AllowedGroups) > 0 {
|
||||
o.Providers[0].Scope += " groups"
|
||||
}
|
||||
}
|
||||
if o.Providers[0].OIDCConfig.UserIDClaim == "" {
|
||||
o.Providers[0].OIDCConfig.UserIDClaim = "email"
|
||||
}
|
||||
}
|
||||
|
||||
if o.SkipJwtBearerTokens {
|
||||
@ -183,18 +172,6 @@ func Validate(o *options.Options) error {
|
||||
msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
|
||||
msgs = parseProviderInfo(o, msgs)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
if o.ReverseProxy {
|
||||
parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)
|
||||
if err != nil {
|
||||
@ -220,79 +197,79 @@ func Validate(o *options.Options) error {
|
||||
|
||||
func parseProviderInfo(o *options.Options, msgs []string) []string {
|
||||
p := &providers.ProviderData{
|
||||
Scope: o.Scope,
|
||||
ClientID: o.ClientID,
|
||||
ClientSecret: o.ClientSecret,
|
||||
ClientSecretFile: o.ClientSecretFile,
|
||||
Prompt: o.Prompt,
|
||||
ApprovalPrompt: o.ApprovalPrompt,
|
||||
AcrValues: o.AcrValues,
|
||||
Scope: o.Providers[0].Scope,
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
ClientSecret: o.Providers[0].ClientSecret,
|
||||
ClientSecretFile: o.Providers[0].ClientSecretFile,
|
||||
Prompt: o.Providers[0].Prompt,
|
||||
ApprovalPrompt: o.Providers[0].ApprovalPrompt,
|
||||
AcrValues: o.Providers[0].AcrValues,
|
||||
}
|
||||
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)
|
||||
p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
|
||||
p.LoginURL, msgs = parseURL(o.Providers[0].LoginURL, "login", msgs)
|
||||
p.RedeemURL, msgs = parseURL(o.Providers[0].RedeemURL, "redeem", msgs)
|
||||
p.ProfileURL, msgs = parseURL(o.Providers[0].ProfileURL, "profile", msgs)
|
||||
p.ValidateURL, msgs = parseURL(o.Providers[0].ValidateURL, "validate", msgs)
|
||||
p.ProtectedResource, msgs = parseURL(o.Providers[0].ProtectedResource, "resource", msgs)
|
||||
|
||||
// Make the OIDC options available to all providers that support it
|
||||
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
|
||||
p.EmailClaim = o.OIDCEmailClaim
|
||||
p.GroupsClaim = o.OIDCGroupsClaim
|
||||
p.AllowUnverifiedEmail = o.Providers[0].OIDCConfig.InsecureAllowUnverifiedEmail
|
||||
p.EmailClaim = o.Providers[0].OIDCConfig.EmailClaim
|
||||
p.GroupsClaim = o.Providers[0].OIDCConfig.GroupsClaim
|
||||
p.Verifier = o.GetOIDCVerifier()
|
||||
|
||||
// TODO (@NickMeves) - Remove This
|
||||
// Backwards Compatibility for Deprecated UserIDClaim option
|
||||
if o.OIDCEmailClaim == providers.OIDCEmailClaim &&
|
||||
o.UserIDClaim != providers.OIDCEmailClaim {
|
||||
p.EmailClaim = o.UserIDClaim
|
||||
if o.Providers[0].OIDCConfig.EmailClaim == providers.OIDCEmailClaim &&
|
||||
o.Providers[0].OIDCConfig.UserIDClaim != providers.OIDCEmailClaim {
|
||||
p.EmailClaim = o.Providers[0].OIDCConfig.UserIDClaim
|
||||
}
|
||||
|
||||
p.SetAllowedGroups(o.AllowedGroups)
|
||||
p.SetAllowedGroups(o.Providers[0].AllowedGroups)
|
||||
|
||||
provider := providers.New(o.ProviderType, p)
|
||||
provider := providers.New(o.Providers[0].Type, p)
|
||||
if provider == nil {
|
||||
msgs = append(msgs, fmt.Sprintf("invalid setting: provider '%s' is not available", o.ProviderType))
|
||||
msgs = append(msgs, fmt.Sprintf("invalid setting: provider '%s' is not available", o.Providers[0].Type))
|
||||
return msgs
|
||||
}
|
||||
o.SetProvider(provider)
|
||||
|
||||
switch p := o.GetProvider().(type) {
|
||||
case *providers.AzureProvider:
|
||||
p.Configure(o.AzureTenant)
|
||||
p.Configure(o.Providers[0].AzureConfig.Tenant)
|
||||
case *providers.GitHubProvider:
|
||||
p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
|
||||
p.SetRepo(o.GitHubRepo, o.GitHubToken)
|
||||
p.SetUsers(o.GitHubUsers)
|
||||
p.SetOrgTeam(o.Providers[0].GitHubConfig.Org, o.Providers[0].GitHubConfig.Team)
|
||||
p.SetRepo(o.Providers[0].GitHubConfig.Repo, o.Providers[0].GitHubConfig.Token)
|
||||
p.SetUsers(o.Providers[0].GitHubConfig.Users)
|
||||
case *providers.KeycloakProvider:
|
||||
// Backwards compatibility with `--keycloak-group` option
|
||||
if len(o.KeycloakGroups) > 0 {
|
||||
p.SetAllowedGroups(o.KeycloakGroups)
|
||||
if len(o.Providers[0].KeycloakConfig.Groups) > 0 {
|
||||
p.SetAllowedGroups(o.Providers[0].KeycloakConfig.Groups)
|
||||
}
|
||||
case *providers.GoogleProvider:
|
||||
if o.GoogleServiceAccountJSON != "" {
|
||||
file, err := os.Open(o.GoogleServiceAccountJSON)
|
||||
if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
|
||||
file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
|
||||
msgs = append(msgs, "invalid Google credentials file: "+o.Providers[0].GoogleConfig.ServiceAccountJSON)
|
||||
} else {
|
||||
groups := o.AllowedGroups
|
||||
groups := o.Providers[0].AllowedGroups
|
||||
// Backwards compatibility with `--google-group` option
|
||||
if len(o.GoogleGroups) > 0 {
|
||||
groups = o.GoogleGroups
|
||||
if len(o.Providers[0].GoogleConfig.Groups) > 0 {
|
||||
groups = o.Providers[0].GoogleConfig.Groups
|
||||
p.SetAllowedGroups(groups)
|
||||
}
|
||||
p.SetGroupRestriction(groups, o.GoogleAdminEmail, file)
|
||||
p.SetGroupRestriction(groups, o.Providers[0].GoogleConfig.AdminEmail, file)
|
||||
}
|
||||
}
|
||||
case *providers.BitbucketProvider:
|
||||
p.SetTeam(o.BitbucketTeam)
|
||||
p.SetRepository(o.BitbucketRepository)
|
||||
p.SetTeam(o.Providers[0].BitbucketConfig.Team)
|
||||
p.SetRepository(o.Providers[0].BitbucketConfig.Repository)
|
||||
case *providers.OIDCProvider:
|
||||
if p.Verifier == nil {
|
||||
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
||||
}
|
||||
case *providers.GitLabProvider:
|
||||
p.Groups = o.GitLabGroup
|
||||
err := p.AddProjects(o.GitlabProjects)
|
||||
p.Groups = o.Providers[0].GitLabConfig.Group
|
||||
err := p.AddProjects(o.Providers[0].GitLabConfig.Projects)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "failed to setup gitlab project access level")
|
||||
}
|
||||
@ -308,7 +285,7 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
|
||||
msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
|
||||
} else {
|
||||
p.Verifier = provider.Verifier(&oidc.Config{
|
||||
ClientID: o.ClientID,
|
||||
ClientID: o.Providers[0].ClientID,
|
||||
})
|
||||
|
||||
p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
|
||||
@ -316,31 +293,31 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
|
||||
}
|
||||
}
|
||||
case *providers.LoginGovProvider:
|
||||
p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
|
||||
p.PubJWKURL, msgs = parseURL(o.Providers[0].LoginGovConfig.PubJWKURL, "pubjwk", msgs)
|
||||
|
||||
// JWT key can be supplied via env variable or file in the filesystem, but not both.
|
||||
switch {
|
||||
case o.JWTKey != "" && o.JWTKeyFile != "":
|
||||
case o.Providers[0].LoginGovConfig.JWTKey != "" && o.Providers[0].LoginGovConfig.JWTKeyFile != "":
|
||||
msgs = append(msgs, "cannot set both jwt-key and jwt-key-file options")
|
||||
case o.JWTKey == "" && o.JWTKeyFile == "":
|
||||
case o.Providers[0].LoginGovConfig.JWTKey == "" && o.Providers[0].LoginGovConfig.JWTKeyFile == "":
|
||||
msgs = append(msgs, "login.gov provider requires a private key for signing JWTs")
|
||||
case o.JWTKey != "":
|
||||
case o.Providers[0].LoginGovConfig.JWTKey != "":
|
||||
// The JWT Key is in the commandline argument
|
||||
signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(o.JWTKey))
|
||||
signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(o.Providers[0].LoginGovConfig.JWTKey))
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not parse RSA Private Key PEM")
|
||||
} else {
|
||||
p.JWTKey = signKey
|
||||
}
|
||||
case o.JWTKeyFile != "":
|
||||
case o.Providers[0].LoginGovConfig.JWTKeyFile != "":
|
||||
// The JWT key is in the filesystem
|
||||
keyData, err := ioutil.ReadFile(o.JWTKeyFile)
|
||||
keyData, err := ioutil.ReadFile(o.Providers[0].LoginGovConfig.JWTKeyFile)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not read key file: "+o.JWTKeyFile)
|
||||
msgs = append(msgs, "could not read key file: "+o.Providers[0].LoginGovConfig.JWTKeyFile)
|
||||
}
|
||||
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not parse private key from PEM file:"+o.JWTKeyFile)
|
||||
msgs = append(msgs, "could not parse private key from PEM file:"+o.Providers[0].LoginGovConfig.JWTKeyFile)
|
||||
} else {
|
||||
p.JWTKey = signKey
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ const (
|
||||
cookieSecret = "secretthirtytwobytes+abcdefghijk"
|
||||
clientID = "bazquux"
|
||||
clientSecret = "xyzzyplugh"
|
||||
providerID = "providerID"
|
||||
)
|
||||
|
||||
func testOptions() *options.Options {
|
||||
@ -27,8 +28,9 @@ func testOptions() *options.Options {
|
||||
URI: "http://127.0.0.1:8080/",
|
||||
})
|
||||
o.Cookie.Secret = cookieSecret
|
||||
o.ClientID = clientID
|
||||
o.ClientSecret = clientSecret
|
||||
o.Providers[0].ID = providerID
|
||||
o.Providers[0].ClientID = clientID
|
||||
o.Providers[0].ClientSecret = clientSecret
|
||||
o.EmailDomains = []string{"*"}
|
||||
return o
|
||||
}
|
||||
@ -48,7 +50,8 @@ func TestNewOptions(t *testing.T) {
|
||||
|
||||
expected := errorMsg([]string{
|
||||
"missing setting: cookie-secret",
|
||||
"missing setting: client-id",
|
||||
"provider has empty id: ids are required for all providers",
|
||||
"provider missing setting: client-id",
|
||||
"missing setting: client-secret or client-secret-file"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
@ -56,8 +59,9 @@ func TestNewOptions(t *testing.T) {
|
||||
func TestClientSecretFileOptionFails(t *testing.T) {
|
||||
o := options.NewOptions()
|
||||
o.Cookie.Secret = cookieSecret
|
||||
o.ClientID = clientID
|
||||
o.ClientSecretFile = clientSecret
|
||||
o.Providers[0].ID = providerID
|
||||
o.Providers[0].ClientID = clientID
|
||||
o.Providers[0].ClientSecretFile = clientSecret
|
||||
o.EmailDomains = []string{"*"}
|
||||
err := Validate(o)
|
||||
assert.NotEqual(t, nil, err)
|
||||
@ -93,8 +97,9 @@ func TestClientSecretFileOption(t *testing.T) {
|
||||
|
||||
o := options.NewOptions()
|
||||
o.Cookie.Secret = cookieSecret
|
||||
o.ClientID = clientID
|
||||
o.ClientSecretFile = clientSecretFileName
|
||||
o.Providers[0].ID = providerID
|
||||
o.Providers[0].ClientID = clientID
|
||||
o.Providers[0].ClientSecretFile = clientSecretFileName
|
||||
o.EmailDomains = []string{"*"}
|
||||
err = Validate(o)
|
||||
assert.Equal(t, nil, err)
|
||||
@ -110,7 +115,7 @@ func TestClientSecretFileOption(t *testing.T) {
|
||||
|
||||
func TestGoogleGroupOptions(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.GoogleGroups = []string{"googlegroup"}
|
||||
o.Providers[0].GoogleConfig.Groups = []string{"googlegroup"}
|
||||
err := Validate(o)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
@ -122,9 +127,9 @@ func TestGoogleGroupOptions(t *testing.T) {
|
||||
|
||||
func TestGoogleGroupInvalidFile(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.GoogleGroups = []string{"test_group"}
|
||||
o.GoogleAdminEmail = "admin@example.com"
|
||||
o.GoogleServiceAccountJSON = "file_doesnt_exist.json"
|
||||
o.Providers[0].GoogleConfig.Groups = []string{"test_group"}
|
||||
o.Providers[0].GoogleConfig.AdminEmail = "admin@example.com"
|
||||
o.Providers[0].GoogleConfig.ServiceAccountJSON = "file_doesnt_exist.json"
|
||||
err := Validate(o)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
@ -225,17 +230,17 @@ func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
|
||||
|
||||
func TestSkipOIDCDiscovery(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.ProviderType = "oidc"
|
||||
o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
|
||||
o.SkipOIDCDiscovery = true
|
||||
o.Providers[0].Type = "oidc"
|
||||
o.Providers[0].OIDCConfig.IssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
|
||||
o.Providers[0].OIDCConfig.SkipDiscovery = true
|
||||
|
||||
err := Validate(o)
|
||||
assert.Equal(t, "invalid configuration:\n"+
|
||||
" missing setting: login-url\n missing setting: redeem-url\n missing setting: oidc-jwks-url", err.Error())
|
||||
|
||||
o.LoginURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in"
|
||||
o.RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in"
|
||||
o.OIDCJwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
|
||||
o.Providers[0].LoginURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in"
|
||||
o.Providers[0].RedeemURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_sign_in"
|
||||
o.Providers[0].OIDCConfig.JwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
|
||||
|
||||
assert.Equal(t, nil, Validate(o))
|
||||
}
|
||||
@ -292,7 +297,7 @@ func TestProviderCAFilesError(t *testing.T) {
|
||||
assert.NoError(t, os.Remove(file.Name()))
|
||||
|
||||
o := testOptions()
|
||||
o.ProviderCAFiles = append(o.ProviderCAFiles, file.Name())
|
||||
o.Providers[0].CAFiles = append(o.Providers[0].CAFiles, file.Name())
|
||||
err = Validate(o)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unable to load provider CA file(s)")
|
||||
|
83
pkg/validation/providers.go
Normal file
83
pkg/validation/providers.go
Normal file
@ -0,0 +1,83 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
)
|
||||
|
||||
// validateProviders is the initial validation migration for multiple providrers
|
||||
// It currently includes only logic that can verify the providers one by one and does not break the valdation pipe
|
||||
func validateProviders(o *options.Options) []string {
|
||||
msgs := []string{}
|
||||
|
||||
// validate general multiple provider configuration
|
||||
if len(o.Providers) == 0 {
|
||||
msgs = append(msgs, "at least one provider has to be defined")
|
||||
}
|
||||
if o.SkipProviderButton && len(o.Providers) > 1 {
|
||||
msgs = append(msgs, "SkipProviderButton and multiple providers are mutually exclusive")
|
||||
}
|
||||
|
||||
providerIDs := make(map[string]struct{})
|
||||
|
||||
for _, provider := range o.Providers {
|
||||
msgs = append(msgs, validateProvider(provider, providerIDs)...)
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func validateProvider(provider options.Provider, providerIDs map[string]struct{}) []string {
|
||||
msgs := []string{}
|
||||
|
||||
if provider.ID == "" {
|
||||
msgs = append(msgs, "provider has empty id: ids are required for all providers")
|
||||
}
|
||||
|
||||
// Ensure provider IDs are unique
|
||||
if _, ok := providerIDs[provider.ID]; ok {
|
||||
msgs = append(msgs, fmt.Sprintf("multiple providers found with id %s: provider ids must be unique", provider.ID))
|
||||
}
|
||||
providerIDs[provider.ID] = struct{}{}
|
||||
|
||||
if provider.ClientID == "" {
|
||||
msgs = append(msgs, "provider missing setting: client-id")
|
||||
}
|
||||
|
||||
// login.gov uses a signed JWT to authenticate, not a client-secret
|
||||
if provider.Type != "login.gov" {
|
||||
if provider.ClientSecret == "" && provider.ClientSecretFile == "" {
|
||||
msgs = append(msgs, "missing setting: client-secret or client-secret-file")
|
||||
}
|
||||
if provider.ClientSecret == "" && provider.ClientSecretFile != "" {
|
||||
_, err := ioutil.ReadFile(provider.ClientSecretFile)
|
||||
if err != nil {
|
||||
msgs = append(msgs, "could not read client secret file: "+provider.ClientSecretFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msgs = append(msgs, validateGoogleConfig(provider)...)
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func validateGoogleConfig(provider options.Provider) []string {
|
||||
msgs := []string{}
|
||||
if len(provider.GoogleConfig.Groups) > 0 ||
|
||||
provider.GoogleConfig.AdminEmail != "" ||
|
||||
provider.GoogleConfig.ServiceAccountJSON != "" {
|
||||
if len(provider.GoogleConfig.Groups) < 1 {
|
||||
msgs = append(msgs, "missing setting: google-group")
|
||||
}
|
||||
if provider.GoogleConfig.AdminEmail == "" {
|
||||
msgs = append(msgs, "missing setting: google-admin-email")
|
||||
}
|
||||
if provider.GoogleConfig.ServiceAccountJSON == "" {
|
||||
msgs = append(msgs, "missing setting: google-service-account-json")
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
84
pkg/validation/providers_test.go
Normal file
84
pkg/validation/providers_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Providers", func() {
|
||||
type validateProvidersTableInput struct {
|
||||
options *options.Options
|
||||
errStrings []string
|
||||
}
|
||||
|
||||
validProvider := options.Provider{
|
||||
ID: "ProviderID",
|
||||
ClientID: "ClientID",
|
||||
ClientSecret: "ClientSecret",
|
||||
}
|
||||
|
||||
validLoginGovProvider := options.Provider{
|
||||
Type: "login.gov",
|
||||
ID: "ProviderIDLoginGov",
|
||||
ClientID: "ClientID",
|
||||
ClientSecret: "ClientSecret",
|
||||
}
|
||||
|
||||
missingIDProvider := options.Provider{
|
||||
ClientID: "ClientID",
|
||||
ClientSecret: "ClientSecret",
|
||||
}
|
||||
|
||||
missingProvider := "at least one provider has to be defined"
|
||||
emptyIDMsg := "provider has empty id: ids are required for all providers"
|
||||
duplicateProviderIDMsg := "multiple providers found with id ProviderID: provider ids must be unique"
|
||||
skipButtonAndMultipleProvidersMsg := "SkipProviderButton and multiple providers are mutually exclusive"
|
||||
|
||||
DescribeTable("validateProviders",
|
||||
func(o *validateProvidersTableInput) {
|
||||
Expect(validateProviders(o.options)).To(ConsistOf(o.errStrings))
|
||||
},
|
||||
Entry("with no providers", &validateProvidersTableInput{
|
||||
options: &options.Options{},
|
||||
errStrings: []string{missingProvider},
|
||||
}),
|
||||
Entry("with valid providers", &validateProvidersTableInput{
|
||||
options: &options.Options{
|
||||
Providers: options.Providers{
|
||||
validProvider,
|
||||
validLoginGovProvider,
|
||||
},
|
||||
},
|
||||
errStrings: []string{},
|
||||
}),
|
||||
Entry("with an empty providerID", &validateProvidersTableInput{
|
||||
options: &options.Options{
|
||||
Providers: options.Providers{
|
||||
missingIDProvider,
|
||||
},
|
||||
},
|
||||
errStrings: []string{emptyIDMsg},
|
||||
}),
|
||||
Entry("with same providerID", &validateProvidersTableInput{
|
||||
options: &options.Options{
|
||||
Providers: options.Providers{
|
||||
validProvider,
|
||||
validProvider,
|
||||
},
|
||||
},
|
||||
errStrings: []string{duplicateProviderIDMsg},
|
||||
}),
|
||||
Entry("with multiple providers and skip provider button", &validateProvidersTableInput{
|
||||
options: &options.Options{
|
||||
SkipProviderButton: true,
|
||||
Providers: options.Providers{
|
||||
validProvider,
|
||||
validLoginGovProvider,
|
||||
},
|
||||
},
|
||||
errStrings: []string{skipButtonAndMultipleProvidersMsg},
|
||||
}),
|
||||
)
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user