mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-04-15 11:56:49 +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
|
## 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
|
# V7.1.2
|
||||||
|
|
||||||
## Release Highlights
|
## Release Highlights
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
http_address="0.0.0.0:4180"
|
http_address="0.0.0.0:4180"
|
||||||
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
||||||
provider="oidc"
|
|
||||||
email_domains="example.com"
|
email_domains="example.com"
|
||||||
oidc_issuer_url="http://dex.localhost:4190/dex"
|
|
||||||
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
|
||||||
client_id="oauth2-proxy"
|
|
||||||
cookie_secure="false"
|
cookie_secure="false"
|
||||||
|
|
||||||
redirect_url="http://localhost:4180/oauth2/callback"
|
redirect_url="http://localhost:4180/oauth2/callback"
|
||||||
|
@ -15,3 +15,9 @@ injectRequestHeaders:
|
|||||||
- name: X-Forwarded-Preferred-Username
|
- name: X-Forwarded-Preferred-Username
|
||||||
values:
|
values:
|
||||||
- claim: preferred_username
|
- 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. |
|
| `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. |
|
| `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. |
|
| `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
|
### 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".
|
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
|
### Header
|
||||||
|
|
||||||
(**Appears on:** [AlphaOptions](#alphaoptions))
|
(**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. |
|
| `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. |
|
| `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
|
### SecretSource
|
||||||
|
|
||||||
(**Appears on:** [ClaimSource](#claimsource), [HeaderValue](#headervalue), [TLS](#tls))
|
(**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/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/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.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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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"
|
upstreams="http://httpbin"
|
||||||
set_basic_auth="true"
|
set_basic_auth="true"
|
||||||
basic_auth_password="super-secret-password"
|
basic_auth_password="super-secret-password"
|
||||||
|
client_id="oauth2-proxy"
|
||||||
|
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
||||||
`
|
`
|
||||||
|
|
||||||
const testAlphaConfig = `
|
const testAlphaConfig = `
|
||||||
@ -57,15 +59,23 @@ injectResponseHeaders:
|
|||||||
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
|
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
|
||||||
server:
|
server:
|
||||||
bindAddress: "127.0.0.1:4180"
|
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 = `
|
const testCoreConfig = `
|
||||||
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
||||||
provider="oidc"
|
|
||||||
email_domains="example.com"
|
email_domains="example.com"
|
||||||
oidc_issuer_url="http://dex.localhost:4190/dex"
|
|
||||||
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
|
||||||
client_id="oauth2-proxy"
|
|
||||||
cookie_secure="false"
|
cookie_secure="false"
|
||||||
|
|
||||||
redirect_url="http://localhost:4180/oauth2/callback"
|
redirect_url="http://localhost:4180/oauth2/callback"
|
||||||
@ -85,11 +95,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
opts.Cookie.Secret = "OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
opts.Cookie.Secret = "OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
|
||||||
opts.ProviderType = "oidc"
|
|
||||||
opts.EmailDomains = []string{"example.com"}
|
opts.EmailDomains = []string{"example.com"}
|
||||||
opts.OIDCIssuerURL = "http://dex.localhost:4190/dex"
|
|
||||||
opts.ClientSecret = "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
|
|
||||||
opts.ClientID = "oauth2-proxy"
|
|
||||||
opts.Cookie.Secure = false
|
opts.Cookie.Secure = false
|
||||||
opts.RawRedirectURL = "http://localhost:4180/oauth2/callback"
|
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.InjectRequestHeaders = append([]options.Header{authHeader}, opts.InjectRequestHeaders...)
|
||||||
opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader)
|
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
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +228,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
|||||||
configContent: testCoreConfig,
|
configContent: testCoreConfig,
|
||||||
alphaConfigContent: testAlphaConfig + ":",
|
alphaConfigContent: testAlphaConfig + ":",
|
||||||
expectedOptions: func() *options.Options { return nil },
|
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{
|
Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
|
||||||
configContent: testCoreConfig + "unknown_field=\"something\"",
|
configContent: testCoreConfig + "unknown_field=\"something\"",
|
||||||
|
@ -122,7 +122,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||||||
Footer: opts.Templates.Footer,
|
Footer: opts.Templates.Footer,
|
||||||
Version: VERSION,
|
Version: VERSION,
|
||||||
Debug: opts.Templates.Debug,
|
Debug: opts.Templates.Debug,
|
||||||
ProviderName: buildProviderName(opts.GetProvider(), opts.ProviderName),
|
ProviderName: buildProviderName(opts.GetProvider(), opts.Providers[0].Name),
|
||||||
SignInMessage: buildSignInMessage(opts),
|
SignInMessage: buildSignInMessage(opts),
|
||||||
DisplayLoginForm: basicAuthValidator != nil && opts.Templates.DisplayLoginForm,
|
DisplayLoginForm: basicAuthValidator != nil && opts.Templates.DisplayLoginForm,
|
||||||
})
|
})
|
||||||
@ -136,7 +136,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.SkipJwtBearerTokens {
|
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 {
|
for _, issuer := range opts.ExtraJwtIssuers {
|
||||||
logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer)
|
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)
|
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"
|
refresh := "disabled"
|
||||||
if opts.Cookie.Refresh != time.Duration(0) {
|
if opts.Cookie.Refresh != time.Duration(0) {
|
||||||
refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh)
|
refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh)
|
||||||
|
@ -981,7 +981,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
|
|||||||
ProviderData: &providers.ProviderData{},
|
ProviderData: &providers.ProviderData{},
|
||||||
ValidToken: opts.providerValidateCookieResponse,
|
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.rw = httptest.NewRecorder()
|
||||||
pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
|
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)
|
err := validation.Validate(pcTest.opts)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -2292,8 +2292,9 @@ func Test_noCacheHeaders(t *testing.T) {
|
|||||||
func baseTestOptions() *options.Options {
|
func baseTestOptions() *options.Options {
|
||||||
opts := options.NewOptions()
|
opts := options.NewOptions()
|
||||||
opts.Cookie.Secret = rawCookieSecret
|
opts.Cookie.Secret = rawCookieSecret
|
||||||
opts.ClientID = clientID
|
opts.Providers[0].ID = "providerID"
|
||||||
opts.ClientSecret = clientSecret
|
opts.Providers[0].ClientID = clientID
|
||||||
|
opts.Providers[0].ClientSecret = clientSecret
|
||||||
opts.EmailDomains = []string{"*"}
|
opts.EmailDomains = []string{"*"}
|
||||||
|
|
||||||
// Default injected headers for legacy configuration
|
// Default injected headers for legacy configuration
|
||||||
@ -2786,7 +2787,7 @@ func TestProxyAllowedGroups(t *testing.T) {
|
|||||||
t.Cleanup(upstreamServer.Close)
|
t.Cleanup(upstreamServer.Close)
|
||||||
|
|
||||||
test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
|
test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
|
||||||
opts.AllowedGroups = tt.allowedGroups
|
opts.Providers[0].AllowedGroups = tt.allowedGroups
|
||||||
opts.UpstreamServers = options.Upstreams{
|
opts.UpstreamServers = options.Upstreams{
|
||||||
{
|
{
|
||||||
ID: upstreamServer.URL,
|
ID: upstreamServer.URL,
|
||||||
@ -2915,7 +2916,7 @@ func TestAuthOnlyAllowedGroups(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
|
test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {
|
||||||
opts.AllowedGroups = tc.allowedGroups
|
opts.Providers[0].AllowedGroups = tc.allowedGroups
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -40,6 +40,9 @@ type AlphaOptions struct {
|
|||||||
// This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
|
// 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.
|
// To use the secure server you must configure a TLS certificate and key.
|
||||||
MetricsServer Server `json:"metricsServer,omitempty"`
|
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
|
// 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.InjectResponseHeaders = a.InjectResponseHeaders
|
||||||
opts.Server = a.Server
|
opts.Server = a.Server
|
||||||
opts.MetricsServer = a.MetricsServer
|
opts.MetricsServer = a.MetricsServer
|
||||||
|
opts.Providers = a.Providers
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractFrom populates the fields in the AlphaOptions with the values from
|
// 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.InjectResponseHeaders = opts.InjectResponseHeaders
|
||||||
a.Server = opts.Server
|
a.Server = opts.Server
|
||||||
a.MetricsServer = opts.MetricsServer
|
a.MetricsServer = opts.MetricsServer
|
||||||
|
a.Providers = opts.Providers
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||||
|
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,6 +22,9 @@ type LegacyOptions struct {
|
|||||||
// Legacy options for the server address and TLS
|
// Legacy options for the server address and TLS
|
||||||
LegacyServer LegacyServer `cfg:",squash"`
|
LegacyServer LegacyServer `cfg:",squash"`
|
||||||
|
|
||||||
|
// Legacy options for single provider
|
||||||
|
LegacyProvider LegacyProvider `cfg:",squash"`
|
||||||
|
|
||||||
Options Options `cfg:",squash"`
|
Options Options `cfg:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +47,15 @@ func NewLegacyOptions() *LegacyOptions {
|
|||||||
HTTPSAddress: ":443",
|
HTTPSAddress: ":443",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
LegacyProvider: LegacyProvider{
|
||||||
|
ProviderType: "google",
|
||||||
|
AzureTenant: "common",
|
||||||
|
ApprovalPrompt: "force",
|
||||||
|
UserIDClaim: "email",
|
||||||
|
OIDCEmailClaim: "email",
|
||||||
|
OIDCGroupsClaim: "groups",
|
||||||
|
},
|
||||||
|
|
||||||
Options: *NewOptions(),
|
Options: *NewOptions(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,6 +66,7 @@ func NewLegacyFlagSet() *pflag.FlagSet {
|
|||||||
flagSet.AddFlagSet(legacyUpstreamsFlagSet())
|
flagSet.AddFlagSet(legacyUpstreamsFlagSet())
|
||||||
flagSet.AddFlagSet(legacyHeadersFlagSet())
|
flagSet.AddFlagSet(legacyHeadersFlagSet())
|
||||||
flagSet.AddFlagSet(legacyServerFlagset())
|
flagSet.AddFlagSet(legacyServerFlagset())
|
||||||
|
flagSet.AddFlagSet(legacyProviderFlagSet())
|
||||||
|
|
||||||
return flagSet
|
return flagSet
|
||||||
}
|
}
|
||||||
@ -65,10 +79,17 @@ func (l *LegacyOptions) ToOptions() (*Options, error) {
|
|||||||
l.Options.UpstreamServers = upstreams
|
l.Options.UpstreamServers = upstreams
|
||||||
|
|
||||||
l.Options.InjectRequestHeaders, l.Options.InjectResponseHeaders = l.LegacyHeaders.convert()
|
l.Options.InjectRequestHeaders, l.Options.InjectResponseHeaders = l.LegacyHeaders.convert()
|
||||||
|
|
||||||
l.Options.Server, l.Options.MetricsServer = l.LegacyServer.convert()
|
l.Options.Server, l.Options.MetricsServer = l.LegacyServer.convert()
|
||||||
|
|
||||||
l.Options.LegacyPreferEmailToUser = l.LegacyHeaders.PreferEmailToUser
|
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
|
return &l.Options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,6 +464,106 @@ func legacyServerFlagset() *pflag.FlagSet {
|
|||||||
return 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) {
|
func (l LegacyServer) convert() (Server, Server) {
|
||||||
appServer := Server{
|
appServer := Server{
|
||||||
BindAddress: l.HTTPAddress,
|
BindAddress: l.HTTPAddress,
|
||||||
@ -482,3 +603,91 @@ func (l LegacyServer) convert() (Server, Server) {
|
|||||||
|
|
||||||
return appServer, metricsServer
|
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.ProxyWebSockets = true
|
||||||
legacyOpts.LegacyUpstreams.SSLUpstreamInsecureSkipVerify = true
|
legacyOpts.LegacyUpstreams.SSLUpstreamInsecureSkipVerify = true
|
||||||
legacyOpts.LegacyUpstreams.Upstreams = []string{"http://foo.bar/baz", "file:///var/lib/website#/bar", "static://204"}
|
legacyOpts.LegacyUpstreams.Upstreams = []string{"http://foo.bar/baz", "file:///var/lib/website#/bar", "static://204"}
|
||||||
|
legacyOpts.LegacyProvider.ClientID = "oauth-proxy"
|
||||||
|
|
||||||
truth := true
|
truth := true
|
||||||
staticCode := 204
|
staticCode := 204
|
||||||
@ -110,6 +111,9 @@ var _ = Describe("Legacy Options", func() {
|
|||||||
BindAddress: "127.0.0.1:4180",
|
BindAddress: "127.0.0.1:4180",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts.Providers[0].ClientID = "oauth-proxy"
|
||||||
|
opts.Providers[0].ID = "google=oauth-proxy"
|
||||||
|
|
||||||
converted, err := legacyOpts.ToOptions()
|
converted, err := legacyOpts.ToOptions()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(converted).To(Equal(opts))
|
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"
|
invalidHTTPErrMsg := "could not parse upstream \":foo\": parse \":foo\": missing protocol scheme"
|
||||||
|
|
||||||
DescribeTable("convertLegacyUpstreams",
|
DescribeTable("convertLegacyUpstreams",
|
||||||
func(o *convertUpstreamsTableInput) {
|
func(in *convertUpstreamsTableInput) {
|
||||||
legacyUpstreams := LegacyUpstreams{
|
legacyUpstreams := LegacyUpstreams{
|
||||||
Upstreams: o.upstreamStrings,
|
Upstreams: in.upstreamStrings,
|
||||||
SSLUpstreamInsecureSkipVerify: skipVerify,
|
SSLUpstreamInsecureSkipVerify: skipVerify,
|
||||||
PassHostHeader: passHostHeader,
|
PassHostHeader: passHostHeader,
|
||||||
ProxyWebSockets: proxyWebSockets,
|
ProxyWebSockets: proxyWebSockets,
|
||||||
@ -207,14 +211,14 @@ var _ = Describe("Legacy Options", func() {
|
|||||||
|
|
||||||
upstreams, err := legacyUpstreams.convert()
|
upstreams, err := legacyUpstreams.convert()
|
||||||
|
|
||||||
if o.errMsg != "" {
|
if in.errMsg != "" {
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
Expect(err.Error()).To(Equal(o.errMsg))
|
Expect(err.Error()).To(Equal(in.errMsg))
|
||||||
} else {
|
} else {
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect(upstreams).To(ConsistOf(o.expectedUpstreams))
|
Expect(upstreams).To(ConsistOf(in.expectedUpstreams))
|
||||||
},
|
},
|
||||||
Entry("with no upstreams", &convertUpstreamsTableInput{
|
Entry("with no upstreams", &convertUpstreamsTableInput{
|
||||||
upstreamStrings: []string{},
|
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() {
|
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() {
|
Context("with a testOptions structure", func() {
|
||||||
type TestOptionSubStruct struct {
|
type TestOptionSubStruct struct {
|
||||||
StringSliceOption []string `flag:"string-slice-option" cfg:"string_slice_option"`
|
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{
|
Entry("with an empty Options struct, should return default values", &testOptionsTableInput{
|
||||||
flagSet: NewFlagSet,
|
flagSet: NewFlagSet,
|
||||||
input: &Options{},
|
input: &Options{},
|
||||||
expectedOutput: NewOptions(),
|
expectedOutput: optionsWithNilProvider,
|
||||||
}),
|
}),
|
||||||
Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{
|
Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{
|
||||||
flagSet: NewLegacyFlagSet,
|
flagSet: NewLegacyFlagSet,
|
||||||
input: &LegacyOptions{},
|
input: &LegacyOptions{},
|
||||||
expectedOutput: NewLegacyOptions(),
|
expectedOutput: legacyOptionsWithNilProvider,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -27,27 +27,10 @@ type Options struct {
|
|||||||
TrustedIPs []string `flag:"trusted-ip" cfg:"trusted_ips"`
|
TrustedIPs []string `flag:"trusted-ip" cfg:"trusted_ips"`
|
||||||
ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
|
ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
|
||||||
RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
|
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"`
|
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"`
|
EmailDomains []string `flag:"email-domain" cfg:"email_domains"`
|
||||||
WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_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"`
|
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
|
||||||
HtpasswdUserGroups []string `flag:"htpasswd-user-group" cfg:"htpasswd_user_groups"`
|
HtpasswdUserGroups []string `flag:"htpasswd-user-group" cfg:"htpasswd_user_groups"`
|
||||||
|
|
||||||
@ -66,6 +49,8 @@ type Options struct {
|
|||||||
Server Server `cfg:",internal"`
|
Server Server `cfg:",internal"`
|
||||||
MetricsServer Server `cfg:",internal"`
|
MetricsServer Server `cfg:",internal"`
|
||||||
|
|
||||||
|
Providers Providers `cfg:",internal"`
|
||||||
|
|
||||||
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
|
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
|
||||||
SkipAuthRoutes []string `flag:"skip-auth-route" cfg:"skip_auth_routes"`
|
SkipAuthRoutes []string `flag:"skip-auth-route" cfg:"skip_auth_routes"`
|
||||||
SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
|
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"`
|
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
|
||||||
SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
|
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"`
|
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"`
|
GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks"`
|
||||||
|
|
||||||
// This is used for backwards compatibility for basic auth users
|
// 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 {
|
func NewOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
ProxyPrefix: "/oauth2",
|
ProxyPrefix: "/oauth2",
|
||||||
ProviderType: "google",
|
Providers: providerDefaults(),
|
||||||
PingPath: "/ping",
|
PingPath: "/ping",
|
||||||
RealClientIPHeader: "X-Real-IP",
|
RealClientIPHeader: "X-Real-IP",
|
||||||
ForceHTTPS: false,
|
ForceHTTPS: false,
|
||||||
Cookie: cookieDefaults(),
|
Cookie: cookieDefaults(),
|
||||||
Session: sessionOptionsDefaults(),
|
Session: sessionOptionsDefaults(),
|
||||||
Templates: templatesDefaults(),
|
Templates: templatesDefaults(),
|
||||||
AzureTenant: "common",
|
|
||||||
SkipAuthPreflight: false,
|
SkipAuthPreflight: false,
|
||||||
Prompt: "", // Change to "login" when ApprovalPrompt officially deprecated
|
|
||||||
ApprovalPrompt: "force",
|
|
||||||
InsecureOIDCAllowUnverifiedEmail: false,
|
|
||||||
SkipOIDCDiscovery: false,
|
|
||||||
Logging: loggingDefaults(),
|
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("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("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("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.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)")
|
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.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.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("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.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(cookieFlagSet())
|
||||||
flagSet.AddFlagSet(loggingFlagSet())
|
flagSet.AddFlagSet(loggingFlagSet())
|
||||||
flagSet.AddFlagSet(templatesFlagSet())
|
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, validateRedisSessionStore(o)...)
|
||||||
msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
|
msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
|
||||||
msgs = append(msgs, prefixValues("injectResponseHeaders: ", validateHeaders(o.InjectResponseHeaders)...)...)
|
msgs = append(msgs, prefixValues("injectResponseHeaders: ", validateHeaders(o.InjectResponseHeaders)...)...)
|
||||||
|
msgs = append(msgs, validateProviders(o)...)
|
||||||
msgs = configureLogger(o.Logging, msgs)
|
msgs = configureLogger(o.Logging, msgs)
|
||||||
msgs = parseSignatureKey(o, msgs)
|
msgs = parseSignatureKey(o, msgs)
|
||||||
|
|
||||||
@ -39,8 +40,8 @@ func Validate(o *options.Options) error {
|
|||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
http.DefaultClient = &http.Client{Transport: insecureTransport}
|
http.DefaultClient = &http.Client{Transport: insecureTransport}
|
||||||
} else if len(o.ProviderCAFiles) > 0 {
|
} else if len(o.Providers[0].CAFiles) > 0 {
|
||||||
pool, err := util.GetCertPool(o.ProviderCAFiles)
|
pool, err := util.GetCertPool(o.Providers[0].CAFiles)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
transport.TLSClientConfig = &tls.Config{
|
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 == "" {
|
if o.AuthenticatedEmailsFile == "" && len(o.EmailDomains) == 0 && o.HtpasswdFile == "" {
|
||||||
msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
|
msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
|
||||||
"\n use email-domain=* to authorize all email addresses")
|
"\n use email-domain=* to authorize all email addresses")
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.OIDCIssuerURL != "" {
|
if o.Providers[0].OIDCConfig.IssuerURL != "" {
|
||||||
|
|
||||||
ctx := context.Background()
|
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
|
// 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
|
// (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.
|
// we get the URLs, we'll just use the non-discovery path.
|
||||||
|
|
||||||
logger.Printf("Performing OIDC Discovery...")
|
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).
|
body, err := requests.New(requestURL).
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Do().
|
Do().
|
||||||
@ -96,23 +82,23 @@ func Validate(o *options.Options) error {
|
|||||||
// Prefer manually configured URLs. It's a bit unclear
|
// Prefer manually configured URLs. It's a bit unclear
|
||||||
// why you'd be doing discovery and also providing the URLs
|
// why you'd be doing discovery and also providing the URLs
|
||||||
// explicitly though...
|
// explicitly though...
|
||||||
if o.LoginURL == "" {
|
if o.Providers[0].LoginURL == "" {
|
||||||
o.LoginURL = body.Get("authorization_endpoint").MustString()
|
o.Providers[0].LoginURL = body.Get("authorization_endpoint").MustString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.RedeemURL == "" {
|
if o.Providers[0].RedeemURL == "" {
|
||||||
o.RedeemURL = body.Get("token_endpoint").MustString()
|
o.Providers[0].RedeemURL = body.Get("token_endpoint").MustString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.OIDCJwksURL == "" {
|
if o.Providers[0].OIDCConfig.JwksURL == "" {
|
||||||
o.OIDCJwksURL = body.Get("jwks_uri").MustString()
|
o.Providers[0].OIDCConfig.JwksURL = body.Get("jwks_uri").MustString()
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.ProfileURL == "" {
|
if o.Providers[0].ProfileURL == "" {
|
||||||
o.ProfileURL = body.Get("userinfo_endpoint").MustString()
|
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.
|
// instead of metadata discovery if we enable -skip-oidc-discovery.
|
||||||
// In this case we need to make sure the required endpoints for
|
// In this case we need to make sure the required endpoints for
|
||||||
// the provider are configured.
|
// the provider are configured.
|
||||||
if o.SkipOIDCDiscovery {
|
if o.Providers[0].OIDCConfig.SkipDiscovery {
|
||||||
if o.LoginURL == "" {
|
if o.Providers[0].LoginURL == "" {
|
||||||
msgs = append(msgs, "missing setting: login-url")
|
msgs = append(msgs, "missing setting: login-url")
|
||||||
}
|
}
|
||||||
if o.RedeemURL == "" {
|
if o.Providers[0].RedeemURL == "" {
|
||||||
msgs = append(msgs, "missing setting: redeem-url")
|
msgs = append(msgs, "missing setting: redeem-url")
|
||||||
}
|
}
|
||||||
if o.OIDCJwksURL == "" {
|
if o.Providers[0].OIDCConfig.JwksURL == "" {
|
||||||
msgs = append(msgs, "missing setting: oidc-jwks-url")
|
msgs = append(msgs, "missing setting: oidc-jwks-url")
|
||||||
}
|
}
|
||||||
keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
|
keySet := oidc.NewRemoteKeySet(ctx, o.Providers[0].OIDCConfig.JwksURL)
|
||||||
o.SetOIDCVerifier(oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
|
o.SetOIDCVerifier(oidc.NewVerifier(o.Providers[0].OIDCConfig.IssuerURL, keySet, &oidc.Config{
|
||||||
ClientID: o.ClientID,
|
ClientID: o.Providers[0].ClientID,
|
||||||
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
// Configure discoverable provider data.
|
// Configure discoverable provider data.
|
||||||
provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
|
provider, err := oidc.NewProvider(ctx, o.Providers[0].OIDCConfig.IssuerURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
|
o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
|
||||||
ClientID: o.ClientID,
|
ClientID: o.Providers[0].ClientID,
|
||||||
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
SkipIssuerCheck: o.Providers[0].OIDCConfig.InsecureSkipIssuerVerification,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
o.LoginURL = provider.Endpoint().AuthURL
|
o.Providers[0].LoginURL = provider.Endpoint().AuthURL
|
||||||
o.RedeemURL = provider.Endpoint().TokenURL
|
o.Providers[0].RedeemURL = provider.Endpoint().TokenURL
|
||||||
}
|
}
|
||||||
if o.Scope == "" {
|
if o.Providers[0].Scope == "" {
|
||||||
o.Scope = "openid email profile"
|
o.Providers[0].Scope = "openid email profile"
|
||||||
|
|
||||||
if len(o.AllowedGroups) > 0 {
|
if len(o.Providers[0].AllowedGroups) > 0 {
|
||||||
o.Scope += " groups"
|
o.Providers[0].Scope += " groups"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if o.Providers[0].OIDCConfig.UserIDClaim == "" {
|
||||||
|
o.Providers[0].OIDCConfig.UserIDClaim = "email"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.SkipJwtBearerTokens {
|
if o.SkipJwtBearerTokens {
|
||||||
@ -183,18 +172,6 @@ func Validate(o *options.Options) error {
|
|||||||
msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
|
msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
|
||||||
msgs = parseProviderInfo(o, msgs)
|
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 {
|
if o.ReverseProxy {
|
||||||
parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)
|
parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -220,79 +197,79 @@ func Validate(o *options.Options) error {
|
|||||||
|
|
||||||
func parseProviderInfo(o *options.Options, msgs []string) []string {
|
func parseProviderInfo(o *options.Options, msgs []string) []string {
|
||||||
p := &providers.ProviderData{
|
p := &providers.ProviderData{
|
||||||
Scope: o.Scope,
|
Scope: o.Providers[0].Scope,
|
||||||
ClientID: o.ClientID,
|
ClientID: o.Providers[0].ClientID,
|
||||||
ClientSecret: o.ClientSecret,
|
ClientSecret: o.Providers[0].ClientSecret,
|
||||||
ClientSecretFile: o.ClientSecretFile,
|
ClientSecretFile: o.Providers[0].ClientSecretFile,
|
||||||
Prompt: o.Prompt,
|
Prompt: o.Providers[0].Prompt,
|
||||||
ApprovalPrompt: o.ApprovalPrompt,
|
ApprovalPrompt: o.Providers[0].ApprovalPrompt,
|
||||||
AcrValues: o.AcrValues,
|
AcrValues: o.Providers[0].AcrValues,
|
||||||
}
|
}
|
||||||
p.LoginURL, msgs = parseURL(o.LoginURL, "login", msgs)
|
p.LoginURL, msgs = parseURL(o.Providers[0].LoginURL, "login", msgs)
|
||||||
p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs)
|
p.RedeemURL, msgs = parseURL(o.Providers[0].RedeemURL, "redeem", msgs)
|
||||||
p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs)
|
p.ProfileURL, msgs = parseURL(o.Providers[0].ProfileURL, "profile", msgs)
|
||||||
p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
|
p.ValidateURL, msgs = parseURL(o.Providers[0].ValidateURL, "validate", msgs)
|
||||||
p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
|
p.ProtectedResource, msgs = parseURL(o.Providers[0].ProtectedResource, "resource", msgs)
|
||||||
|
|
||||||
// Make the OIDC options available to all providers that support it
|
// Make the OIDC options available to all providers that support it
|
||||||
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
|
p.AllowUnverifiedEmail = o.Providers[0].OIDCConfig.InsecureAllowUnverifiedEmail
|
||||||
p.EmailClaim = o.OIDCEmailClaim
|
p.EmailClaim = o.Providers[0].OIDCConfig.EmailClaim
|
||||||
p.GroupsClaim = o.OIDCGroupsClaim
|
p.GroupsClaim = o.Providers[0].OIDCConfig.GroupsClaim
|
||||||
p.Verifier = o.GetOIDCVerifier()
|
p.Verifier = o.GetOIDCVerifier()
|
||||||
|
|
||||||
// TODO (@NickMeves) - Remove This
|
// TODO (@NickMeves) - Remove This
|
||||||
// Backwards Compatibility for Deprecated UserIDClaim option
|
// Backwards Compatibility for Deprecated UserIDClaim option
|
||||||
if o.OIDCEmailClaim == providers.OIDCEmailClaim &&
|
if o.Providers[0].OIDCConfig.EmailClaim == providers.OIDCEmailClaim &&
|
||||||
o.UserIDClaim != providers.OIDCEmailClaim {
|
o.Providers[0].OIDCConfig.UserIDClaim != providers.OIDCEmailClaim {
|
||||||
p.EmailClaim = o.UserIDClaim
|
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 {
|
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
|
return msgs
|
||||||
}
|
}
|
||||||
o.SetProvider(provider)
|
o.SetProvider(provider)
|
||||||
|
|
||||||
switch p := o.GetProvider().(type) {
|
switch p := o.GetProvider().(type) {
|
||||||
case *providers.AzureProvider:
|
case *providers.AzureProvider:
|
||||||
p.Configure(o.AzureTenant)
|
p.Configure(o.Providers[0].AzureConfig.Tenant)
|
||||||
case *providers.GitHubProvider:
|
case *providers.GitHubProvider:
|
||||||
p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
|
p.SetOrgTeam(o.Providers[0].GitHubConfig.Org, o.Providers[0].GitHubConfig.Team)
|
||||||
p.SetRepo(o.GitHubRepo, o.GitHubToken)
|
p.SetRepo(o.Providers[0].GitHubConfig.Repo, o.Providers[0].GitHubConfig.Token)
|
||||||
p.SetUsers(o.GitHubUsers)
|
p.SetUsers(o.Providers[0].GitHubConfig.Users)
|
||||||
case *providers.KeycloakProvider:
|
case *providers.KeycloakProvider:
|
||||||
// Backwards compatibility with `--keycloak-group` option
|
// Backwards compatibility with `--keycloak-group` option
|
||||||
if len(o.KeycloakGroups) > 0 {
|
if len(o.Providers[0].KeycloakConfig.Groups) > 0 {
|
||||||
p.SetAllowedGroups(o.KeycloakGroups)
|
p.SetAllowedGroups(o.Providers[0].KeycloakConfig.Groups)
|
||||||
}
|
}
|
||||||
case *providers.GoogleProvider:
|
case *providers.GoogleProvider:
|
||||||
if o.GoogleServiceAccountJSON != "" {
|
if o.Providers[0].GoogleConfig.ServiceAccountJSON != "" {
|
||||||
file, err := os.Open(o.GoogleServiceAccountJSON)
|
file, err := os.Open(o.Providers[0].GoogleConfig.ServiceAccountJSON)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
groups := o.AllowedGroups
|
groups := o.Providers[0].AllowedGroups
|
||||||
// Backwards compatibility with `--google-group` option
|
// Backwards compatibility with `--google-group` option
|
||||||
if len(o.GoogleGroups) > 0 {
|
if len(o.Providers[0].GoogleConfig.Groups) > 0 {
|
||||||
groups = o.GoogleGroups
|
groups = o.Providers[0].GoogleConfig.Groups
|
||||||
p.SetAllowedGroups(groups)
|
p.SetAllowedGroups(groups)
|
||||||
}
|
}
|
||||||
p.SetGroupRestriction(groups, o.GoogleAdminEmail, file)
|
p.SetGroupRestriction(groups, o.Providers[0].GoogleConfig.AdminEmail, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *providers.BitbucketProvider:
|
case *providers.BitbucketProvider:
|
||||||
p.SetTeam(o.BitbucketTeam)
|
p.SetTeam(o.Providers[0].BitbucketConfig.Team)
|
||||||
p.SetRepository(o.BitbucketRepository)
|
p.SetRepository(o.Providers[0].BitbucketConfig.Repository)
|
||||||
case *providers.OIDCProvider:
|
case *providers.OIDCProvider:
|
||||||
if p.Verifier == nil {
|
if p.Verifier == nil {
|
||||||
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
||||||
}
|
}
|
||||||
case *providers.GitLabProvider:
|
case *providers.GitLabProvider:
|
||||||
p.Groups = o.GitLabGroup
|
p.Groups = o.Providers[0].GitLabConfig.Group
|
||||||
err := p.AddProjects(o.GitlabProjects)
|
err := p.AddProjects(o.Providers[0].GitLabConfig.Projects)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msgs = append(msgs, "failed to setup gitlab project access level")
|
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")
|
msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
|
||||||
} else {
|
} else {
|
||||||
p.Verifier = provider.Verifier(&oidc.Config{
|
p.Verifier = provider.Verifier(&oidc.Config{
|
||||||
ClientID: o.ClientID,
|
ClientID: o.Providers[0].ClientID,
|
||||||
})
|
})
|
||||||
|
|
||||||
p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
|
p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
|
||||||
@ -316,31 +293,31 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *providers.LoginGovProvider:
|
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.
|
// JWT key can be supplied via env variable or file in the filesystem, but not both.
|
||||||
switch {
|
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")
|
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")
|
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
|
// 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 {
|
if err != nil {
|
||||||
msgs = append(msgs, "could not parse RSA Private Key PEM")
|
msgs = append(msgs, "could not parse RSA Private Key PEM")
|
||||||
} else {
|
} else {
|
||||||
p.JWTKey = signKey
|
p.JWTKey = signKey
|
||||||
}
|
}
|
||||||
case o.JWTKeyFile != "":
|
case o.Providers[0].LoginGovConfig.JWTKeyFile != "":
|
||||||
// The JWT key is in the filesystem
|
// 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 {
|
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)
|
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
p.JWTKey = signKey
|
p.JWTKey = signKey
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ const (
|
|||||||
cookieSecret = "secretthirtytwobytes+abcdefghijk"
|
cookieSecret = "secretthirtytwobytes+abcdefghijk"
|
||||||
clientID = "bazquux"
|
clientID = "bazquux"
|
||||||
clientSecret = "xyzzyplugh"
|
clientSecret = "xyzzyplugh"
|
||||||
|
providerID = "providerID"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testOptions() *options.Options {
|
func testOptions() *options.Options {
|
||||||
@ -27,8 +28,9 @@ func testOptions() *options.Options {
|
|||||||
URI: "http://127.0.0.1:8080/",
|
URI: "http://127.0.0.1:8080/",
|
||||||
})
|
})
|
||||||
o.Cookie.Secret = cookieSecret
|
o.Cookie.Secret = cookieSecret
|
||||||
o.ClientID = clientID
|
o.Providers[0].ID = providerID
|
||||||
o.ClientSecret = clientSecret
|
o.Providers[0].ClientID = clientID
|
||||||
|
o.Providers[0].ClientSecret = clientSecret
|
||||||
o.EmailDomains = []string{"*"}
|
o.EmailDomains = []string{"*"}
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
@ -48,7 +50,8 @@ func TestNewOptions(t *testing.T) {
|
|||||||
|
|
||||||
expected := errorMsg([]string{
|
expected := errorMsg([]string{
|
||||||
"missing setting: cookie-secret",
|
"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"})
|
"missing setting: client-secret or client-secret-file"})
|
||||||
assert.Equal(t, expected, err.Error())
|
assert.Equal(t, expected, err.Error())
|
||||||
}
|
}
|
||||||
@ -56,8 +59,9 @@ func TestNewOptions(t *testing.T) {
|
|||||||
func TestClientSecretFileOptionFails(t *testing.T) {
|
func TestClientSecretFileOptionFails(t *testing.T) {
|
||||||
o := options.NewOptions()
|
o := options.NewOptions()
|
||||||
o.Cookie.Secret = cookieSecret
|
o.Cookie.Secret = cookieSecret
|
||||||
o.ClientID = clientID
|
o.Providers[0].ID = providerID
|
||||||
o.ClientSecretFile = clientSecret
|
o.Providers[0].ClientID = clientID
|
||||||
|
o.Providers[0].ClientSecretFile = clientSecret
|
||||||
o.EmailDomains = []string{"*"}
|
o.EmailDomains = []string{"*"}
|
||||||
err := Validate(o)
|
err := Validate(o)
|
||||||
assert.NotEqual(t, nil, err)
|
assert.NotEqual(t, nil, err)
|
||||||
@ -93,8 +97,9 @@ func TestClientSecretFileOption(t *testing.T) {
|
|||||||
|
|
||||||
o := options.NewOptions()
|
o := options.NewOptions()
|
||||||
o.Cookie.Secret = cookieSecret
|
o.Cookie.Secret = cookieSecret
|
||||||
o.ClientID = clientID
|
o.Providers[0].ID = providerID
|
||||||
o.ClientSecretFile = clientSecretFileName
|
o.Providers[0].ClientID = clientID
|
||||||
|
o.Providers[0].ClientSecretFile = clientSecretFileName
|
||||||
o.EmailDomains = []string{"*"}
|
o.EmailDomains = []string{"*"}
|
||||||
err = Validate(o)
|
err = Validate(o)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
@ -110,7 +115,7 @@ func TestClientSecretFileOption(t *testing.T) {
|
|||||||
|
|
||||||
func TestGoogleGroupOptions(t *testing.T) {
|
func TestGoogleGroupOptions(t *testing.T) {
|
||||||
o := testOptions()
|
o := testOptions()
|
||||||
o.GoogleGroups = []string{"googlegroup"}
|
o.Providers[0].GoogleConfig.Groups = []string{"googlegroup"}
|
||||||
err := Validate(o)
|
err := Validate(o)
|
||||||
assert.NotEqual(t, nil, err)
|
assert.NotEqual(t, nil, err)
|
||||||
|
|
||||||
@ -122,9 +127,9 @@ func TestGoogleGroupOptions(t *testing.T) {
|
|||||||
|
|
||||||
func TestGoogleGroupInvalidFile(t *testing.T) {
|
func TestGoogleGroupInvalidFile(t *testing.T) {
|
||||||
o := testOptions()
|
o := testOptions()
|
||||||
o.GoogleGroups = []string{"test_group"}
|
o.Providers[0].GoogleConfig.Groups = []string{"test_group"}
|
||||||
o.GoogleAdminEmail = "admin@example.com"
|
o.Providers[0].GoogleConfig.AdminEmail = "admin@example.com"
|
||||||
o.GoogleServiceAccountJSON = "file_doesnt_exist.json"
|
o.Providers[0].GoogleConfig.ServiceAccountJSON = "file_doesnt_exist.json"
|
||||||
err := Validate(o)
|
err := Validate(o)
|
||||||
assert.NotEqual(t, nil, err)
|
assert.NotEqual(t, nil, err)
|
||||||
|
|
||||||
@ -225,17 +230,17 @@ func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
|
|||||||
|
|
||||||
func TestSkipOIDCDiscovery(t *testing.T) {
|
func TestSkipOIDCDiscovery(t *testing.T) {
|
||||||
o := testOptions()
|
o := testOptions()
|
||||||
o.ProviderType = "oidc"
|
o.Providers[0].Type = "oidc"
|
||||||
o.OIDCIssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
|
o.Providers[0].OIDCConfig.IssuerURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/v2.0/"
|
||||||
o.SkipOIDCDiscovery = true
|
o.Providers[0].OIDCConfig.SkipDiscovery = true
|
||||||
|
|
||||||
err := Validate(o)
|
err := Validate(o)
|
||||||
assert.Equal(t, "invalid configuration:\n"+
|
assert.Equal(t, "invalid configuration:\n"+
|
||||||
" missing setting: login-url\n missing setting: redeem-url\n missing setting: oidc-jwks-url", err.Error())
|
" 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.Providers[0].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.Providers[0].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].OIDCConfig.JwksURL = "https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys"
|
||||||
|
|
||||||
assert.Equal(t, nil, Validate(o))
|
assert.Equal(t, nil, Validate(o))
|
||||||
}
|
}
|
||||||
@ -292,7 +297,7 @@ func TestProviderCAFilesError(t *testing.T) {
|
|||||||
assert.NoError(t, os.Remove(file.Name()))
|
assert.NoError(t, os.Remove(file.Name()))
|
||||||
|
|
||||||
o := testOptions()
|
o := testOptions()
|
||||||
o.ProviderCAFiles = append(o.ProviderCAFiles, file.Name())
|
o.Providers[0].CAFiles = append(o.Providers[0].CAFiles, file.Name())
|
||||||
err = Validate(o)
|
err = Validate(o)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "unable to load provider CA file(s)")
|
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