mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-10 15:47:36 +02:00
authenticate: programmatic access support
- authenticate: added a token exchange api endpoint that converts an identity provider's JWT into a pomerium session. - internal/identity: authenticate now passes context. - internal/identity: removed extraneous GetSignInURL from okta. - internal/sessions: add rest store - update go.mod / go.sum depedencies. - docs: add programmatic examples in shell and python
This commit is contained in:
parent
2025c54899
commit
cf0f98536a
22 changed files with 910 additions and 256 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
||||||
# Pomerium Changelog
|
# Pomerium Changelog
|
||||||
|
|
||||||
|
## vUNRELEASED
|
||||||
|
|
||||||
|
### NEW
|
||||||
|
|
||||||
|
- Add programmatic authentication support. [GH-177]
|
||||||
|
|
||||||
|
### CHANGED
|
||||||
|
|
||||||
|
### FIXED
|
||||||
|
|
||||||
## v0.0.5
|
## v0.0.5
|
||||||
|
|
||||||
### NEW
|
### NEW
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/config"
|
"github.com/pomerium/pomerium/internal/config"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
"github.com/pomerium/pomerium/internal/identity"
|
"github.com/pomerium/pomerium/internal/identity"
|
||||||
"github.com/pomerium/pomerium/internal/sessions"
|
"github.com/pomerium/pomerium/internal/sessions"
|
||||||
|
@ -49,6 +48,7 @@ type Authenticate struct {
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
csrfStore sessions.CSRFStore
|
csrfStore sessions.CSRFStore
|
||||||
sessionStore sessions.SessionStore
|
sessionStore sessions.SessionStore
|
||||||
|
restStore sessions.SessionStore
|
||||||
cipher cryptutil.Cipher
|
cipher cryptutil.Cipher
|
||||||
provider identity.Authenticator
|
provider identity.Authenticator
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,6 @@ func New(opts config.Options) (*Authenticate, error) {
|
||||||
CookieExpire: opts.CookieExpire,
|
CookieExpire: opts.CookieExpire,
|
||||||
CookieCipher: cipher,
|
CookieCipher: cipher,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -91,13 +90,17 @@ func New(opts config.Options) (*Authenticate, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
restStore, err := sessions.NewRestStore(&sessions.RestStoreOptions{Cipher: cipher})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &Authenticate{
|
return &Authenticate{
|
||||||
SharedKey: opts.SharedKey,
|
SharedKey: opts.SharedKey,
|
||||||
RedirectURL: &redirectURL,
|
RedirectURL: &redirectURL,
|
||||||
templates: templates.New(),
|
templates: templates.New(),
|
||||||
csrfStore: cookieStore,
|
csrfStore: cookieStore,
|
||||||
sessionStore: cookieStore,
|
sessionStore: cookieStore,
|
||||||
|
restStore: restStore,
|
||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
provider: provider,
|
provider: provider,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -39,6 +39,8 @@ func (a *Authenticate) Handler() http.Handler {
|
||||||
// authenticate-server endpoints
|
// authenticate-server endpoints
|
||||||
mux.Handle("/sign_in", validate.ThenFunc(a.SignIn))
|
mux.Handle("/sign_in", validate.ThenFunc(a.SignIn))
|
||||||
mux.Handle("/sign_out", validate.ThenFunc(a.SignOut)) // POST
|
mux.Handle("/sign_out", validate.ThenFunc(a.SignOut)) // POST
|
||||||
|
// programmatic authentication endpoints
|
||||||
|
mux.Handle("/api/v1/token", c.ThenFunc(a.ExchangeToken))
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,12 +76,12 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
||||||
log.FromRequest(r).Debug().Err(err).Msg("proxy: invalid session")
|
log.FromRequest(r).Debug().Err(err).Msg("authenticate: invalid session")
|
||||||
a.sessionStore.ClearSession(w, r)
|
a.sessionStore.ClearSession(w, r)
|
||||||
a.OAuthStart(w, r)
|
a.OAuthStart(w, r)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
log.FromRequest(r).Error().Err(err).Msg("proxy: unexpected error")
|
log.FromRequest(r).Error().Err(err).Msg("authenticate: unexpected error")
|
||||||
httpErr := &httputil.Error{Message: "An unexpected error occurred", Code: http.StatusInternalServerError}
|
httpErr := &httputil.Error{Message: "An unexpected error occurred", Code: http.StatusInternalServerError}
|
||||||
httputil.ErrorResponse(w, r, httpErr)
|
httputil.ErrorResponse(w, r, httpErr)
|
||||||
return
|
return
|
||||||
|
@ -137,7 +139,7 @@ func getAuthCodeRedirectURL(redirectURL *url.URL, state, authCode string) string
|
||||||
func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
|
func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
log.Error().Err(err).Msg("authenticate: error SignOut form")
|
log.Error().Err(err).Msg("authenticate: error SignOut form")
|
||||||
httpErr := &httputil.Error{Code: http.StatusInternalServerError}
|
httpErr := &httputil.Error{Code: http.StatusBadRequest}
|
||||||
httputil.ErrorResponse(w, r, httpErr)
|
httputil.ErrorResponse(w, r, httpErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -237,11 +239,10 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
|
||||||
if code == "" {
|
if code == "" {
|
||||||
log.FromRequest(r).Error().Msg("authenticate: provider missing code")
|
log.FromRequest(r).Error().Msg("authenticate: provider missing code")
|
||||||
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Missing Code"}
|
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Missing Code"}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the returned code with the identity provider
|
// validate the returned code with the identity provider
|
||||||
session, err := a.provider.Authenticate(code)
|
session, err := a.provider.Authenticate(r.Context(), code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("authenticate: error redeeming authenticate code")
|
log.FromRequest(r).Error().Err(err).Msg("authenticate: error redeeming authenticate code")
|
||||||
return "", httputil.Error{Code: http.StatusInternalServerError, Message: err.Error()}
|
return "", httputil.Error{Code: http.StatusInternalServerError, Message: err.Error()}
|
||||||
|
@ -275,11 +276,39 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
|
||||||
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Invalid Redirect URI domain"}
|
return "", httputil.Error{Code: http.StatusBadRequest, Message: "Invalid Redirect URI domain"}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.sessionStore.SaveSession(w, r, session)
|
if err := a.sessionStore.SaveSession(w, r, session); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("authenticate: failed saving new session")
|
log.Error().Err(err).Msg("authenticate: failed saving new session")
|
||||||
return "", httputil.Error{Code: http.StatusInternalServerError, Message: "Internal Error"}
|
return "", httputil.Error{Code: http.StatusInternalServerError, Message: "Internal Error"}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect, nil
|
return redirect, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExchangeToken takes an identity provider issued JWT as input ('id_token)
|
||||||
|
// and exchanges that token for a pomerium session. The provided token's
|
||||||
|
// audience ('aud') attribute must match Pomerium's client_id.
|
||||||
|
func (a *Authenticate) ExchangeToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
code := r.Form.Get("id_token")
|
||||||
|
if code == "" {
|
||||||
|
log.FromRequest(r).Error().Msg("authenticate: provider missing id token")
|
||||||
|
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusBadRequest, Message: "missing id token"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session, err := a.provider.IDTokenToSession(r.Context(), code)
|
||||||
|
if err != nil {
|
||||||
|
log.FromRequest(r).Error().Err(err).Msg("authenticate: error exchanging identity provider code")
|
||||||
|
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusInternalServerError, Message: "could not exchange identity for session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info().Interface("session", session).Msg("Session")
|
||||||
|
if err := a.restStore.SaveSession(w, r, session); err != nil {
|
||||||
|
log.Error().Err(err).Msg("authenticate: failed returning new session")
|
||||||
|
httputil.ErrorResponse(w, r, &httputil.Error{Code: http.StatusInternalServerError, Message: "authenticate: failed returning new session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -272,40 +272,10 @@ func TestAuthenticate_SignOut(t *testing.T) {
|
||||||
wantCode int
|
wantCode int
|
||||||
wantBody string
|
wantBody string
|
||||||
}{
|
}{
|
||||||
{"good post",
|
{"good post", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{}, &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusFound, ""},
|
||||||
http.MethodPost,
|
{"failed revoke", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{RevokeError: errors.New("OH NO")}, &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusBadRequest, "could not revoke"},
|
||||||
"https://corp.pomerium.io/",
|
{"malformed form", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{}, &sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusBadRequest, ""},
|
||||||
"sig",
|
{"load session error", http.MethodPost, "https://corp.pomerium.io/", "sig", "ts", identity.MockProvider{}, &sessions.MockSessionStore{LoadError: errors.New("hi"), Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", Email: "blah@blah.com", RefreshDeadline: time.Now().Add(10 * time.Second)}}, http.StatusFound, ""},
|
||||||
"ts",
|
|
||||||
identity.MockProvider{},
|
|
||||||
&sessions.MockSessionStore{
|
|
||||||
Session: &sessions.SessionState{
|
|
||||||
AccessToken: "AccessToken",
|
|
||||||
RefreshToken: "RefreshToken",
|
|
||||||
Email: "blah@blah.com",
|
|
||||||
|
|
||||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
http.StatusFound,
|
|
||||||
""},
|
|
||||||
{"failed revoke",
|
|
||||||
http.MethodPost,
|
|
||||||
"https://corp.pomerium.io/",
|
|
||||||
"sig",
|
|
||||||
"ts",
|
|
||||||
identity.MockProvider{RevokeError: errors.New("OH NO")},
|
|
||||||
&sessions.MockSessionStore{
|
|
||||||
Session: &sessions.SessionState{
|
|
||||||
AccessToken: "AccessToken",
|
|
||||||
RefreshToken: "RefreshToken",
|
|
||||||
Email: "blah@blah.com",
|
|
||||||
|
|
||||||
RefreshDeadline: time.Now().Add(10 * time.Second),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"could not revoke"},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -321,7 +291,9 @@ func TestAuthenticate_SignOut(t *testing.T) {
|
||||||
params.Add("ts", tt.ts)
|
params.Add("ts", tt.ts)
|
||||||
params.Add("redirect_uri", tt.redirectURL)
|
params.Add("redirect_uri", tt.redirectURL)
|
||||||
u.RawQuery = params.Encode()
|
u.RawQuery = params.Encode()
|
||||||
|
if tt.name == "malformed form" {
|
||||||
|
u.RawQuery = "example=%zzzzz"
|
||||||
|
}
|
||||||
r := httptest.NewRequest(tt.method, u.String(), nil)
|
r := httptest.NewRequest(tt.method, u.String(), nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
@ -678,3 +650,50 @@ func TestAuthenticate_getOAuthCallback(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthenticate_ExchangeToken(t *testing.T) {
|
||||||
|
cipher := &cryptutil.MockCipher{}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
idToken string
|
||||||
|
restStore sessions.SessionStore
|
||||||
|
cipher cryptutil.Cipher
|
||||||
|
provider identity.MockProvider
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"good", http.MethodPost, "token", &sessions.RestStore{Cipher: cipher}, cipher, identity.MockProvider{IDTokenToSessionResponse: sessions.SessionState{IDToken: "ok"}}, ""},
|
||||||
|
{"could not exchange identity for session", http.MethodPost, "token", &sessions.RestStore{Cipher: cipher}, cipher, identity.MockProvider{IDTokenToSessionError: errors.New("error")}, "could not exchange identity for session"},
|
||||||
|
{"missing token", http.MethodPost, "", &sessions.RestStore{Cipher: cipher}, cipher, identity.MockProvider{IDTokenToSessionResponse: sessions.SessionState{IDToken: "ok"}}, "missing id token"},
|
||||||
|
{"save error", http.MethodPost, "token", &sessions.MockSessionStore{SaveError: errors.New("error")}, cipher, identity.MockProvider{IDTokenToSessionResponse: sessions.SessionState{IDToken: "ok"}}, "failed returning new session"},
|
||||||
|
{"malformed form", http.MethodPost, "token", &sessions.RestStore{Cipher: cipher}, cipher, identity.MockProvider{IDTokenToSessionResponse: sessions.SessionState{IDToken: "ok"}}, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
a := &Authenticate{
|
||||||
|
restStore: tt.restStore,
|
||||||
|
cipher: tt.cipher,
|
||||||
|
provider: tt.provider,
|
||||||
|
}
|
||||||
|
form := url.Values{}
|
||||||
|
if tt.idToken != "" {
|
||||||
|
form.Add("id_token", tt.idToken)
|
||||||
|
}
|
||||||
|
rawForm := form.Encode()
|
||||||
|
|
||||||
|
if tt.name == "malformed form" {
|
||||||
|
rawForm = "example=%zzzzz"
|
||||||
|
}
|
||||||
|
r := httptest.NewRequest(tt.method, "/", strings.NewReader(rawForm))
|
||||||
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
a.ExchangeToken(w, r)
|
||||||
|
got := w.Body.String()
|
||||||
|
if !strings.Contains(got, tt.want) {
|
||||||
|
t.Errorf("Authenticate.ExchangeToken() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ function docsSidebar(title) {
|
||||||
{
|
{
|
||||||
title,
|
title,
|
||||||
collapsable: false,
|
collapsable: false,
|
||||||
children: ["", "identity-providers", "signed-headers", "certificates", "examples", "impersonation", "upgrading"]
|
children: ["", "identity-providers", "signed-headers", "certificates", "examples", "impersonation", "programmatic-access", "upgrading"]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
69
docs/docs/programmatic-access.md
Normal file
69
docs/docs/programmatic-access.md
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
---
|
||||||
|
title: Programmatic access
|
||||||
|
description: >-
|
||||||
|
This article describes how to configure pomerium to be used to enable
|
||||||
|
machine-to-machine programmatic access.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Programmatic access
|
||||||
|
|
||||||
|
This page describes how to access Pomerium endpoints programmatically.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Every identity provider has slightly different methods for issuing OAuth 2.0 access tokens [suitable][proof key for code exchange] for machine-to-machine use, please review your identity provider's documentation. For example:
|
||||||
|
|
||||||
|
- [Google Oauth2 2.0 for Desktop Apps](https://developers.google.com/identity/protocols/OAuth2InstalledApp)
|
||||||
|
- [Okta PKCE Flow](https://developer.okta.com/docs/concepts/auth-overview/#authorization-code-flow)
|
||||||
|
- [Azure Active Directory using the OAuth 2.0 code grant flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code)
|
||||||
|
|
||||||
|
For the sake of illustration, this guide and example scripts will use Google as the underlying identity provider.
|
||||||
|
|
||||||
|
### Identity Provider Configuration
|
||||||
|
|
||||||
|
To configure programmatic access for Pomerium we'll need to set up **an additional** OAuth 2.0 client ID that can issue `id_tokens` whose [audience](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation) matches the Client ID of Pomerium. Follow these instructions adapted from [Google's documentation](https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_desktop_app):
|
||||||
|
|
||||||
|
1. Go to the [Credentials page](https://console.cloud.google.com/apis/credentials).
|
||||||
|
2. Select the project with the Pomerium secured resource.
|
||||||
|
3. Click **Create credentials**, then select **OAuth Client ID**.
|
||||||
|
4. Under **Application type**, select **Other**, add a **Name**, then click **Create**.
|
||||||
|
5. On the OAuth client window that appears, note the **client ID** and **client secret**.
|
||||||
|
6. On the **Credentials** window, your new **Other** credentials appear along with the primary client ID that's used to access your application.
|
||||||
|
|
||||||
|
### High level flow
|
||||||
|
|
||||||
|
The application interacting with Pomerium will roughly have to manage the following access flow.
|
||||||
|
|
||||||
|
1. A user authenticates with the OpenID Connect identity provider. This typically requires handling the [Proof Key for Code Exchange] process.
|
||||||
|
2. Exchange the code from the [Proof Key for Code Exchange] for a valid `refresh_token`.
|
||||||
|
3. Using the `refresh_token` from the last step, request the identity provider issue a new `id_token` which has our Pomerium app's `client_id` as the `audience`.
|
||||||
|
4. Exchange the identity provider issued `id_token` for a `pomerium` token (e.g. `https://authenticate.{your-domain}/api/v1/token`).
|
||||||
|
5. Use the pomerium issued `Token` [authorization bearer token] for all requests to Pomerium protected endpoints until it's `Expiry`. Authorization policy will be tied to the user as normal.
|
||||||
|
|
||||||
|
### Expiration and revocation
|
||||||
|
|
||||||
|
Your application should handle token expiration. If the session expires before work is done, the identity provider issued `refresh_token` can be used to create a new valid session by repeating steps 3 and on.
|
||||||
|
|
||||||
|
Also, you should write your code to anticipate the possibility that a granted `refresh_token` may stop working. For example, a refresh token might stop working if the underlying user changes passwords, revokes access, or if the administrator removes rotates or deletes the OAuth Client ID.
|
||||||
|
|
||||||
|
## Example Code
|
||||||
|
|
||||||
|
It's not as bad as it sounds. Please see the following minimal but complete examples.
|
||||||
|
|
||||||
|
### Python
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/programmatic_access.py --client-secret REPLACE_ME \
|
||||||
|
--client-id 851877082059-85tfqg9hlm8j9km5d9uripd0dvk72mvk.apps.googleusercontent.com \
|
||||||
|
--pomerium-client-id 851877082059-bfgkpj09noog7as3gpc3t7r6n9sjbgs6.apps.googleusercontent.com
|
||||||
|
```
|
||||||
|
|
||||||
|
<<< @/scripts/programmatic_access.py
|
||||||
|
|
||||||
|
### Bash
|
||||||
|
|
||||||
|
<<< @/scripts/programmatic_access.sh
|
||||||
|
|
||||||
|
[authorization bearer token]: https://developers.google.com/gmail/markup/actions/verifying-bearer-tokens
|
||||||
|
[identity provider]: ../docs/identity-providers.md
|
||||||
|
[proof key for code exchange]: https://tools.ietf.org/html/rfc7636
|
24
go.mod
24
go.mod
|
@ -3,24 +3,30 @@ module github.com/pomerium/pomerium
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go v0.40.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/golang/mock v1.2.0
|
github.com/golang/mock v1.3.1
|
||||||
github.com/golang/protobuf v1.3.1
|
github.com/golang/protobuf v1.3.1
|
||||||
github.com/google/go-cmp v0.3.0
|
github.com/google/go-cmp v0.3.0
|
||||||
github.com/magiconair/properties v1.8.1 // indirect
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
github.com/mitchellh/hashstructure v1.0.0
|
github.com/mitchellh/hashstructure v1.0.0
|
||||||
|
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||||
github.com/pomerium/go-oidc v2.0.0+incompatible
|
github.com/pomerium/go-oidc v2.0.0+incompatible
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
github.com/rs/zerolog v1.14.3
|
github.com/rs/zerolog v1.14.3
|
||||||
github.com/spf13/viper v1.3.2
|
github.com/spf13/afero v1.2.2 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/viper v1.4.0
|
||||||
github.com/stretchr/testify v1.3.0 // indirect
|
github.com/stretchr/testify v1.3.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
|
go.opencensus.io v0.22.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65
|
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
|
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a
|
||||||
golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4 // indirect
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||||
golang.org/x/text v0.3.2 // indirect
|
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae // indirect
|
||||||
google.golang.org/api v0.1.0
|
google.golang.org/api v0.6.0
|
||||||
google.golang.org/grpc v1.19.1
|
google.golang.org/appengine v1.6.1 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20190611190212-a7e196e89fd3 // indirect
|
||||||
|
google.golang.org/grpc v1.21.1
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1
|
gopkg.in/square/go-jose.v2 v2.3.1
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
)
|
)
|
||||||
|
|
180
go.sum
180
go.sum
|
@ -1,39 +1,79 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.40.0 h1:FjSY7bOj+WzJe6TZRVtXI2b9kAYvtNg4lMbcH2+MUkk=
|
||||||
|
cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
|
@ -43,9 +83,13 @@ github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9
|
||||||
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
||||||
|
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
@ -53,96 +97,156 @@ github.com/pomerium/go-oidc v2.0.0+incompatible h1:gVvG/ExWsHQqatV+uceROnGmbVYF4
|
||||||
github.com/pomerium/go-oidc v2.0.0+incompatible/go.mod h1:DRsGVw6MOgxbfq4Y57jKOE8lbEfayxeiY0A8/4vxjBM=
|
github.com/pomerium/go-oidc v2.0.0+incompatible/go.mod h1:DRsGVw6MOgxbfq4Y57jKOE8lbEfayxeiY0A8/4vxjBM=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0=
|
github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0=
|
||||||
github.com/rs/zerolog v1.14.3/go.mod h1:3WXPzbXEEliJ+a6UFE4vhIxV8qR1EML6ngzP9ug4eYg=
|
github.com/rs/zerolog v1.14.3/go.mod h1:3WXPzbXEEliJ+a6UFE4vhIxV8qR1EML6ngzP9ug4eYg=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||||
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
|
||||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
|
||||||
|
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY=
|
||||||
|
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4 h1:VSJ45BzqrVgR4clSx415y1rHH7QAGhGt71J0ZmhLYrc=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
|
||||||
|
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.6.0 h1:2tJEkRfnZL5g1GeBUlITh/rqT5HG3sFcoVCUUxmgJ2g=
|
||||||
|
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898 h1:yvw+zsSmSM02Z5H3ZdEV7B7Ql7eFrjQTnmByJvK+3J8=
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/genproto v0.0.0-20190611190212-a7e196e89fd3 h1:0LGHEA/u5XLibPOx6D7D8FBT/ax6wT57vNKY0QckCwo=
|
||||||
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
|
google.golang.org/genproto v0.0.0-20190611190212-a7e196e89fd3/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
|
|
@ -33,7 +33,7 @@ type GoogleProvider struct {
|
||||||
apiClient *admin.Service
|
apiClient *admin.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGoogleProvider returns a new GoogleProvider and sets the provider url endpoints.
|
// NewGoogleProvider instantiates an OpenID Connect (OIDC) session with Google.
|
||||||
func NewGoogleProvider(p *Provider) (*GoogleProvider, error) {
|
func NewGoogleProvider(p *Provider) (*GoogleProvider, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if p.ProviderURL == "" {
|
if p.ProviderURL == "" {
|
||||||
|
@ -123,33 +123,68 @@ func (p *GoogleProvider) Revoke(accessToken string) error {
|
||||||
// cookies, re-authorization will not bring back refresh_token. A work around to this is to add
|
// cookies, re-authorization will not bring back refresh_token. A work around to this is to add
|
||||||
// prompt=consent to the OAuth redirect URL and will always return a refresh_token.
|
// prompt=consent to the OAuth redirect URL and will always return a refresh_token.
|
||||||
// https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
// https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||||
// https://developers.google.com/identity/protocols/OAuth2WebServer#offline
|
|
||||||
// https://stackoverflow.com/a/10857806/10592439
|
|
||||||
func (p *GoogleProvider) GetSignInURL(state string) string {
|
func (p *GoogleProvider) GetSignInURL(state string) string {
|
||||||
return p.oauth.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"))
|
return p.oauth.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.ApprovalForce)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate creates an identity session with google from a authorization code, and follows up
|
// Authenticate creates an identity session with google from a authorization code, and follows up
|
||||||
// call to the admin/group api to check what groups the user is in.
|
// call to the admin/group api to check what groups the user is in.
|
||||||
func (p *GoogleProvider) Authenticate(code string) (*sessions.SessionState, error) {
|
func (p *GoogleProvider) Authenticate(ctx context.Context, code string) (*sessions.SessionState, error) {
|
||||||
ctx := context.Background()
|
|
||||||
// convert authorization code into a token
|
// convert authorization code into a token
|
||||||
oauth2Token, err := p.oauth.Exchange(ctx, code)
|
oauth2Token, err := p.oauth.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("identity/google: token exchange failed %v", err)
|
return nil, fmt.Errorf("identity/google: token exchange failed %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// id_token contains claims about the authenticated user
|
// id_token is a JWT that contains identity information about the user
|
||||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("identity/google: response did not contain an id_token")
|
return nil, fmt.Errorf("identity/google: response did not contain an id_token")
|
||||||
}
|
}
|
||||||
// Parse and verify ID Token payload.
|
session, err := p.IDTokenToSession(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
session.AccessToken = oauth2Token.AccessToken
|
||||||
|
session.RefreshToken = oauth2Token.RefreshToken
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh renews a user's session using an oidc refresh token withoutreprompting the user.
|
||||||
|
// Group membership is also refreshed.
|
||||||
|
// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
|
||||||
|
func (p *GoogleProvider) Refresh(ctx context.Context, s *sessions.SessionState) (*sessions.SessionState, error) {
|
||||||
|
if s.RefreshToken == "" {
|
||||||
|
return nil, errors.New("identity: missing refresh token")
|
||||||
|
}
|
||||||
|
t := oauth2.Token{RefreshToken: s.RefreshToken}
|
||||||
|
newToken, err := p.oauth.TokenSource(ctx, &t).Token()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("identity: refresh failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// id_token contains claims about the authenticated user
|
||||||
|
rawIDToken, ok := newToken.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("identity/google: response did not contain an id_token")
|
||||||
|
}
|
||||||
|
newSession, err := p.IDTokenToSession(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newSession.AccessToken = newToken.AccessToken
|
||||||
|
newSession.RefreshToken = s.RefreshToken
|
||||||
|
return newSession, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDTokenToSession takes an identity provider issued JWT as input ('id_token')
|
||||||
|
// and returns a session state. The provided token's audience ('aud') must
|
||||||
|
// match Pomerium's client_id.
|
||||||
|
func (p *GoogleProvider) IDTokenToSession(ctx context.Context, rawIDToken string) (*sessions.SessionState, error) {
|
||||||
idToken, err := p.verifier.Verify(ctx, rawIDToken)
|
idToken, err := p.verifier.Verify(ctx, rawIDToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("identity/google: could not verify id_token %v", err)
|
return nil, fmt.Errorf("identity/google: could not verify id_token %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var claims struct {
|
var claims struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
@ -167,39 +202,13 @@ func (p *GoogleProvider) Authenticate(code string) (*sessions.SessionState, erro
|
||||||
|
|
||||||
return &sessions.SessionState{
|
return &sessions.SessionState{
|
||||||
IDToken: rawIDToken,
|
IDToken: rawIDToken,
|
||||||
AccessToken: oauth2Token.AccessToken,
|
RefreshDeadline: idToken.Expiry.Truncate(time.Second),
|
||||||
RefreshToken: oauth2Token.RefreshToken,
|
|
||||||
RefreshDeadline: oauth2Token.Expiry.Truncate(time.Second),
|
|
||||||
Email: claims.Email,
|
Email: claims.Email,
|
||||||
User: idToken.Subject,
|
User: idToken.Subject,
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh renews a user's session using an oid refresh token withoutreprompting the user.
|
|
||||||
// Group membership is also refreshed.
|
|
||||||
// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
|
|
||||||
func (p *GoogleProvider) Refresh(ctx context.Context, s *sessions.SessionState) (*sessions.SessionState, error) {
|
|
||||||
if s.RefreshToken == "" {
|
|
||||||
return nil, errors.New("identity: missing refresh token")
|
|
||||||
}
|
|
||||||
t := oauth2.Token{RefreshToken: s.RefreshToken}
|
|
||||||
newToken, err := p.oauth.TokenSource(ctx, &t).Token()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("identity: refresh failed")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.AccessToken = newToken.AccessToken
|
|
||||||
s.RefreshDeadline = newToken.Expiry.Truncate(time.Second)
|
|
||||||
// validate groups
|
|
||||||
groups, err := p.UserGroups(ctx, s.User)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("identity/google: could not retrieve groups %v", err)
|
|
||||||
}
|
|
||||||
s.Groups = groups
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserGroups returns a slice of group names a given user is in
|
// UserGroups returns a slice of group names a given user is in
|
||||||
// NOTE: groups via Directory API is limited to 1 QPS!
|
// NOTE: groups via Directory API is limited to 1 QPS!
|
||||||
// https://developers.google.com/admin-sdk/directory/v1/reference/groups/list
|
// https://developers.google.com/admin-sdk/directory/v1/reference/groups/list
|
||||||
|
|
|
@ -74,8 +74,7 @@ func NewAzureProvider(p *Provider) (*AzureProvider, error) {
|
||||||
|
|
||||||
// Authenticate creates an identity session with azure from a authorization code, and follows up
|
// Authenticate creates an identity session with azure from a authorization code, and follows up
|
||||||
// call to the groups api to check what groups the user is in.
|
// call to the groups api to check what groups the user is in.
|
||||||
func (p *AzureProvider) Authenticate(code string) (*sessions.SessionState, error) {
|
func (p *AzureProvider) Authenticate(ctx context.Context, code string) (*sessions.SessionState, error) {
|
||||||
ctx := context.Background()
|
|
||||||
// convert authorization code into a token
|
// convert authorization code into a token
|
||||||
oauth2Token, err := p.oauth.Exchange(ctx, code)
|
oauth2Token, err := p.oauth.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -88,11 +87,23 @@ func (p *AzureProvider) Authenticate(code string) (*sessions.SessionState, error
|
||||||
return nil, fmt.Errorf("identity/microsoft: response did not contain an id_token")
|
return nil, fmt.Errorf("identity/microsoft: response did not contain an id_token")
|
||||||
}
|
}
|
||||||
// Parse and verify ID Token payload.
|
// Parse and verify ID Token payload.
|
||||||
|
session, err := p.IDTokenToSession(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("identity/microsoft: could not verify id_token %v", err)
|
||||||
|
}
|
||||||
|
session.AccessToken = oauth2Token.AccessToken
|
||||||
|
session.RefreshToken = oauth2Token.RefreshToken
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDTokenToSession takes an identity provider issued JWT as input ('id_token')
|
||||||
|
// and returns a session state. The provided token's audience ('aud') must
|
||||||
|
// match Pomerium's client_id.
|
||||||
|
func (p *AzureProvider) IDTokenToSession(ctx context.Context, rawIDToken string) (*sessions.SessionState, error) {
|
||||||
idToken, err := p.verifier.Verify(ctx, rawIDToken)
|
idToken, err := p.verifier.Verify(ctx, rawIDToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("identity/microsoft: could not verify id_token %v", err)
|
return nil, fmt.Errorf("identity/microsoft: could not verify id_token %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var claims struct {
|
var claims struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
EmailVerified bool `json:"email_verified"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
@ -101,8 +112,6 @@ func (p *AzureProvider) Authenticate(code string) (*sessions.SessionState, error
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
return nil, fmt.Errorf("identity/microsoft: failed to parse id_token claims %v", err)
|
return nil, fmt.Errorf("identity/microsoft: failed to parse id_token claims %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// google requires additional call to retrieve groups.
|
|
||||||
groups, err := p.UserGroups(ctx, claims.Email)
|
groups, err := p.UserGroups(ctx, claims.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("identity/microsoft: could not retrieve groups %v", err)
|
return nil, fmt.Errorf("identity/microsoft: could not retrieve groups %v", err)
|
||||||
|
@ -110,9 +119,7 @@ func (p *AzureProvider) Authenticate(code string) (*sessions.SessionState, error
|
||||||
|
|
||||||
return &sessions.SessionState{
|
return &sessions.SessionState{
|
||||||
IDToken: rawIDToken,
|
IDToken: rawIDToken,
|
||||||
AccessToken: oauth2Token.AccessToken,
|
RefreshDeadline: idToken.Expiry.Truncate(time.Second),
|
||||||
RefreshToken: oauth2Token.RefreshToken,
|
|
||||||
RefreshDeadline: oauth2Token.Expiry.Truncate(time.Second),
|
|
||||||
Email: claims.Email,
|
Email: claims.Email,
|
||||||
User: idToken.Subject,
|
User: idToken.Subject,
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
type MockProvider struct {
|
type MockProvider struct {
|
||||||
AuthenticateResponse sessions.SessionState
|
AuthenticateResponse sessions.SessionState
|
||||||
AuthenticateError error
|
AuthenticateError error
|
||||||
|
IDTokenToSessionResponse sessions.SessionState
|
||||||
|
IDTokenToSessionError error
|
||||||
ValidateResponse bool
|
ValidateResponse bool
|
||||||
ValidateError error
|
ValidateError error
|
||||||
RefreshResponse *sessions.SessionState
|
RefreshResponse *sessions.SessionState
|
||||||
|
@ -19,10 +21,15 @@ type MockProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate is a mocked providers function.
|
// Authenticate is a mocked providers function.
|
||||||
func (mp MockProvider) Authenticate(code string) (*sessions.SessionState, error) {
|
func (mp MockProvider) Authenticate(ctx context.Context, code string) (*sessions.SessionState, error) {
|
||||||
return &mp.AuthenticateResponse, mp.AuthenticateError
|
return &mp.AuthenticateResponse, mp.AuthenticateError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDTokenToSession is a mocked providers function.
|
||||||
|
func (mp MockProvider) IDTokenToSession(ctx context.Context, code string) (*sessions.SessionState, error) {
|
||||||
|
return &mp.IDTokenToSessionResponse, mp.IDTokenToSessionError
|
||||||
|
}
|
||||||
|
|
||||||
// Validate is a mocked providers function.
|
// Validate is a mocked providers function.
|
||||||
func (mp MockProvider) Validate(ctx context.Context, s string) (bool, error) {
|
func (mp MockProvider) Validate(ctx context.Context, s string) (bool, error) {
|
||||||
return mp.ValidateResponse, mp.ValidateError
|
return mp.ValidateResponse, mp.ValidateError
|
||||||
|
|
|
@ -14,7 +14,7 @@ type OIDCProvider struct {
|
||||||
*Provider
|
*Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOIDCProvider creates a new instance of an OpenID Connect provider.
|
// NewOIDCProvider creates a new instance of a generic OpenID Connect provider.
|
||||||
func NewOIDCProvider(p *Provider) (*OIDCProvider, error) {
|
func NewOIDCProvider(p *Provider) (*OIDCProvider, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if p.ProviderURL == "" {
|
if p.ProviderURL == "" {
|
||||||
|
|
|
@ -83,12 +83,6 @@ func (p *OktaProvider) Revoke(token string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSignInURL returns the sign in url with typical oauth parameters
|
|
||||||
// Google requires access type offline
|
|
||||||
func (p *OktaProvider) GetSignInURL(state string) string {
|
|
||||||
return p.oauth.AuthCodeURL(state, oauth2.AccessTypeOffline)
|
|
||||||
}
|
|
||||||
|
|
||||||
type accessToken struct {
|
type accessToken struct {
|
||||||
Subject string `json:"sub"`
|
Subject string `json:"sub"`
|
||||||
Groups []string `json:"groups"`
|
Groups []string `json:"groups"`
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Package identity provides support for making OpenID Connect and OAuth2 authorized and
|
// Package identity provides support for making OpenID Connect (OIDC)
|
||||||
// authenticated HTTP requests with third party identity providers.
|
// and OAuth2 authenticated HTTP requests with third party identity providers.
|
||||||
package identity // import "github.com/pomerium/pomerium/internal/identity"
|
package identity // import "github.com/pomerium/pomerium/internal/identity"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -44,14 +44,15 @@ type UserGrouper interface {
|
||||||
|
|
||||||
// Authenticator is an interface representing the ability to authenticate with an identity provider.
|
// Authenticator is an interface representing the ability to authenticate with an identity provider.
|
||||||
type Authenticator interface {
|
type Authenticator interface {
|
||||||
Authenticate(string) (*sessions.SessionState, error)
|
Authenticate(context.Context, string) (*sessions.SessionState, error)
|
||||||
|
IDTokenToSession(context.Context, string) (*sessions.SessionState, error)
|
||||||
Validate(context.Context, string) (bool, error)
|
Validate(context.Context, string) (bool, error)
|
||||||
Refresh(context.Context, *sessions.SessionState) (*sessions.SessionState, error)
|
Refresh(context.Context, *sessions.SessionState) (*sessions.SessionState, error)
|
||||||
Revoke(string) error
|
Revoke(string) error
|
||||||
GetSignInURL(state string) string
|
GetSignInURL(state string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new identity provider based given its name.
|
// New returns a new identity provider based on its name.
|
||||||
// Returns an error if selected provided not found or if the identity provider is not known.
|
// Returns an error if selected provided not found or if the identity provider is not known.
|
||||||
func New(providerName string, p *Provider) (a Authenticator, err error) {
|
func New(providerName string, p *Provider) (a Authenticator, err error) {
|
||||||
switch providerName {
|
switch providerName {
|
||||||
|
@ -124,10 +125,37 @@ func (p *Provider) Validate(ctx context.Context, idToken string) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDTokenToSession takes an identity provider issued JWT as input ('id_token')
|
||||||
|
// and returns a session state. The provided token's audience ('aud') must
|
||||||
|
// match Pomerium's client_id.
|
||||||
|
func (p *Provider) IDTokenToSession(ctx context.Context, rawIDToken string) (*sessions.SessionState, error) {
|
||||||
|
idToken, err := p.verifier.Verify(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("identity: could not verify id_token: %v", err)
|
||||||
|
}
|
||||||
|
// extract additional, non-oidc standard claims
|
||||||
|
var claims struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
}
|
||||||
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
|
return nil, fmt.Errorf("identity: failed to parse id_token claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sessions.SessionState{
|
||||||
|
IDToken: rawIDToken,
|
||||||
|
User: idToken.Subject,
|
||||||
|
RefreshDeadline: idToken.Expiry.Truncate(time.Second),
|
||||||
|
Email: claims.Email,
|
||||||
|
Groups: claims.Groups,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Authenticate creates a session with an identity provider from a authorization code
|
// Authenticate creates a session with an identity provider from a authorization code
|
||||||
func (p *Provider) Authenticate(code string) (*sessions.SessionState, error) {
|
func (p *Provider) Authenticate(ctx context.Context, code string) (*sessions.SessionState, error) {
|
||||||
ctx := context.Background()
|
// exchange authorization for a oidc token
|
||||||
// convert authorization code into a token
|
|
||||||
oauth2Token, err := p.oauth.Exchange(ctx, code)
|
oauth2Token, err := p.oauth.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("identity: failed token exchange: %v", err)
|
return nil, fmt.Errorf("identity: failed token exchange: %v", err)
|
||||||
|
@ -137,36 +165,18 @@ func (p *Provider) Authenticate(code string) (*sessions.SessionState, error) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("token response did not contain an id_token")
|
return nil, fmt.Errorf("token response did not contain an id_token")
|
||||||
}
|
}
|
||||||
// Parse and verify ID Token payload.
|
session, err := p.IDTokenToSession(ctx, rawIDToken)
|
||||||
idToken, err := p.verifier.Verify(ctx, rawIDToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("identity: could not verify id_token: %v", err)
|
return nil, fmt.Errorf("identity: could not verify id_token: %v", err)
|
||||||
}
|
}
|
||||||
|
session.AccessToken = oauth2Token.AccessToken
|
||||||
|
session.RefreshToken = oauth2Token.RefreshToken
|
||||||
|
|
||||||
// Extract id_token which contains claims about the authenticated user
|
return session, nil
|
||||||
var claims struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
EmailVerified bool `json:"email_verified"`
|
|
||||||
Groups []string `json:"groups"`
|
|
||||||
}
|
|
||||||
// parse claims from the raw, encoded jwt token
|
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
|
||||||
return nil, fmt.Errorf("identity: failed to parse id_token claims: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sessions.SessionState{
|
|
||||||
IDToken: rawIDToken,
|
|
||||||
AccessToken: oauth2Token.AccessToken,
|
|
||||||
RefreshToken: oauth2Token.RefreshToken,
|
|
||||||
RefreshDeadline: oauth2Token.Expiry.Truncate(time.Second),
|
|
||||||
Email: claims.Email,
|
|
||||||
User: idToken.Subject,
|
|
||||||
Groups: claims.Groups,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh renews a user's session using an oid refresh token without reprompting the user.
|
// Refresh renews a user's session using therefresh_token without reprompting
|
||||||
// Group membership is also refreshed.
|
// the user. If supported, group membership is also refreshed.
|
||||||
// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
|
// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
|
||||||
func (p *Provider) Refresh(ctx context.Context, s *sessions.SessionState) (*sessions.SessionState, error) {
|
func (p *Provider) Refresh(ctx context.Context, s *sessions.SessionState) (*sessions.SessionState, error) {
|
||||||
if s.RefreshToken == "" {
|
if s.RefreshToken == "" {
|
||||||
|
|
107
internal/sessions/rest_store.go
Normal file
107
internal/sessions/rest_store.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package sessions // import "github.com/pomerium/pomerium/internal/sessions"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultBearerTokenHeader is default header name for the authorization bearer
|
||||||
|
// token header as defined in rfc2617
|
||||||
|
// https://tools.ietf.org/html/rfc6750#section-2.1
|
||||||
|
const DefaultBearerTokenHeader = "Authorization"
|
||||||
|
|
||||||
|
// RestStore is a session store suitable for REST
|
||||||
|
type RestStore struct {
|
||||||
|
Name string
|
||||||
|
Cipher cryptutil.Cipher
|
||||||
|
// Expire time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestStoreOptions contains the options required to build a new RestStore.
|
||||||
|
type RestStoreOptions struct {
|
||||||
|
Name string
|
||||||
|
Cipher cryptutil.Cipher
|
||||||
|
// Expire time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRestStore creates a new RestStore from a set of RestStoreOptions.
|
||||||
|
func NewRestStore(opts *RestStoreOptions) (*RestStore, error) {
|
||||||
|
if opts.Name == "" {
|
||||||
|
opts.Name = DefaultBearerTokenHeader
|
||||||
|
}
|
||||||
|
if opts.Cipher == nil {
|
||||||
|
return nil, fmt.Errorf("internal/sessions: cipher cannot be nil")
|
||||||
|
}
|
||||||
|
return &RestStore{
|
||||||
|
Name: opts.Name,
|
||||||
|
// Expire: opts.Expire,
|
||||||
|
Cipher: opts.Cipher,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearSession functions differently because REST is stateless, we instead
|
||||||
|
// inform the client that this token is no longer valid.
|
||||||
|
// https://tools.ietf.org/html/rfc6750
|
||||||
|
func (s *RestStore) ClearSession(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
errMsg := `
|
||||||
|
{
|
||||||
|
"error": "invalid_token",
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"error_description": "The token has expired."
|
||||||
|
}`
|
||||||
|
w.Write([]byte(errMsg))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSession attempts to load a pomerium session from a Bearer Token set
|
||||||
|
// in the authorization header.
|
||||||
|
func (s *RestStore) LoadSession(r *http.Request) (*SessionState, error) {
|
||||||
|
authHeader := r.Header.Get(s.Name)
|
||||||
|
split := strings.Split(authHeader, "Bearer")
|
||||||
|
if authHeader == "" || len(split) != 2 {
|
||||||
|
return nil, errors.New("internal/sessions: no bearer token header found")
|
||||||
|
}
|
||||||
|
token := strings.TrimSpace(split[1])
|
||||||
|
session, err := UnmarshalSession(token, s.Cipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestStoreResponse is the JSON struct returned to the client.
|
||||||
|
type RestStoreResponse struct {
|
||||||
|
// Token is the encrypted pomerium session that can be used to
|
||||||
|
// programmatically authenticate with pomerium.
|
||||||
|
Token string
|
||||||
|
// In addition to the token, non-sensitive meta data is returned to help
|
||||||
|
// the client manage token renewals.
|
||||||
|
Expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveSession returns an encrypted pomerium session as a JSON object with
|
||||||
|
// associated, non sensitive meta-data like
|
||||||
|
func (s *RestStore) SaveSession(w http.ResponseWriter, r *http.Request, sessionState *SessionState) error {
|
||||||
|
encToken, err := MarshalSession(sessionState, s.Cipher)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jsonBytes, err := json.Marshal(
|
||||||
|
&RestStoreResponse{
|
||||||
|
Token: encToken,
|
||||||
|
Expiry: sessionState.RefreshDeadline,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("internal/sessions: couldn't marshal token struct: %v", err)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(jsonBytes)
|
||||||
|
return nil
|
||||||
|
}
|
135
internal/sessions/rest_store_test.go
Normal file
135
internal/sessions/rest_store_test.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package sessions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRestStore_SaveSession(t *testing.T) {
|
||||||
|
now := time.Date(2008, 1, 8, 17, 5, 05, 0, time.UTC)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
optionsName string
|
||||||
|
optionsCipher cryptutil.Cipher
|
||||||
|
sessionState *SessionState
|
||||||
|
wantErr bool
|
||||||
|
wantSaveResponse string
|
||||||
|
}{
|
||||||
|
{"good", "Authenticate", &cryptutil.MockCipher{MarshalResponse: "test"}, &SessionState{RefreshDeadline: now}, false, `{"Token":"test","Expiry":"2008-01-08T17:05:05Z"}`},
|
||||||
|
{"bad session marshal", "Authenticate", &cryptutil.MockCipher{MarshalError: errors.New("error")}, &SessionState{RefreshDeadline: now}, true, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s, err := NewRestStore(
|
||||||
|
&RestStoreOptions{
|
||||||
|
Name: tt.optionsName,
|
||||||
|
Cipher: tt.optionsCipher,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewRestStore err %v", err)
|
||||||
|
}
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
if err := s.SaveSession(w, r, tt.sessionState); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("RestStore.SaveSession() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
resp := w.Result()
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
if diff := cmp.Diff(string(body), tt.wantSaveResponse); diff != "" {
|
||||||
|
t.Errorf("RestStore.SaveSession() got / want diff \n%s\n", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRestStore(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
optionsName string
|
||||||
|
optionsCipher cryptutil.Cipher
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"good", "Authenticate", &cryptutil.MockCipher{}, false},
|
||||||
|
{"good default to authenticate", "", &cryptutil.MockCipher{}, false},
|
||||||
|
{"empty cipher", "Authenticate", nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := NewRestStore(
|
||||||
|
&RestStoreOptions{
|
||||||
|
Name: tt.optionsName,
|
||||||
|
Cipher: tt.optionsCipher,
|
||||||
|
})
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("NewRestStore() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestStore_ClearSession(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{"always returns reset!", http.StatusUnauthorized},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &RestStore{Name: "Authenticate", Cipher: &cryptutil.MockCipher{}}
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.ClearSession(w, r)
|
||||||
|
resp := w.Result()
|
||||||
|
if diff := cmp.Diff(resp.StatusCode, tt.expectedStatus); diff != "" {
|
||||||
|
t.Errorf("RestStore.ClearSession() got / want diff \n%s\n", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestStore_LoadSession(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
optionsName string
|
||||||
|
optionsCipher cryptutil.Cipher
|
||||||
|
token string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"good", "Authorization", &cryptutil.MockCipher{}, "test", false},
|
||||||
|
{"empty auth header", "", &cryptutil.MockCipher{}, "", true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &RestStore{
|
||||||
|
Name: tt.optionsName,
|
||||||
|
Cipher: tt.optionsCipher,
|
||||||
|
}
|
||||||
|
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
|
||||||
|
if tt.optionsName != "" {
|
||||||
|
r.Header.Set(tt.optionsName, fmt.Sprintf(("Bearer %s"), tt.token))
|
||||||
|
|
||||||
|
}
|
||||||
|
_, err := s.LoadSession(r)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("RestStore.LoadSession() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ type StateParameter struct {
|
||||||
RedirectURI string `json:"redirect_uri"`
|
RedirectURI string `json:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler returns a http handler for a Proxy
|
// Handler returns the proxy service's ServeMux
|
||||||
func (p *Proxy) Handler() http.Handler {
|
func (p *Proxy) Handler() http.Handler {
|
||||||
// validation middleware chain
|
// validation middleware chain
|
||||||
validate := middleware.NewChain()
|
validate := middleware.NewChain()
|
||||||
|
@ -62,13 +62,11 @@ func (p *Proxy) SignOutCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
// OAuthStart begins the authenticate flow, encrypting the redirect url
|
// OAuthStart begins the authenticate flow, encrypting the redirect url
|
||||||
// in a request to the provider's sign in endpoint.
|
// in a request to the provider's sign in endpoint.
|
||||||
func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||||
requestURI := r.URL.String()
|
|
||||||
callbackURL := p.GetRedirectURL(r.Host)
|
|
||||||
|
|
||||||
// CSRF value used to mitigate replay attacks.
|
// create a CSRF value used to mitigate replay attacks.
|
||||||
state := &StateParameter{
|
state := &StateParameter{
|
||||||
SessionID: fmt.Sprintf("%x", cryptutil.GenerateKey()),
|
SessionID: fmt.Sprintf("%x", cryptutil.GenerateKey()),
|
||||||
RedirectURI: requestURI,
|
RedirectURI: r.URL.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt, and save CSRF state. Will be checked on callback.
|
// Encrypt, and save CSRF state. Will be checked on callback.
|
||||||
|
@ -104,7 +102,7 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
signinURL := p.GetSignInURL(p.AuthenticateURL, callbackURL, remoteState)
|
signinURL := p.GetSignInURL(p.AuthenticateURL, p.GetRedirectURL(r.Host), remoteState)
|
||||||
log.FromRequest(r).Debug().Str("SigninURL", signinURL.String()).Msg("proxy: oauth start")
|
log.FromRequest(r).Debug().Str("SigninURL", signinURL.String()).Msg("proxy: oauth start")
|
||||||
|
|
||||||
// Redirect the user to the authenticate service along with the encrypted
|
// Redirect the user to the authenticate service along with the encrypted
|
||||||
|
@ -185,14 +183,14 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
// shouldSkipAuthentication contains conditions for skipping authentication.
|
// shouldSkipAuthentication contains conditions for skipping authentication.
|
||||||
// Conditions should be few in number and have strong justifications.
|
// Conditions should be few in number and have strong justifications.
|
||||||
func (p *Proxy) shouldSkipAuthentication(r *http.Request) bool {
|
func (p *Proxy) shouldSkipAuthentication(r *http.Request) bool {
|
||||||
pol, foundPolicy := p.policy(r)
|
policy, policyExists := p.policy(r)
|
||||||
|
|
||||||
if isCORSPreflight(r) && foundPolicy && pol.CORSAllowPreflight {
|
if isCORSPreflight(r) && policyExists && policy.CORSAllowPreflight {
|
||||||
log.FromRequest(r).Debug().Msg("proxy: skipping authentication for valid CORS preflight request")
|
log.FromRequest(r).Debug().Msg("proxy: skipping authentication for valid CORS preflight request")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundPolicy && pol.AllowPublicUnauthenticatedAccess {
|
if policyExists && policy.AllowPublicUnauthenticatedAccess {
|
||||||
log.FromRequest(r).Debug().Msg("proxy: skipping authentication for public route")
|
log.FromRequest(r).Debug().Msg("proxy: skipping authentication for public route")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -214,7 +212,13 @@ func isCORSPreflight(r *http.Request) bool {
|
||||||
// or starting the authenticate service for validation if not.
|
// or starting the authenticate service for validation if not.
|
||||||
func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||||
if !p.shouldSkipAuthentication(r) {
|
if !p.shouldSkipAuthentication(r) {
|
||||||
s, err := p.sessionStore.LoadSession(r)
|
s, err := p.restStore.LoadSession(r)
|
||||||
|
if err != nil {
|
||||||
|
log.FromRequest(r).Debug().Err(err).Msg("proxy: no bearer auth token found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
s, err = p.sessionStore.LoadSession(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
||||||
|
@ -229,6 +233,7 @@ func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = p.authenticate(w, r, s); err != nil {
|
if err = p.authenticate(w, r, s); err != nil {
|
||||||
p.sessionStore.ClearSession(w, r)
|
p.sessionStore.ClearSession(w, r)
|
||||||
|
|
|
@ -510,3 +510,50 @@ func TestProxy_Impersonate(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxy_OAuthCallback(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
csrf sessions.MockCSRFStore
|
||||||
|
session sessions.MockSessionStore
|
||||||
|
authenticator clients.MockAuthenticate
|
||||||
|
params map[string]string
|
||||||
|
wantCode int
|
||||||
|
}{
|
||||||
|
{"good", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusFound},
|
||||||
|
{"error", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"error": "some error"}, http.StatusBadRequest},
|
||||||
|
{"state err", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "error"}, http.StatusInternalServerError},
|
||||||
|
{"csrf err", sessions.MockCSRFStore{GetError: errors.New("error")}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusBadRequest},
|
||||||
|
{"unmarshal err", sessions.MockCSRFStore{Cookie: &http.Cookie{Name: "something_csrf", Value: "unmarshal error"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||||
|
{"malformed", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
proxy, err := New(testOptions())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
proxy.sessionStore = &tt.session
|
||||||
|
proxy.csrfStore = tt.csrf
|
||||||
|
proxy.AuthenticateClient = tt.authenticator
|
||||||
|
proxy.cipher = mockCipher{}
|
||||||
|
// proxy.Csrf
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/.pomerium/callback", nil)
|
||||||
|
q := req.URL.Query()
|
||||||
|
for k, v := range tt.params {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
if tt.name == "malformed" {
|
||||||
|
req.URL.RawQuery = "email=%zzzzz"
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
proxy.OAuthCallback(w, req)
|
||||||
|
if status := w.Code; status != tt.wantCode {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v", status, tt.wantCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/config"
|
"github.com/pomerium/pomerium/internal/config"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/cryptutil"
|
"github.com/pomerium/pomerium/internal/cryptutil"
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
"github.com/pomerium/pomerium/internal/policy"
|
"github.com/pomerium/pomerium/internal/policy"
|
||||||
|
@ -38,10 +37,10 @@ const (
|
||||||
func ValidateOptions(o config.Options) error {
|
func ValidateOptions(o config.Options) error {
|
||||||
decoded, err := base64.StdEncoding.DecodeString(o.SharedKey)
|
decoded, err := base64.StdEncoding.DecodeString(o.SharedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("authorize: `SHARED_SECRET` setting is invalid base64: %v", err)
|
return fmt.Errorf("`SHARED_SECRET` setting is invalid base64: %v", err)
|
||||||
}
|
}
|
||||||
if len(decoded) != 32 {
|
if len(decoded) != 32 {
|
||||||
return fmt.Errorf("authorize: `SHARED_SECRET` want 32 but got %d bytes", len(decoded))
|
return fmt.Errorf("`SHARED_SECRET` want 32 but got %d bytes", len(decoded))
|
||||||
}
|
}
|
||||||
if len(o.Policies) == 0 {
|
if len(o.Policies) == 0 {
|
||||||
return errors.New("missing setting: no policies defined")
|
return errors.New("missing setting: no policies defined")
|
||||||
|
@ -92,6 +91,7 @@ type Proxy struct {
|
||||||
cipher cryptutil.Cipher
|
cipher cryptutil.Cipher
|
||||||
csrfStore sessions.CSRFStore
|
csrfStore sessions.CSRFStore
|
||||||
sessionStore sessions.SessionStore
|
sessionStore sessions.SessionStore
|
||||||
|
restStore sessions.SessionStore
|
||||||
|
|
||||||
redirectURL *url.URL
|
redirectURL *url.URL
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
|
@ -130,7 +130,10 @@ func New(opts config.Options) (*Proxy, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
restStore, err := sessions.NewRestStore(&sessions.RestStoreOptions{Cipher: cipher})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
p := &Proxy{
|
p := &Proxy{
|
||||||
routeConfigs: make(map[string]*routeConfig),
|
routeConfigs: make(map[string]*routeConfig),
|
||||||
// services
|
// services
|
||||||
|
@ -139,6 +142,7 @@ func New(opts config.Options) (*Proxy, error) {
|
||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
csrfStore: cookieStore,
|
csrfStore: cookieStore,
|
||||||
sessionStore: cookieStore,
|
sessionStore: cookieStore,
|
||||||
|
restStore: restStore,
|
||||||
SharedKey: opts.SharedKey,
|
SharedKey: opts.SharedKey,
|
||||||
redirectURL: &url.URL{Path: "/.pomerium/callback"},
|
redirectURL: &url.URL{Path: "/.pomerium/callback"},
|
||||||
templates: templates.New(),
|
templates: templates.New(),
|
||||||
|
@ -283,7 +287,7 @@ func urlParse(uri string) (*url.URL, error) {
|
||||||
return url.ParseRequestURI(uri)
|
return url.ParseRequestURI(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOptions updates internal structres based on config.Options
|
// UpdateOptions updates internal structures based on config.Options
|
||||||
func (p *Proxy) UpdateOptions(o config.Options) error {
|
func (p *Proxy) UpdateOptions(o config.Options) error {
|
||||||
log.Info().Msg("proxy: updating options")
|
log.Info().Msg("proxy: updating options")
|
||||||
err := p.UpdatePolicies(o)
|
err := p.UpdatePolicies(o)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package proxy // import "github.com/pomerium/pomerium/proxy"
|
package proxy // import "github.com/pomerium/pomerium/proxy"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -11,8 +10,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/config"
|
"github.com/pomerium/pomerium/internal/config"
|
||||||
"github.com/pomerium/pomerium/internal/sessions"
|
|
||||||
"github.com/pomerium/pomerium/proxy/clients"
|
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/policy"
|
"github.com/pomerium/pomerium/internal/policy"
|
||||||
)
|
)
|
||||||
|
@ -173,6 +170,8 @@ func TestOptions_Validate(t *testing.T) {
|
||||||
invalidSignKey.SigningKey = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw^"
|
invalidSignKey.SigningKey = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw^"
|
||||||
badSharedKey := testOptions()
|
badSharedKey := testOptions()
|
||||||
badSharedKey.SharedKey = ""
|
badSharedKey.SharedKey = ""
|
||||||
|
sharedKeyBadBas64 := testOptions()
|
||||||
|
sharedKeyBadBas64.SharedKey = "%(*@389"
|
||||||
missingPolicy := testOptions()
|
missingPolicy := testOptions()
|
||||||
missingPolicy.Policies = []policy.Policy{}
|
missingPolicy.Policies = []policy.Policy{}
|
||||||
|
|
||||||
|
@ -193,6 +192,7 @@ func TestOptions_Validate(t *testing.T) {
|
||||||
{"no shared secret", badSharedKey, true},
|
{"no shared secret", badSharedKey, true},
|
||||||
{"invalid signing key", invalidSignKey, true},
|
{"invalid signing key", invalidSignKey, true},
|
||||||
{"missing policy", missingPolicy, true},
|
{"missing policy", missingPolicy, true},
|
||||||
|
{"shared secret bad base64", sharedKeyBadBas64, 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) {
|
||||||
|
@ -240,49 +240,6 @@ func TestNew(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxy_OAuthCallback(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
csrf sessions.MockCSRFStore
|
|
||||||
session sessions.MockSessionStore
|
|
||||||
authenticator clients.MockAuthenticate
|
|
||||||
params map[string]string
|
|
||||||
wantCode int
|
|
||||||
}{
|
|
||||||
{"good", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusFound},
|
|
||||||
{"error", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"error": "some error"}, http.StatusBadRequest},
|
|
||||||
{"state err", sessions.MockCSRFStore{ResponseCSRF: "ok", GetError: nil, Cookie: &http.Cookie{Name: "something_csrf", Value: "csrf_state"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "error"}, http.StatusInternalServerError},
|
|
||||||
{"csrf err", sessions.MockCSRFStore{GetError: errors.New("error")}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusBadRequest},
|
|
||||||
{"unmarshal err", sessions.MockCSRFStore{Cookie: &http.Cookie{Name: "something_csrf", Value: "unmarshal error"}}, sessions.MockSessionStore{Session: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken", RefreshDeadline: time.Now().Add(-10 * time.Second)}}, clients.MockAuthenticate{RedeemResponse: &sessions.SessionState{AccessToken: "AccessToken", RefreshToken: "RefreshToken"}}, map[string]string{"code": "code", "state": "state"}, http.StatusInternalServerError},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
proxy, err := New(testOptions())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
proxy.sessionStore = &tt.session
|
|
||||||
proxy.csrfStore = tt.csrf
|
|
||||||
proxy.AuthenticateClient = tt.authenticator
|
|
||||||
proxy.cipher = mockCipher{}
|
|
||||||
// proxy.Csrf
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/.pomerium/callback", nil)
|
|
||||||
q := req.URL.Query()
|
|
||||||
for k, v := range tt.params {
|
|
||||||
q.Add(k, v)
|
|
||||||
}
|
|
||||||
req.URL.RawQuery = q.Encode()
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
proxy.OAuthCallback(w, req)
|
|
||||||
if status := w.Code; status != tt.wantCode {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v", status, tt.wantCode)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_UpdateOptions(t *testing.T) {
|
func Test_UpdateOptions(t *testing.T) {
|
||||||
|
|
||||||
good := testOptions()
|
good := testOptions()
|
||||||
|
|
79
scripts/programmatic_access.py
Executable file
79
scripts/programmatic_access.py
Executable file
|
@ -0,0 +1,79 @@
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--openid-configuration',
|
||||||
|
default="https://accounts.google.com/.well-known/openid-configuration")
|
||||||
|
parser.add_argument('--client-id')
|
||||||
|
parser.add_argument('--client-secret')
|
||||||
|
parser.add_argument('--pomerium-client-id')
|
||||||
|
parser.add_argument('--code')
|
||||||
|
parser.add_argument('--pomerium-token-url',
|
||||||
|
default="https://authenticate.corp.beyondperimeter.com/api/v1/token")
|
||||||
|
parser.add_argument('--pomerium-token')
|
||||||
|
parser.add_argument('--pomerium-url', default="https://httpbin.corp.beyondperimeter.com/get")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parser.parse_args()
|
||||||
|
code = args.code
|
||||||
|
pomerium_token = args.pomerium_token
|
||||||
|
oidc_document = requests.get(args.openid_configuration).json()
|
||||||
|
token_url = oidc_document['token_endpoint']
|
||||||
|
print(token_url)
|
||||||
|
sign_in_url = oidc_document['authorization_endpoint']
|
||||||
|
|
||||||
|
if not code and not pomerium_token:
|
||||||
|
if not args.client_id:
|
||||||
|
print("client-id is required")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sign_in_url = "{}?response_type=code&scope=openid%20email&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id={}".format(
|
||||||
|
oidc_document['authorization_endpoint'], args.client_id)
|
||||||
|
print("Access code not set, so we'll do the process interactively!")
|
||||||
|
print("Go to the url : {}".format(sign_in_url))
|
||||||
|
code = input("Complete the login and enter your code:")
|
||||||
|
print(code)
|
||||||
|
|
||||||
|
if not pomerium_token:
|
||||||
|
req = requests.post(
|
||||||
|
token_url, {
|
||||||
|
'client_id': args.client_id,
|
||||||
|
'client_secret': args.client_secret,
|
||||||
|
'code': code,
|
||||||
|
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
|
'grant_type': 'authorization_code'
|
||||||
|
})
|
||||||
|
|
||||||
|
refresh_token = req.json()['refresh_token']
|
||||||
|
print("refresh token: {}".format(refresh_token))
|
||||||
|
|
||||||
|
print("create a new id_token with our pomerium app as the audience")
|
||||||
|
req = requests.post(
|
||||||
|
token_url, {
|
||||||
|
'refresh_token': refresh_token,
|
||||||
|
'client_id': args.client_id,
|
||||||
|
'client_secret': args.client_secret,
|
||||||
|
'audience': args.pomerium_client_id,
|
||||||
|
'grant_type': 'refresh_token'
|
||||||
|
})
|
||||||
|
id_token = req.json()['id_token']
|
||||||
|
print("pomerium id_token: {}".format(id_token))
|
||||||
|
|
||||||
|
print("exchange our identity providers id token for a pomerium bearer token")
|
||||||
|
req = requests.post(args.pomerium_token_url, {'id_token': id_token})
|
||||||
|
pomerium_token = req.json()['Token']
|
||||||
|
print("pomerium bearer token is: {}".format(pomerium_token))
|
||||||
|
|
||||||
|
req = requests.get(args.pomerium_url, headers={'Authorization': 'Bearer ' + pomerium_token})
|
||||||
|
json_formatted = json.dumps(req.json(), indent=1)
|
||||||
|
print(json_formatted)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
53
scripts/programmatic_access.sh
Executable file
53
scripts/programmatic_access.sh
Executable file
|
@ -0,0 +1,53 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Create a new OAUTH2 provider DISTINCT from your pomerium configuration
|
||||||
|
# Select type as "OTHER"
|
||||||
|
CLIENT_ID='REPLACE-ME.apps.googleusercontent.com'
|
||||||
|
CLIENT_SECRET='REPLACE-ME'
|
||||||
|
SIGNIN_URL='https://accounts.google.com/o/oauth2/v2/auth?client_id='$CLIENT_ID'&response_type=code&scope=openid%20email&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob'
|
||||||
|
|
||||||
|
# This would be your pomerium client id
|
||||||
|
POMERIUM_CLIENT_ID='REPLACE-ME.apps.googleusercontent.com'
|
||||||
|
|
||||||
|
echo "Follow the following URL to get an offline auth code from your IdP"
|
||||||
|
echo $SIGNIN_URL
|
||||||
|
|
||||||
|
read -p 'Enter the authorization code as a result of logging in: ' CODE
|
||||||
|
echo $CODE
|
||||||
|
|
||||||
|
echo "Exchange our authorization code to get a refresh_token"
|
||||||
|
echo "refresh_tokens can be used to generate indefinite access tokens / id_tokens"
|
||||||
|
curl \
|
||||||
|
-d client_id=$CLIENT_ID \
|
||||||
|
-d client_secret=$CLIENT_SECRET \
|
||||||
|
-d code=$CODE \
|
||||||
|
-d redirect_uri=urn:ietf:wg:oauth:2.0:oob \
|
||||||
|
-d grant_type=authorization_code \
|
||||||
|
https://www.googleapis.com/oauth2/v4/token
|
||||||
|
|
||||||
|
read -p 'Enter the refresh token result:' REFRESH_TOKEN
|
||||||
|
echo $REFRESH_TOKEN
|
||||||
|
|
||||||
|
echo "Use our refresh_token to create a new id_token with an audience of pomerium's oauth client"
|
||||||
|
curl \
|
||||||
|
-d client_id=$CLIENT_ID \
|
||||||
|
-d client_secret=$CLIENT_SECRET \
|
||||||
|
-d refresh_token=$REFRESH_TOKEN \
|
||||||
|
-d grant_type=refresh_token \
|
||||||
|
-d audience=$POMERIUM_CLIENT_ID \
|
||||||
|
https://www.googleapis.com/oauth2/v4/token
|
||||||
|
|
||||||
|
echo "now we have an id_token with an audience that matches that of our pomerium app"
|
||||||
|
read -p 'Enter the resulting id_token:' ID_TOKEN
|
||||||
|
echo $ID_TOKEN
|
||||||
|
|
||||||
|
curl -X POST \
|
||||||
|
-d id_token=$ID_TOKEN \
|
||||||
|
https://authenticate.corp.beyondperimeter.com/api/v1/token
|
||||||
|
|
||||||
|
read -p 'Enter the resulting Token:' POMERIUM_ACCESS_TOKEN
|
||||||
|
echo $POMERIUM_ACCESS_TOKEN
|
||||||
|
|
||||||
|
echo "we have our bearer token that can be used with pomerium now"
|
||||||
|
curl \
|
||||||
|
-H "Authorization: Bearer ${POMERIUM_ACCESS_TOKEN}" \
|
||||||
|
"https://httpbin.corp.beyondperimeter.com/"
|
Loading…
Add table
Add a link
Reference in a new issue