func getDriver() (drv driver) { var err error switch { case len(global.dbURL) > 0: drv, err = newDBDriver(global.dbURL) case len(global.endpoint) > 0: if len(global.creds.ID) == 0 || len(global.creds.Secret) == 0 { err = errors.New("--client-id/--client-secret flags unset") } else { pcfg, err := oidc.FetchProviderConfig(http.DefaultClient, global.endpoint) if err != nil { stderr("Unable to fetch provider config: %v", err) os.Exit(1) } drv, err = newAPIDriver(pcfg, global.creds) } default: err = errors.New("--endpoint/--db-url flags unset") } if err != nil { stderr("Unable to configure dexctl driver: %v", err) os.Exit(1) } return }
func (op *OIDCProvider) DiscoverConfig() (config *oidc.ProviderConfig, shouldSync bool, err error) { // If discovery URI is explicitly defined, use it instead of the standard issuer-based discovery. if op.DiscoveryURI != "" || op.DisableConfigValidation { config, err = op.FetchCustomProviderConfig(op.DiscoveryURI) shouldSync = false } else { var standardConfig oidc.ProviderConfig maxRetryAttempts := 5 for i := 1; i <= maxRetryAttempts; i++ { standardConfig, err = oidc.FetchProviderConfig(http.DefaultClient, op.Issuer) if err == nil { config = &standardConfig shouldSync = true break } base.LogTo("OIDC+", "Unable to fetch provider config from discovery endpoint for %s (attempt %v/%v): %v", op.Issuer, i, maxRetryAttempts, err) time.Sleep(500 * time.Millisecond) } } return config, shouldSync, err }
func (a *OIDCAuthenticator) client() (*oidc.Client, error) { // Fast check to see if client has already been initialized. if client := a.oidcClient.Load(); client != nil { return client.(*oidc.Client), nil } // Acquire lock, then recheck initialization. a.mu.Lock() defer a.mu.Unlock() if client := a.oidcClient.Load(); client != nil { return client.(*oidc.Client), nil } // Try to initialize client. providerConfig, err := oidc.FetchProviderConfig(a.httpClient, strings.TrimSuffix(a.issuerURL, "/")) if err != nil { glog.Errorf("oidc authenticator: failed to fetch provider discovery data: %v", err) return nil, fmt.Errorf("fetch provider config: %v", err) } clientConfig := oidc.ClientConfig{ HTTPClient: a.httpClient, Credentials: oidc.ClientCredentials{ID: a.trustedClientID}, ProviderConfig: providerConfig, } client, err := oidc.NewClient(clientConfig) if err != nil { glog.Errorf("oidc authenticator: failed to create client: %v", err) return nil, fmt.Errorf("create client: %v", err) } // SyncProviderConfig will start a goroutine to periodically synchronize the provider config. // The synchronization interval is set by the expiration length of the config, and has a mininum // and maximum threshold. stop := client.SyncProviderConfig(a.issuerURL) a.oidcClient.Store(client) a.close = func() { // This assumes the stop is an unbuffered channel. // So instead of closing the channel, we send am empty struct here. // This guarantees that when this function returns, there is no flying requests, // because a send to an unbuffered channel happens after the receive from the channel. stop <- struct{}{} } return client, nil }
func NewClient(clientID, clientSecret, discovery, redirectURL string, tlsConfig *tls.Config) (*Client, *ClientCredsTokenManager) { cc := oidc.ClientCredentials{ ID: clientID, Secret: clientSecret, } httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}} var cfg oidc.ProviderConfig var err error for { cfg, err = oidc.FetchProviderConfig(httpClient, discovery) if err == nil { break } sleep := 3 * time.Second log.Errorf("Failed fetching provider config, trying again in %v: %v", sleep, err) time.Sleep(sleep) } log.Infof("Fetched provider config from %s", discovery) ccfg := oidc.ClientConfig{ HTTPClient: httpClient, ProviderConfig: cfg, Credentials: cc, RedirectURL: redirectURL, } client, err := NewOIDCClient(ccfg) if err != nil { log.Fatalf("Unable to create Client: %v", err) } client.SyncProviderConfig(discovery) tm := NewClientCredsTokenManager(client, discovery) tm.Run() tm.WaitUntilInitialSync() return client, tm }
func NewOIDCClient(id, secret, discovery string) (*Client, error) { var cfg oidc.ProviderConfig var err error for { cfg, err = oidc.FetchProviderConfig(http.DefaultClient, discovery) if err == nil { break } sleep := 1 * time.Second fmt.Printf("Failed fetching provider config, trying again in %v: %v\n", sleep, err) time.Sleep(sleep) } c := Client{ providerConfig: newProviderConfigRepo(cfg), httpClient: http.DefaultClient, } return &c, nil }
func mockClient(srv *server.Server, ci client.Client) (*oidc.Client, error) { hdlr := srv.HTTPHandler() sClient := &phttp.HandlerClient{Handler: hdlr} cfg, err := oidc.FetchProviderConfig(sClient, srv.IssuerURL.String()) if err != nil { return nil, fmt.Errorf("failed to fetch provider config: %v", err) } jwks, err := srv.KeyManager.JWKs() if err != nil { return nil, fmt.Errorf("failed to generate JWKs: %v", err) } ks := key.NewPublicKeySet(jwks, time.Now().Add(1*time.Hour)) ccfg := oidc.ClientConfig{ HTTPClient: sClient, ProviderConfig: cfg, Credentials: ci.Credentials, KeySet: *ks, } return oidc.NewClient(ccfg) }
// New creates a new OpenID Connect client with the given issuerURL and clientID. // NOTE(yifan): For now we assume the server provides the "jwks_uri" so we don't // need to manager the key sets by ourselves. func New(issuerURL, clientID, caFile, usernameClaim string) (*OIDCAuthenticator, error) { var cfg oidc.ProviderConfig var err error var roots *x509.CertPool url, err := url.Parse(issuerURL) if err != nil { return nil, err } if url.Scheme != "https" { return nil, fmt.Errorf("'oidc-issuer-url' (%q) has invalid scheme (%q), require 'https'", issuerURL, url.Scheme) } if caFile != "" { roots, err = util.CertPoolFromFile(caFile) if err != nil { glog.Errorf("Failed to read the CA file: %v", err) } } if roots == nil { glog.Info("No x509 certificates provided, will use host's root CA set") } // Copied from http.DefaultTransport. tr := &http.Transport{ // According to golang's doc, if RootCAs is nil, // TLS uses the host's root CA set. TLSClientConfig: &tls.Config{RootCAs: roots}, Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 10 * time.Second, } hc := &http.Client{} hc.Transport = tr for i := 0; i <= maxRetries; i++ { if i == maxRetries { return nil, fmt.Errorf("failed to fetch provider config after %v retries", maxRetries) } cfg, err = oidc.FetchProviderConfig(hc, issuerURL) if err == nil { break } glog.Errorf("Failed to fetch provider config, trying again in %v: %v", retryBackoff, err) time.Sleep(retryBackoff) } glog.Infof("Fetched provider config from %s: %#v", issuerURL, cfg) if cfg.KeysEndpoint == "" { return nil, fmt.Errorf("OIDC provider must provide 'jwks_uri' for public key discovery") } ccfg := oidc.ClientConfig{ HTTPClient: hc, Credentials: oidc.ClientCredentials{ID: clientID}, ProviderConfig: cfg, } client, err := oidc.NewClient(ccfg) if err != nil { return nil, err } // SyncProviderConfig will start a goroutine to periodically synchronize the provider config. // The synchronization interval is set by the expiration length of the config, and has a mininum // and maximum threshold. client.SyncProviderConfig(issuerURL) return &OIDCAuthenticator{ccfg, client, usernameClaim}, nil }
func newOIDCAuthProvider(_ string, cfg map[string]string, persister rest.AuthProviderConfigPersister) (rest.AuthProvider, error) { issuer := cfg[cfgIssuerUrl] if issuer == "" { return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl) } clientID := cfg[cfgClientID] if clientID == "" { return nil, fmt.Errorf("Must provide %s", cfgClientID) } clientSecret := cfg[cfgClientSecret] if clientSecret == "" { return nil, fmt.Errorf("Must provide %s", cfgClientSecret) } var certAuthData []byte var err error if cfg[cfgCertificateAuthorityData] != "" { certAuthData, err = base64.StdEncoding.DecodeString(cfg[cfgCertificateAuthorityData]) if err != nil { return nil, err } } clientConfig := rest.Config{ TLSClientConfig: rest.TLSClientConfig{ CAFile: cfg[cfgCertificateAuthority], CAData: certAuthData, }, } trans, err := rest.TransportFor(&clientConfig) if err != nil { return nil, err } hc := &http.Client{Transport: trans} providerCfg, err := oidc.FetchProviderConfig(hc, issuer) if err != nil { return nil, fmt.Errorf("error fetching provider config: %v", err) } scopes := strings.Split(cfg[cfgExtraScopes], ",") oidcCfg := oidc.ClientConfig{ HTTPClient: hc, Credentials: oidc.ClientCredentials{ ID: clientID, Secret: clientSecret, }, ProviderConfig: providerCfg, Scope: append(scopes, oidc.DefaultScope...), } client, err := oidc.NewClient(oidcCfg) if err != nil { return nil, fmt.Errorf("error creating OIDC Client: %v", err) } oClient := &oidcClient{client} var initialIDToken jose.JWT if cfg[cfgIDToken] != "" { initialIDToken, err = jose.ParseJWT(cfg[cfgIDToken]) if err != nil { return nil, err } } return &oidcAuthProvider{ initialIDToken: initialIDToken, refresher: &idTokenRefresher{ client: oClient, cfg: cfg, persister: persister, }, }, nil }
func TestHTTPExchangeTokenRefreshToken(t *testing.T) { password, err := user.NewPasswordFromPlaintext("woof") if err != nil { t.Fatalf("unexpectd error: %q", err) } passwordInfo := user.PasswordInfo{ UserID: "elroy77", Password: password, } cfg := &connector.LocalConnectorConfig{ ID: "local", } validRedirURL := url.URL{ Scheme: "http", Host: "client.example.com", Path: "/callback", } ci := client.Client{ Credentials: oidc.ClientCredentials{ ID: validRedirURL.Host, Secret: base64.URLEncoding.EncodeToString([]byte("secret")), }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ validRedirURL, }, }, } dbMap := db.NewMemDB() clientRepo, clientManager, err := makeClientRepoAndManager(dbMap, []client.LoadableClient{{ Client: ci, }}) if err != nil { t.Fatalf("Failed to create client identity manager: " + err.Error()) } passwordInfoRepo, err := db.NewPasswordInfoRepoFromPasswordInfos(db.NewMemDB(), []user.PasswordInfo{passwordInfo}) if err != nil { t.Fatalf("Failed to create password info repo: %v", err) } issuerURL := url.URL{Scheme: "http", Host: "server.example.com"} sm := manager.NewSessionManager(db.NewSessionRepo(dbMap), db.NewSessionKeyRepo(dbMap)) k, err := key.GeneratePrivateKey() if err != nil { t.Fatalf("Unable to generate RSA key: %v", err) } km := key.NewPrivateKeyManager() err = km.Set(key.NewPrivateKeySet([]*key.PrivateKey{k}, time.Now().Add(time.Minute))) if err != nil { t.Fatalf("Unexpected error: %v", err) } usr := user.User{ ID: "ID-test", Email: "*****@*****.**", DisplayName: "displayname", } userRepo := db.NewUserRepo(db.NewMemDB()) if err := userRepo.Create(nil, usr); err != nil { t.Fatalf("Unexpected error: %v", err) } refreshTokenRepo := refreshtest.NewTestRefreshTokenRepo() srv := &server.Server{ IssuerURL: issuerURL, KeyManager: km, SessionManager: sm, ClientRepo: clientRepo, ClientManager: clientManager, Templates: template.New(connector.LoginPageTemplateName), Connectors: []connector.Connector{}, UserRepo: userRepo, PasswordInfoRepo: passwordInfoRepo, RefreshTokenRepo: refreshTokenRepo, } if err = srv.AddConnector(cfg); err != nil { t.Fatalf("Unexpected error: %v", err) } sClient := &phttp.HandlerClient{Handler: srv.HTTPHandler()} pcfg, err := oidc.FetchProviderConfig(sClient, issuerURL.String()) if err != nil { t.Fatalf("Failed to fetch provider config: %v", err) } ks := key.NewPublicKeySet([]jose.JWK{k.JWK()}, time.Now().Add(1*time.Hour)) ccfg := oidc.ClientConfig{ HTTPClient: sClient, ProviderConfig: pcfg, Credentials: ci.Credentials, RedirectURL: validRedirURL.String(), KeySet: *ks, } cl, err := oidc.NewClient(ccfg) if err != nil { t.Fatalf("Failed creating oidc.Client: %v", err) } m := http.NewServeMux() var claims jose.Claims var refresh string m.HandleFunc("/callback", handleCallbackFunc(cl, &claims, &refresh)) cClient := &phttp.HandlerClient{Handler: m} // this will actually happen due to some interaction between the // end-user and a remote identity provider sessionID, err := sm.NewSession("bogus_idpc", ci.Credentials.ID, "bogus", url.URL{}, "", false, []string{"openid", "offline_access", "email", "profile"}) if err != nil { t.Fatalf("Unexpected error: %v", err) } if _, err = sm.AttachRemoteIdentity(sessionID, passwordInfo.Identity()); err != nil { t.Fatalf("Unexpected error: %v", err) } if _, err = sm.AttachUser(sessionID, usr.ID); err != nil { t.Fatalf("Unexpected error: %v", err) } key, err := sm.NewSessionKey(sessionID) if err != nil { t.Fatalf("Unexpected error: %v", err) } req, err := http.NewRequest("GET", fmt.Sprintf("http://client.example.com/callback?code=%s", key), nil) if err != nil { t.Fatalf("Failed creating HTTP request: %v", err) } resp, err := cClient.Do(req) if err != nil { t.Fatalf("Failed resolving HTTP requests against /callback: %v", err) } if err := verifyUserClaims(claims, &ci, &usr, issuerURL); err != nil { t.Fatalf("Failed to verify claims: %v", err) } if resp.StatusCode != http.StatusOK { t.Fatalf("Received status code %d, want %d", resp.StatusCode, http.StatusOK) } if refresh == "" { t.Fatalf("No refresh token") } // Use refresh token to get a new ID token. token, err := cl.RefreshToken(refresh) if err != nil { t.Fatalf("Unexpected error: %v", err) } claims, err = token.Claims() if err != nil { t.Fatalf("Failed parsing claims from client token: %v", err) } if err := verifyUserClaims(claims, &ci, &usr, issuerURL); err != nil { t.Fatalf("Failed to verify claims: %v", err) } }
func main() { fs := flag.NewFlagSet("oidc-app", flag.ExitOnError) listen := fs.String("listen", "http://127.0.0.1:5555", "") redirectURL := fs.String("redirect-url", "http://127.0.0.1:5555/callback", "") clientID := fs.String("client-id", "", "") clientSecret := fs.String("client-secret", "", "") caFile := fs.String("trusted-ca-file", "", "the TLS CA file, if empty then the host's root CA will be used") discovery := fs.String("discovery", "https://accounts.google.com", "") logDebug := fs.Bool("log-debug", false, "log debug-level information") logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps") if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if err := pflag.SetFlagsFromEnv(fs, "EXAMPLE_APP"); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if *logDebug { log.EnableDebug() } if *logTimestamps { log.EnableTimestamps() } if *clientID == "" { log.Fatal("--client-id must be set") } if *clientSecret == "" { log.Fatal("--client-secret must be set") } l, err := url.Parse(*listen) if err != nil { log.Fatalf("Unable to use --listen flag: %v", err) } _, p, err := net.SplitHostPort(l.Host) if err != nil { log.Fatalf("Unable to parse host from --listen flag: %v", err) } cc := oidc.ClientCredentials{ ID: *clientID, Secret: *clientSecret, } var tlsConfig tls.Config if *caFile != "" { roots := x509.NewCertPool() pemBlock, err := ioutil.ReadFile(*caFile) if err != nil { log.Fatalf("Unable to read ca file: %v", err) } roots.AppendCertsFromPEM(pemBlock) tlsConfig.RootCAs = roots } httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tlsConfig}} var cfg oidc.ProviderConfig for { cfg, err = oidc.FetchProviderConfig(httpClient, *discovery) if err == nil { break } sleep := 3 * time.Second log.Errorf("Failed fetching provider config, trying again in %v: %v", sleep, err) time.Sleep(sleep) } log.Infof("Fetched provider config from %s: %#v", *discovery, cfg) ccfg := oidc.ClientConfig{ HTTPClient: httpClient, ProviderConfig: cfg, Credentials: cc, RedirectURL: *redirectURL, } client, err := oidc.NewClient(ccfg) if err != nil { log.Fatalf("Unable to create Client: %v", err) } client.SyncProviderConfig(*discovery) redirectURLParsed, err := url.Parse(*redirectURL) if err != nil { log.Fatalf("Unable to parse url from --redirect-url flag: %v", err) } hdlr := NewClientHandler(client, *discovery, *redirectURLParsed) httpsrv := &http.Server{ Addr: fmt.Sprintf(":%s", p), Handler: hdlr, } log.Infof("Binding to %s...", httpsrv.Addr) log.Fatal(httpsrv.ListenAndServe()) }
func main() { fs := flag.NewFlagSet("oidc-example-cli", flag.ExitOnError) clientID := fs.String("client-id", "", "") clientSecret := fs.String("client-secret", "", "") discovery := fs.String("discovery", "https://accounts.google.com", "") if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if *clientID == "" { fmt.Println("--client-id must be set") os.Exit(2) } if *clientSecret == "" { fmt.Println("--client-secret must be set") os.Exit(2) } cc := oidc.ClientCredentials{ ID: *clientID, Secret: *clientSecret, } fmt.Printf("fetching provider config from %s...", *discovery) // NOTE: A real CLI would cache this config, or provide it via flags/config file. var cfg oidc.ProviderConfig var err error for { cfg, err = oidc.FetchProviderConfig(http.DefaultClient, *discovery) if err == nil { break } sleep := 1 * time.Second fmt.Printf("failed fetching provider config, trying again in %v: %v\n", sleep, err) time.Sleep(sleep) } fmt.Printf("fetched provider config from %s: %#v\n\n", *discovery, cfg) ccfg := oidc.ClientConfig{ ProviderConfig: cfg, Credentials: cc, } client, err := oidc.NewClient(ccfg) if err != nil { fmt.Printf("unable to create Client: %v\n", err) os.Exit(1) } tok, err := client.ClientCredsToken([]string{"openid"}) if err != nil { fmt.Printf("unable to verify auth code with issuer: %v\n", err) os.Exit(1) } fmt.Printf("got jwt: %v\n\n", tok.Encode()) claims, err := tok.Claims() if err != nil { fmt.Printf("unable to construct claims: %v\n", err) os.Exit(1) } fmt.Printf("got claims %#v...\n", claims) }
func main() { fs := flag.NewFlagSet("example-cli", flag.ExitOnError) clientID := fs.String("client-id", "", "") clientSecret := fs.String("client-secret", "", "") discovery := fs.String("discovery", "http://localhost:5556", "") logDebug := fs.Bool("log-debug", false, "log debug-level information") logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps") if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if err := pflag.SetFlagsFromEnv(fs, "EXAMPLE_CLI"); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if *logDebug { log.EnableDebug() } if *logTimestamps { log.EnableTimestamps() } if *clientID == "" { fmt.Println("--client-id must be set") os.Exit(2) } if *clientSecret == "" { fmt.Println("--client-secret must be set") os.Exit(2) } cc := oidc.ClientCredentials{ ID: *clientID, Secret: *clientSecret, } // NOTE: A real CLI would cache this config, or provide it via flags/config file. var cfg oidc.ProviderConfig var err error for { cfg, err = oidc.FetchProviderConfig(http.DefaultClient, *discovery) if err == nil { break } sleep := 1 * time.Second fmt.Printf("Failed fetching provider config, trying again in %v: %v\n", sleep, err) time.Sleep(sleep) } fmt.Printf("Fetched provider config from %s: %#v\n\n", *discovery, cfg) ccfg := oidc.ClientConfig{ ProviderConfig: cfg, Credentials: cc, } client, err := oidc.NewClient(ccfg) if err != nil { log.Fatalf("Unable to create Client: %v", err) } tok, err := client.ClientCredsToken([]string{"openid"}) if err != nil { fmt.Printf("unable to verify auth code with issuer: %v\n", err) os.Exit(1) } fmt.Printf("got jwt: %v\n\n", tok.Encode()) claims, err := tok.Claims() if err != nil { fmt.Printf("unable to construct claims: %v\n", err) os.Exit(1) } fmt.Printf("got claims %#v...\n", claims) }
func main() { log.SetOutput(os.Stderr) fs := flag.NewFlagSet("oidc-example-app", flag.ExitOnError) listen := fs.String("listen", defaultListenHost, "serve traffic on this address (<host>:<port>)") redirectURL := fs.String("redirect-url", fmt.Sprintf("http://%s%s", defaultListenHost, pathCallback), "") clientID := fs.String("client-id", "", "") clientSecret := fs.String("client-secret", "", "") discovery := fs.String("discovery", "https://accounts.google.com", "") if err := fs.Parse(os.Args[1:]); err != nil { log.Fatalf("failed parsing flags: %v", err) } if *clientID == "" { log.Fatal("--client-id must be set") } if *clientSecret == "" { log.Fatal("--client-secret must be set") } _, _, err := net.SplitHostPort(*listen) if err != nil { log.Fatalf("unable to parse host:port from --listen flag: %v", err) } cc := oidc.ClientCredentials{ ID: *clientID, Secret: *clientSecret, } log.Printf("fetching provider config from %s...", *discovery) var cfg oidc.ProviderConfig for { cfg, err = oidc.FetchProviderConfig(http.DefaultClient, *discovery) if err == nil { break } sleep := 3 * time.Second log.Printf("failed fetching provider config, trying again in %v: %v", sleep, err) time.Sleep(sleep) } log.Printf("fetched provider config from %s: %#v", *discovery, cfg) ccfg := oidc.ClientConfig{ ProviderConfig: cfg, Credentials: cc, RedirectURL: *redirectURL, } client, err := oidc.NewClient(ccfg) if err != nil { log.Fatalf("unable to create Client: %v", err) } client.SyncProviderConfig(*discovery) redirectURLParsed, err := url.Parse(*redirectURL) if err != nil { log.Fatalf("unable to parse url from --redirect-url flag: %v", err) } hdlr := NewClientHandler(client, *redirectURLParsed) httpsrv := &http.Server{ Addr: fmt.Sprintf(*listen), Handler: hdlr, } log.Printf("binding to %s...", httpsrv.Addr) log.Fatal(httpsrv.ListenAndServe()) }
// New creates a new OpenID Connect client with the given issuerURL and clientID. // NOTE(yifan): For now we assume the server provides the "jwks_uri" so we don't // need to manager the key sets by ourselves. func New(opts OIDCOptions) (*OIDCAuthenticator, error) { var cfg oidc.ProviderConfig var err error var roots *x509.CertPool url, err := url.Parse(opts.IssuerURL) if err != nil { return nil, err } if url.Scheme != "https" { return nil, fmt.Errorf("'oidc-issuer-url' (%q) has invalid scheme (%q), require 'https'", opts.IssuerURL, url.Scheme) } if opts.CAFile != "" { roots, err = crypto.CertPoolFromFile(opts.CAFile) if err != nil { glog.Errorf("Failed to read the CA file: %v", err) } } if roots == nil { glog.Info("No x509 certificates provided, will use host's root CA set") } // Copied from http.DefaultTransport. tr := net.SetTransportDefaults(&http.Transport{ // According to golang's doc, if RootCAs is nil, // TLS uses the host's root CA set. TLSClientConfig: &tls.Config{RootCAs: roots}, }) hc := &http.Client{} hc.Transport = tr maxRetries := opts.MaxRetries if maxRetries < 0 { maxRetries = DefaultRetries } retryBackoff := opts.RetryBackoff if retryBackoff < 0 { retryBackoff = DefaultBackoff } for i := 0; i <= maxRetries; i++ { if i == maxRetries { return nil, fmt.Errorf("failed to fetch provider config after %v retries", maxRetries) } cfg, err = oidc.FetchProviderConfig(hc, strings.TrimSuffix(opts.IssuerURL, "/")) if err == nil { break } glog.Errorf("Failed to fetch provider config, trying again in %v: %v", retryBackoff, err) time.Sleep(retryBackoff) } glog.Infof("Fetched provider config from %s: %#v", opts.IssuerURL, cfg) ccfg := oidc.ClientConfig{ HTTPClient: hc, Credentials: oidc.ClientCredentials{ID: opts.ClientID}, ProviderConfig: cfg, } client, err := oidc.NewClient(ccfg) if err != nil { return nil, err } // SyncProviderConfig will start a goroutine to periodically synchronize the provider config. // The synchronization interval is set by the expiration length of the config, and has a mininum // and maximum threshold. stop := client.SyncProviderConfig(opts.IssuerURL) return &OIDCAuthenticator{ ccfg, client, opts.UsernameClaim, opts.GroupsClaim, stop, maxRetries, retryBackoff, }, nil }
func NewOIDCProvider(p *ProviderData) *OIDCProvider { var err error p.ProviderName = "OpenID Connect" cc := oidc.ClientCredentials{ ID: p.ClientID, Secret: p.ClientSecret, } var tlsConfig tls.Config // TODO: do handling of custom certs httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tlsConfig}} var cfg oidc.ProviderConfig for { cfg, err = oidc.FetchProviderConfig(httpClient, p.DiscoveryURL.String()) if err == nil { break } sleep := 3 * time.Second log.Printf("Failed fetching provider config, trying again in %v: %v", sleep, err) time.Sleep(sleep) } u, err := url.Parse(cfg.TokenEndpoint) if err != nil { panic(err) } p.ValidateURL = u u, err = url.Parse(cfg.AuthEndpoint) if err != nil { panic(err) } p.RedeemURL = u p.Scope = "email" ccfg := oidc.ClientConfig{ HTTPClient: httpClient, ProviderConfig: cfg, Credentials: cc, } client, err := oidc.NewClient(ccfg) if err != nil { log.Fatalf("Unable to create Client: %v", err) } client.SyncProviderConfig(p.DiscoveryURL.String()) oac, err := client.OAuthClient() if err != nil { panic("unable to proceed") } login, err := url.Parse(oac.AuthCodeURL("", "", "")) if err != nil { panic("unable to proceed") } p.LoginURL = login return &OIDCProvider{ ProviderData: p, clientConfig: ccfg, } }
func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { issuer := cfg[cfgIssuerUrl] if issuer == "" { return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl) } clientID := cfg[cfgClientID] if clientID == "" { return nil, fmt.Errorf("Must provide %s", cfgClientID) } clientSecret := cfg[cfgClientSecret] if clientSecret == "" { return nil, fmt.Errorf("Must provide %s", cfgClientSecret) } // Check cache for existing provider. if provider, ok := cache.getClient(issuer, clientID, clientSecret); ok { return provider, nil } var certAuthData []byte var err error if cfg[cfgCertificateAuthorityData] != "" { certAuthData, err = base64.StdEncoding.DecodeString(cfg[cfgCertificateAuthorityData]) if err != nil { return nil, err } } clientConfig := restclient.Config{ TLSClientConfig: restclient.TLSClientConfig{ CAFile: cfg[cfgCertificateAuthority], CAData: certAuthData, }, } trans, err := restclient.TransportFor(&clientConfig) if err != nil { return nil, err } hc := &http.Client{Transport: trans} providerCfg, err := oidc.FetchProviderConfig(hc, issuer) if err != nil { return nil, fmt.Errorf("error fetching provider config: %v", err) } scopes := strings.Split(cfg[cfgExtraScopes], ",") oidcCfg := oidc.ClientConfig{ HTTPClient: hc, Credentials: oidc.ClientCredentials{ ID: clientID, Secret: clientSecret, }, ProviderConfig: providerCfg, Scope: append(scopes, oidc.DefaultScope...), } client, err := oidc.NewClient(oidcCfg) if err != nil { return nil, fmt.Errorf("error creating OIDC Client: %v", err) } provider := &oidcAuthProvider{ client: &oidcClient{client}, cfg: cfg, persister: persister, now: time.Now, } return cache.setClient(issuer, clientID, clientSecret, provider), nil }
func main() { fs := flag.NewFlagSet("oidc-app", flag.ExitOnError) listen := fs.String("listen", "http://127.0.0.1:5555", "") redirectURL := fs.String("redirect-url", "http://127.0.0.1:5555/callback", "") clientID := fs.String("client-id", "example-app", "") clientSecret := fs.String("client-secret", "ZXhhbXBsZS1hcHAtc2VjcmV0", "") caFile := fs.String("trusted-ca-file", "", "the TLS CA file, if empty then the host's root CA will be used") certFile := fs.String("tls-cert-file", "", "the TLS cert file. If empty, the app will listen on HTTP") keyFile := fs.String("tls-key-file", "", "the TLS key file. If empty, the app will listen on HTTP") discovery := fs.String("discovery", "http://127.0.0.1:5556", "") logDebug := fs.Bool("log-debug", false, "log debug-level information") logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps") if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if err := pflag.SetFlagsFromEnv(fs, "EXAMPLE_APP"); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if *logDebug { log.EnableDebug() } if *logTimestamps { log.EnableTimestamps() } if *clientID == "" { log.Fatal("--client-id must be set") } if *clientSecret == "" { log.Fatal("--client-secret must be set") } l, err := url.Parse(*listen) if err != nil { log.Fatalf("Unable to use --listen flag: %v", err) } _, p, err := net.SplitHostPort(l.Host) if err != nil { log.Fatalf("Unable to parse host from --listen flag: %v", err) } redirectURLParsed, err := url.Parse(*redirectURL) if err != nil { log.Fatalf("Unable to parse url from --redirect-url flag: %v", err) } useTLS := *keyFile != "" && *certFile != "" if useTLS && (redirectURLParsed.Scheme != "https" || l.Scheme != "https") { log.Fatalf(`TLS Cert File and Key File were provided. Ensure listen and redirect URLs are using the "https://" scheme.`) } cc := oidc.ClientCredentials{ ID: *clientID, Secret: *clientSecret, } var tlsConfig tls.Config if *caFile != "" { roots := x509.NewCertPool() pemBlock, err := ioutil.ReadFile(*caFile) if err != nil { log.Fatalf("Unable to read ca file: %v", err) } roots.AppendCertsFromPEM(pemBlock) tlsConfig.RootCAs = roots } httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tlsConfig}} var cfg oidc.ProviderConfig for { cfg, err = oidc.FetchProviderConfig(httpClient, *discovery) if err == nil { break } sleep := 3 * time.Second log.Errorf("Failed fetching provider config, trying again in %v: %v", sleep, err) time.Sleep(sleep) } log.Infof("Fetched provider config from %s: %#v", *discovery, cfg) ccfg := oidc.ClientConfig{ HTTPClient: httpClient, ProviderConfig: cfg, Credentials: cc, RedirectURL: *redirectURL, Scope: append(oidc.DefaultScope, "offline_access"), } client, err := oidc.NewClient(ccfg) if err != nil { log.Fatalf("Unable to create Client: %v", err) } client.SyncProviderConfig(*discovery) hdlr := NewClientHandler(client, *discovery, *redirectURLParsed) httpsrv := &http.Server{ Addr: fmt.Sprintf(":%s", p), Handler: hdlr, } indexBytes, err := Asset("data/index.html") if err != nil { log.Fatalf("could not load template: %q", err) } indexTemplate = template.Must(template.New("root").Parse(string(indexBytes))) log.Infof("Binding to %s...", httpsrv.Addr) if useTLS { log.Info("Key and cert file provided. Using TLS") log.Fatal(httpsrv.ListenAndServeTLS(*certFile, *keyFile)) } else { log.Fatal(httpsrv.ListenAndServe()) } }
func connectClient(connect2registry, connect2catalog, connect2api bool) { discovery := os.Getenv("DEX_WORKER_ISSUER") cfg, err := oidc.FetchProviderConfig(http.DefaultClient, discovery) if err != nil { logrus.Fatalln("Unable to fetch provider configs") } ccfg := oidc.ClientConfig{ ProviderConfig: cfg, Credentials: cc, } //logrus.Printf("ccfg.Credentials: %+v",ccfg.Credentials) //logrus.Printf("ccfg.ProviderConfig: %+v",ccfg.ProviderConfig) myclient, err := oidc.NewClient(ccfg) if err != nil { logrus.Fatalf("Unable to create Client: %v", err) } tok, err := myclient.ClientCredsToken([]string{"openid"}) if err != nil { logrus.Printf("Token: %+v ", tok) logrus.Errorf("Error: %+v", err) logrus.Fatalf("Unable to get token") } jwtCreds := NewOauthAccess(tok.Encode()) //logrus.Println("got the jwtCreds") claims, err := tok.Claims() clientsub, _, _ = claims.StringClaim("sub") //logrus.Println("Get the clientsub", clientsub) if err != nil { logrus.Fatalln("unable to get getClientIdAndEmail", err) } if len(cafile) > 0 { auth, err := credentials.NewClientTLSFromFile(cafile, "") if err != nil { panic(err) } else { opts = append(opts, grpc.WithTransportCredentials(auth)) } } else { jwtCreds.RequireTLS = false opts = append(opts, grpc.WithInsecure()) } opts = append(opts, grpc.WithPerRPCCredentials(&jwtCreds)) if connect2api { apiUrl = os.Getenv("OTSIMO_SERVICES_API_URL") if apiUrl == "" { panic("OTSIMO_SERVICES_API_URL must set") } apiConn, _ := grpc.Dial(apiUrl, opts...) apiClient = apipb.NewApiServiceClient(apiConn) /* if err != nil { logrus.Println(" cannot dial ") logrus.Println(err) } else { logrus.Println(" dialed ") } if apiClient == nil { logrus.Println(" apiClient error ") } else { logrus.Println(" apiClient ") } */ } //------------------------------------------------------------------------------------------------------------------ if connect2registry { registryUrl = os.Getenv("OTSIMOCTL_REGISTRY") if registryUrl == "" { panic("OTSIMOCTL_REGISTRY must set") } regConn, _ := grpc.Dial(registryUrl, opts...) registryClient = apipb.NewRegistryServiceClient(regConn) /* if err != nil { logrus.Println(" cannot dial ") logrus.Println(err) } else { logrus.Println(" dialed ") } if registryClient == nil { logrus.Println(" catalogClt error ") } else { logrus.Println(" catalogClient ") } */ } //------------------------------------------------------------------------------------------------------------------ if connect2catalog { catalogUrl = os.Getenv("OTSIMOCTL_CATALOG") if catalogUrl == "" { panic("OTSIMOCTL_CATALOG must set") } catConn, _ := grpc.Dial(catalogUrl, opts...) catalogClient = apipb.NewCatalogServiceClient(catConn) /* if err != nil { logrus.Println(" cannot dial ") logrus.Println(err) } else { logrus.Println(" dialed ") } if catalogClient == nil { logrus.Println(" catalogClt error ") } else { logrus.Println(" catalogClient ") } */ } }