2020-04-13 14:50:34 +02:00
|
|
|
package validation
|
2014-11-09 21:51:10 +02:00
|
|
|
|
|
|
|
import (
|
2017-05-09 20:20:35 +02:00
|
|
|
"context"
|
2015-11-16 05:08:30 +02:00
|
|
|
"crypto"
|
2017-03-29 16:57:07 +02:00
|
|
|
"crypto/tls"
|
2014-11-09 21:51:10 +02:00
|
|
|
"fmt"
|
2019-03-21 00:15:47 +02:00
|
|
|
"io/ioutil"
|
2016-07-19 21:51:25 +02:00
|
|
|
"net/http"
|
2014-11-09 21:51:10 +02:00
|
|
|
"net/url"
|
2015-08-20 12:07:02 +02:00
|
|
|
"os"
|
2015-03-15 18:23:13 +02:00
|
|
|
"strings"
|
2015-03-30 21:48:30 +02:00
|
|
|
|
2020-04-13 14:50:34 +02:00
|
|
|
"github.com/coreos/go-oidc"
|
2019-03-20 15:44:51 +02:00
|
|
|
"github.com/dgrijalva/jwt-go"
|
2017-09-13 00:59:00 +02:00
|
|
|
"github.com/mbland/hmacauth"
|
2020-09-29 18:44:42 +02:00
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
|
|
|
|
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
|
2014-11-09 21:51:10 +02:00
|
|
|
)
|
|
|
|
|
2018-12-20 11:30:42 +02:00
|
|
|
// Validate checks that required options are set and validates those that they
|
|
|
|
// are of the correct format
|
2020-04-13 14:50:34 +02:00
|
|
|
func Validate(o *options.Options) error {
|
2020-05-25 13:43:24 +02:00
|
|
|
msgs := validateCookie(o.Cookie)
|
2020-07-15 00:02:10 +02:00
|
|
|
msgs = append(msgs, validateSessionCookieMinimal(o)...)
|
2020-08-07 00:43:01 +02:00
|
|
|
msgs = append(msgs, validateRedisSessionStore(o)...)
|
2020-07-23 11:47:31 +02:00
|
|
|
msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
|
2020-12-12 20:05:01 +02:00
|
|
|
msgs = append(msgs, prefixValues("injectResponseHeaders: ", validateHeaders(o.InjectResponseHeaders)...)...)
|
2020-05-24 12:16:45 +02:00
|
|
|
|
2017-05-09 20:20:35 +02:00
|
|
|
if o.SSLInsecureSkipVerify {
|
2020-07-21 03:49:45 +02:00
|
|
|
// InsecureSkipVerify is a configurable option we allow
|
2020-07-20 07:24:18 +02:00
|
|
|
/* #nosec G402 */
|
2017-05-09 20:20:35 +02:00
|
|
|
insecureTransport := &http.Transport{
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
|
|
}
|
|
|
|
http.DefaultClient = &http.Client{Transport: insecureTransport}
|
2020-07-03 17:09:17 +02:00
|
|
|
} else if len(o.ProviderCAFiles) > 0 {
|
|
|
|
pool, err := util.GetCertPool(o.ProviderCAFiles)
|
|
|
|
if err == nil {
|
|
|
|
transport := &http.Transport{
|
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
RootCAs: pool,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
http.DefaultClient = &http.Client{Transport: transport}
|
|
|
|
} else {
|
|
|
|
msgs = append(msgs, fmt.Sprintf("unable to load provider CA file(s): %v", err))
|
|
|
|
}
|
2017-05-09 20:20:35 +02:00
|
|
|
}
|
|
|
|
|
2014-11-09 21:51:10 +02:00
|
|
|
if o.ClientID == "" {
|
2015-03-15 18:23:13 +02:00
|
|
|
msgs = append(msgs, "missing setting: client-id")
|
2014-11-09 21:51:10 +02:00
|
|
|
}
|
2019-03-20 15:44:51 +02:00
|
|
|
// login.gov uses a signed JWT to authenticate, not a client-secret
|
2020-04-13 14:50:34 +02:00
|
|
|
if o.ProviderType != "login.gov" {
|
2020-02-15 15:44:39 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2014-11-09 21:51:10 +02:00
|
|
|
}
|
2015-07-24 22:09:33 +02:00
|
|
|
if o.AuthenticatedEmailsFile == "" && len(o.EmailDomains) == 0 && o.HtpasswdFile == "" {
|
2017-08-05 18:54:31 +02:00
|
|
|
msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
|
|
|
|
"\n use email-domain=* to authorize all email addresses")
|
2015-07-24 22:09:33 +02:00
|
|
|
}
|
2014-11-09 21:51:10 +02:00
|
|
|
|
2017-05-09 20:20:35 +02:00
|
|
|
if o.OIDCIssuerURL != "" {
|
2019-03-04 15:54:22 +02:00
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
2020-04-19 13:19:21 +02:00
|
|
|
if o.InsecureOIDCSkipIssuerVerification && !o.SkipOIDCDiscovery {
|
|
|
|
// go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
|
|
|
|
// (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
|
|
|
|
// we get the URLs, we'll just use the non-discovery path.
|
|
|
|
|
|
|
|
logger.Printf("Performing OIDC Discovery...")
|
|
|
|
|
2020-07-03 20:27:25 +02:00
|
|
|
requestURL := strings.TrimSuffix(o.OIDCIssuerURL, "/") + "/.well-known/openid-configuration"
|
|
|
|
body, err := requests.New(requestURL).
|
|
|
|
WithContext(ctx).
|
2020-07-06 18:42:26 +02:00
|
|
|
Do().
|
2020-07-03 20:27:25 +02:00
|
|
|
UnmarshalJSON()
|
|
|
|
if err != nil {
|
2020-08-10 12:44:08 +02:00
|
|
|
logger.Errorf("error: failed to discover OIDC configuration: %v", err)
|
2020-04-19 13:19:21 +02:00
|
|
|
} else {
|
2020-07-03 20:27:25 +02:00
|
|
|
// Prefer manually configured URLs. It's a bit unclear
|
|
|
|
// why you'd be doing discovery and also providing the URLs
|
|
|
|
// explicitly though...
|
|
|
|
if o.LoginURL == "" {
|
|
|
|
o.LoginURL = body.Get("authorization_endpoint").MustString()
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.RedeemURL == "" {
|
|
|
|
o.RedeemURL = body.Get("token_endpoint").MustString()
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.OIDCJwksURL == "" {
|
|
|
|
o.OIDCJwksURL = body.Get("jwks_uri").MustString()
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.ProfileURL == "" {
|
|
|
|
o.ProfileURL = body.Get("userinfo_endpoint").MustString()
|
|
|
|
}
|
|
|
|
|
|
|
|
o.SkipOIDCDiscovery = true
|
2020-04-19 13:19:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 15:54:22 +02:00
|
|
|
// Construct a manual IDTokenVerifier from issuer URL & JWKS URI
|
|
|
|
// instead of metadata discovery if we enable -skip-oidc-discovery.
|
|
|
|
// In this case we need to make sure the required endpoints for
|
|
|
|
// the provider are configured.
|
|
|
|
if o.SkipOIDCDiscovery {
|
|
|
|
if o.LoginURL == "" {
|
|
|
|
msgs = append(msgs, "missing setting: login-url")
|
|
|
|
}
|
|
|
|
if o.RedeemURL == "" {
|
|
|
|
msgs = append(msgs, "missing setting: redeem-url")
|
|
|
|
}
|
|
|
|
if o.OIDCJwksURL == "" {
|
|
|
|
msgs = append(msgs, "missing setting: oidc-jwks-url")
|
|
|
|
}
|
|
|
|
keySet := oidc.NewRemoteKeySet(ctx, o.OIDCJwksURL)
|
2020-04-13 14:50:34 +02:00
|
|
|
o.SetOIDCVerifier(oidc.NewVerifier(o.OIDCIssuerURL, keySet, &oidc.Config{
|
2020-04-19 13:19:21 +02:00
|
|
|
ClientID: o.ClientID,
|
|
|
|
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
2020-04-13 14:50:34 +02:00
|
|
|
}))
|
2019-03-04 15:54:22 +02:00
|
|
|
} else {
|
|
|
|
// Configure discoverable provider data.
|
|
|
|
provider, err := oidc.NewProvider(ctx, o.OIDCIssuerURL)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-04-13 14:50:34 +02:00
|
|
|
o.SetOIDCVerifier(provider.Verifier(&oidc.Config{
|
2020-04-19 13:19:21 +02:00
|
|
|
ClientID: o.ClientID,
|
|
|
|
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
|
2020-04-13 14:50:34 +02:00
|
|
|
}))
|
2019-03-04 15:54:22 +02:00
|
|
|
|
|
|
|
o.LoginURL = provider.Endpoint().AuthURL
|
|
|
|
o.RedeemURL = provider.Endpoint().TokenURL
|
2017-05-09 20:20:35 +02:00
|
|
|
}
|
|
|
|
if o.Scope == "" {
|
|
|
|
o.Scope = "openid email profile"
|
2020-07-28 20:42:09 +02:00
|
|
|
|
|
|
|
if len(o.AllowedGroups) > 0 {
|
|
|
|
o.Scope += " groups"
|
|
|
|
}
|
2017-05-09 20:20:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-17 22:49:14 +02:00
|
|
|
if o.SkipJwtBearerTokens {
|
|
|
|
// Configure extra issuers
|
|
|
|
if len(o.ExtraJwtIssuers) > 0 {
|
2019-06-06 01:09:29 +02:00
|
|
|
var jwtIssuers []jwtIssuer
|
2019-05-01 19:00:54 +02:00
|
|
|
jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
|
|
|
|
for _, jwtIssuer := range jwtIssuers {
|
|
|
|
verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
|
2019-01-17 22:49:14 +02:00
|
|
|
if err != nil {
|
2019-05-01 19:00:54 +02:00
|
|
|
msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
|
2019-01-17 22:49:14 +02:00
|
|
|
}
|
2020-04-13 14:50:34 +02:00
|
|
|
o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), verifier))
|
2019-01-17 22:49:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-13 14:50:34 +02:00
|
|
|
var redirectURL *url.URL
|
|
|
|
redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs)
|
|
|
|
o.SetRedirectURL(redirectURL)
|
2014-11-09 21:51:10 +02:00
|
|
|
|
2020-05-26 20:56:10 +02:00
|
|
|
msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
|
2015-03-30 21:48:30 +02:00
|
|
|
msgs = parseProviderInfo(o, msgs)
|
2015-01-12 11:18:41 +02:00
|
|
|
|
2015-08-20 12:07:02 +02:00
|
|
|
if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
|
|
|
|
if len(o.GoogleGroups) < 1 {
|
|
|
|
msgs = append(msgs, "missing setting: google-group")
|
|
|
|
}
|
|
|
|
if o.GoogleAdminEmail == "" {
|
|
|
|
msgs = append(msgs, "missing setting: google-admin-email")
|
|
|
|
}
|
|
|
|
if o.GoogleServiceAccountJSON == "" {
|
|
|
|
msgs = append(msgs, "missing setting: google-service-account-json")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-16 05:08:30 +02:00
|
|
|
msgs = parseSignatureKey(o, msgs)
|
2020-06-14 21:58:44 +02:00
|
|
|
msgs = configureLogger(o.Logging, msgs)
|
2015-11-16 05:08:30 +02:00
|
|
|
|
2020-05-12 19:41:25 +02:00
|
|
|
if o.ReverseProxy {
|
2020-05-23 16:17:41 +02:00
|
|
|
parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)
|
2020-05-12 19:41:25 +02:00
|
|
|
if err != nil {
|
|
|
|
msgs = append(msgs, fmt.Sprintf("real_client_ip_header (%s) not accepted parameter value: %v", o.RealClientIPHeader, err))
|
|
|
|
}
|
2020-04-13 14:50:34 +02:00
|
|
|
o.SetRealClientIPParser(parser)
|
2020-05-12 01:32:30 +02:00
|
|
|
|
|
|
|
// Allow the logger to get client IPs
|
|
|
|
logger.SetGetClientFunc(func(r *http.Request) string {
|
|
|
|
return ip.GetClientString(o.GetRealClientIPParser(), r, false)
|
|
|
|
})
|
2020-05-12 19:41:25 +02:00
|
|
|
}
|
|
|
|
|
2020-09-23 03:54:32 +02:00
|
|
|
// Do this after ReverseProxy validation for TrustedIP coordinated checks
|
|
|
|
msgs = append(msgs, validateAllowlists(o)...)
|
2020-07-11 12:10:58 +02:00
|
|
|
|
2015-03-15 18:23:13 +02:00
|
|
|
if len(msgs) != 0 {
|
2020-04-14 10:36:44 +02:00
|
|
|
return fmt.Errorf("invalid configuration:\n %s",
|
2015-03-15 18:23:13 +02:00
|
|
|
strings.Join(msgs, "\n "))
|
|
|
|
}
|
2014-11-09 21:51:10 +02:00
|
|
|
return nil
|
|
|
|
}
|
2015-03-30 21:48:30 +02:00
|
|
|
|
2020-04-13 14:50:34 +02:00
|
|
|
func parseProviderInfo(o *options.Options, msgs []string) []string {
|
2015-07-26 01:27:49 +02:00
|
|
|
p := &providers.ProviderData{
|
2020-02-15 15:44:39 +02:00
|
|
|
Scope: o.Scope,
|
|
|
|
ClientID: o.ClientID,
|
|
|
|
ClientSecret: o.ClientSecret,
|
|
|
|
ClientSecretFile: o.ClientSecretFile,
|
2020-03-14 11:53:43 +02:00
|
|
|
Prompt: o.Prompt,
|
2020-02-15 15:44:39 +02:00
|
|
|
ApprovalPrompt: o.ApprovalPrompt,
|
2020-03-17 19:57:33 +02:00
|
|
|
AcrValues: o.AcrValues,
|
2015-07-26 01:27:49 +02:00
|
|
|
}
|
2015-11-09 01:47:44 +02:00
|
|
|
p.LoginURL, msgs = parseURL(o.LoginURL, "login", msgs)
|
|
|
|
p.RedeemURL, msgs = parseURL(o.RedeemURL, "redeem", msgs)
|
|
|
|
p.ProfileURL, msgs = parseURL(o.ProfileURL, "profile", msgs)
|
|
|
|
p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
|
2015-11-09 10:28:34 +02:00
|
|
|
p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
|
2015-05-21 05:23:48 +02:00
|
|
|
|
2020-11-28 02:17:59 +02:00
|
|
|
// Make the OIDC options available to all providers that support it
|
|
|
|
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
|
2020-11-28 22:33:05 +02:00
|
|
|
p.EmailClaim = o.OIDCEmailClaim
|
2020-11-28 02:17:59 +02:00
|
|
|
p.GroupsClaim = o.OIDCGroupsClaim
|
2020-11-16 04:57:48 +02:00
|
|
|
p.Verifier = o.GetOIDCVerifier()
|
|
|
|
|
2020-11-28 22:33:05 +02:00
|
|
|
// TODO (@NickMeves) - Remove This
|
|
|
|
// Backwards Compatibility for Deprecated UserIDClaim option
|
|
|
|
if o.OIDCEmailClaim == providers.OIDCEmailClaim &&
|
|
|
|
o.UserIDClaim != providers.OIDCEmailClaim {
|
|
|
|
p.EmailClaim = o.UserIDClaim
|
|
|
|
}
|
|
|
|
|
2020-09-27 04:00:44 +02:00
|
|
|
p.SetAllowedGroups(o.AllowedGroups)
|
|
|
|
|
2020-11-11 07:53:56 +02:00
|
|
|
provider := providers.New(o.ProviderType, p)
|
|
|
|
if provider == nil {
|
|
|
|
msgs = append(msgs, fmt.Sprintf("invalid setting: provider '%s' is not available", o.ProviderType))
|
|
|
|
return msgs
|
|
|
|
}
|
|
|
|
o.SetProvider(provider)
|
|
|
|
|
2020-04-13 14:50:34 +02:00
|
|
|
switch p := o.GetProvider().(type) {
|
2015-11-09 10:28:34 +02:00
|
|
|
case *providers.AzureProvider:
|
|
|
|
p.Configure(o.AzureTenant)
|
2015-05-21 05:23:48 +02:00
|
|
|
case *providers.GitHubProvider:
|
|
|
|
p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
|
2020-05-11 19:02:40 +02:00
|
|
|
p.SetRepo(o.GitHubRepo, o.GitHubToken)
|
2020-06-01 21:02:07 +02:00
|
|
|
p.SetUsers(o.GitHubUsers)
|
2019-07-28 15:54:39 +02:00
|
|
|
case *providers.KeycloakProvider:
|
2020-12-12 22:57:32 +02:00
|
|
|
// Backwards compatibility with `--keycloak-group` option
|
|
|
|
if len(o.KeycloakGroups) > 0 {
|
|
|
|
p.SetAllowedGroups(o.KeycloakGroups)
|
|
|
|
}
|
2015-08-20 12:07:02 +02:00
|
|
|
case *providers.GoogleProvider:
|
|
|
|
if o.GoogleServiceAccountJSON != "" {
|
|
|
|
file, err := os.Open(o.GoogleServiceAccountJSON)
|
|
|
|
if err != nil {
|
|
|
|
msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
|
|
|
|
} else {
|
2020-09-27 04:24:06 +02:00
|
|
|
groups := o.AllowedGroups
|
|
|
|
// Backwards compatibility with `--google-group` option
|
|
|
|
if len(o.GoogleGroups) > 0 {
|
|
|
|
groups = o.GoogleGroups
|
|
|
|
p.SetAllowedGroups(groups)
|
|
|
|
}
|
|
|
|
p.SetGroupRestriction(groups, o.GoogleAdminEmail, file)
|
2015-08-20 12:07:02 +02:00
|
|
|
}
|
|
|
|
}
|
2019-08-16 15:53:22 +02:00
|
|
|
case *providers.BitbucketProvider:
|
|
|
|
p.SetTeam(o.BitbucketTeam)
|
|
|
|
p.SetRepository(o.BitbucketRepository)
|
2017-05-09 20:20:35 +02:00
|
|
|
case *providers.OIDCProvider:
|
2020-11-16 04:57:48 +02:00
|
|
|
if p.Verifier == nil {
|
2017-05-09 20:20:35 +02:00
|
|
|
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
|
|
|
}
|
2019-08-06 13:20:54 +02:00
|
|
|
case *providers.GitLabProvider:
|
2020-06-27 00:26:07 +02:00
|
|
|
p.Groups = o.GitLabGroup
|
2020-12-05 20:57:33 +02:00
|
|
|
err := p.AddProjects(o.GitlabProjects)
|
|
|
|
if err != nil {
|
|
|
|
msgs = append(msgs, "failed to setup gitlab project access level")
|
|
|
|
}
|
|
|
|
p.SetAllowedGroups(p.PrefixAllowedGroups())
|
|
|
|
p.SetProjectScope()
|
2019-08-06 13:20:54 +02:00
|
|
|
|
2020-11-16 04:57:48 +02:00
|
|
|
if p.Verifier == nil {
|
2019-08-06 13:20:54 +02:00
|
|
|
// Initialize with default verifier for gitlab.com
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
provider, err := oidc.NewProvider(ctx, "https://gitlab.com")
|
|
|
|
if err != nil {
|
|
|
|
msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
|
|
|
|
} else {
|
|
|
|
p.Verifier = provider.Verifier(&oidc.Config{
|
|
|
|
ClientID: o.ClientID,
|
|
|
|
})
|
|
|
|
|
|
|
|
p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
|
|
|
|
p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
|
|
|
|
}
|
|
|
|
}
|
2019-03-20 15:44:51 +02:00
|
|
|
case *providers.LoginGovProvider:
|
|
|
|
p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
|
2019-03-21 00:15:47 +02:00
|
|
|
|
|
|
|
// JWT key can be supplied via env variable or file in the filesystem, but not both.
|
|
|
|
switch {
|
|
|
|
case o.JWTKey != "" && o.JWTKeyFile != "":
|
|
|
|
msgs = append(msgs, "cannot set both jwt-key and jwt-key-file options")
|
|
|
|
case o.JWTKey == "" && o.JWTKeyFile == "":
|
2019-03-20 15:44:51 +02:00
|
|
|
msgs = append(msgs, "login.gov provider requires a private key for signing JWTs")
|
2019-03-21 00:15:47 +02:00
|
|
|
case o.JWTKey != "":
|
|
|
|
// The JWT Key is in the commandline argument
|
2019-03-20 15:44:51 +02:00
|
|
|
signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(o.JWTKey))
|
|
|
|
if err != nil {
|
|
|
|
msgs = append(msgs, "could not parse RSA Private Key PEM")
|
|
|
|
} else {
|
|
|
|
p.JWTKey = signKey
|
|
|
|
}
|
2019-03-21 00:15:47 +02:00
|
|
|
case o.JWTKeyFile != "":
|
|
|
|
// The JWT key is in the filesystem
|
|
|
|
keyData, err := ioutil.ReadFile(o.JWTKeyFile)
|
|
|
|
if err != nil {
|
|
|
|
msgs = append(msgs, "could not read key file: "+o.JWTKeyFile)
|
|
|
|
}
|
|
|
|
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
|
|
|
|
if err != nil {
|
|
|
|
msgs = append(msgs, "could not parse private key from PEM file:"+o.JWTKeyFile)
|
|
|
|
} else {
|
|
|
|
p.JWTKey = signKey
|
|
|
|
}
|
2019-03-20 15:44:51 +02:00
|
|
|
}
|
2015-05-21 05:23:48 +02:00
|
|
|
}
|
2015-03-30 21:48:30 +02:00
|
|
|
return msgs
|
|
|
|
}
|
2015-11-16 05:08:30 +02:00
|
|
|
|
2020-04-13 14:50:34 +02:00
|
|
|
func parseSignatureKey(o *options.Options, msgs []string) []string {
|
2015-11-16 05:08:30 +02:00
|
|
|
if o.SignatureKey == "" {
|
|
|
|
return msgs
|
|
|
|
}
|
|
|
|
|
|
|
|
components := strings.Split(o.SignatureKey, ":")
|
|
|
|
if len(components) != 2 {
|
|
|
|
return append(msgs, "invalid signature hash:key spec: "+
|
|
|
|
o.SignatureKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
algorithm, secretKey := components[0], components[1]
|
2018-11-29 16:26:41 +02:00
|
|
|
var hash crypto.Hash
|
|
|
|
var err error
|
|
|
|
if hash, err = hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
|
2015-11-16 05:08:30 +02:00
|
|
|
return append(msgs, "unsupported signature hash algorithm: "+
|
|
|
|
o.SignatureKey)
|
|
|
|
}
|
2020-04-13 14:50:34 +02:00
|
|
|
o.SetSignatureData(&options.SignatureData{Hash: hash, Key: secretKey})
|
2015-11-16 05:08:30 +02:00
|
|
|
return msgs
|
|
|
|
}
|
2016-06-20 13:17:39 +02:00
|
|
|
|
2019-05-01 19:00:54 +02:00
|
|
|
// parseJwtIssuers takes in an array of strings in the form of issuer=audience
|
2019-06-06 01:09:29 +02:00
|
|
|
// and parses to an array of jwtIssuer structs.
|
|
|
|
func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
|
2020-04-14 10:36:44 +02:00
|
|
|
parsedIssuers := make([]jwtIssuer, 0, len(issuers))
|
2019-05-01 19:00:54 +02:00
|
|
|
for _, jwtVerifier := range issuers {
|
2019-01-17 22:49:14 +02:00
|
|
|
components := strings.Split(jwtVerifier, "=")
|
|
|
|
if len(components) < 2 {
|
2019-05-01 19:00:54 +02:00
|
|
|
msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier))
|
|
|
|
continue
|
2019-01-17 22:49:14 +02:00
|
|
|
}
|
|
|
|
uri, audience := components[0], strings.Join(components[1:], "=")
|
2019-06-06 01:09:29 +02:00
|
|
|
parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience})
|
2019-01-17 22:49:14 +02:00
|
|
|
}
|
2019-05-01 19:00:54 +02:00
|
|
|
return parsedIssuers, msgs
|
|
|
|
}
|
|
|
|
|
2019-06-06 01:09:29 +02:00
|
|
|
// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
|
2019-05-01 19:00:54 +02:00
|
|
|
// a verifier for that issuer.
|
2019-06-06 01:09:29 +02:00
|
|
|
func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
|
2019-05-01 19:00:54 +02:00
|
|
|
config := &oidc.Config{
|
|
|
|
ClientID: jwtIssuer.audience,
|
|
|
|
}
|
|
|
|
// Try as an OpenID Connect Provider first
|
|
|
|
var verifier *oidc.IDTokenVerifier
|
|
|
|
provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI)
|
|
|
|
if err != nil {
|
|
|
|
// Try as JWKS URI
|
|
|
|
jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
|
2020-07-06 18:42:26 +02:00
|
|
|
if err := requests.New(jwksURI).Do().Error(); err != nil {
|
2019-05-01 19:00:54 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-03 20:27:25 +02:00
|
|
|
|
2019-05-01 19:00:54 +02:00
|
|
|
verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
|
|
|
|
} else {
|
|
|
|
verifier = provider.Verifier(config)
|
|
|
|
}
|
|
|
|
return verifier, nil
|
2019-01-17 22:49:14 +02:00
|
|
|
}
|
|
|
|
|
2020-04-13 14:50:34 +02:00
|
|
|
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
|
|
|
|
type jwtIssuer struct {
|
|
|
|
issuerURI string
|
|
|
|
audience string
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
|
|
|
|
parsed, err := url.Parse(toParse)
|
|
|
|
if err != nil {
|
|
|
|
return nil, append(msgs, fmt.Sprintf(
|
|
|
|
"error parsing %s-url=%q %s", urltype, toParse, err))
|
|
|
|
}
|
|
|
|
return parsed, msgs
|
|
|
|
}
|