authenticate: infer settings from authenticate url (#83)

This commit is contained in:
Bobby DeSimone 2019-04-10 12:16:00 -07:00 committed by GitHub
parent 06da599fbc
commit 603e6a17b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 182 additions and 257 deletions

View file

@ -1,6 +1,7 @@
<!-- Thanks for sending a pull request! <!--
Thanks for sending a pull request!
We generally follow Go coding and contributing conventions We generally follow Go coding and contributing conventions
which you can read about here https://golang.org/doc/contribute.html#commit_messages https://golang.org/doc/contribute.html#commit_messages
Here's an example of a good PR/Commit: Here's an example of a good PR/Commit:
@ -16,9 +17,8 @@ Here's an example of a good PR/Commit:
--> -->
<!-- handy checklist ; not required-->
**Checklist**: **Checklist**:
- [ ] documentation updated - [ ] updated docs
- [ ] unit tests added - [ ] unit tests added
- [ ] related issues referenced - [ ] related issues referenced
- [ ] ready for review - [ ] ready for review

View file

@ -1,8 +1,19 @@
# Pomerium Changelog # Pomerium Changelog
## vUNRELEASED
### FEATURES
### CHANGED
- Removed `PROXY_ROOT_DOMAIN` config option which is now inferred from `AUTHENTICATE_SERVICE_URL`. Only callback requests originating from a URL on the same sub-domain are permitted.
- Removed `REDIRECT_URL` config option which is now inferred from `AUTHENTICATE_SERVICE_URL` (e.g. `https://$AUTHENTICATE_SERVICE_URL/oauth2/callback`).
### FIXED
## v0.0.3 ## v0.0.3
**FEATURES:** ### FEATURES
- **Authorization** : The authorization module adds support for per-route access policy. In this release we support the most common forms of identity based access policy: `allowed_users`, `allowed_groups`, and `allowed_domains`. In future versions, the authorization module will also support context and device based authorization policy and decisions. See website documentation for more details. - **Authorization** : The authorization module adds support for per-route access policy. In this release we support the most common forms of identity based access policy: `allowed_users`, `allowed_groups`, and `allowed_domains`. In future versions, the authorization module will also support context and device based authorization policy and decisions. See website documentation for more details.
- **Group Support** : The authenticate service now retrieves a user's group membership information during authentication and refresh. This change may require additional identity provider configuration; all of which are described in the [updated docs](https://www.pomerium.io/docs/identity-providers.html). A brief summary of the requirements for each IdP are as follows: - **Group Support** : The authenticate service now retrieves a user's group membership information during authentication and refresh. This change may require additional identity provider configuration; all of which are described in the [updated docs](https://www.pomerium.io/docs/identity-providers.html). A brief summary of the requirements for each IdP are as follows:
@ -14,13 +25,13 @@
- **WebSocket Support** : With [Go 1.12](https://golang.org/doc/go1.12#net/http/httputil) pomerium automatically proxies WebSocket requests. - **WebSocket Support** : With [Go 1.12](https://golang.org/doc/go1.12#net/http/httputil) pomerium automatically proxies WebSocket requests.
**CHANGED**: ### CHANGED
- Add `LOG_LEVEL` config setting that allows for setting the desired minimum log level for an event to be logged. [GH-74] - Add `LOG_LEVEL` config setting that allows for setting the desired minimum log level for an event to be logged. [GH-74]
- Changed `POMERIUM_DEBUG` config setting to just do console-pretty printing. No longer sets log level. [GH-74] - Changed `POMERIUM_DEBUG` config setting to just do console-pretty printing. No longer sets log level. [GH-74]
- Updated `generate_wildcard_cert.sh` to generate a elliptic curve 256 cert by default. - Updated `generate_wildcard_cert.sh` to generate a elliptic curve 256 cert by default.
- Updated `env.example` to include a `POLICY` setting example. - Updated `env.example` to include a `POLICY` setting example.
- Added `IDP_SERVICE_ACCOUNT` to `env.example` . - Added `IDP_SERVICE_ACCOUNT` to `env.example` .
- Removed `PROXY_ROOT_DOMAIN` settings which has been replaced by `POLICY`.
- Removed `ALLOWED_DOMAINS` settings which has been replaced by `POLICY`. Authorization is now handled by the authorization service and is defined in the policy configuration files. - Removed `ALLOWED_DOMAINS` settings which has been replaced by `POLICY`. Authorization is now handled by the authorization service and is defined in the policy configuration files.
- Removed `ROUTES` settings which has been replaced by `POLICY`. - Removed `ROUTES` settings which has been replaced by `POLICY`.
- Add refresh endpoint `${url}/.pomerium/refresh` which forces a token refresh and responds with the json result. - Add refresh endpoint `${url}/.pomerium/refresh` which forces a token refresh and responds with the json result.
@ -32,6 +43,6 @@
- **Removed gitlab provider**. We can't support groups until [this gitlab bug](https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387) is fixed. - **Removed gitlab provider**. We can't support groups until [this gitlab bug](https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387) is fixed.
- Request context is now maintained throughout request-flow via the [context package](https://golang.org/pkg/context/) enabling timeouts, request tracing, and cancellation. - Request context is now maintained throughout request-flow via the [context package](https://golang.org/pkg/context/) enabling timeouts, request tracing, and cancellation.
**FIXED:** ### FIXED
- `http.Server` and `httputil.NewSingleHostReverseProxy` now uses pomerium's logging package instead of the standard library's built in one. [GH-58] - `http.Server` and `httputil.NewSingleHostReverseProxy` now uses pomerium's logging package instead of the standard library's built in one. [GH-58]

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/pomerium/envconfig" "github.com/pomerium/envconfig"
@ -27,13 +26,10 @@ var defaultOptions = &Options{
// Options details the available configuration settings for the authenticate service // Options details the available configuration settings for the authenticate service
type Options struct { type Options struct {
AuthenticateURL *url.URL `envconfig:"AUTHENTICATE_SERVICE_URL"`
// SharedKey is used to authenticate requests between services // SharedKey is used to authenticate requests between services
SharedKey string `envconfig:"SHARED_SECRET"` SharedKey string `envconfig:"SHARED_SECRET"`
// RedirectURL specifies the callback url following third party authentication
RedirectURL *url.URL `envconfig:"REDIRECT_URL"`
ProxyRootDomains []string `envconfig:"PROXY_ROOT_DOMAIN"`
// Session/Cookie management // Session/Cookie management
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
CookieName string CookieName string
@ -67,31 +63,24 @@ func OptionsFromEnvConfig() (*Options, error) {
// The checks do not modify the internal state of the Option structure. Returns // The checks do not modify the internal state of the Option structure. Returns
// on first error found. // on first error found.
func (o *Options) Validate() error { func (o *Options) Validate() error {
if o.RedirectURL == nil { if o.AuthenticateURL == nil {
return errors.New("missing setting: identity provider redirect url") return errors.New("authenticate: 'AUTHENTICATE_SERVICE_URL' missing")
}
redirectPath := "/oauth2/callback"
if o.RedirectURL.Path != redirectPath {
return fmt.Errorf("`setting` redirect-url was %s path should be %s", o.RedirectURL.Path, redirectPath)
} }
if o.ClientID == "" { if o.ClientID == "" {
return errors.New("missing setting: client id") return errors.New("authenticate: 'IDP_CLIENT_ID' missing")
} }
if o.ClientSecret == "" { if o.ClientSecret == "" {
return errors.New("missing setting: client secret") return errors.New("authenticate: 'IDP_CLIENT_SECRET' missing")
}
if len(o.ProxyRootDomains) == 0 {
return errors.New("missing setting: proxy root domain")
} }
if o.SharedKey == "" { if o.SharedKey == "" {
return errors.New("missing setting: shared secret") return errors.New("authenticate: 'SHARED_SECRET' missing")
} }
decodedCookieSecret, err := base64.StdEncoding.DecodeString(o.CookieSecret) decodedCookieSecret, err := base64.StdEncoding.DecodeString(o.CookieSecret)
if err != nil { if err != nil {
return fmt.Errorf("cookie secret is invalid base64: %v", err) return fmt.Errorf("authenticate: 'COOKIE_SECRET' must be base64 encoded: %v", err)
} }
if len(decodedCookieSecret) != 32 { if len(decodedCookieSecret) != 32 {
return fmt.Errorf("cookie secret expects 32 bytes but got %d", len(decodedCookieSecret)) return fmt.Errorf("authenticate: 'COOKIE_SECRET' should be 32; got %d", len(decodedCookieSecret))
} }
return nil return nil
} }
@ -100,13 +89,11 @@ func (o *Options) Validate() error {
type Authenticate struct { type Authenticate struct {
SharedKey string SharedKey string
RedirectURL *url.URL RedirectURL *url.URL
ProxyRootDomains []string
templates *template.Template templates *template.Template
csrfStore sessions.CSRFStore csrfStore sessions.CSRFStore
sessionStore sessions.SessionStore sessionStore sessions.SessionStore
cipher cryptutil.Cipher cipher cryptutil.Cipher
provider identity.Authenticator provider identity.Authenticator
} }
@ -118,7 +105,6 @@ func New(opts *Options) (*Authenticate, error) {
if err := opts.Validate(); err != nil { if err := opts.Validate(); err != nil {
return nil, err return nil, err
} }
// checked by validate
decodedCookieSecret, _ := base64.StdEncoding.DecodeString(opts.CookieSecret) decodedCookieSecret, _ := base64.StdEncoding.DecodeString(opts.CookieSecret)
cipher, err := cryptutil.NewCipher([]byte(decodedCookieSecret)) cipher, err := cryptutil.NewCipher([]byte(decodedCookieSecret))
if err != nil { if err != nil {
@ -136,11 +122,12 @@ func New(opts *Options) (*Authenticate, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
redirectURL := opts.AuthenticateURL
redirectURL.Path = "/oauth2/callback"
provider, err := identity.New( provider, err := identity.New(
opts.Provider, opts.Provider,
&identity.Provider{ &identity.Provider{
RedirectURL: opts.RedirectURL, RedirectURL: redirectURL,
ProviderName: opts.Provider, ProviderName: opts.Provider,
ProviderURL: opts.ProviderURL, ProviderURL: opts.ProviderURL,
ClientID: opts.ClientID, ClientID: opts.ClientID,
@ -152,26 +139,13 @@ func New(opts *Options) (*Authenticate, error) {
return nil, err return nil, err
} }
p := &Authenticate{ return &Authenticate{
SharedKey: opts.SharedKey, SharedKey: opts.SharedKey,
RedirectURL: opts.RedirectURL, RedirectURL: redirectURL,
ProxyRootDomains: dotPrependDomains(opts.ProxyRootDomains),
templates: templates.New(), templates: templates.New(),
csrfStore: cookieStore, csrfStore: cookieStore,
sessionStore: cookieStore, sessionStore: cookieStore,
cipher: cipher, cipher: cipher,
provider: provider, provider: provider,
} }, nil
return p, nil
}
func dotPrependDomains(d []string) []string {
for i := range d {
if d[i] != "" && !strings.HasPrefix(d[i], ".") {
d[i] = fmt.Sprintf(".%s", d[i])
}
}
return d
} }

View file

@ -11,8 +11,7 @@ import (
func testOptions() *Options { func testOptions() *Options {
redirectURL, _ := url.Parse("https://example.com/oauth2/callback") redirectURL, _ := url.Parse("https://example.com/oauth2/callback")
return &Options{ return &Options{
ProxyRootDomains: []string{"example.com"}, AuthenticateURL: redirectURL,
RedirectURL: redirectURL,
SharedKey: "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=", SharedKey: "80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=",
ClientID: "test-client-id", ClientID: "test-client-id",
ClientSecret: "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw=", ClientSecret: "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw=",
@ -26,16 +25,11 @@ func testOptions() *Options {
func TestOptions_Validate(t *testing.T) { func TestOptions_Validate(t *testing.T) {
good := testOptions() good := testOptions()
badRedirectURL := testOptions() badRedirectURL := testOptions()
badRedirectURL.RedirectURL = nil badRedirectURL.AuthenticateURL = nil
malformedRedirectURL := testOptions()
redirectURL, _ := url.Parse("https://example.com/oauth3/callback")
malformedRedirectURL.RedirectURL = redirectURL
emptyClientID := testOptions() emptyClientID := testOptions()
emptyClientID.ClientID = "" emptyClientID.ClientID = ""
emptyClientSecret := testOptions() emptyClientSecret := testOptions()
emptyClientSecret.ClientSecret = "" emptyClientSecret.ClientSecret = ""
proxyRootDomains := testOptions()
proxyRootDomains.ProxyRootDomains = nil
emptyCookieSecret := testOptions() emptyCookieSecret := testOptions()
emptyCookieSecret.CookieSecret = "" emptyCookieSecret.CookieSecret = ""
invalidCookieSecret := testOptions() invalidCookieSecret := testOptions()
@ -53,14 +47,12 @@ func TestOptions_Validate(t *testing.T) {
{"minimum options", good, false}, {"minimum options", good, false},
{"nil options", &Options{}, true}, {"nil options", &Options{}, true},
{"bad redirect url", badRedirectURL, true}, {"bad redirect url", badRedirectURL, true},
{"malformed redirect url", malformedRedirectURL, true},
{"no cookie secret", emptyCookieSecret, true}, {"no cookie secret", emptyCookieSecret, true},
{"invalid cookie secret", invalidCookieSecret, true}, {"invalid cookie secret", invalidCookieSecret, true},
{"short cookie secret", shortCookieLength, true}, {"short cookie secret", shortCookieLength, true},
{"no shared secret", badSharedKey, true}, {"no shared secret", badSharedKey, true},
{"no client id", emptyClientID, true}, {"no client id", emptyClientID, true},
{"no client secret", emptyClientSecret, true}, {"no client secret", emptyClientSecret, true},
{"empty root domains", proxyRootDomains, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -83,7 +75,7 @@ func TestOptionsFromEnvConfig(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{"good default, no env settings", defaultOptions, "", "", false}, {"good default, no env settings", defaultOptions, "", "", false},
{"bad url", nil, "REDIRECT_URL", "%.rjlw", true}, {"bad url", nil, "AUTHENTICATE_SERVICE_URL", "%.rjlw", true},
{"good duration", defaultOptions, "COOKIE_EXPIRE", "1m", false}, {"good duration", defaultOptions, "COOKIE_EXPIRE", "1m", false},
{"bad duration", nil, "COOKIE_REFRESH", "1sm", true}, {"bad duration", nil, "COOKIE_REFRESH", "1sm", true},
} }
@ -105,33 +97,12 @@ func TestOptionsFromEnvConfig(t *testing.T) {
} }
} }
func Test_dotPrependDomains(t *testing.T) {
tests := []struct {
name string
d []string
want []string
}{
{"single domain", []string{"google.com"}, []string{".google.com"}},
{"multiple domain", []string{"google.com", "bing.com"}, []string{".google.com", ".bing.com"}},
{"empty", []string{""}, []string{""}},
{"nested subdomain", []string{"some.really.long.domain.com"}, []string{".some.really.long.domain.com"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := dotPrependDomains(tt.d); !reflect.DeepEqual(got, tt.want) {
t.Errorf("dotPrependDomains() = %v, want %v", got, tt.want)
}
})
}
}
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
good := testOptions() good := testOptions()
good.Provider = "google" good.Provider = "google"
badRedirectURL := testOptions() badRedirectURL := testOptions()
badRedirectURL.RedirectURL = nil badRedirectURL.AuthenticateURL = nil
tests := []struct { tests := []struct {
name string name string

View file

@ -53,7 +53,7 @@ func (a *Authenticate) Handler() http.Handler {
stdMiddleware = stdMiddleware.Append(middleware.RequestIDHandler("req_id", "Request-Id")) stdMiddleware = stdMiddleware.Append(middleware.RequestIDHandler("req_id", "Request-Id"))
validateSignatureMiddleware := stdMiddleware.Append( validateSignatureMiddleware := stdMiddleware.Append(
middleware.ValidateSignature(a.SharedKey), middleware.ValidateSignature(a.SharedKey),
middleware.ValidateRedirectURI(a.ProxyRootDomains)) middleware.ValidateRedirectURI(a.RedirectURL))
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/robots.txt", stdMiddleware.ThenFunc(a.RobotsTxt)) mux.Handle("/robots.txt", stdMiddleware.ThenFunc(a.RobotsTxt))
@ -190,7 +190,7 @@ func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
httputil.ErrorResponse(w, r, "No session found to log out", http.StatusBadRequest) httputil.ErrorResponse(w, r, "No session found to log out", http.StatusBadRequest)
return return
} }
if r.Method == "GET" { if r.Method == http.MethodGet {
signature := r.Form.Get("sig") signature := r.Form.Get("sig")
timestamp := r.Form.Get("ts") timestamp := r.Form.Get("ts")
destinationURL, err := url.Parse(redirectURI) destinationURL, err := url.Parse(redirectURI)
@ -237,13 +237,13 @@ func (a *Authenticate) OAuthStart(w http.ResponseWriter, r *http.Request) {
a.csrfStore.SetCSRF(w, r, nonce) a.csrfStore.SetCSRF(w, r, nonce)
// verify redirect uri is from the root domain // verify redirect uri is from the root domain
if !middleware.ValidRedirectURI(authRedirectURL.String(), a.ProxyRootDomains) { if !middleware.SameSubdomain(authRedirectURL, a.RedirectURL) {
httputil.ErrorResponse(w, r, "Invalid redirect parameter", http.StatusBadRequest) httputil.ErrorResponse(w, r, "Invalid redirect parameter", http.StatusBadRequest)
return return
} }
// verify proxy url is from the root domain // verify proxy url is from the root domain
proxyRedirectURL, err := url.Parse(authRedirectURL.Query().Get("redirect_uri")) proxyRedirectURL, err := url.Parse(authRedirectURL.Query().Get("redirect_uri"))
if err != nil || !middleware.ValidRedirectURI(proxyRedirectURL.String(), a.ProxyRootDomains) { if err != nil || !middleware.SameSubdomain(proxyRedirectURL, a.RedirectURL) {
httputil.ErrorResponse(w, r, "Invalid redirect parameter", http.StatusBadRequest) httputil.ErrorResponse(w, r, "Invalid redirect parameter", http.StatusBadRequest)
return return
} }
@ -329,8 +329,14 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "CSRF failed"} return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "CSRF failed"}
} }
if !middleware.ValidRedirectURI(redirect, a.ProxyRootDomains) { redirectURL, err := url.Parse(redirect)
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Invalid Redirect URI"} if err != nil {
log.FromRequest(r).Error().Err(err).Msg("authenticate: couldn't parse redirect url")
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Couldn't parse redirect url"}
}
if !middleware.SameSubdomain(redirectURL, a.RedirectURL) {
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Invalid Redirect URI domain"}
} }
err = a.sessionStore.SaveSession(w, r, session) err = a.sessionStore.SaveSession(w, r, session)

View file

@ -21,7 +21,6 @@ func testAuthenticate() *Authenticate {
var auth Authenticate var auth Authenticate
auth.RedirectURL, _ = url.Parse("https://auth.example.com/oauth/callback") auth.RedirectURL, _ = url.Parse("https://auth.example.com/oauth/callback")
auth.SharedKey = "IzY7MOZwzfOkmELXgozHDKTxoT3nOYhwkcmUVINsRww=" auth.SharedKey = "IzY7MOZwzfOkmELXgozHDKTxoT3nOYhwkcmUVINsRww="
auth.ProxyRootDomains = []string{"example.com"}
auth.templates = templates.New() auth.templates = templates.New()
return &auth return &auth
} }
@ -446,7 +445,6 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
redirectURL string redirectURL string
sig string sig string
ts string ts string
allowedDomains []string
provider identity.Authenticator provider identity.Authenticator
csrfStore sessions.MockCSRFStore csrfStore sessions.MockCSRFStore
@ -458,7 +456,6 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
"https://corp.pomerium.io/", "https://corp.pomerium.io/",
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
fmt.Sprint(time.Now().Unix()), fmt.Sprint(time.Now().Unix()),
[]string{".pomerium.io"},
identity.MockProvider{}, identity.MockProvider{},
sessions.MockCSRFStore{}, sessions.MockCSRFStore{},
http.StatusFound, http.StatusFound,
@ -468,17 +465,6 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
"https://corp.pomerium.io/", "https://corp.pomerium.io/",
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
fmt.Sprint(time.Now().Add(10 * time.Hour).Unix()), fmt.Sprint(time.Now().Add(10 * time.Hour).Unix()),
[]string{".pomerium.io"},
identity.MockProvider{},
sessions.MockCSRFStore{},
http.StatusBadRequest,
},
{"domain not in allowed domains",
http.MethodGet,
"https://corp.pomerium.io/",
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
fmt.Sprint(time.Now().Unix()),
[]string{"not.pomerium.io"},
identity.MockProvider{}, identity.MockProvider{},
sessions.MockCSRFStore{}, sessions.MockCSRFStore{},
http.StatusBadRequest, http.StatusBadRequest,
@ -488,7 +474,6 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
"", "",
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
fmt.Sprint(time.Now().Unix()), fmt.Sprint(time.Now().Unix()),
[]string{".pomerium.io"},
identity.MockProvider{}, identity.MockProvider{},
sessions.MockCSRFStore{}, sessions.MockCSRFStore{},
http.StatusBadRequest, http.StatusBadRequest,
@ -498,7 +483,6 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
"https://pomerium.com%zzzzz", "https://pomerium.com%zzzzz",
redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"), redirectURLSignature("https://corp.pomerium.io/", time.Now(), "secret"),
fmt.Sprint(time.Now().Unix()), fmt.Sprint(time.Now().Unix()),
[]string{".pomerium.io"},
identity.MockProvider{}, identity.MockProvider{},
sessions.MockCSRFStore{}, sessions.MockCSRFStore{},
http.StatusBadRequest, http.StatusBadRequest,
@ -507,7 +491,6 @@ func TestAuthenticate_OAuthStart(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
a := &Authenticate{ a := &Authenticate{
ProxyRootDomains: tt.allowedDomains,
RedirectURL: uriParse("http://www.pomerium.io"), RedirectURL: uriParse("http://www.pomerium.io"),
csrfStore: tt.csrfStore, csrfStore: tt.csrfStore,
provider: tt.provider, provider: tt.provider,
@ -543,7 +526,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
paramErr string paramErr string
code string code string
state string state string
validDomains []string authenticateURL string
session sessions.SessionStore session sessions.SessionStore
provider identity.MockProvider provider identity.MockProvider
csrfStore sessions.MockCSRFStore csrfStore sessions.MockCSRFStore
@ -556,8 +539,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -578,8 +560,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -601,8 +582,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -623,8 +603,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateError: errors.New("error"), AuthenticateError: errors.New("error"),
@ -640,8 +619,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{SaveError: errors.New("error")}, &sessions.MockSessionStore{SaveError: errors.New("error")},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -663,8 +641,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"idp error", "idp error",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -685,8 +662,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"", "",
base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:https://corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -707,8 +683,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
"nonce:https://corp.pomerium.io", "nonce:https://corp.pomerium.io",
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -729,8 +704,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce")), base64.URLEncoding.EncodeToString([]byte("nonce")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -751,8 +725,7 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
"", "",
"code", "code",
base64.URLEncoding.EncodeToString([]byte("nonce:corp.pomerium.io")), base64.URLEncoding.EncodeToString([]byte("nonce:corp.pomerium.io")),
[]string{"pomerium.io"}, "https://authenticate.pomerium.io",
&sessions.MockSessionStore{}, &sessions.MockSessionStore{},
identity.MockProvider{ identity.MockProvider{
AuthenticateResponse: sessions.SessionState{ AuthenticateResponse: sessions.SessionState{
@ -771,11 +744,12 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
authURL, _ := url.Parse(tt.authenticateURL)
a := &Authenticate{ a := &Authenticate{
RedirectURL: authURL,
sessionStore: tt.session, sessionStore: tt.session,
csrfStore: tt.csrfStore, csrfStore: tt.csrfStore,
provider: tt.provider, provider: tt.provider,
ProxyRootDomains: tt.validDomains,
} }
u, _ := url.Parse("/oauthGet") u, _ := url.Parse("/oauthGet")
params, _ := url.ParseQuery(u.RawQuery) params, _ := url.ParseQuery(u.RawQuery)

View file

@ -40,7 +40,7 @@ func main() {
fmt.Printf("%s", version.FullVersion()) fmt.Printf("%s", version.FullVersion())
os.Exit(0) os.Exit(0)
} }
log.Info().Str("version", version.FullVersion()).Str("service", mainOpts.Services).Msg("cmd/pomerium") log.Info().Str("version", version.FullVersion()).Str("user-agent", version.UserAgent()).Str("service", mainOpts.Services).Msg("cmd/pomerium")
grpcAuth := middleware.NewSharedSecretCred(mainOpts.SharedKey) grpcAuth := middleware.NewSharedSecretCred(mainOpts.SharedKey)
grpcOpts := []grpc.ServerOption{grpc.UnaryInterceptor(grpcAuth.ValidateRequest)} grpcOpts := []grpc.ServerOption{grpc.UnaryInterceptor(grpcAuth.ValidateRequest)}
@ -57,7 +57,7 @@ func main() {
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("cmd/pomerium: new authenticate") log.Fatal().Err(err).Msg("cmd/pomerium: new authenticate")
} }
authHost = opts.RedirectURL.Host authHost = opts.AuthenticateURL.Host
pbAuthenticate.RegisterAuthenticatorServer(grpcServer, authenticateService) pbAuthenticate.RegisterAuthenticatorServer(grpcServer, authenticateService)
} }

View file

@ -107,22 +107,14 @@ Certificate key is the x509 _private-key_ used to establish secure HTTP and gRPC
## Authenticate Service ## Authenticate Service
### Redirect URL ### Authenticate Service URL
- Environmental Variable: `REDIRECT_URL` - Environmental Variable: `AUTHENTICATE_SERVICE_URL`
- Type: `URL` - Type: `URL`
- Required - Required
- Example: `https://auth.corp.example.com/oauth2/callback` - Example: `https://authenticate.corp.example.com`
Redirect URL is the url the user will be redirected to following authentication with the third-party identity provider (IdP). Note the URL ends with `/oauth2/callback`. This setting will mirror the URL set when configuring your [identity provider]. Typically, on the provider side, this is called an _authorized callback url_. Authenticate Service URL is the externally accessible URL for the authenticate service.
### Proxy Root Domains
- Environmental Variable: `PROXY_ROOT_DOMAIN`
- Type: `[]string` (e.g. comma separated list of strings)
- Required
Proxy Root Domains specifies the sub-domains that can proxy requests. For example, `httpbin.corp.example.com` would be a valid domain under the proxy root domain `corp.example.com`. If a proxy service attempts to authenticate a user from a non-whitelisted domain, an error will be returned.
### Identity Provider Name ### Identity Provider Name
@ -191,7 +183,7 @@ Signing key is the base64 encoded key used to sign outbound requests. For more i
- Environmental Variable: `AUTHENTICATE_SERVICE_URL` - Environmental Variable: `AUTHENTICATE_SERVICE_URL`
- Type: `URL` - Type: `URL`
- Required - Required
- Example: `https://auth.corp.example.com` - Example: `https://authenticate.corp.example.com`
Authenticate Service URL is the externally accessible URL for the authenticate service. Authenticate Service URL is the externally accessible URL for the authenticate service.
@ -227,7 +219,7 @@ Authorize Internal Service URL is the internally routed dns name of the authoriz
- Environmental Variable: `OVERRIDE_CERTIFICATE_NAME` - Environmental Variable: `OVERRIDE_CERTIFICATE_NAME`
- Type: `int` - Type: `int`
- Optional (but typically required if Authenticate Internal Service Address is set) - Optional (but typically required if Authenticate Internal Service Address is set)
- Example: `*.corp.example.com` if wild card or `authenticate.corp.example.com` - Example: `*.corp.example.com` if wild card or `authenticate.corp.example.com`/`authorize.corp.example.com`
When Authenticate Internal Service Address is set, secure service communication can fail because the external certificate name will not match the internally routed service url. This setting allows you to override that check. When Authenticate Internal Service Address is set, secure service communication can fail because the external certificate name will not match the internally routed service url. This setting allows you to override that check.

View file

@ -13,18 +13,16 @@ services:
environment: environment:
- POMERIUM_DEBUG=true - POMERIUM_DEBUG=true
- SERVICES=all - SERVICES=all
- REDIRECT_URL=https://auth.corp.beyondperimeter.com/oauth2/callback
- IDP_PROVIDER=google - IDP_PROVIDER=google
- IDP_PROVIDER_URL=https://accounts.google.com - IDP_PROVIDER_URL=https://accounts.google.com
- IDP_CLIENT_ID=REPLACE_ME.apps.googleusercontent.com - IDP_CLIENT_ID=REPLACE_ME.apps.googleusercontent.com
- IDP_CLIENT_SECRET=REPLACE_ME - IDP_CLIENT_SECRET=REPLACE_ME
- PROXY_ROOT_DOMAIN=beyondperimeter.com
- SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M= - SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M=
- COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI= - COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI=
- CERTIFICATE_FILE=cert.pem - CERTIFICATE_FILE=cert.pem
- CERTIFICATE_KEY_FILE=privkey.pem - CERTIFICATE_KEY_FILE=privkey.pem
- AUTHENTICATE_SERVICE_URL=https://auth.corp.beyondperimeter.com - AUTHENTICATE_SERVICE_URL=https://authenticate.corp.beyondperimeter.com
- AUTHORIZE_SERVICE_URL=https://access.corp.beyondperimeter.com - AUTHORIZE_SERVICE_URL=https://authorize.corp.beyondperimeter.com
- POLICY_FILE=./policy.yaml - POLICY_FILE=./policy.yaml
volumes: volumes:
- ./cert.pem:/pomerium/cert.pem:ro - ./cert.pem:/pomerium/cert.pem:ro

View file

@ -19,18 +19,16 @@ services:
environment: environment:
- POMERIUM_DEBUG=true - POMERIUM_DEBUG=true
- SERVICES=authenticate - SERVICES=authenticate
- REDIRECT_URL=https://auth.corp.beyondperimeter.com/oauth2/callback
# Identity Provider Settings (Must be changed!) # Identity Provider Settings (Must be changed!)
- IDP_PROVIDER=google - IDP_PROVIDER=google
- IDP_PROVIDER_URL=https://accounts.google.com - IDP_PROVIDER_URL=https://accounts.google.com
- IDP_CLIENT_ID=REPLACE_ME.apps.googleusercontent.com - IDP_CLIENT_ID=REPLACE_ME.apps.googleusercontent.com
- IDP_CLIENT_SECRET=REPLACE_ME - IDP_CLIENT_SECRET=REPLACE_ME
- PROXY_ROOT_DOMAIN=corp.beyondperimeter.com
- SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M= - SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M=
- COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI= - COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI=
# nginx settings # nginx settings
- VIRTUAL_PROTO=https - VIRTUAL_PROTO=https
- VIRTUAL_HOST=auth.corp.beyondperimeter.com - VIRTUAL_HOST=authenticate.corp.beyondperimeter.com
- VIRTUAL_PORT=443 - VIRTUAL_PORT=443
volumes: volumes:
- ./cert.pem:/pomerium/cert.pem:ro - ./cert.pem:/pomerium/cert.pem:ro
@ -45,8 +43,8 @@ services:
- POMERIUM_DEBUG=true - POMERIUM_DEBUG=true
- SERVICES=proxy - SERVICES=proxy
- POLICY_FILE=policy.yaml - POLICY_FILE=policy.yaml
- AUTHENTICATE_SERVICE_URL=https://auth.corp.beyondperimeter.com - AUTHENTICATE_SERVICE_URL=https://authenticate.corp.beyondperimeter.com
- AUTHORIZE_SERVICE_URL=https://access.corp.beyondperimeter.com - AUTHORIZE_SERVICE_URL=https://authorize.corp.beyondperimeter.com
# IMPORTANT! If you are running pomerium behind another ingress (loadbalancer/firewall/etc) # IMPORTANT! If you are running pomerium behind another ingress (loadbalancer/firewall/etc)
# you must tell pomerium proxy how to communicate using an internal hostname for RPC # you must tell pomerium proxy how to communicate using an internal hostname for RPC
- AUTHENTICATE_INTERNAL_URL=pomerium-authenticate:443 - AUTHENTICATE_INTERNAL_URL=pomerium-authenticate:443
@ -77,7 +75,7 @@ services:
- SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M= - SHARED_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M=
# nginx settings # nginx settings
- VIRTUAL_PROTO=https - VIRTUAL_PROTO=https
- VIRTUAL_HOST=access.corp.beyondperimeter.com - VIRTUAL_HOST=authorize.corp.beyondperimeter.com
- VIRTUAL_PORT=443 - VIRTUAL_PORT=443
volumes: volumes:
- ./cert.pem:/pomerium/cert.pem:ro - ./cert.pem:/pomerium/cert.pem:ro

View file

@ -25,16 +25,12 @@ spec:
env: env:
- name: SERVICES - name: SERVICES
value: authenticate value: authenticate
- name: REDIRECT_URL
value: https://auth.corp.beyondperimeter.com/oauth2/callback
- name: IDP_PROVIDER - name: IDP_PROVIDER
value: google value: google
- name: IDP_PROVIDER_URL - name: IDP_PROVIDER_URL
value: https://accounts.google.com value: https://accounts.google.com
- name: IDP_CLIENT_ID - name: IDP_CLIENT_ID
value: 851877082059-bfgkpj09noog7as3gpc3t7r6n9sjbgs6.apps.googleusercontent.com value: 851877082059-bfgkpj09noog7as3gpc3t7r6n9sjbgs6.apps.googleusercontent.com
- name: PROXY_ROOT_DOMAIN
value: beyondperimeter.com
- name: SHARED_SECRET - name: SHARED_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View file

@ -16,8 +16,8 @@ spec:
- secretName: pomerium-tls - secretName: pomerium-tls
hosts: hosts:
- "*.corp.beyondperimeter.com" - "*.corp.beyondperimeter.com"
- "auth.corp.beyondperimeter.com" - "authenticate.corp.beyondperimeter.com"
- "access.corp.beyondperimeter.com" - "authorize.corp.beyondperimeter.com"
rules: rules:
- host: "*.corp.beyondperimeter.com" - host: "*.corp.beyondperimeter.com"
@ -28,14 +28,14 @@ spec:
serviceName: pomerium-proxy-service serviceName: pomerium-proxy-service
servicePort: https servicePort: https
- host: "auth.corp.beyondperimeter.com" - host: "authenticate.corp.beyondperimeter.com"
http: http:
paths: paths:
- paths: - paths:
backend: backend:
serviceName: pomerium-authenticate-service serviceName: pomerium-authenticate-service
servicePort: https servicePort: https
- host: "access.corp.beyondperimeter.com" - host: "authorize.corp.beyondperimeter.com"
http: http:
paths: paths:
- paths: - paths:

View file

@ -12,8 +12,8 @@ spec:
- secretName: pomerium-tls - secretName: pomerium-tls
hosts: hosts:
- "*.corp.beyondperimeter.com" - "*.corp.beyondperimeter.com"
- "auth.corp.beyondperimeter.com" - "authenticate.corp.beyondperimeter.com"
- "access.corp.beyondperimeter.com" - "authorize.corp.beyondperimeter.com"
rules: rules:
- host: "*.corp.beyondperimeter.com" - host: "*.corp.beyondperimeter.com"
@ -23,14 +23,14 @@ spec:
backend: backend:
serviceName: pomerium-proxy-service serviceName: pomerium-proxy-service
servicePort: https servicePort: https
- host: "auth.corp.beyondperimeter.com" - host: "authenticate.corp.beyondperimeter.com"
http: http:
paths: paths:
- paths: - paths:
backend: backend:
serviceName: pomerium-authenticate-service serviceName: pomerium-authenticate-service
servicePort: https servicePort: https
- host: "access.corp.beyondperimeter.com" - host: "authorize.corp.beyondperimeter.com"
http: http:
paths: paths:
- paths: - paths:

View file

@ -26,11 +26,11 @@ spec:
- name: SERVICES - name: SERVICES
value: proxy value: proxy
- name: AUTHORIZE_SERVICE_URL - name: AUTHORIZE_SERVICE_URL
value: https://access.corp.beyondperimeter.com value: https://authorize.corp.beyondperimeter.com
- name: AUTHORIZE_INTERNAL_URL - name: AUTHORIZE_INTERNAL_URL
value: "pomerium-authorize-service.pomerium.svc.cluster.local" value: "pomerium-authorize-service.pomerium.svc.cluster.local"
- name: AUTHENTICATE_SERVICE_URL - name: AUTHENTICATE_SERVICE_URL
value: https://auth.corp.beyondperimeter.com value: https://authenticate.corp.beyondperimeter.com
- name: AUTHENTICATE_INTERNAL_URL - name: AUTHENTICATE_INTERNAL_URL
value: "pomerium-authenticate-service.pomerium.svc.cluster.local" value: "pomerium-authenticate-service.pomerium.svc.cluster.local"
- name: OVERRIDE_CERTIFICATE_NAME - name: OVERRIDE_CERTIFICATE_NAME

View file

@ -14,7 +14,7 @@ There are a few configuration steps required for identity provider integration.
In this guide we'll cover how to do the following for each identity provider: In this guide we'll cover how to do the following for each identity provider:
1. Set a **[Redirect URL]** pointing back to Pomerium. 1. Set a **Redirect URL** pointing back to Pomerium. That is, `https://${AUTHENTICATE_SERVICE_URL}/oauth2/callback`
2. Generate a **[Client ID]** and **[Client Secret]**. 2. Generate a **[Client ID]** and **[Client Secret]**.
3. Configure Pomerium to use the **[Client ID]** and **[Client Secret]** keys. 3. Configure Pomerium to use the **[Client ID]** and **[Client Secret]** keys.
@ -69,7 +69,7 @@ Click on **Save** and the key will be displayed. **Make sure to copy the value o
![Creating a Key](./microsoft/azure-create-key.png) ![Creating a Key](./microsoft/azure-create-key.png)
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://authenticate.corp.beyondperimeter.com/oauth2/callback`.
![Add Reply URL](./microsoft/azure-redirect-url.png) ![Add Reply URL](./microsoft/azure-redirect-url.png)
@ -109,7 +109,6 @@ Finally, configure Pomerium with the identity provider settings retrieved in the
```bash ```bash
# Azure # Azure
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
IDP_PROVIDER="azure" IDP_PROVIDER="azure"
IDP_PROVIDER_URL="https://login.microsoftonline.com/{REPLACE-ME-SEE-ABOVE}/v2.0" IDP_PROVIDER_URL="https://login.microsoftonline.com/{REPLACE-ME-SEE-ABOVE}/v2.0"
IDP_CLIENT_ID="REPLACE-ME" IDP_CLIENT_ID="REPLACE-ME"
@ -133,7 +132,7 @@ On the **Applications** page, add a new application by setting the following par
Field | Description Field | Description
------------ | -------------------------------------------------------------------- ------------ | --------------------------------------------------------------------
Name | The name of your web app Name | The name of your web app
Redirect URI | [Redirect URL] (e.g.`https://auth.corp.example.com/oauth2/callback`) Redirect URI | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`)
Scopes | **Must** select **read_user** and **openid** Scopes | **Must** select **read_user** and **openid**
![Create New Credentials](./gitlab/gitlab-create-application.png) ![Create New Credentials](./gitlab/gitlab-create-application.png)
@ -147,7 +146,6 @@ 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. Set [Client ID] and [Client Secret] in Pomerium's settings. Your [environmental variables] should look something like this.
```bash ```bash
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
IDP_PROVIDER="gitlab" IDP_PROVIDER="gitlab"
# NOTE!!! Provider url is optional, but should be set if you are running an on-premise instance # 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` # defaults to : https://gitlab.com, a local copy would look something like `http://gitlab.corp.beyondperimeter.com`
@ -175,7 +173,7 @@ On the **Create [Client ID]** page, select **Web application**. In the new field
Field | Description Field | Description
------------------------ | -------------------------------------------------------------------- ------------------------ | --------------------------------------------------------------------
Name | The name of your web app Name | The name of your web app
Authorized redirect URIs | [Redirect URL] (e.g.`https://auth.corp.example.com/oauth2/callback`) Authorized redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`)
![Web App Credentials Configuration](./google/google-create-client-id-config.png) ![Web App Credentials Configuration](./google/google-create-client-id-config.png)
@ -229,7 +227,6 @@ Next we'll delegate G-suite group membership access to the service account we ju
Your [environmental variables] should look something like this. Your [environmental variables] should look something like this.
```bash ```bash
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
IDP_PROVIDER="google" IDP_PROVIDER="google"
IDP_PROVIDER_URL="https://accounts.google.com" IDP_PROVIDER_URL="https://accounts.google.com"
IDP_CLIENT_ID="yyyy.apps.googleusercontent.com" IDP_CLIENT_ID="yyyy.apps.googleusercontent.com"
@ -253,7 +250,7 @@ Field | Description
---------------------------- | --------------------------------------------------------------------- ---------------------------- | ---------------------------------------------------------------------
Name | The name of your application. Name | The name of your application.
Base URIs (optional) | The domain(s) of your application. Base URIs (optional) | The domain(s) of your application.
Login redirect URIs | [Redirect URL] (e.g.`https://auth.corp.example.com/oauth2/callback`). Login redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`).
Group assignments (optional) | The user groups that can sign in to this application. Group assignments (optional) | The user groups that can sign in to this application.
Grant type allowed | **You must enable Refresh Token.** Grant type allowed | **You must enable Refresh Token.**
@ -296,7 +293,6 @@ Include in | Any scope
Finally, configure Pomerium with the identity provider settings retrieved in the pervious steps. Your [environmental variables] should look something like this. Finally, configure Pomerium with the identity provider settings retrieved in the pervious steps. Your [environmental variables] should look something like this.
```bash ```bash
REDIRECT_URL="https://sso-auth.corp.beyondperimeter.com/oauth2/callback"
IDP_PROVIDER="okta" IDP_PROVIDER="okta"
IDP_PROVIDER_URL="https://dev-108295-admin.oktapreview.com/" IDP_PROVIDER_URL="https://dev-108295-admin.oktapreview.com/"
IDP_CLIENT_ID="0oairksnr0C0fEJ7l0h7" IDP_CLIENT_ID="0oairksnr0C0fEJ7l0h7"
@ -319,7 +315,7 @@ On the App Configuration page, **name the app** and **select a logo**. Select **
![One Login select logo](./one-login/one-login-select-logo.png) ![One Login select logo](./one-login/one-login-select-logo.png)
Next, set set the **Redirect URI's** setting to be Pomerium's [redirect url]. Next, set set the **Redirect URI's** setting to be Pomerium's redirect url `https://${AUTHENTICATE_SERVICE_URL}/oauth2/callback`.
![One Login set callback url](./one-login/one-login-callback-url.png) ![One Login set callback url](./one-login/one-login-callback-url.png)
@ -345,7 +341,6 @@ To return the user's Active Directory field, configure the group to return `memb
Finally, configure Pomerium with the identity provider settings retrieved in the pervious steps. Your [environmental variables] should look something like this. Finally, configure Pomerium with the identity provider settings retrieved in the pervious steps. Your [environmental variables] should look something like this.
```bash ```bash
REDIRECT_URL="https://auth.corp.beyondperimeter.com/oauth2/callback"
IDP_PROVIDER="onelogin" IDP_PROVIDER="onelogin"
IDP_PROVIDER_URL="https://openid-connect.onelogin.com/oidc" IDP_PROVIDER_URL="https://openid-connect.onelogin.com/oidc"
IDP_CLIENT_ID="9e613ce0-1622-0137-452d-0a93c31f8392142934" IDP_CLIENT_ID="9e613ce0-1622-0137-452d-0a93c31f8392142934"
@ -361,4 +356,3 @@ After reloading Pomerium, you should be able to see any login events from your O
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable [environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
[oauth2]: https://oauth.net/2/ [oauth2]: https://oauth.net/2/
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect [openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect
[redirect url]: ./config-reference.html#redirect-url

View file

@ -173,10 +173,8 @@ OVERRIDE_CERTIFICATE_NAME | `*.int.nas.example.com`
IDP_CLIENT_SECRET | Values from setting up your [identity provider] IDP_CLIENT_SECRET | Values from setting up your [identity provider]
IDP_CLIENT_ID | Values from setting up your [identity provider] IDP_CLIENT_ID | Values from setting up your [identity provider]
IDP_PROVIDER | Values from setting up your [identity provider] (e.g. `google`) IDP_PROVIDER | Values from setting up your [identity provider] (e.g. `google`)
REDIRECT_URL | `https://authenticate.int.nas.example.com/oauth2/callback`
COOKIE_SECRET | output of `head -c32 /dev/urandom | base64` COOKIE_SECRET | output of `head -c32 /dev/urandom | base64`
SHARED_SECRET | output of `head -c32 /dev/urandom | base64` SHARED_SECRET | output of `head -c32 /dev/urandom | base64`
PROXY_ROOT_DOMAIN | `int.nas.example.com`
AUTHORIZE_SERVICE_URL | `https://authorize.int.nas.example.com` AUTHORIZE_SERVICE_URL | `https://authorize.int.nas.example.com`
AUTHENTICATE_SERVICE_URL | `https://authenticate.int.nas.example.com` AUTHENTICATE_SERVICE_URL | `https://authenticate.int.nas.example.com`
AUTHORIZE_INTERNAL_URL | `localhost:443` AUTHORIZE_INTERNAL_URL | `localhost:443`

View file

@ -3,7 +3,11 @@
# Main configuration flags # Main configuration flags
# export ADDRESS=":8443" # optional, default is 443 # export ADDRESS=":8443" # optional, default is 443
# export POMERIUM_DEBUG=true # optional, default is false # export POMERIUM_DEBUG=true # optional, default is false
# export SERVICE="all" # optional, default is all. # export SERVICE="all" # optional, default is all
# export LOG_LEVEL="info" # optional, default is debug
export AUTHENTICATE_SERVICE_URL=https://authenticate.corp.example.com
export AUTHORIZE_SERVICE_URL=https://authorize.corp.example.com
# Certificates can be loaded as files or base64 encoded bytes. If neither is set, a # Certificates can be loaded as files or base64 encoded bytes. If neither is set, a
# pomerium will attempt to locate a pair in the root directory # pomerium will attempt to locate a pair in the root directory
@ -12,8 +16,6 @@ export CERTIFICATE_KEY_FILE="./privkey.pem" # optional, defaults to `./certprivk
# export CERTIFICATE="xxxxxx" # base64 encoded cert, eg. `base64 -i cert.pem` # export CERTIFICATE="xxxxxx" # base64 encoded cert, eg. `base64 -i cert.pem`
# export CERTIFICATE_KEY="xxxx" # base64 encoded key, eg. `base64 -i privkey.pem` # export CERTIFICATE_KEY="xxxx" # base64 encoded key, eg. `base64 -i privkey.pem`
# The URL that the identity provider will call back after authenticating the user
export REDIRECT_URL="https://sso-auth.corp.example.com/oauth2/callback"
# Generate 256 bit random keys e.g. `head -c32 /dev/urandom | base64` # Generate 256 bit random keys e.g. `head -c32 /dev/urandom | base64`
export SHARED_SECRET=9wiTZq4qvmS/plYQyvzGKWPlH/UBy0DMYMA2x/zngrM= export SHARED_SECRET=9wiTZq4qvmS/plYQyvzGKWPlH/UBy0DMYMA2x/zngrM=
export COOKIE_SECRET=uPGHo1ujND/k3B9V6yr52Gweq3RRYfFho98jxDG5Br8= export COOKIE_SECRET=uPGHo1ujND/k3B9V6yr52Gweq3RRYfFho98jxDG5Br8=

View file

@ -53,7 +53,7 @@ func ValidateClientSecret(sharedSecret string) func(next http.Handler) http.Hand
// ValidateRedirectURI checks the redirect uri in the query parameters and ensures that // ValidateRedirectURI checks the redirect uri in the query parameters and ensures that
// the its domain is in the list of proxy root domains. // the its domain is in the list of proxy root domains.
func ValidateRedirectURI(proxyRootDomains []string) func(next http.Handler) http.Handler { func ValidateRedirectURI(rootDomain *url.URL) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() err := r.ParseForm()
@ -61,8 +61,12 @@ func ValidateRedirectURI(proxyRootDomains []string) func(next http.Handler) http
httputil.ErrorResponse(w, r, err.Error(), http.StatusBadRequest) httputil.ErrorResponse(w, r, err.Error(), http.StatusBadRequest)
return return
} }
redirectURI := r.Form.Get("redirect_uri") redirectURI, err := url.Parse(r.Form.Get("redirect_uri"))
if !ValidRedirectURI(redirectURI, proxyRootDomains) { if err != nil {
httputil.ErrorResponse(w, r, err.Error(), http.StatusBadRequest)
return
}
if !SameSubdomain(redirectURI, rootDomain) {
httputil.ErrorResponse(w, r, "Invalid redirect parameter", http.StatusBadRequest) httputil.ErrorResponse(w, r, "Invalid redirect parameter", http.StatusBadRequest)
return return
} }
@ -71,24 +75,22 @@ func ValidateRedirectURI(proxyRootDomains []string) func(next http.Handler) http
} }
} }
// ValidRedirectURI checks if a URL's domain is one in the list of proxy root domains. // SameSubdomain checks to see if two URLs share the same root domain.
func ValidRedirectURI(uri string, rootDomains []string) bool { func SameSubdomain(u, j *url.URL) bool {
if uri == "" || len(rootDomains) == 0 { if u.Hostname() == "" || j.Hostname() == "" {
return false return false
} }
redirectURL, err := url.Parse(uri) uParts := strings.Split(u.Hostname(), ".")
if err != nil || redirectURL.Host == "" { jParts := strings.Split(j.Hostname(), ".")
if len(uParts) != len(jParts) {
return false return false
} }
for _, domain := range rootDomains { for i := 1; i < len(uParts); i++ {
if domain == "" { if uParts[i] != jParts[i] {
return false return false
} }
if strings.HasSuffix(redirectURL.Hostname(), domain) { }
return true return true
}
}
return false
} }
// ValidateSignature ensures the request is valid and has been signed with // ValidateSignature ensures the request is valid and has been signed with

View file

@ -10,25 +10,29 @@ import (
"time" "time"
) )
func Test_ValidRedirectURI(t *testing.T) { func Test_SameSubdomain(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
uri string uri string
rootDomains []string rootDomains string
want bool want bool
}{ }{
{"good url redirect", "https://example.com/redirect", []string{"example.com"}, true}, {"good url redirect", "https://example.com/redirect", "https://example.com", true},
{"bad domain", "https://example.com/redirect", []string{"notexample.com"}, false}, {"simple sub", "https://auth.example.com", "https://test.example.com", true},
{"malformed url", "^example.com/redirect", []string{"notexample.com"}, false}, {"mismatched lengths", "https://auth.auth.example.com", "https://test.example.com", false},
{"empty domain list", "https://example.com/redirect", []string{}, false}, {"bad domain", "https://auth.example.com/redirect", "https://test.notexample.com", false},
{"empty domain", "https://example.com/redirect", []string{""}, false}, {"malformed url", "^example.com/redirect", "https://notexample.com", false},
{"empty url", "", []string{"example.com"}, false}, {"empty domain list", "https://example.com/redirect", ".com", false},
{"empty domain", "https://example.com/redirect", "", false},
{"empty url", "", "example.com", false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := ValidRedirectURI(tt.uri, tt.rootDomains); got != tt.want { u, _ := url.Parse(tt.uri)
t.Errorf("ValidRedirectURI() = %v, want %v", got, tt.want) j, _ := url.Parse(tt.rootDomains)
if got := SameSubdomain(u, j); got != tt.want {
t.Errorf("SameSubdomain() = %v, want %v", got, tt.want)
} }
}) })
} }
@ -119,16 +123,21 @@ func TestSetHeaders(t *testing.T) {
func TestValidateRedirectURI(t *testing.T) { func TestValidateRedirectURI(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
proxyRootDomains []string rootDomain string
redirectURI string redirectURI string
status int status int
}{ }{
{"simple", []string{"google.com"}, "https://google.com", http.StatusOK}, {"simple", "https://auth.google.com", "https://b.google.com", http.StatusOK},
{"bad match", []string{"aol.com"}, "https://google.com", http.StatusBadRequest}, {"deep ok", "https://a.some.really.deep.sub.domain.google.com", "https://b.some.really.deep.sub.domain.google.com", http.StatusOK},
{"with cname", []string{"google.com"}, "https://www.google.com", http.StatusOK}, {"bad match", "https://auth.aol.com", "https://test.google.com", http.StatusBadRequest},
{"with path", []string{"google.com"}, "https://www.google.com/path", http.StatusOK}, {"bad simple", "https://auth.corp.aol.com", "https://test.corp.google.com", http.StatusBadRequest},
{"http", []string{"google.com"}, "http://www.google.com/path", http.StatusOK}, {"deep bad", "https://a.some.really.deep.sub.domain.scroogle.com", "https://b.some.really.deep.sub.domain.google.com", http.StatusBadRequest},
{"malformed, invalid hex digits", []string{"google.com"}, "%zzzzz", http.StatusBadRequest}, {"with cname", "https://auth.google.com", "https://www.google.com", http.StatusOK},
{"with path", "https://auth.google.com", "https://www.google.com/path", http.StatusOK},
{"http mistmatch", "https://auth.google.com", "http://www.google.com/path", http.StatusOK},
{"http", "http://auth.google.com", "http://www.google.com/path", http.StatusOK},
{"ip", "http://1.1.1.1", "http://8.8.8.8", http.StatusBadRequest},
{"malformed, invalid hex digits", "https://auth.google.com", "%zzzzz", http.StatusBadRequest},
} }
for _, tt := range tests { for _, tt := range tests {
@ -141,7 +150,8 @@ func TestValidateRedirectURI(t *testing.T) {
w.Write([]byte("Hi")) w.Write([]byte("Hi"))
}) })
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
handler := ValidateRedirectURI(tt.proxyRootDomains)(testHandler) u, _ := url.Parse(tt.rootDomain)
handler := ValidateRedirectURI(u)(testHandler)
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
if rr.Code != tt.status { if rr.Code != tt.status {
t.Errorf("Status code differs. got %d want %d", rr.Code, tt.status) t.Errorf("Status code differs. got %d want %d", rr.Code, tt.status)

View file

@ -37,7 +37,6 @@ echo " replace configuration settings to meet your specific needs and identity p
helm install ./helm/ \ helm install ./helm/ \
--set service.type="NodePort" \ --set service.type="NodePort" \
--set config.rootDomain="corp.pomerium.io" \
--set ingress.secret.name="pomerium-tls" \ --set ingress.secret.name="pomerium-tls" \
--set ingress.secret.cert=$(base64 -i "$HOME/.acme.sh/*.corp.pomerium.io_ecc/*.corp.pomerium.io.cer") \ --set ingress.secret.cert=$(base64 -i "$HOME/.acme.sh/*.corp.pomerium.io_ecc/*.corp.pomerium.io.cer") \
--set ingress.secret.key=$(base64 -i "$HOME/.acme.sh/*.corp.pomerium.io_ecc/*.corp.pomerium.io.key") \ --set ingress.secret.key=$(base64 -i "$HOME/.acme.sh/*.corp.pomerium.io_ecc/*.corp.pomerium.io.key") \