mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-12 16:47:41 +02:00
authenticate/providers : add gitlab support (#28)
- Add UserInfo struct and implementation to gather additional user information if the endpoint exists. - Add example docker-compose.yml for on-prem gitlab. - Add gitlab docs. - Removed explicit email checks in handlers. - Providers are now a protected type on provider data. - Alphabetized provider list. - Refactored authenticate.New to be more concise.
This commit is contained in:
parent
426e003b03
commit
b9c298d278
16 changed files with 510 additions and 182 deletions
|
@ -29,7 +29,7 @@ var defaultOptions = &Options{
|
|||
|
||||
// Options permits the configuration of the authentication service
|
||||
type Options struct {
|
||||
RedirectURL *url.URL `envconfig:"REDIRECT_URL" ` // e.g. auth.example.com/oauth/callback
|
||||
RedirectURL *url.URL `envconfig:"REDIRECT_URL"`
|
||||
|
||||
SharedKey string `envconfig:"SHARED_SECRET"`
|
||||
|
||||
|
@ -49,10 +49,14 @@ type Options struct {
|
|||
SessionLifetimeTTL time.Duration `envconfig:"SESSION_LIFETIME_TTL"`
|
||||
|
||||
// Authentication provider configuration vars
|
||||
ClientID string `envconfig:"IDP_CLIENT_ID"` // IdP ClientID
|
||||
ClientSecret string `envconfig:"IDP_CLIENT_SECRET"` // IdP Secret
|
||||
Provider string `envconfig:"IDP_PROVIDER"` //Provider name e.g. "oidc","okta","google",etc
|
||||
ProviderURL string `envconfig:"IDP_PROVIDER_URL"`
|
||||
// See: https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749
|
||||
ClientID string `envconfig:"IDP_CLIENT_ID"`
|
||||
ClientSecret string `envconfig:"IDP_CLIENT_SECRET"`
|
||||
Provider string `envconfig:"IDP_PROVIDER"`
|
||||
ProviderURL string `envconfig:"IDP_PROVIDER_URL"`
|
||||
// Scopes is an optional setting corresponding to OAuth 2.0 specification's access scopes
|
||||
// issuing an Access Token. Named providers are already set with good defaults.
|
||||
// Most likely only overrides if using the generic OIDC provider.
|
||||
Scopes []string `envconfig:"IDP_SCOPE"`
|
||||
SkipProviderButton bool `envconfig:"SKIP_PROVIDER_BUTTON"`
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ func (p *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) {
|
|||
p.SignInPage(w, r)
|
||||
}
|
||||
case sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
||||
log.Error().Err(err).Msg("authenticate.SignIn : invalid cookie cookie")
|
||||
log.Error().Err(err).Msg("authenticate.SignIn")
|
||||
p.sessionStore.ClearSession(w, r)
|
||||
if p.skipProviderButton {
|
||||
p.skipButtonOAuthStart(w, r)
|
||||
|
@ -394,9 +394,9 @@ func (p *Authenticate) redeemCode(host, code string) (*sessions.SessionState, er
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if session.Email == "" {
|
||||
return nil, fmt.Errorf("no email included in session")
|
||||
}
|
||||
// if session.Email == "" {
|
||||
// return nil, fmt.Errorf("no email included in session")
|
||||
// }
|
||||
|
||||
return session, nil
|
||||
|
||||
|
@ -459,7 +459,7 @@ func (p *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
|
|||
log.Ctx(r.Context()).Info().Str("email", session.Email).Msg("authentication complete")
|
||||
err = p.sessionStore.SaveSession(w, r, session)
|
||||
if err != nil {
|
||||
log.Ctx(r.Context()).Error().Err(err).Msg("internal error")
|
||||
log.Error().Err(err).Msg("internal error")
|
||||
return "", httputil.HTTPError{Code: http.StatusInternalServerError, Message: "Internal Error"}
|
||||
}
|
||||
return redirect, nil
|
||||
|
@ -476,6 +476,7 @@ func (p *Authenticate) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
httputil.ErrorResponse(w, r, h.Message, h.Code)
|
||||
return
|
||||
default:
|
||||
log.Error().Err(err).Msg("authenticate.OAuthCallback")
|
||||
httputil.ErrorResponse(w, r, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
79
authenticate/providers/gitlab.go
Normal file
79
authenticate/providers/gitlab.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package providers // import "github.com/pomerium/pomerium/internal/providers"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
oidc "github.com/pomerium/go-oidc"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/pomerium/pomerium/authenticate/circuit"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
)
|
||||
|
||||
const defaultGitlabProviderURL = "https://gitlab.com"
|
||||
|
||||
// GitlabProvider is an implementation of the Provider interface.
|
||||
type GitlabProvider struct {
|
||||
*ProviderData
|
||||
cb *circuit.Breaker
|
||||
}
|
||||
|
||||
// NewGitlabProvider returns a new Gitlab identity provider; defaults to the hosted version.
|
||||
//
|
||||
// Unlike other providers, `email` is not returned from the initial OIDC token. To retrieve email,
|
||||
// a secondary call must be made to the user's info endpoint. Unfortunately, email is not guaranteed
|
||||
// or even likely to be returned even if the user has it set as their email must be set to public.
|
||||
// As pomerium is currently very email centric, I would caution using until Gitlab fixes the issue.
|
||||
//
|
||||
// See :
|
||||
// - https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387
|
||||
// - https://docs.gitlab.com/ee/integration/openid_connect_provider.html
|
||||
// - https://docs.gitlab.com/ee/integration/oauth_provider.html
|
||||
// - https://docs.gitlab.com/ee/api/oauth2.html
|
||||
// - https://gitlab.com/.well-known/openid-configuration
|
||||
func NewGitlabProvider(p *ProviderData) (*GitlabProvider, error) {
|
||||
ctx := context.Background()
|
||||
if p.ProviderURL == "" {
|
||||
p.ProviderURL = defaultGitlabProviderURL
|
||||
}
|
||||
var err error
|
||||
p.provider, err = oidc.NewProvider(ctx, p.ProviderURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Scopes = []string{oidc.ScopeOpenID, "read_user"}
|
||||
|
||||
p.verifier = p.provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.oauth = &oauth2.Config{
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
Endpoint: p.provider.Endpoint(),
|
||||
RedirectURL: p.RedirectURL.String(),
|
||||
Scopes: p.Scopes,
|
||||
}
|
||||
gitlabProvider := &GitlabProvider{
|
||||
ProviderData: p,
|
||||
}
|
||||
gitlabProvider.cb = circuit.NewBreaker(&circuit.Options{
|
||||
HalfOpenConcurrentRequests: 2,
|
||||
OnStateChange: gitlabProvider.cbStateChange,
|
||||
OnBackoff: gitlabProvider.cbBackoff,
|
||||
ShouldTripFunc: func(c circuit.Counts) bool { return c.ConsecutiveFailures >= 3 },
|
||||
ShouldResetFunc: func(c circuit.Counts) bool { return c.ConsecutiveSuccesses >= 6 },
|
||||
BackoffDurationFunc: circuit.ExponentialBackoffDuration(
|
||||
time.Duration(200)*time.Second,
|
||||
time.Duration(500)*time.Millisecond),
|
||||
})
|
||||
|
||||
return gitlabProvider, nil
|
||||
}
|
||||
|
||||
func (p *GitlabProvider) cbBackoff(duration time.Duration, reset time.Time) {
|
||||
log.Info().Dur("duration", duration).Msg("authenticate/providers/gitlab.cbBackoff")
|
||||
|
||||
}
|
||||
|
||||
func (p *GitlabProvider) cbStateChange(from, to circuit.State) {
|
||||
log.Info().Str("from", from.String()).Str("to", to.String()).Msg("authenticate/providers/gitlab.cbStateChange")
|
||||
}
|
|
@ -32,16 +32,17 @@ func NewGoogleProvider(p *ProviderData) (*GoogleProvider, error) {
|
|||
if p.ProviderURL == "" {
|
||||
p.ProviderURL = defaultGoogleProviderURL
|
||||
}
|
||||
provider, err := oidc.NewProvider(ctx, p.ProviderURL)
|
||||
var err error
|
||||
p.provider, err = oidc.NewProvider(ctx, p.ProviderURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.verifier = provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.verifier = p.provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.oauth = &oauth2.Config{
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Endpoint: p.provider.Endpoint(),
|
||||
RedirectURL: p.RedirectURL.String(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
|
@ -49,12 +50,12 @@ func NewGoogleProvider(p *ProviderData) (*GoogleProvider, error) {
|
|||
googleProvider := &GoogleProvider{
|
||||
ProviderData: p,
|
||||
}
|
||||
// google supports a revokation endpoint
|
||||
// google supports a revocation endpoint
|
||||
var claims struct {
|
||||
RevokeURL string `json:"revocation_endpoint"`
|
||||
}
|
||||
|
||||
if err := provider.Claims(&claims); err != nil {
|
||||
if err := p.provider.Claims(&claims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -38,16 +38,17 @@ func NewAzureProvider(p *ProviderData) (*AzureProvider, error) {
|
|||
p.ProviderURL = defaultAzureProviderURL
|
||||
}
|
||||
log.Info().Msgf("provider url %s", p.ProviderURL)
|
||||
provider, err := oidc.NewProvider(ctx, p.ProviderURL)
|
||||
var err error
|
||||
p.provider, err = oidc.NewProvider(ctx, p.ProviderURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.verifier = provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.verifier = p.provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.oauth = &oauth2.Config{
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Endpoint: p.provider.Endpoint(),
|
||||
RedirectURL: p.RedirectURL.String(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
|
@ -60,7 +61,7 @@ func NewAzureProvider(p *ProviderData) (*AzureProvider, error) {
|
|||
RevokeURL string `json:"end_session_endpoint"`
|
||||
}
|
||||
|
||||
if err := provider.Claims(&claims); err != nil {
|
||||
if err := p.provider.Claims(&claims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
// OIDCProvider provides a standard, OpenID Connect implementation
|
||||
// of an authorization identity provider.
|
||||
// see : https://openid.net/specs/openid-connect-core-1_0.html
|
||||
type OIDCProvider struct {
|
||||
*ProviderData
|
||||
}
|
||||
|
@ -20,15 +21,16 @@ func NewOIDCProvider(p *ProviderData) (*OIDCProvider, error) {
|
|||
if p.ProviderURL == "" {
|
||||
return nil, errors.New("missing required provider url")
|
||||
}
|
||||
provider, err := oidc.NewProvider(ctx, p.ProviderURL)
|
||||
var err error
|
||||
p.provider, err = oidc.NewProvider(ctx, p.ProviderURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.verifier = provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.verifier = p.provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.oauth = &oauth2.Config{
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Endpoint: p.provider.Endpoint(),
|
||||
RedirectURL: p.RedirectURL.String(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
|
|
|
@ -28,26 +28,27 @@ func NewOktaProvider(p *ProviderData) (*OktaProvider, error) {
|
|||
if p.ProviderURL == "" {
|
||||
return nil, errors.New("missing required provider url")
|
||||
}
|
||||
provider, err := oidc.NewProvider(ctx, p.ProviderURL)
|
||||
var err error
|
||||
p.provider, err = oidc.NewProvider(ctx, p.ProviderURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.verifier = provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.verifier = p.provider.Verifier(&oidc.Config{ClientID: p.ClientID})
|
||||
p.oauth = &oauth2.Config{
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Endpoint: p.provider.Endpoint(),
|
||||
RedirectURL: p.RedirectURL.String(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
oktaProvider := OktaProvider{ProviderData: p}
|
||||
|
||||
// okta supports a revokation endpoint
|
||||
// okta supports a revocation endpoint
|
||||
var claims struct {
|
||||
RevokeURL string `json:"revocation_endpoint"`
|
||||
}
|
||||
|
||||
if err := provider.Claims(&claims); err != nil {
|
||||
if err := p.provider.Claims(&claims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,11 @@ package providers // import "github.com/pomerium/pomerium/internal/providers"
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
|
@ -15,17 +18,19 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// AzureProviderName identifies the Azure provider
|
||||
// AzureProviderName identifies the Azure identity provider
|
||||
AzureProviderName = "azure"
|
||||
// GoogleProviderName identifies the Google provider
|
||||
// GitlabProviderName identifies the GitLab identity provider
|
||||
GitlabProviderName = "gitlab"
|
||||
// GoogleProviderName identifies the Google identity provider
|
||||
GoogleProviderName = "google"
|
||||
// OIDCProviderName identifes a generic OpenID connect provider
|
||||
// OIDCProviderName identifies a generic OpenID connect provider
|
||||
OIDCProviderName = "oidc"
|
||||
// OktaProviderName identifes the Okta identity provider
|
||||
// OktaProviderName identifies the Okta identity provider
|
||||
OktaProviderName = "okta"
|
||||
)
|
||||
|
||||
// Provider is an interface exposing functions necessary to authenticate with a given provider.
|
||||
// Provider is an interface exposing functions necessary to interact with a given provider.
|
||||
type Provider interface {
|
||||
Data() *ProviderData
|
||||
Redeem(string) (*sessions.SessionState, error)
|
||||
|
@ -34,38 +39,31 @@ type Provider interface {
|
|||
RefreshSessionIfNeeded(*sessions.SessionState) (bool, error)
|
||||
Revoke(*sessions.SessionState) error
|
||||
RefreshAccessToken(string) (string, time.Duration, error)
|
||||
// Stop()
|
||||
}
|
||||
|
||||
// New returns a new identity provider based on available name.
|
||||
// Defaults to google.
|
||||
func New(provider string, p *ProviderData) (Provider, error) {
|
||||
// New returns a new identity provider based given its name.
|
||||
// Returns an error if selected provided not found or if the provider fails to instantiate.
|
||||
func New(provider string, pd *ProviderData) (Provider, error) {
|
||||
var err error
|
||||
var p Provider
|
||||
switch provider {
|
||||
case OIDCProviderName:
|
||||
p, err := NewOIDCProvider(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
case AzureProviderName:
|
||||
p, err := NewAzureProvider(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
p, err = NewAzureProvider(pd)
|
||||
case GitlabProviderName:
|
||||
p, err = NewGitlabProvider(pd)
|
||||
case GoogleProviderName:
|
||||
p, err = NewGoogleProvider(pd)
|
||||
case OIDCProviderName:
|
||||
p, err = NewOIDCProvider(pd)
|
||||
case OktaProviderName:
|
||||
p, err := NewOktaProvider(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
p, err = NewOktaProvider(pd)
|
||||
default:
|
||||
p, err := NewGoogleProvider(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
return nil, fmt.Errorf("authenticate: provider %q not found", provider)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ProviderData holds the fields associated with providers
|
||||
|
@ -79,6 +77,7 @@ type ProviderData struct {
|
|||
Scopes []string
|
||||
SessionLifetimeTTL time.Duration
|
||||
|
||||
provider *oidc.Provider
|
||||
verifier *oidc.IDTokenVerifier
|
||||
oauth *oauth2.Config
|
||||
}
|
||||
|
@ -100,7 +99,7 @@ func (p *ProviderData) ValidateSessionState(s *sessions.SessionState) bool {
|
|||
ctx := context.Background()
|
||||
_, err := p.verifier.Verify(ctx, s.IDToken)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate/providers.ValidateSessionState : failed to verify session state")
|
||||
log.Error().Err(err).Msg("authenticate/providers: failed to verify session state")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -112,31 +111,47 @@ func (p *ProviderData) Redeem(code string) (*sessions.SessionState, error) {
|
|||
// convert authorization code into a token
|
||||
token, err := p.oauth.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate/providers.Redeem : token exchange failed")
|
||||
return nil, fmt.Errorf("token exchange: %v", err)
|
||||
return nil, fmt.Errorf("authenticate/providers: failed token exchange: %v", err)
|
||||
}
|
||||
s, err := p.createSessionState(ctx, token)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate/providers.Redeem : unable to update session")
|
||||
return nil, fmt.Errorf("unable to update session: %v", err)
|
||||
return nil, fmt.Errorf("authenticate/providers: unable to update session: %v", err)
|
||||
}
|
||||
|
||||
// check if provider has info endpoint, try to hit that and gather more info
|
||||
// especially useful if initial request did not contain email
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
||||
var claims struct {
|
||||
UserInfoURL string `json:"userinfo_endpoint"`
|
||||
}
|
||||
|
||||
if err := p.provider.Claims(&claims); err != nil || claims.UserInfoURL == "" {
|
||||
log.Error().Err(err).Msg("authenticate/providers: failed retrieving userinfo_endpoint")
|
||||
} else {
|
||||
// userinfo endpoint found and valid
|
||||
userInfo, err := p.UserInfo(ctx, claims.UserInfoURL, oauth2.StaticTokenSource(token))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authenticate/providers: can't parse userinfo_endpoint: %v", err)
|
||||
}
|
||||
s.Email = userInfo.Email
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// RefreshSessionIfNeeded will refresh the session state if it's deadline is expired
|
||||
func (p *ProviderData) RefreshSessionIfNeeded(s *sessions.SessionState) (bool, error) {
|
||||
if !sessionRefreshRequired(s) {
|
||||
log.Info().Msg("authenticate/providers.RefreshSessionIfNeeded : session refresh not needed")
|
||||
log.Debug().Msg("authenticate/providers: session refresh not needed")
|
||||
return false, nil
|
||||
}
|
||||
origExpiration := s.RefreshDeadline
|
||||
err := p.redeemRefreshToken(s)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate/providers.RefreshSession")
|
||||
return false, fmt.Errorf("unable to redeem refresh token: %v", err)
|
||||
return false, fmt.Errorf("authenticate/providers: couldn't refresh token: %v", err)
|
||||
}
|
||||
|
||||
log.Info().Msgf("authenticate/providers.Redeem refreshed id token %s (expired on %s)", s, origExpiration)
|
||||
log.Debug().Time("NewDeadline", s.RefreshDeadline).Time("OldDeadline", origExpiration).Msgf("authenticate/providers refreshed")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
@ -152,15 +167,13 @@ func (p *ProviderData) redeemRefreshToken(s *sessions.SessionState) error {
|
|||
// returns a TokenSource automatically refreshing it as necessary using the provided context
|
||||
token, err := p.oauth.TokenSource(ctx, t).Token()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate/providers failed to get token")
|
||||
return fmt.Errorf("failed to get token: %v", err)
|
||||
return fmt.Errorf("authenticate/providers: failed to get token: %v", err)
|
||||
}
|
||||
log.Info().Msg("authenticate/providers.oidc.redeemRefreshToken 4")
|
||||
|
||||
newSession, err := p.createSessionState(ctx, token)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate/providers unable to update session")
|
||||
return fmt.Errorf("unable to update session: %v", err)
|
||||
return fmt.Errorf("authenticate/providers: unable to update session: %v", err)
|
||||
}
|
||||
s.AccessToken = newSession.AccessToken
|
||||
s.IDToken = newSession.IDToken
|
||||
|
@ -184,17 +197,11 @@ func (p *ProviderData) createSessionState(ctx context.Context, token *oauth2.Tok
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("token response did not contain an id_token")
|
||||
}
|
||||
log.Info().
|
||||
Bool("ctx", ctx == nil).
|
||||
Bool("Verifier", p.verifier == nil).
|
||||
Str("rawIDToken", rawIDToken).
|
||||
Msg("authenticate/providers.oidc.createSessionState 2")
|
||||
|
||||
// Parse and verify ID Token payload.
|
||||
idToken, err := p.verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("authenticate/providers could not verify id_token")
|
||||
return nil, fmt.Errorf("could not verify id_token: %v", err)
|
||||
return nil, fmt.Errorf("authenticate/providers: could not verify id_token: %v", err)
|
||||
}
|
||||
|
||||
// Extract custom claims.
|
||||
|
@ -204,23 +211,27 @@ func (p *ProviderData) createSessionState(ctx context.Context, token *oauth2.Tok
|
|||
}
|
||||
// parse claims from the raw, encoded jwt token
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse id_token claims: %v", err)
|
||||
}
|
||||
|
||||
if claims.Email == "" {
|
||||
return nil, fmt.Errorf("id_token did not contain an email")
|
||||
}
|
||||
if claims.Verified != nil && !*claims.Verified {
|
||||
return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
|
||||
return nil, fmt.Errorf("authenticate/providers: failed to parse id_token claims: %v", err)
|
||||
}
|
||||
log.Debug().
|
||||
Str("AccessToken", token.AccessToken).
|
||||
Str("IDToken", rawIDToken).
|
||||
Str("claims.Email", claims.Email).
|
||||
Str("RefreshToken", token.RefreshToken).
|
||||
Str("idToken.Subject", idToken.Subject).
|
||||
Str("idToken.Nonce", idToken.Nonce).
|
||||
Str("RefreshDeadline", idToken.Expiry.String()).
|
||||
Str("LifetimeDeadline", idToken.Expiry.String()).
|
||||
Msg("authenticate/providers.createSessionState")
|
||||
|
||||
return &sessions.SessionState{
|
||||
AccessToken: token.AccessToken,
|
||||
IDToken: rawIDToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
RefreshDeadline: token.Expiry,
|
||||
LifetimeDeadline: token.Expiry,
|
||||
RefreshDeadline: idToken.Expiry,
|
||||
LifetimeDeadline: idToken.Expiry,
|
||||
Email: claims.Email,
|
||||
User: idToken.Subject,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -228,7 +239,7 @@ func (p *ProviderData) createSessionState(ctx context.Context, token *oauth2.Tok
|
|||
// prompting the user for permission.
|
||||
func (p *ProviderData) RefreshAccessToken(refreshToken string) (string, time.Duration, error) {
|
||||
if refreshToken == "" {
|
||||
return "", 0, errors.New("missing refresh token")
|
||||
return "", 0, errors.New("authenticate/providers: missing refresh token")
|
||||
}
|
||||
ctx := context.Background()
|
||||
c := oauth2.Config{
|
||||
|
@ -250,14 +261,85 @@ func (p *ProviderData) RefreshAccessToken(refreshToken string) (string, time.Dur
|
|||
return newToken.AccessToken, newToken.Expiry.Sub(time.Now()), nil
|
||||
}
|
||||
|
||||
// Revoke enables a user to revoke her tokenn. Though many providers such as
|
||||
// google and okta provide revoke endpoints, since it's not officially supported
|
||||
// as part of OpenID Connect, the default implementation throws an error.
|
||||
// Revoke enables a user to revoke her token. If the identity provider supports revocation
|
||||
// the endpoint is available, otherwise an error is thrown.
|
||||
func (p *ProviderData) Revoke(s *sessions.SessionState) error {
|
||||
return errors.New("revoke not implemented")
|
||||
return errors.New("authenticate/providers: revoke not implemented")
|
||||
}
|
||||
|
||||
func sessionRefreshRequired(s *sessions.SessionState) bool {
|
||||
return s == nil || s.RefreshDeadline.After(time.Now()) || s.RefreshToken == ""
|
||||
|
||||
}
|
||||
|
||||
// UserInfo represents the OpenID Connect userinfo claims.
|
||||
// see: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
||||
type UserInfo struct {
|
||||
// Stanard OIDC User fields
|
||||
Subject string `json:"sub"`
|
||||
Profile string `json:"profile"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
// custom claims
|
||||
Name string `json:"name"` // google, gitlab
|
||||
GivenName string `json:"given_name"` // google
|
||||
FamilyName string `json:"family_name"` // google
|
||||
Picture string `json:"picture"` // google,gitlab
|
||||
Locale string `json:"locale"` // google
|
||||
Groups []string `json:"groups"` // gitlab
|
||||
|
||||
claims []byte
|
||||
}
|
||||
|
||||
// Claims unmarshals the raw JSON object claims into the provided object.
|
||||
func (u *UserInfo) Claims(v interface{}) error {
|
||||
if u.claims == nil {
|
||||
return errors.New("authenticate/providers: claims not set")
|
||||
}
|
||||
return json.Unmarshal(u.claims, v)
|
||||
}
|
||||
|
||||
// UserInfo uses the token source to query the provider's user info endpoint.
|
||||
func (p *ProviderData) UserInfo(ctx context.Context, uri string, tokenSource oauth2.TokenSource) (*UserInfo, error) {
|
||||
if uri == "" {
|
||||
return nil, errors.New("authenticate/providers: user info endpoint is not supported by this provider")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authenticate/providers: create GET request: %v", err)
|
||||
}
|
||||
|
||||
token, err := tokenSource.Token()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authenticate/providers: get access token: %v", err)
|
||||
}
|
||||
token.SetAuthHeader(req)
|
||||
|
||||
resp, err := doRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||
}
|
||||
|
||||
var userInfo UserInfo
|
||||
if err := json.Unmarshal(body, &userInfo); err != nil {
|
||||
return nil, fmt.Errorf("authenticate/providers failed to decode userinfo: %v", err)
|
||||
}
|
||||
userInfo.claims = body
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
client := http.DefaultClient
|
||||
if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
|
||||
client = c
|
||||
}
|
||||
return client.Do(req.WithContext(ctx))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# NOTE! Generate new SHARED_SECRET and COOKIE_SECRET keys!
|
||||
# NOTE! Replace `corp.beyondperimeter.com` with whatever your domain is
|
||||
# NOTE! Make sure certificate files (cert.pem/privkey.pem) are in the same directory as this file
|
||||
# NOTE! Wrap URLs in quotes to avoid parse errors
|
||||
version: "3"
|
||||
services:
|
||||
# NGINX routes to pomerium's services depending on the request.
|
||||
|
@ -28,7 +29,7 @@ services:
|
|||
- REDIRECT_URL=https://sso-auth.corp.beyondperimeter.com/oauth2/callback
|
||||
# Identity Provider Settings (Must be changed!)
|
||||
- IDP_PROVIDER="google"
|
||||
- IDP_PROVIDER_URL=https://accounts.google.com
|
||||
- IDP_PROVIDER_URL="https://accounts.google.com"
|
||||
- IDP_CLIENT_ID=851877082059-bfgkpj09noog7as3gpc3t7r6n9sjbgs6.apps.googleusercontent.com
|
||||
- IDP_CLIENT_SECRET=P34wwijKRNP3skP5ag5I12kz
|
||||
- SCOPE="openid email"
|
||||
|
|
102
docs/examples/gitlab.docker-compose.yml
Normal file
102
docs/examples/gitlab.docker-compose.yml
Normal file
|
@ -0,0 +1,102 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
# NGINX routes to pomerium's services depending on the request.
|
||||
nginx:
|
||||
image: jwilder/nginx-proxy:latest
|
||||
ports:
|
||||
- "443:443"
|
||||
volumes:
|
||||
# NOTE!!! : nginx must be supplied with your wildcard certificates. And it expects
|
||||
# it in the format of whatever your wildcard domain name is in.
|
||||
# see : https://github.com/jwilder/nginx-proxy#wildcard-certificates
|
||||
# So, if your subdomain is corp.beyondperimeter.com, you'd have the following :
|
||||
- ./cert.pem:/etc/nginx/certs/corp.beyondperimeter.com.crt:ro
|
||||
- ./privkey.pem:/etc/nginx/certs/corp.beyondperimeter.com.key:ro
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
|
||||
pomerium-authenticate:
|
||||
build: .
|
||||
restart: always
|
||||
depends_on:
|
||||
- "gitlab"
|
||||
environment:
|
||||
- POMERIUM_DEBUG=true
|
||||
- SERVICES=authenticate
|
||||
# auth settings
|
||||
- REDIRECT_URL=https://sso-auth.corp.beyondperimeter.com/oauth2/callback
|
||||
- IDP_PROVIDER="gitlab"
|
||||
- IDP_PROVIDER_URL=https://gitlab.corp.beyondperimeter.com
|
||||
- IDP_CLIENT_ID=022dbbd09402441dc7af1924b679bc5e6f5bf0d7a555e55b38c51e2e4e6cee76
|
||||
- IDP_CLIENT_SECRET=fb7598c520c346915ee369eee57688938fe4f31329a308c4669074da562714b2
|
||||
- PROXY_ROOT_DOMAIN=beyondperimeter.com
|
||||
- ALLOWED_DOMAINS=*
|
||||
- SKIP_PROVIDER_BUTTON=false
|
||||
# shared service settings
|
||||
# Generate 256 bit random keys e.g. `head -c32 /dev/urandom | base64`
|
||||
- SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M=
|
||||
- COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI=
|
||||
- VIRTUAL_PROTO=https
|
||||
- VIRTUAL_HOST=sso-auth.corp.beyondperimeter.com
|
||||
- VIRTUAL_PORT=443
|
||||
volumes: # volumes is optional; used if passing certificates as files
|
||||
- ./cert.pem:/pomerium/cert.pem:ro
|
||||
- ./privkey.pem:/pomerium/privkey.pem:ro
|
||||
expose:
|
||||
- 443
|
||||
|
||||
pomerium-proxy:
|
||||
build: .
|
||||
restart: always
|
||||
environment:
|
||||
- POMERIUM_DEBUG=true
|
||||
- SERVICES=proxy
|
||||
# proxy settings
|
||||
- AUTHENTICATE_SERVICE_URL=https://sso-auth.corp.beyondperimeter.com
|
||||
- ROUTES=https://httpbin.corp.beyondperimeter.com=http://httpbin,https://hello.corp.beyondperimeter.com=http://hello-world/
|
||||
# Generate 256 bit random keys e.g. `head -c32 /dev/urandom | base64`
|
||||
- SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M=
|
||||
- COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI=
|
||||
- SIGNING_KEY=LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU0zbXBaSVdYQ1g5eUVneFU2czU3Q2J0YlVOREJTQ0VBdFFGNWZVV0hwY1FvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFaFBRditMQUNQVk5tQlRLMHhTVHpicEVQa1JyazFlVXQxQk9hMzJTRWZVUHpOaTRJV2VaLwpLS0lUdDJxMUlxcFYyS01TYlZEeXI5aWp2L1hoOThpeUV3PT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
|
||||
# nginx settings
|
||||
- VIRTUAL_PROTO=https
|
||||
- VIRTUAL_HOST=*.corp.beyondperimeter.com
|
||||
- VIRTUAL_PORT=443
|
||||
volumes: # volumes is optional; used if passing certificates as files
|
||||
- ./cert.pem:/pomerium/cert.pem:ro
|
||||
- ./privkey.pem:/pomerium/privkey.pem:ro
|
||||
expose:
|
||||
- 443
|
||||
|
||||
# https://httpbin.corp.beyondperimeter.com
|
||||
httpbin:
|
||||
image: kennethreitz/httpbin:latest
|
||||
expose:
|
||||
- 80
|
||||
# https://hello.corp.beyondperimeter.com
|
||||
hello-world:
|
||||
image: tutum/hello-world:latest
|
||||
expose:
|
||||
- 80
|
||||
gitlab:
|
||||
hostname: gitlab.corp.beyondperimeter.com
|
||||
image: gitlab/gitlab-ce:latest
|
||||
restart: always
|
||||
expose:
|
||||
- 443
|
||||
- 80
|
||||
- 22
|
||||
environment:
|
||||
GITLAB_OMNIBUS_CONFIG: |
|
||||
external_url 'https://gitlab.corp.beyondperimeter.com'
|
||||
nginx['ssl_certificate'] = '/etc/gitlab/trusted-certs/corp.beyondperimeter.com.crt'
|
||||
nginx['ssl_certificate_key'] = '/etc/gitlab/trusted-certs/corp.beyondperimeter.com.key'
|
||||
VIRTUAL_PROTO: https
|
||||
VIRTUAL_HOST: gitlab.corp.beyondperimeter.com
|
||||
VIRTUAL_PORT: 443
|
||||
volumes:
|
||||
- ./cert.pem:/etc/gitlab/trusted-certs/corp.beyondperimeter.com.crt
|
||||
- ./privkey.pem:/etc/gitlab/trusted-certs/corp.beyondperimeter.com.key
|
||||
- $HOME/gitlab/config:/etc/gitlab
|
||||
- $HOME/gitlab/logs:/var/log/gitlab
|
||||
- $HOME/gitlab/data:/var/opt/gitlab
|
BIN
docs/guide/gitlab/gitlab-create-application.png
Normal file
BIN
docs/guide/gitlab/gitlab-create-application.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 395 KiB |
BIN
docs/guide/gitlab/gitlab-credentials.png
Normal file
BIN
docs/guide/gitlab/gitlab-credentials.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 240 KiB |
BIN
docs/guide/gitlab/gitlab-verify-access.png
Normal file
BIN
docs/guide/gitlab/gitlab-verify-access.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 262 KiB |
|
@ -1,6 +1,9 @@
|
|||
---
|
||||
title: Identity Providers
|
||||
description: This article describes how to connect pomerium to third-party identity providers / single-sign-on services. You will need to generate keys, copy these into your promerium settings, and enable the connection.
|
||||
description: >-
|
||||
This article describes how to connect pomerium to third-party identity
|
||||
providers / single-sign-on services. You will need to generate keys, copy
|
||||
these into your promerium settings, and enable the connection.
|
||||
---
|
||||
|
||||
# Identity Provider Configuration
|
||||
|
@ -10,89 +13,19 @@ This article describes how to configure pomerium to use a third-party identity s
|
|||
There are a few configuration steps required for identity provider integration. Most providers support [OpenID Connect] which provides a standardized interface for authentication. In this guide we'll cover how to do the following for each identity provider:
|
||||
|
||||
1. Establish a **Redirect URL** with the identity provider which is called after authentication.
|
||||
1. Generate a **Client ID** and **Client Secret**.
|
||||
1. Configure pomerium to use the **Client ID** and **Client Secret** keys.
|
||||
|
||||
## Google
|
||||
|
||||
Log in to your Google account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials). Navigate to **Credentials** using the left-hand menu.
|
||||
|
||||

|
||||
|
||||
On the **Credentials** page, click **Create credentials** and choose **OAuth Client ID**.
|
||||
|
||||

|
||||
|
||||
On the **Create Client ID** page, select **Web application**. In the new fields that display, set the following parameters:
|
||||
|
||||
| Field | Description |
|
||||
| ------------------------ | ----------------------------------------- |
|
||||
| Name | The name of your web app |
|
||||
| Authorized redirect URIs | `https://${redirect-url}/oauth2/callback` |
|
||||
|
||||

|
||||
|
||||
Click **Create** to proceed.
|
||||
|
||||
Your `Client ID` and `Client Secret` will be displayed:
|
||||
|
||||

|
||||
|
||||
Set `Client ID` and `Client Secret` in Pomerium's settings. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
|
||||
IDP_PROVIDER="google"
|
||||
IDP_PROVIDER_URL="https://accounts.google.com"
|
||||
IDP_CLIENT_ID="yyyy.apps.googleusercontent.com"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
|
||||
## Okta
|
||||
|
||||
[Log in to your Okta account](https://login.okta.com) and head to your Okta dashboard. Select **Applications** on the top menu. On the Applications page, click the **Add Application** button to create a new app.
|
||||
|
||||

|
||||
|
||||
On the **Create New Application** page, select the **Web** for your application.
|
||||
|
||||

|
||||
|
||||
Next, provide the following information for your application settings:
|
||||
|
||||
| Field | Description |
|
||||
| ---------------------------- | ----------------------------------------------------- |
|
||||
| Name | The name of your application. |
|
||||
| Base URIs (optional) | The domain(s) of your application. |
|
||||
| Login redirect URIs | `https://${redirect-url}/oauth2/callback`. |
|
||||
| Group assignments (optional) | The user groups that can sign in to this application. |
|
||||
| Grant type allowed | **You must enable Refresh Token.** |
|
||||
|
||||

|
||||
|
||||
Click **Done** to proceed. You'll be taken to the **General** page of your app.
|
||||
|
||||
Go to the **General** page of your app and scroll down to the **Client Credentials** section. This section contains the **Client ID** and **Client Secret** to be used in the next step.
|
||||

|
||||
|
||||
At this point, you will configure the integration from the Pomerium side. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
|
||||
IDP_PROVIDER="okta"
|
||||
IDP_PROVIDER_URL="https://dev-108295-admin.oktapreview.com/"
|
||||
IDP_CLIENT_ID="0oairksnr0C0fEJ7l0h7"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
2. Generate a **Client ID** and **Client Secret**.
|
||||
3. Configure pomerium to use the **Client ID** and **Client Secret** keys.
|
||||
|
||||
## Azure
|
||||
|
||||
If you plan on allowing users to log in using a Microsoft Azure Active Directory account, either from your company or from external directories, you must register your application through the Microsoft Azure portal. If you don't have a Microsoft Azure account, you can [signup](https://azure.microsoft.com/en-us/free) for free.
|
||||
|
||||
You can access the Azure management portal from your Microsoft service, or visit [https://portal.azure.com](https://portal.azure.com) and sign in to Azure using the global administrator account used to create the Office 365 organization.
|
||||
You can access the Azure management portal from your Microsoft service, or visit <https://portal.azure.com> and sign in to Azure using the global administrator account used to create the Office 365 organization.
|
||||
|
||||
::: tip
|
||||
|
||||
There is no way to create an application that integrates with Microsoft Azure AD without having **your own** Microsoft Azure AD instance.
|
||||
|
||||
:::
|
||||
|
||||
If you have an Office 365 account, you can use the account's Azure AD instance instead of creating a new one. To find your Office 365 account's Azure AD instance:
|
||||
|
@ -125,21 +58,20 @@ Next you will need to create a key which will be used as the **Client Secret** i
|
|||
Enter a name for the key and choose the desired duration.
|
||||
|
||||
::: tip
|
||||
|
||||
If you choose an expiring key, make sure to record the expiration date in your calendar, as you will need to renew the key (get a new one) before that day in order to ensure users don't experience a service interruption.
|
||||
|
||||
:::
|
||||
|
||||
Click on **Save** and the key will be displayed. **Make sure to copy the value of this key before leaving this screen**, otherwise you may need to create a new key. This value is used as the **Client Secret**.
|
||||
|
||||

|
||||
|
||||
Next you need to ensure that the Pomerium's Redirect URL is listed in allowed reply URLs for the created application. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app. Then click **Settings** -> **Reply URLs** and add Pomerium's redirect URL. For example,
|
||||
`https://sso-auth.corp.beyondperimeter.com/oauth2/callback`.
|
||||
Next you need to ensure that the Pomerium's Redirect URL is listed in allowed reply URLs for the created application. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app. Then click **Settings** -> **Reply URLs** and add Pomerium's redirect URL. For example, `https://sso-auth.corp.beyondperimeter.com/oauth2/callback`.
|
||||
|
||||

|
||||
|
||||
The final, and most unique step to Azure AD provider, is to take note of your specific endpoint. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app.
|
||||

|
||||
Click on **Endpoints**
|
||||
The final, and most unique step to Azure AD provider, is to take note of your specific endpoint. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app.  Click on **Endpoints**
|
||||
|
||||

|
||||
|
||||
|
@ -156,7 +88,123 @@ IDP_PROVIDER="azure"
|
|||
IDP_PROVIDER_URL="https://login.microsoftonline.com/{REPLACE-ME-SEE-ABOVE}/v2.0"
|
||||
IDP_CLIENT_ID="REPLACE-ME"
|
||||
IDP_CLIENT_SECRET="REPLACE-ME"
|
||||
```
|
||||
|
||||
## Gitlab
|
||||
|
||||
:::warning
|
||||
|
||||
Gitlab currently does not provide callers with a user email, under any scope, to a caller unless that user has selected her email to be public. Because Pomerium is by nature very centric, users are cautioned from using Pomerium until [this gitlab bug](https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387) is fixed.
|
||||
|
||||
:::
|
||||
|
||||
Log in to your Gitlab account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials).
|
||||
|
||||
Navigate to **User Settings** then **Applications** using the left-hand menu.
|
||||
|
||||
On the **Applications** page, add a new application by setting the following parameters:
|
||||
|
||||
Field | Description
|
||||
------------ | --------------------------------------------
|
||||
Name | The name of your web app
|
||||
Redirect URI | `https://${redirect-url}/oauth2/callback`
|
||||
Scopes | **Must** select **read_user** and **openid**
|
||||
|
||||

|
||||
|
||||
1.Click **Save Application** to proceed.
|
||||
|
||||
Your `Client ID` and `Client Secret` will be displayed:
|
||||
|
||||

|
||||
|
||||
Set `Client ID` and `Client Secret` in Pomerium's settings. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
|
||||
IDP_PROVIDER="gitlab"
|
||||
# NOTE!!! Provider url is optional, but should be set if you are running an on-premise instance
|
||||
# defaults to : https://gitlab.com, a local copy would look something like `http://gitlab.corp.beyondperimeter.com`
|
||||
IDP_PROVIDER_URL="https://gitlab.com"
|
||||
IDP_CLIENT_ID="yyyy"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
|
||||
When a user first uses pomerium to login, they will be presented with an authorization screen similar to the following.
|
||||
|
||||

|
||||
|
||||
## Google
|
||||
|
||||
Log in to your Google account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials). Navigate to **Credentials** using the left-hand menu.
|
||||
|
||||

|
||||
|
||||
On the **Credentials** page, click **Create credentials** and choose **OAuth Client ID**.
|
||||
|
||||

|
||||
|
||||
On the **Create Client ID** page, select **Web application**. In the new fields that display, set the following parameters:
|
||||
|
||||
Field | Description
|
||||
------------------------ | -----------------------------------------
|
||||
Name | The name of your web app
|
||||
Authorized redirect URIs | `https://${redirect-url}/oauth2/callback`
|
||||
|
||||

|
||||
|
||||
Click **Create** to proceed.
|
||||
|
||||
Your `Client ID` and `Client Secret` will be displayed:
|
||||
|
||||

|
||||
|
||||
Set `Client ID` and `Client Secret` in Pomerium's settings. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
|
||||
IDP_PROVIDER="google"
|
||||
IDP_PROVIDER_URL="https://accounts.google.com"
|
||||
IDP_CLIENT_ID="yyyy.apps.googleusercontent.com"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
|
||||
## Okta
|
||||
|
||||
[Log in to your Okta account](https://login.okta.com) and head to your Okta dashboard. Select **Applications** on the top menu. On the Applications page, click the **Add Application** button to create a new app.
|
||||
|
||||

|
||||
|
||||
On the **Create New Application** page, select the **Web** for your application.
|
||||
|
||||

|
||||
|
||||
Next, provide the following information for your application settings:
|
||||
|
||||
Field | Description
|
||||
---------------------------- | -----------------------------------------------------
|
||||
Name | The name of your application.
|
||||
Base URIs (optional) | The domain(s) of your application.
|
||||
Login redirect URIs | `https://${redirect-url}/oauth2/callback`.
|
||||
Group assignments (optional) | The user groups that can sign in to this application.
|
||||
Grant type allowed | **You must enable Refresh Token.**
|
||||
|
||||

|
||||
|
||||
Click **Done** to proceed. You'll be taken to the **General** page of your app.
|
||||
|
||||
Go to the **General** page of your app and scroll down to the **Client Credentials** section. This section contains the **Client ID** and **Client Secret** to be used in the next step.
|
||||
|
||||

|
||||
|
||||
At this point, you will configure the integration from the Pomerium side. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
|
||||
IDP_PROVIDER="okta"
|
||||
IDP_PROVIDER_URL="https://dev-108295-admin.oktapreview.com/"
|
||||
IDP_CLIENT_ID="0oairksnr0C0fEJ7l0h7"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
|
||||
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
|
|
18
env.example
18
env.example
|
@ -24,24 +24,30 @@ export COOKIE_SECRET=uPGHo1ujND/k3B9V6yr52Gweq3RRYfFho98jxDG5Br8=
|
|||
|
||||
# Identity Provider Settings
|
||||
|
||||
# OKTA
|
||||
# export IDP_PROVIDER="okta
|
||||
# export IDP_CLIENT_ID="REPLACEME"
|
||||
# export IDP_CLIENT_SECRET="REPLACEME"
|
||||
# export IDP_PROVIDER_URL="https://REPLACEME.oktapreview.com/oauth2/default"
|
||||
|
||||
# Azure
|
||||
# export IDP_PROVIDER="azure"
|
||||
# export IDP_PROVIDER_URL="https://login.microsoftonline.com/REPLACEME/v2.0"
|
||||
# export IDP_CLIENT_ID="REPLACEME
|
||||
# export IDP_CLIENT_SECRET="REPLACEME"
|
||||
|
||||
# Gitlab
|
||||
# export IDP_PROVIDER="gitlab"
|
||||
# export IDP_PROVIDER_URL="https://gitlab.onprem.example.com" # optional, defaults to `https://gitlab.com`
|
||||
# export IDP_CLIENT_ID="REPLACEME
|
||||
# export IDP_CLIENT_SECRET="REPLACEME"
|
||||
|
||||
## GOOGLE
|
||||
export IDP_PROVIDER="google"
|
||||
export IDP_PROVIDER_URL="https://accounts.google.com" # optional for google
|
||||
export IDP_CLIENT_ID="REPLACE-ME.googleusercontent.com"
|
||||
export IDP_CLIENT_SECRET="REPLACEME"
|
||||
|
||||
# OKTA
|
||||
# export IDP_PROVIDER="okta
|
||||
# export IDP_CLIENT_ID="REPLACEME"
|
||||
# export IDP_CLIENT_SECRET="REPLACEME"
|
||||
# export IDP_PROVIDER_URL="https://REPLACEME.oktapreview.com/oauth2/default"
|
||||
|
||||
# export SCOPE="openid email" # generally, you want the default OIDC scopes
|
||||
|
||||
# k/v seperated list of simple routes. If no scheme is set, HTTPS will be used.
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
)
|
||||
|
||||
// ErrInvalidSession is an error for invalid sessions.
|
||||
var ErrInvalidSession = errors.New("invalid session")
|
||||
var ErrInvalidSession = errors.New("internal/sessions: invalid session")
|
||||
|
||||
// CSRFStore has the functions for setting, getting, and clearing the CSRF cookie
|
||||
type CSRFStore interface {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue