1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2024-11-28 09:08:44 +02:00
oauth2-proxy/pkg/validation/options.go

475 lines
15 KiB
Go
Raw Normal View History

2020-04-13 14:50:34 +02:00
package validation
2014-11-09 21:51:10 +02:00
import (
"context"
"crypto"
"crypto/tls"
2014-11-09 21:51:10 +02:00
"fmt"
2019-03-21 00:15:47 +02:00
"io/ioutil"
"net/http"
2014-11-09 21:51:10 +02:00
"net/url"
"os"
"regexp"
"sort"
"strings"
2020-04-13 14:50:34 +02:00
"github.com/coreos/go-oidc"
add login.gov provider (#55) * first stab at login.gov provider * fixing bugs now that I think I understand things better * fixing up dependencies * remove some debug stuff * Fixing all dependencies to point at my fork * forgot to hit save on the github rehome here * adding options for setting keys and so on, use JWT workflow instead of PKCE * forgot comma * was too aggressive with search/replace * need JWTKey to be byte array * removed custom refresh stuff * do our own custom jwt claim and store it in the normal session store * golang json types are strange * I have much to learn about golang * fix time and signing key * add http lib * fixed claims up since we don't need custom claims * add libs * forgot ioutil * forgot ioutil * moved back to pusher location * changed proxy github location back so that it builds externally, fixed up []byte stuff, removed client_secret if we are using login.gov * update dependencies * do JWTs properly * finished oidc flow, fixed up tests to work better * updated comments, added test that we set expiresOn properly * got confused with header and post vs get * clean up debug and test dir * add login.gov to README, remove references to my repo * forgot to remove un-needed code * can use sample_key* instead of generating your own * updated changelog * apparently golint wants comments like this * linter wants non-standard libs in a separate grouping * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * remove sample_key, improve comments related to client-secret, fix changelog related to PR feedback * github doesn't seem to do gofmt when merging. :-) * update CODEOWNERS * check the nonce * validate the JWT fully * forgot to add pubjwk-url to README * unexport the struct * fix up the err masking that travis found * update nonce comment by request of @JoelSpeed * argh. Thought I'd formatted the merge properly, but apparently not. * fixed test to not fail if the query time was greater than zero
2019-03-20 15:44:51 +02:00
"github.com/dgrijalva/jwt-go"
"github.com/mbland/hmacauth"
2020-03-29 15:54:36 +02:00
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
2020-05-23 16:17:41 +02:00
"github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
2020-03-29 15:54:36 +02:00
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests"
2020-03-29 15:54:36 +02:00
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
"github.com/oauth2-proxy/oauth2-proxy/providers"
2014-11-09 21:51:10 +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 {
if o.SSLInsecureSkipVerify {
// TODO: Accept a certificate bundle.
insecureTransport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
http.DefaultClient = &http.Client{Transport: insecureTransport}
}
msgs := make([]string, 0)
var cipher encryption.Cipher
if o.Cookie.Secret == "" {
msgs = append(msgs, "missing setting: cookie-secret")
} else {
validCookieSecretSize := false
for _, i := range []int{16, 24, 32} {
if len(encryption.SecretBytes(o.Cookie.Secret)) == i {
validCookieSecretSize = true
}
}
var decoded bool
if string(encryption.SecretBytes(o.Cookie.Secret)) != o.Cookie.Secret {
decoded = true
}
if !validCookieSecretSize {
var suffix string
if decoded {
suffix = " note: cookie secret was base64 decoded"
}
msgs = append(msgs,
fmt.Sprintf("Cookie secret must be 16, 24, or 32 bytes to create an AES cipher. Got %d bytes.%s",
len(encryption.SecretBytes(o.Cookie.Secret)), suffix))
} else {
var err error
cipher, err = encryption.NewBase64Cipher(encryption.NewCFBCipher, encryption.SecretBytes(o.Cookie.Secret))
if err != nil {
msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
}
}
2014-11-09 21:51:10 +02:00
}
2014-11-09 21:51:10 +02:00
if o.ClientID == "" {
msgs = append(msgs, "missing setting: client-id")
2014-11-09 21:51:10 +02:00
}
add login.gov provider (#55) * first stab at login.gov provider * fixing bugs now that I think I understand things better * fixing up dependencies * remove some debug stuff * Fixing all dependencies to point at my fork * forgot to hit save on the github rehome here * adding options for setting keys and so on, use JWT workflow instead of PKCE * forgot comma * was too aggressive with search/replace * need JWTKey to be byte array * removed custom refresh stuff * do our own custom jwt claim and store it in the normal session store * golang json types are strange * I have much to learn about golang * fix time and signing key * add http lib * fixed claims up since we don't need custom claims * add libs * forgot ioutil * forgot ioutil * moved back to pusher location * changed proxy github location back so that it builds externally, fixed up []byte stuff, removed client_secret if we are using login.gov * update dependencies * do JWTs properly * finished oidc flow, fixed up tests to work better * updated comments, added test that we set expiresOn properly * got confused with header and post vs get * clean up debug and test dir * add login.gov to README, remove references to my repo * forgot to remove un-needed code * can use sample_key* instead of generating your own * updated changelog * apparently golint wants comments like this * linter wants non-standard libs in a separate grouping * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * remove sample_key, improve comments related to client-secret, fix changelog related to PR feedback * github doesn't seem to do gofmt when merging. :-) * update CODEOWNERS * check the nonce * validate the JWT fully * forgot to add pubjwk-url to README * unexport the struct * fix up the err masking that travis found * update nonce comment by request of @JoelSpeed * argh. Thought I'd formatted the merge properly, but apparently not. * fixed test to not fail if the query time was greater than zero
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" {
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
}
if o.AuthenticatedEmailsFile == "" && len(o.EmailDomains) == 0 && o.HtpasswdFile == "" {
msgs = append(msgs, "missing setting for email validation: email-domain or authenticated-emails-file required."+
"\n use email-domain=* to authorize all email addresses")
}
2014-11-09 21:51:10 +02:00
if o.SetBasicAuth && o.SetAuthorization {
msgs = append(msgs, "mutually exclusive: set-basic-auth and set-authorization-header can not both be true")
}
if o.OIDCIssuerURL != "" {
ctx := context.Background()
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...")
if req, err := http.NewRequest("GET", strings.TrimSuffix(o.OIDCIssuerURL, "/")+"/.well-known/openid-configuration", nil); err == nil {
if body, err := requests.Request(req); err == nil {
// 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
} else {
logger.Printf("error: failed to discover OIDC configuration: %v", err)
}
} else {
logger.Printf("error: failed parsing OIDC discovery URL: %v", err)
}
}
// 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{
ClientID: o.ClientID,
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
2020-04-13 14:50:34 +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{
ClientID: o.ClientID,
SkipIssuerCheck: o.InsecureOIDCSkipIssuerVerification,
2020-04-13 14:50:34 +02:00
}))
o.LoginURL = provider.Endpoint().AuthURL
o.RedeemURL = provider.Endpoint().TokenURL
}
if o.Scope == "" {
o.Scope = "openid email profile"
}
}
if o.PreferEmailToUser && !o.PassBasicAuth && !o.PassUserHeaders {
msgs = append(msgs, "PreferEmailToUser should only be used with PassBasicAuth or PassUserHeaders")
}
if o.SkipJwtBearerTokens {
// If we are using an oidc provider, go ahead and add that provider to the list
2020-04-13 14:50:34 +02:00
if o.GetOIDCVerifier() != nil {
o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), o.GetOIDCVerifier()))
}
// 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)
if err != nil {
2019-05-01 19:00:54 +02:00
msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
}
2020-04-13 14:50:34 +02:00
o.SetJWTBearerVerifiers(append(o.GetJWTBearerVerifiers(), verifier))
}
}
}
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
for _, u := range o.Upstreams {
upstreamURL, err := url.Parse(u)
2014-11-09 21:51:10 +02:00
if err != nil {
msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
} else {
if upstreamURL.Path == "" {
upstreamURL.Path = "/"
}
2020-04-13 14:50:34 +02:00
o.SetProxyURLs(append(o.GetProxyURLs(), upstreamURL))
2014-11-09 21:51:10 +02:00
}
}
for _, u := range o.SkipAuthRegex {
2020-04-13 12:34:25 +02:00
compiledRegex, err := regexp.Compile(u)
if err != nil {
msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
continue
}
2020-04-13 14:50:34 +02:00
o.SetCompiledRegex(append(o.GetCompiledRegex(), compiledRegex))
}
msgs = parseProviderInfo(o, msgs)
o.Session.Cipher = cipher
sessionStore, err := sessions.NewSessionStore(&o.Session, &o.Cookie)
2019-05-07 15:27:09 +02:00
if err != nil {
msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
} else {
2020-04-13 14:50:34 +02:00
o.SetSessionStore(sessionStore)
2019-05-07 15:27:09 +02:00
}
if o.Cookie.Refresh >= o.Cookie.Expire {
msgs = append(msgs, fmt.Sprintf(
"cookie_refresh (%s) must be less than "+
"cookie_expire (%s)",
o.Cookie.Refresh.String(),
o.Cookie.Expire.String()))
}
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")
}
}
switch o.Cookie.SameSite {
case "", "none", "lax", "strict":
default:
msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.Cookie.SameSite))
}
// Sort cookie domains by length, so that we try longer (and more specific)
// domains first
sort.Slice(o.Cookie.Domains, func(i, j int) bool {
return len(o.Cookie.Domains[i]) > len(o.Cookie.Domains[j])
})
msgs = parseSignatureKey(o, msgs)
msgs = validateCookieName(o, msgs)
msgs = configureLogger(o.Logging, o.PingPath, msgs)
if o.ReverseProxy {
2020-05-23 16:17:41 +02:00
parser, err := ip.GetRealClientIPParser(o.RealClientIPHeader)
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)
// Allow the logger to get client IPs
logger.SetGetClientFunc(func(r *http.Request) string {
return ip.GetClientString(o.GetRealClientIPParser(), r, false)
})
}
if len(msgs) != 0 {
return fmt.Errorf("invalid configuration:\n %s",
strings.Join(msgs, "\n "))
}
2014-11-09 21:51:10 +02:00
return nil
}
2020-04-13 14:50:34 +02:00
func parseProviderInfo(o *options.Options, msgs []string) []string {
p := &providers.ProviderData{
Scope: o.Scope,
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,
ClientSecretFile: o.ClientSecretFile,
Prompt: o.Prompt,
ApprovalPrompt: o.ApprovalPrompt,
AcrValues: o.AcrValues,
}
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-04-13 14:50:34 +02:00
o.SetProvider(providers.New(o.ProviderType, p))
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)
p.SetRepo(o.GitHubRepo, o.GitHubToken)
p.SetUsers(o.GitHubUsers)
2019-07-28 15:54:39 +02:00
case *providers.KeycloakProvider:
p.SetGroup(o.KeycloakGroup)
case *providers.GoogleProvider:
if o.GoogleServiceAccountJSON != "" {
file, err := os.Open(o.GoogleServiceAccountJSON)
if err != nil {
msgs = append(msgs, "invalid Google credentials file: "+o.GoogleServiceAccountJSON)
} else {
p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file)
}
}
case *providers.BitbucketProvider:
p.SetTeam(o.BitbucketTeam)
p.SetRepository(o.BitbucketRepository)
case *providers.OIDCProvider:
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
p.UserIDClaim = o.UserIDClaim
2020-04-13 14:50:34 +02:00
if o.GetOIDCVerifier() == nil {
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
} else {
2020-04-13 14:50:34 +02:00
p.Verifier = o.GetOIDCVerifier()
}
case *providers.GitLabProvider:
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
p.Group = o.GitLabGroup
p.EmailDomains = o.EmailDomains
2020-04-13 14:50:34 +02:00
if o.GetOIDCVerifier() != nil {
p.Verifier = o.GetOIDCVerifier()
} else {
// 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)
}
}
add login.gov provider (#55) * first stab at login.gov provider * fixing bugs now that I think I understand things better * fixing up dependencies * remove some debug stuff * Fixing all dependencies to point at my fork * forgot to hit save on the github rehome here * adding options for setting keys and so on, use JWT workflow instead of PKCE * forgot comma * was too aggressive with search/replace * need JWTKey to be byte array * removed custom refresh stuff * do our own custom jwt claim and store it in the normal session store * golang json types are strange * I have much to learn about golang * fix time and signing key * add http lib * fixed claims up since we don't need custom claims * add libs * forgot ioutil * forgot ioutil * moved back to pusher location * changed proxy github location back so that it builds externally, fixed up []byte stuff, removed client_secret if we are using login.gov * update dependencies * do JWTs properly * finished oidc flow, fixed up tests to work better * updated comments, added test that we set expiresOn properly * got confused with header and post vs get * clean up debug and test dir * add login.gov to README, remove references to my repo * forgot to remove un-needed code * can use sample_key* instead of generating your own * updated changelog * apparently golint wants comments like this * linter wants non-standard libs in a separate grouping * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * remove sample_key, improve comments related to client-secret, fix changelog related to PR feedback * github doesn't seem to do gofmt when merging. :-) * update CODEOWNERS * check the nonce * validate the JWT fully * forgot to add pubjwk-url to README * unexport the struct * fix up the err masking that travis found * update nonce comment by request of @JoelSpeed * argh. Thought I'd formatted the merge properly, but apparently not. * fixed test to not fail if the query time was greater than zero
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 == "":
add login.gov provider (#55) * first stab at login.gov provider * fixing bugs now that I think I understand things better * fixing up dependencies * remove some debug stuff * Fixing all dependencies to point at my fork * forgot to hit save on the github rehome here * adding options for setting keys and so on, use JWT workflow instead of PKCE * forgot comma * was too aggressive with search/replace * need JWTKey to be byte array * removed custom refresh stuff * do our own custom jwt claim and store it in the normal session store * golang json types are strange * I have much to learn about golang * fix time and signing key * add http lib * fixed claims up since we don't need custom claims * add libs * forgot ioutil * forgot ioutil * moved back to pusher location * changed proxy github location back so that it builds externally, fixed up []byte stuff, removed client_secret if we are using login.gov * update dependencies * do JWTs properly * finished oidc flow, fixed up tests to work better * updated comments, added test that we set expiresOn properly * got confused with header and post vs get * clean up debug and test dir * add login.gov to README, remove references to my repo * forgot to remove un-needed code * can use sample_key* instead of generating your own * updated changelog * apparently golint wants comments like this * linter wants non-standard libs in a separate grouping * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * remove sample_key, improve comments related to client-secret, fix changelog related to PR feedback * github doesn't seem to do gofmt when merging. :-) * update CODEOWNERS * check the nonce * validate the JWT fully * forgot to add pubjwk-url to README * unexport the struct * fix up the err masking that travis found * update nonce comment by request of @JoelSpeed * argh. Thought I'd formatted the merge properly, but apparently not. * fixed test to not fail if the query time was greater than zero
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
add login.gov provider (#55) * first stab at login.gov provider * fixing bugs now that I think I understand things better * fixing up dependencies * remove some debug stuff * Fixing all dependencies to point at my fork * forgot to hit save on the github rehome here * adding options for setting keys and so on, use JWT workflow instead of PKCE * forgot comma * was too aggressive with search/replace * need JWTKey to be byte array * removed custom refresh stuff * do our own custom jwt claim and store it in the normal session store * golang json types are strange * I have much to learn about golang * fix time and signing key * add http lib * fixed claims up since we don't need custom claims * add libs * forgot ioutil * forgot ioutil * moved back to pusher location * changed proxy github location back so that it builds externally, fixed up []byte stuff, removed client_secret if we are using login.gov * update dependencies * do JWTs properly * finished oidc flow, fixed up tests to work better * updated comments, added test that we set expiresOn properly * got confused with header and post vs get * clean up debug and test dir * add login.gov to README, remove references to my repo * forgot to remove un-needed code * can use sample_key* instead of generating your own * updated changelog * apparently golint wants comments like this * linter wants non-standard libs in a separate grouping * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * remove sample_key, improve comments related to client-secret, fix changelog related to PR feedback * github doesn't seem to do gofmt when merging. :-) * update CODEOWNERS * check the nonce * validate the JWT fully * forgot to add pubjwk-url to README * unexport the struct * fix up the err masking that travis found * update nonce comment by request of @JoelSpeed * argh. Thought I'd formatted the merge properly, but apparently not. * fixed test to not fail if the query time was greater than zero
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
}
add login.gov provider (#55) * first stab at login.gov provider * fixing bugs now that I think I understand things better * fixing up dependencies * remove some debug stuff * Fixing all dependencies to point at my fork * forgot to hit save on the github rehome here * adding options for setting keys and so on, use JWT workflow instead of PKCE * forgot comma * was too aggressive with search/replace * need JWTKey to be byte array * removed custom refresh stuff * do our own custom jwt claim and store it in the normal session store * golang json types are strange * I have much to learn about golang * fix time and signing key * add http lib * fixed claims up since we don't need custom claims * add libs * forgot ioutil * forgot ioutil * moved back to pusher location * changed proxy github location back so that it builds externally, fixed up []byte stuff, removed client_secret if we are using login.gov * update dependencies * do JWTs properly * finished oidc flow, fixed up tests to work better * updated comments, added test that we set expiresOn properly * got confused with header and post vs get * clean up debug and test dir * add login.gov to README, remove references to my repo * forgot to remove un-needed code * can use sample_key* instead of generating your own * updated changelog * apparently golint wants comments like this * linter wants non-standard libs in a separate grouping * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * Update options.go Co-Authored-By: timothy-spencer <timothy.spencer@gsa.gov> * remove sample_key, improve comments related to client-secret, fix changelog related to PR feedback * github doesn't seem to do gofmt when merging. :-) * update CODEOWNERS * check the nonce * validate the JWT fully * forgot to add pubjwk-url to README * unexport the struct * fix up the err masking that travis found * update nonce comment by request of @JoelSpeed * argh. Thought I'd formatted the merge properly, but apparently not. * fixed test to not fail if the query time was greater than zero
2019-03-20 15:44:51 +02:00
}
2015-05-21 05:23:48 +02:00
}
return msgs
}
2020-04-13 14:50:34 +02:00
func parseSignatureKey(o *options.Options, msgs []string) []string {
if o.SignatureKey == "" {
return msgs
}
components := strings.Split(o.SignatureKey, ":")
if len(components) != 2 {
return append(msgs, "invalid signature hash:key spec: "+
o.SignatureKey)
}
algorithm, secretKey := components[0], components[1]
2018-11-29 16:26:41 +02:00
var hash crypto.Hash
var err error
if hash, err = hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
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})
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) {
parsedIssuers := make([]jwtIssuer, 0, len(issuers))
2019-05-01 19:00:54 +02:00
for _, jwtVerifier := range issuers {
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
}
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-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"
_, err := http.NewRequest("GET", jwksURI, nil)
if err != nil {
return nil, err
}
verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
} else {
verifier = provider.Verifier(config)
}
return verifier, nil
}
2020-04-13 14:50:34 +02:00
func validateCookieName(o *options.Options, msgs []string) []string {
cookie := &http.Cookie{Name: o.Cookie.Name}
if cookie.String() == "" {
return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.Cookie.Name))
}
return msgs
}
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
}