func (cfg *OIDCConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *template.Template) (Connector, error) { ns.Path = path.Join(ns.Path, httpPathCallback) ccfg := oidc.ClientConfig{ RedirectURL: ns.String(), Credentials: oidc.ClientCredentials{ ID: cfg.ClientID, Secret: cfg.ClientSecret, }, } cl, err := oidc.NewClient(ccfg) if err != nil { return nil, err } idpc := &OIDCConnector{ id: cfg.ID, issuerURL: cfg.IssuerURL, cbURL: ns, loginFunc: lf, client: cl, trustedEmailProvider: cfg.TrustedEmailProvider, emailClaim: cfg.EmailClaim, } return idpc, nil }
func newAPIDriver(pcfg oidc.ProviderConfig, creds oidc.ClientCredentials) (driver, error) { ccfg := oidc.ClientConfig{ ProviderConfig: pcfg, Credentials: creds, } oc, err := oidc.NewClient(ccfg) if err != nil { return nil, err } trans := &oidc.AuthenticatedTransport{ TokenRefresher: &oidc.ClientCredsTokenRefresher{ Issuer: pcfg.Issuer, OIDCClient: oc, }, RoundTripper: http.DefaultTransport, } hc := &http.Client{Transport: trans} svc, err := schema.NewWithBasePath(hc, pcfg.Issuer) if err != nil { return nil, err } return &apiDriver{svc: svc}, nil }
func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err error) { c, err := oidc.NewClient(p.clientConfig) if err != nil { log.Fatalf("Unable to create Client: %v", err) } tok, err := c.ExchangeAuthCode(code) if err != nil { log.Printf("exchange auth error: %v\n", err) return nil, err } claims, err := tok.Claims() if err != nil { log.Printf("token claims error: %v", err) return nil, err } s = &SessionState{ AccessToken: tok.Data(), RefreshToken: tok.Data(), ExpiresOn: time.Now().Add(time.Duration(claims["exp"].(float64)) * time.Second).Truncate(time.Second), Email: claims["email"].(string), } return }
func (op *OIDCProvider) InitOIDCClient() error { if op.Issuer == "" { return fmt.Errorf("Issuer not defined for OpenID Connect provider %+v", op) } config, shouldSyncConfig, err := op.DiscoverConfig() if err != nil || config == nil { base.Warn("Error during OIDC discovery - unable to initialize client: %v", err) return err } clientCredentials := oidc.ClientCredentials{ ID: *op.ClientID, } if op.ValidationKey != nil { clientCredentials.Secret = *op.ValidationKey } clientConfig := oidc.ClientConfig{ ProviderConfig: *config, Credentials: clientCredentials, RedirectURL: *op.CallbackURL, } if op.Scope != nil || len(op.Scope) > 0 { clientConfig.Scope = op.Scope } else { clientConfig.Scope = []string{"openid", "email"} } op.OIDCClient, err = oidc.NewClient(clientConfig) if err != nil { return err } // Start process for ongoing sync of the provider config if shouldSyncConfig { base.LogTo("OIDC", "Not synchronizing provider config for issuer %s...", op.Issuer) op.OIDCClient.SyncProviderConfig(op.Issuer) } // Initialize the prefix for users created for this provider if err = op.InitUserPrefix(); err != nil { return err } return nil }
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 }
// Helper to get an OIDC client. Blocks until successful. func GetOIDCClient(clientID, clientSecret, discoveryURL, redirectURL string) (*oidc.Client, error) { cc := oidc.ClientCredentials{ ID: clientID, Secret: clientSecret, } var cfg oidc.ProviderConfig var err error cfg = oidc.WaitForProviderConfig(http.DefaultClient, discoveryURL) ccfg := oidc.ClientConfig{ ProviderConfig: cfg, Credentials: cc, RedirectURL: redirectURL, Scope: []string{"offline_access", "openid", "email", "profile"}, } oidcClient, err := oidc.NewClient(ccfg) if err != nil { return nil, err } oidcClient.SyncProviderConfig(discoveryURL) return oidcClient, 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) }
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 TestLoginURL(t *testing.T) { lf := func(ident oidc.Identity, sessionKey string) (redirectURL string, err error) { return } tests := []struct { cid string redir string state string scope []string prompt string v url.Values }{ // Standard example { cid: "fake-client-id", redir: "http://example.com/oauth-redirect", state: "fake-session-id", scope: []string{"openid", "email", "profile"}, prompt: "", v: url.Values{ "response_type": {"code"}, "state": {"fake-session-id"}, "redirect_uri": {"http://example.com/oauth-redirect"}, "scope": {"openid email profile"}, "client_id": {"fake-client-id"}, }, }, // No scope { cid: "fake-client-id", redir: "http://example.com/oauth-redirect", state: "fake-session-id", scope: []string{}, prompt: "", v: url.Values{ "response_type": {"code"}, "state": {"fake-session-id"}, "redirect_uri": {"http://example.com/oauth-redirect"}, "scope": {""}, "client_id": {"fake-client-id"}, }, }, // No state { cid: "fake-client-id", redir: "http://example.com/oauth-redirect", state: "", scope: []string{}, prompt: "", v: url.Values{ "response_type": {"code"}, "state": {""}, "redirect_uri": {"http://example.com/oauth-redirect"}, "scope": {""}, "client_id": {"fake-client-id"}, }, }, // Force prompt { cid: "fake-client-id", redir: "http://example.com/oauth-redirect", state: "fake-session-id", scope: []string{"openid", "email", "profile"}, prompt: "select_account", v: url.Values{ "response_type": {"code"}, "prompt": {"select_account"}, "state": {"fake-session-id"}, "redirect_uri": {"http://example.com/oauth-redirect"}, "scope": {"openid email profile"}, "client_id": {"fake-client-id"}, }, }, } for i, tt := range tests { cfg := oidc.ClientConfig{ Credentials: oidc.ClientCredentials{ID: tt.cid, Secret: "fake-client-secret"}, RedirectURL: tt.redir, ProviderConfig: oidc.ProviderConfig{ AuthEndpoint: "http://example.com/authorize", TokenEndpoint: "http://example.com/token", }, Scope: tt.scope, } cl, err := oidc.NewClient(cfg) if err != nil { t.Errorf("test: %d. unexpected error: %v", i, err) } cn := &OIDCConnector{ loginFunc: lf, client: cl, } lu, err := cn.LoginURL(tt.state, tt.prompt) if err != nil { t.Errorf("test: %d. want: no url error, got: error, error: %v", i, err) } u, err := url.Parse(lu) if err != nil { t.Errorf("test: %d. want: parsable url, got: unparsable url, error: %v", i, err) } got := u.Query() if !reflect.DeepEqual(tt.v, got) { t.Errorf("test: %d.\nwant: %v\ngot: %v", i, tt.v, got) } } }
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-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, } }
// 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 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", "", "") 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-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 ") } */ } }