github.com/argoproj/argo-cd@v1.8.7/util/oidc/provider.go (about) 1 package oidc 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strings" 8 9 gooidc "github.com/coreos/go-oidc" 10 log "github.com/sirupsen/logrus" 11 "golang.org/x/oauth2" 12 ) 13 14 // Provider is a wrapper around go-oidc provider to also provide the following features: 15 // 1. lazy initialization/querying of the provider 16 // 2. automatic detection of change in signing keys 17 // 3. convenience function for verifying tokens 18 // We have to initialize the provider lazily since Argo CD can be an OIDC client to itself (in the 19 // case of dex reverse proxy), which presents a chicken-and-egg problem of (1) serving dex over 20 // HTTP, and (2) querying the OIDC provider (ourself) to initialize the OIDC client. 21 type Provider interface { 22 Endpoint() (*oauth2.Endpoint, error) 23 24 ParseConfig() (*OIDCConfiguration, error) 25 26 Verify(clientID, tokenString string) (*gooidc.IDToken, error) 27 } 28 29 type providerImpl struct { 30 issuerURL string 31 client *http.Client 32 goOIDCProvider *gooidc.Provider 33 } 34 35 // NewOIDCProvider initializes an OIDC provider 36 func NewOIDCProvider(issuerURL string, client *http.Client) Provider { 37 return &providerImpl{ 38 issuerURL: issuerURL, 39 client: client, 40 } 41 } 42 43 // oidcProvider lazily initializes, memoizes, and returns the OIDC provider. 44 func (p *providerImpl) provider() (*gooidc.Provider, error) { 45 if p.goOIDCProvider != nil { 46 return p.goOIDCProvider, nil 47 } 48 prov, err := p.newGoOIDCProvider() 49 if err != nil { 50 return nil, err 51 } 52 p.goOIDCProvider = prov 53 return p.goOIDCProvider, nil 54 } 55 56 // newGoOIDCProvider creates a new instance of go-oidc.Provider querying the well known oidc 57 // configuration path (http://example-argocd.com/api/dex/.well-known/openid-configuration) 58 func (p *providerImpl) newGoOIDCProvider() (*gooidc.Provider, error) { 59 log.Infof("Initializing OIDC provider (issuer: %s)", p.issuerURL) 60 ctx := gooidc.ClientContext(context.Background(), p.client) 61 prov, err := gooidc.NewProvider(ctx, p.issuerURL) 62 if err != nil { 63 return nil, fmt.Errorf("Failed to query provider %q: %v", p.issuerURL, err) 64 } 65 s, _ := ParseConfig(prov) 66 log.Infof("OIDC supported scopes: %v", s.ScopesSupported) 67 return prov, nil 68 } 69 70 func (p *providerImpl) Verify(clientID, tokenString string) (*gooidc.IDToken, error) { 71 ctx := context.Background() 72 prov, err := p.provider() 73 if err != nil { 74 return nil, err 75 } 76 verifier := prov.Verifier(&gooidc.Config{ClientID: clientID}) 77 idToken, err := verifier.Verify(ctx, tokenString) 78 if err != nil { 79 // HACK: if we failed token verification, it's possible the reason was because dex 80 // restarted and has new JWKS signing keys (we do not back dex with persistent storage 81 // so keys might be regenerated). Detect this by: 82 // 1. looking for the specific error message 83 // 2. re-initializing the OIDC provider 84 // 3. re-attempting token verification 85 // NOTE: the error message is sensitive to implementation of verifier.Verify() 86 if !strings.Contains(err.Error(), "failed to verify signature") { 87 return nil, err 88 } 89 newProvider, retryErr := p.newGoOIDCProvider() 90 if retryErr != nil { 91 // return original error if we fail to re-initialize OIDC 92 return nil, err 93 } 94 verifier = newProvider.Verifier(&gooidc.Config{ClientID: clientID}) 95 idToken, err = verifier.Verify(ctx, tokenString) 96 if err != nil { 97 return nil, err 98 } 99 // If we get here, we successfully re-initialized OIDC and after re-initialization, 100 // the token is now valid. 101 log.Info("New OIDC settings detected") 102 p.goOIDCProvider = newProvider 103 } 104 return idToken, nil 105 } 106 107 func (p *providerImpl) Endpoint() (*oauth2.Endpoint, error) { 108 prov, err := p.provider() 109 if err != nil { 110 return nil, err 111 } 112 endpoint := prov.Endpoint() 113 return &endpoint, nil 114 } 115 116 // ParseConfig parses the OIDC Config into the concrete datastructure 117 func (p *providerImpl) ParseConfig() (*OIDCConfiguration, error) { 118 prov, err := p.provider() 119 if err != nil { 120 return nil, err 121 } 122 return ParseConfig(prov) 123 } 124 125 // ParseConfig parses the OIDC Config into the concrete datastructure 126 func ParseConfig(provider *gooidc.Provider) (*OIDCConfiguration, error) { 127 var conf OIDCConfiguration 128 err := provider.Claims(&conf) 129 if err != nil { 130 return nil, err 131 } 132 return &conf, nil 133 }