Compare commits
202 Commits
session-st
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
44cdcc79c3 | ||
|
a122ac60e4 | ||
|
85a1ed5135 | ||
|
82a3d5afdc | ||
|
6683e35008 | ||
|
b83b7565f3 | ||
|
71dfd44149 | ||
|
d00c14a2a7 | ||
|
44ea6920a7 | ||
|
fa6c4792a1 | ||
|
a165928458 | ||
|
d5d4878a29 | ||
|
c4559ea372 | ||
|
a65d38d181 | ||
|
57851f6850 | ||
|
7e3ad6b215 | ||
|
c941f3ce0d | ||
|
9240538939 | ||
|
272fb96024 | ||
|
bc5fc5a513 | ||
|
49e124eb87 | ||
|
6453e78db3 | ||
|
b167744b0a | ||
|
fb52bdb90c | ||
|
c457eeb711 | ||
|
9938bb95d9 | ||
|
4b985992d8 | ||
|
8b61559b8d | ||
|
e1b70dc9f0 | ||
|
9e37de53e3 | ||
|
18156713e3 | ||
|
14c25c1d8a | ||
|
a91cce7ab9 | ||
|
02dfa87f11 | ||
|
7134d22bcc | ||
|
d85660248c | ||
|
64672c34eb | ||
|
c3eac4f6d4 | ||
|
4de49983fb | ||
|
5f9a65f6b1 | ||
|
7d910c0ae8 | ||
|
69c723af81 | ||
|
a882788efb | ||
|
88a7f9f483 | ||
|
8a24dd797f | ||
|
d346219293 | ||
|
1ab63304a1 | ||
|
436936836d | ||
|
a025228a6d | ||
|
4eab98e65b | ||
|
53524875d1 | ||
|
800a3694c2 | ||
|
583ec18fa2 | ||
|
3f219bd85c | ||
|
23309adc7c | ||
|
6c4aca957e | ||
|
e48d28d1b9 | ||
|
4a6b703c54 | ||
|
93cb575d7c | ||
|
f537720b52 | ||
|
122ec45dd8 | ||
|
0d94f5e515 | ||
|
2eecf756e4 | ||
|
8635391543 | ||
|
f29e353586 | ||
|
2c104c4e7d | ||
|
7bf00b7f4a | ||
|
7b1132df13 | ||
|
6bf3f2a51b | ||
|
f00a474d91 | ||
|
b57d7f77e1 | ||
|
84da3c3d8c | ||
|
9ed5623f2a | ||
|
7236039b9d | ||
|
289dfce28a | ||
|
4e10cc76e0 | ||
|
08021429ea | ||
|
c4f20fff3d | ||
|
ec97000169 | ||
|
03f218a63c | ||
|
bc81a0f6e4 | ||
|
e952ab4bdf | ||
|
56f51417ae | ||
|
816c2a6da9 | ||
|
d7e88a4718 | ||
|
874c147e04 | ||
|
bdcdfb74f9 | ||
|
f0d006259e | ||
|
6311fa2950 | ||
|
630db3769b | ||
|
4bc0a91e2e | ||
|
179ee6c2db | ||
|
e92e2f0cb4 | ||
|
27bdb194b1 | ||
|
c98ff79aba | ||
|
e245ef4854 | ||
|
a83c5eabb6 | ||
|
9823971b7d | ||
|
776d063b98 | ||
|
39b6a42d43 | ||
|
018a25be04 | ||
|
ecd0f89c84 | ||
|
387a7267e1 | ||
|
4eefc01600 | ||
|
aa37564655 | ||
|
85c5cef783 | ||
|
ce7e384095 | ||
|
b9cfa8f49f | ||
|
924eab6355 | ||
|
5bcb998e6b | ||
|
d24aacdb5c | ||
|
411adf6f21 | ||
|
317f09f41e | ||
|
3881955605 | ||
|
bd651df3c2 | ||
|
058ffd1047 | ||
|
5a50f6223f | ||
|
100f126405 | ||
|
2f6dcf3b5f | ||
|
48dbb391bc | ||
|
54d91c69cc | ||
|
350c1cd127 | ||
|
58b06ce761 | ||
|
79acef9036 | ||
|
10f65e0381 | ||
|
1ff74d322a | ||
|
69cb34a04e | ||
|
187960e9d8 | ||
|
8413c30c26 | ||
|
b895f49c52 | ||
|
8083501da6 | ||
|
0af18d6d7c | ||
|
77e1fff753 | ||
|
0d6fa6216d | ||
|
6366690927 | ||
|
417fde190c | ||
|
fb9616160e | ||
|
d1ef14becc | ||
|
d69560d020 | ||
|
7a8fb58ad1 | ||
|
8027cc454e | ||
|
f35c82bb0f | ||
|
9e59b4f62e | ||
|
572646e0d5 | ||
|
78feaec6fa | ||
|
55a853cf51 | ||
|
405f9b3bb0 | ||
|
4721da02f2 | ||
|
c1ae0ca807 | ||
|
22199fa417 | ||
|
3155ada287 | ||
|
2e2327af6c | ||
|
ae0258a203 | ||
|
518c1d3e8e | ||
|
fc06e2dbef | ||
|
5095c3647d | ||
|
4f5dbace9f | ||
|
7e7bfb5daf | ||
|
bc3d75a2ed | ||
|
42f14a41d9 | ||
|
a7693cc72a | ||
|
93df7d9132 | ||
|
a6b8f7bde2 | ||
|
2f61e42c37 | ||
|
f435fa68ab | ||
|
130d03758d | ||
|
7a1fc52e33 | ||
|
b255ed56ef | ||
|
2c566a5f5b | ||
|
296d989e58 | ||
|
f2562e8973 | ||
|
42731f0617 | ||
|
b1bd3280db | ||
|
e881612ea6 | ||
|
b6c60f52ee | ||
|
1355c1ce30 | ||
|
df6b6b7ce0 | ||
|
40cf6b2626 | ||
|
006322562d | ||
|
f0b6f1525b | ||
|
29fb71fac5 | ||
|
37475637cd | ||
|
e7d29590cd | ||
|
b05eb71adf | ||
|
0d56a4c570 | ||
|
60bb8fc7ea | ||
|
076484297e | ||
|
e374805f8e | ||
|
d3f0cb43ca | ||
|
f26ed5f3d1 | ||
|
91346df5ac | ||
|
10e240c8bf | ||
|
d40a61613e | ||
|
093f9da881 | ||
|
76bd23738f | ||
|
37e31b5f09 | ||
|
c61f3a1c65 | ||
|
34cbe0497c | ||
|
fbee5eae16 | ||
|
17e97ab884 | ||
|
7179c5796a | ||
|
3f2fab10e6 |
10
.github/CODEOWNERS
vendored
10
.github/CODEOWNERS
vendored
@ -1,6 +1,6 @@
|
|||||||
# Default owner should be a Pusher cloud-team member unless overridden by later
|
# Default owner should be a Pusher cloud-team member or another maintainer
|
||||||
# rules in this file
|
# unless overridden by later rules in this file
|
||||||
* @pusher/cloud-team
|
* @pusher/cloud-team @syscll @steakunderscore
|
||||||
|
|
||||||
# login.gov provider
|
# login.gov provider
|
||||||
# Note: If @timothy-spencer terms out of his appointment, your best bet
|
# Note: If @timothy-spencer terms out of his appointment, your best bet
|
||||||
@ -10,3 +10,7 @@
|
|||||||
# or the public devops channel at https://chat.18f.gov/).
|
# or the public devops channel at https://chat.18f.gov/).
|
||||||
providers/logingov.go @timothy-spencer
|
providers/logingov.go @timothy-spencer
|
||||||
providers/logingov_test.go @timothy-spencer
|
providers/logingov_test.go @timothy-spencer
|
||||||
|
|
||||||
|
# Bitbucket provider
|
||||||
|
providers/bitbucket.go @aledeganopix4d
|
||||||
|
providers/bitbucket_test.go @aledeganopix4d
|
||||||
|
13
.golangci.yml
Normal file
13
.golangci.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
run:
|
||||||
|
deadline: 120s
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- govet
|
||||||
|
- golint
|
||||||
|
- ineffassign
|
||||||
|
- goconst
|
||||||
|
- deadcode
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
enable-all: false
|
||||||
|
disable-all: true
|
10
.travis.yml
10
.travis.yml
@ -1,16 +1,12 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
- 1.12.x
|
||||||
install:
|
install:
|
||||||
# Fetch dependencies
|
# Fetch dependencies
|
||||||
- wget -O dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
|
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.17.1
|
||||||
- chmod +x dep
|
- GO111MODULE=on go mod download
|
||||||
- mv dep $GOPATH/bin/dep
|
|
||||||
script:
|
script:
|
||||||
- ./configure
|
- ./configure && make test
|
||||||
# Run tests
|
|
||||||
- make test
|
|
||||||
sudo: false
|
sudo: false
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
88
CHANGELOG.md
88
CHANGELOG.md
@ -1,16 +1,85 @@
|
|||||||
# Vx.x.x (Pre-release)
|
# Vx.x.x (Pre-release)
|
||||||
|
|
||||||
|
## Changes since v4.0.0
|
||||||
|
|
||||||
|
- [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka)
|
||||||
|
|
||||||
|
# v4.0.0
|
||||||
|
|
||||||
|
## Release Highlights
|
||||||
|
- Documentation is now on a [microsite](https://pusher.github.io/oauth2_proxy/)
|
||||||
|
- Health check logging can now be disabled for quieter logs
|
||||||
|
- Authorization Header JWTs can now be verified by the proxy to skip authentication for machine users
|
||||||
|
- Sessions can now be stored in Redis. This reduces refresh failures and uses smaller cookies (Recommended for those using OIDC refreshing)
|
||||||
|
- Logging overhaul allows customisable logging formats
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
- This release includes a number of breaking changes that will require users to
|
||||||
|
reconfigure their proxies. Please read the Breaking Changes below thoroughly.
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
|
||||||
- [#146](https://github.com/pusher/oauth2_proxy/pull/146) Use full email address as `User` if the auth response did not contain a `User` field (@gargath)
|
- [#231](https://github.com/pusher/oauth2_proxy/pull/231) Rework GitLab provider
|
||||||
|
- This PR changes the configuration options for the GitLab provider to use
|
||||||
|
a self-hosted instance. You now need to specify a `-oidc-issuer-url` rather than
|
||||||
|
explicit `-login-url`, `-redeem-url` and `-validate-url` parameters.
|
||||||
|
- [#186](https://github.com/pusher/oauth2_proxy/pull/186) Make config consistent
|
||||||
|
- This PR changes configuration options so that all flags have a config counterpart
|
||||||
|
of the same name but with underscores (`_`) in place of hyphens (`-`).
|
||||||
|
This change affects the following flags:
|
||||||
|
- The `--tls-key` flag is now `--tls-key-file` to be consistent with existing
|
||||||
|
file flags and the existing config and environment settings
|
||||||
|
- The `--tls-cert` flag is now `--tls-cert-file` to be consistent with existing
|
||||||
|
file flags and the existing config and environment settings
|
||||||
|
This change affects the following existing configuration options:
|
||||||
|
- The `proxy-prefix` option is now `proxy_prefix`.
|
||||||
|
This PR changes environment variables so that all flags have an environment
|
||||||
|
counterpart of the same name but capitalised, with underscores (`_`) in place
|
||||||
|
of hyphens (`-`) and with the prefix `OAUTH2_PROXY_`.
|
||||||
|
This change affects the following existing environment variables:
|
||||||
|
- The `OAUTH2_SKIP_OIDC_DISCOVERY` environment variable is now `OAUTH2_PROXY_SKIP_OIDC_DISCOVERY`.
|
||||||
|
- The `OAUTH2_OIDC_JWKS_URL` environment variable is now `OAUTH2_PROXY_OIDC_JWKS_URL`.
|
||||||
|
- [#146](https://github.com/pusher/oauth2_proxy/pull/146) Use full email address as `User` if the auth response did not contain a `User` field
|
||||||
- This change modifies the contents of the `X-Forwarded-User` header supplied by the proxy for users where the auth response from the IdP did not contain
|
- This change modifies the contents of the `X-Forwarded-User` header supplied by the proxy for users where the auth response from the IdP did not contain
|
||||||
a username.
|
a username.
|
||||||
In that case, this header used to only contain the local part of the user's email address (e.g. `john.doe` for `john.doe@example.com`) but now contains
|
In that case, this header used to only contain the local part of the user's email address (e.g. `john.doe` for `john.doe@example.com`) but now contains
|
||||||
the user's full email address instead.
|
the user's full email address instead.
|
||||||
|
- [#170](https://github.com/pusher/oauth2_proxy/pull/170) Pre-built binary tarballs changed format
|
||||||
|
- The pre-built binary tarballs again match the format of the [bitly](https://github.com/bitly/oauth2_proxy) repository, where the unpacked directory
|
||||||
|
has the same name as the tarball and the binary is always named `oauth2_proxy`. This was done to restore compatibility with third-party automation
|
||||||
|
recipes like https://github.com/jhoblitt/puppet-oauth2_proxy.
|
||||||
|
|
||||||
## Changes since v3.2.0
|
## Changes since v3.2.0
|
||||||
|
|
||||||
- [#147](https://github.com/pusher/outh2_proxy/pull/147) Add SessionStore interfaces and initial implementation (@JoelSpeed)
|
- [#234](https://github.com/pusher/oauth2_proxy/pull/234) Added option `-ssl-upstream-insecure-skip-validation` to skip validation of upstream SSL certificates (@jansinger)
|
||||||
|
- [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant)
|
||||||
|
- [#231](https://github.com/pusher/oauth2_proxy/pull/231) Add optional group membership and email domain checks to the GitLab provider (@Overv)
|
||||||
|
- [#226](https://github.com/pusher/oauth2_proxy/pull/226) Made setting of proxied headers deterministic based on configuration alone (@aeijdenberg)
|
||||||
|
- [#178](https://github.com/pusher/oauth2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes)
|
||||||
|
- [#209](https://github.com/pusher/oauth2_proxy/pull/209) Improve docker build caching of layers (@dekimsey)
|
||||||
|
- [#186](https://github.com/pusher/oauth2_proxy/pull/186) Make config consistent (@JoelSpeed)
|
||||||
|
- [#187](https://github.com/pusher/oauth2_proxy/pull/187) Move root packages to pkg folder (@JoelSpeed)
|
||||||
|
- [#65](https://github.com/pusher/oauth2_proxy/pull/65) Improvements to authenticate requests with a JWT bearer token in the `Authorization` header via
|
||||||
|
the `-skip-jwt-bearer-token` options. (@brianv0)
|
||||||
|
- Additional verifiers can be configured via the `-extra-jwt-issuers` flag if the JWT issuers is either an OpenID provider or has a JWKS URL
|
||||||
|
(e.g. `https://example.com/.well-known/jwks.json`).
|
||||||
|
- [#180](https://github.com/pusher/oauth2_proxy/pull/180) Minor refactor of core proxying path (@aeijdenberg).
|
||||||
|
- [#175](https://github.com/pusher/oauth2_proxy/pull/175) Bump go-oidc to v2.0.0 (@aeijdenberg).
|
||||||
|
- Includes fix for potential signature checking issue when OIDC discovery is skipped.
|
||||||
|
- [#155](https://github.com/pusher/oauth2_proxy/pull/155) Add RedisSessionStore implementation (@brianv0, @JoelSpeed)
|
||||||
|
- Implement flags to configure the redis session store
|
||||||
|
- `-session-store-type=redis` Sets the store type to redis
|
||||||
|
- `-redis-connection-url` Sets the Redis connection URL
|
||||||
|
- `-redis-use-sentinel=true` Enables Redis Sentinel support
|
||||||
|
- `-redis-sentinel-master-name` Sets the Sentinel master name, if sentinel is enabled
|
||||||
|
- `-redis-sentinel-connection-urls` Defines the Redis Sentinel Connection URLs, if sentinel is enabled
|
||||||
|
- Introduces the concept of a session ticket. Tickets are composed of the cookie name, a session ID, and a secret.
|
||||||
|
- Redis Sessions are stored encrypted with a per-session secret
|
||||||
|
- Added tests for server based session stores
|
||||||
|
- [#168](https://github.com/pusher/oauth2_proxy/pull/168) Drop Go 1.11 support in Travis (@JoelSpeed)
|
||||||
|
- [#169](https://github.com/pusher/oauth2_proxy/pull/169) Update Alpine to 3.9 (@kskewes)
|
||||||
|
- [#148](https://github.com/pusher/oauth2_proxy/pull/148) Implement SessionStore interface within proxy (@JoelSpeed)
|
||||||
|
- [#147](https://github.com/pusher/oauth2_proxy/pull/147) Add SessionStore interfaces and initial implementation (@JoelSpeed)
|
||||||
- Allows for multiple different session storage implementations including client and server side
|
- Allows for multiple different session storage implementations including client and server side
|
||||||
- Adds tests suite for interface to ensure consistency across implementations
|
- Adds tests suite for interface to ensure consistency across implementations
|
||||||
- Refactor some configuration options (around cookies) into packages
|
- Refactor some configuration options (around cookies) into packages
|
||||||
@ -32,8 +101,21 @@
|
|||||||
- Implement two new flags to customize the logging format
|
- Implement two new flags to customize the logging format
|
||||||
- `-standard-logging-format` Sets the format for standard logging
|
- `-standard-logging-format` Sets the format for standard logging
|
||||||
- `-auth-logging-format` Sets the format for auth logging
|
- `-auth-logging-format` Sets the format for auth logging
|
||||||
|
|
||||||
- [#111](https://github.com/pusher/oauth2_proxy/pull/111) Add option for telling where to find a login.gov JWT key file (@timothy-spencer)
|
- [#111](https://github.com/pusher/oauth2_proxy/pull/111) Add option for telling where to find a login.gov JWT key file (@timothy-spencer)
|
||||||
|
- [#170](https://github.com/pusher/oauth2_proxy/pull/170) Restore binary tarball contents to be compatible with bitlys original tarballs (@zeha)
|
||||||
|
- [#185](https://github.com/pusher/oauth2_proxy/pull/185) Fix an unsupported protocol scheme error during token validation when using the Azure provider (@jonas)
|
||||||
|
- [#141](https://github.com/pusher/oauth2_proxy/pull/141) Check google group membership based on email address (@bchess)
|
||||||
|
- Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized.
|
||||||
|
- [#195](https://github.com/pusher/oauth2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore)
|
||||||
|
- [#198](https://github.com/pusher/oauth2_proxy/pull/198) Switch from gometalinter to golangci-lint (@steakunderscore)
|
||||||
|
- [#159](https://github.com/pusher/oauth2_proxy/pull/159) Add option to skip the OIDC provider verified email check: `--insecure-oidc-allow-unverified-email` (@djfinlay)
|
||||||
|
- [#210](https://github.com/pusher/oauth2_proxy/pull/210) Update base image from Alpine 3.9 to 3.10 (@steakunderscore)
|
||||||
|
- [#201](https://github.com/pusher/oauth2_proxy/pull/201) Add Bitbucket as new OAuth2 provider, accepts email, team and repository permissions to determine authorization (@aledeganopix4d)
|
||||||
|
- Implement flags to enable Bitbucket authentication:
|
||||||
|
- `-bitbucket-repository` Restrict authorization to users that can access this repository
|
||||||
|
- `-bitbucket-team` Restrict authorization to users that are part of this Bitbucket team
|
||||||
|
- [#211](https://github.com/pusher/oauth2_proxy/pull/211) Switch from dep to go modules (@steakunderscore)
|
||||||
|
- [#145](https://github.com/pusher/oauth2_proxy/pull/145) Add support for OIDC UserInfo endpoint email verification (@rtluckie)
|
||||||
|
|
||||||
# v3.2.0
|
# v3.2.0
|
||||||
|
|
||||||
|
12
Dockerfile
12
Dockerfile
@ -1,15 +1,17 @@
|
|||||||
FROM golang:1.12-stretch AS builder
|
FROM golang:1.12-stretch AS builder
|
||||||
|
|
||||||
# Download tools
|
# Download tools
|
||||||
RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
|
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1
|
||||||
RUN chmod +x $GOPATH/bin/dep
|
|
||||||
|
|
||||||
# Copy sources
|
# Copy sources
|
||||||
WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy
|
WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Fetch dependencies
|
# Fetch dependencies
|
||||||
RUN dep ensure --vendor-only
|
COPY go.mod go.sum ./
|
||||||
|
RUN GO111MODULE=on go mod download
|
||||||
|
|
||||||
|
# Now pull in our code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# Build binary and make sure there is at least an empty key file.
|
# Build binary and make sure there is at least an empty key file.
|
||||||
# This is useful for GCP App Engine custom runtime builds, because
|
# This is useful for GCP App Engine custom runtime builds, because
|
||||||
@ -20,7 +22,7 @@ RUN dep ensure --vendor-only
|
|||||||
RUN ./configure && make build && touch jwt_signing_key.pem
|
RUN ./configure && make build && touch jwt_signing_key.pem
|
||||||
|
|
||||||
# Copy binary to alpine
|
# Copy binary to alpine
|
||||||
FROM alpine:3.8
|
FROM alpine:3.10
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy
|
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy
|
||||||
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem
|
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
FROM golang:1.12-stretch AS builder
|
FROM golang:1.12-stretch AS builder
|
||||||
|
|
||||||
# Download tools
|
# Download tools
|
||||||
RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
|
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1
|
||||||
RUN chmod +x $GOPATH/bin/dep
|
|
||||||
|
|
||||||
# Copy sources
|
# Copy sources
|
||||||
WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy
|
WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Fetch dependencies
|
# Fetch dependencies
|
||||||
RUN dep ensure --vendor-only
|
COPY go.mod go.sum ./
|
||||||
|
RUN GO111MODULE=on go mod download
|
||||||
|
|
||||||
|
# Now pull in our code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# Build binary and make sure there is at least an empty key file.
|
# Build binary and make sure there is at least an empty key file.
|
||||||
# This is useful for GCP App Engine custom runtime builds, because
|
# This is useful for GCP App Engine custom runtime builds, because
|
||||||
@ -20,7 +22,7 @@ RUN dep ensure --vendor-only
|
|||||||
RUN ./configure && GOARCH=arm64 make build && touch jwt_signing_key.pem
|
RUN ./configure && GOARCH=arm64 make build && touch jwt_signing_key.pem
|
||||||
|
|
||||||
# Copy binary to alpine
|
# Copy binary to alpine
|
||||||
FROM arm64v8/alpine:3.8
|
FROM arm64v8/alpine:3.10
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy
|
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy
|
||||||
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem
|
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
FROM golang:1.12-stretch AS builder
|
FROM golang:1.12-stretch AS builder
|
||||||
|
|
||||||
# Download tools
|
# Download tools
|
||||||
RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
|
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1
|
||||||
RUN chmod +x $GOPATH/bin/dep
|
|
||||||
|
|
||||||
# Copy sources
|
# Copy sources
|
||||||
WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy
|
WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Fetch dependencies
|
# Fetch dependencies
|
||||||
RUN dep ensure --vendor-only
|
COPY go.mod go.sum ./
|
||||||
|
RUN GO111MODULE=on go mod download
|
||||||
|
|
||||||
|
# Now pull in our code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# Build binary and make sure there is at least an empty key file.
|
# Build binary and make sure there is at least an empty key file.
|
||||||
# This is useful for GCP App Engine custom runtime builds, because
|
# This is useful for GCP App Engine custom runtime builds, because
|
||||||
@ -20,7 +22,7 @@ RUN dep ensure --vendor-only
|
|||||||
RUN ./configure && GOARCH=arm GOARM=6 make build && touch jwt_signing_key.pem
|
RUN ./configure && GOARCH=arm GOARM=6 make build && touch jwt_signing_key.pem
|
||||||
|
|
||||||
# Copy binary to alpine
|
# Copy binary to alpine
|
||||||
FROM arm32v6/alpine:3.8
|
FROM arm32v6/alpine:3.10
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy
|
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy
|
||||||
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem
|
COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem
|
||||||
|
365
Gopkg.lock
generated
365
Gopkg.lock
generated
@ -1,365 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:b24249f5a5e6fbe1eddc94b25973172339ccabeadef4779274f3ed0167c18812"
|
|
||||||
name = "cloud.google.com/go"
|
|
||||||
packages = ["compute/metadata"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "2d3a6656c17a60b0815b7e06ab0be04eacb6e613"
|
|
||||||
version = "v0.16.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:289dd4d7abfb3ad2b5f728fbe9b1d5c1bf7d265a3eb9ef92869af1f7baba4c7a"
|
|
||||||
name = "github.com/BurntSushi/toml"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
|
|
||||||
version = "v0.3.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:512883404c2a99156e410e9880e3bb35ecccc0c07c1159eb204b5f3ef3c431b3"
|
|
||||||
name = "github.com/bitly/go-simplejson"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "aabad6e819789e569bd6aabf444c935aa9ba1e44"
|
|
||||||
version = "v0.5.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "v2"
|
|
||||||
digest = "1:e5a238f8fa890e529d7e493849bbae8988c9e70344e4630cc4f9a11b00516afb"
|
|
||||||
name = "github.com/coreos/go-oidc"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "77e7f2010a464ade7338597afe650dfcffbe2ca8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
|
||||||
name = "github.com/davecgh/go-spew"
|
|
||||||
packages = ["spew"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:6098222470fe0172157ce9bbef5d2200df4edde17ee649c5d6e48330e4afa4c6"
|
|
||||||
name = "github.com/dgrijalva/jwt-go"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
|
||||||
version = "v3.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:3b760d3b93f994df8eb1d9ebfad17d3e9e37edcb7f7efaa15b427c0d7a64f4e4"
|
|
||||||
name = "github.com/golang/protobuf"
|
|
||||||
packages = ["proto"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:b3c5b95e56c06f5aa72cb2500e6ee5f44fcd122872d4fec2023a488e561218bc"
|
|
||||||
name = "github.com/hpcloud/tail"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"ratelimiter",
|
|
||||||
"util",
|
|
||||||
"watch",
|
|
||||||
"winfile",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "a30252cb686a21eb2d0b98132633053ec2f7f1e5"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:af67386ca553c04c6222f7b5b2f17bc97a5dfb3b81b706882c7fd8c72c30cf8f"
|
|
||||||
name = "github.com/mbland/hmacauth"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "107c17adcc5eccc9935cd67d9bc2feaf5255d2cb"
|
|
||||||
version = "1.0.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:15c0562bca5d78ac087fb39c211071dc124e79fb18f8b7c3f8a0bc7ffcb2a38e"
|
|
||||||
name = "github.com/mreiferson/go-options"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "20ba7d382d05facb01e02eb777af0c5f229c5c95"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:a3735b0978a8b53fc2ac97a6f46ca1189f0712a00df86d6ec4cf26c1a25e6d77"
|
|
||||||
name = "github.com/onsi/ginkgo"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"config",
|
|
||||||
"internal/codelocation",
|
|
||||||
"internal/containernode",
|
|
||||||
"internal/failer",
|
|
||||||
"internal/leafnodes",
|
|
||||||
"internal/remote",
|
|
||||||
"internal/spec",
|
|
||||||
"internal/spec_iterator",
|
|
||||||
"internal/specrunner",
|
|
||||||
"internal/suite",
|
|
||||||
"internal/testingtproxy",
|
|
||||||
"internal/writer",
|
|
||||||
"reporters",
|
|
||||||
"reporters/stenographer",
|
|
||||||
"reporters/stenographer/support/go-colorable",
|
|
||||||
"reporters/stenographer/support/go-isatty",
|
|
||||||
"types",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "eea6ad008b96acdaa524f5b409513bf062b500ad"
|
|
||||||
version = "v1.8.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:dbafce2fddb1ca331646fe2ac9c9413980368b19a60a4406a6e5861680bd73be"
|
|
||||||
name = "github.com/onsi/gomega"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"format",
|
|
||||||
"internal/assertion",
|
|
||||||
"internal/asyncassertion",
|
|
||||||
"internal/oraclematcher",
|
|
||||||
"internal/testingtsupport",
|
|
||||||
"matchers",
|
|
||||||
"matchers/support/goraph/bipartitegraph",
|
|
||||||
"matchers/support/goraph/edge",
|
|
||||||
"matchers/support/goraph/node",
|
|
||||||
"matchers/support/goraph/util",
|
|
||||||
"types",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "90e289841c1ed79b7a598a7cd9959750cb5e89e2"
|
|
||||||
version = "v1.5.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
|
||||||
name = "github.com/pmezard/go-difflib"
|
|
||||||
packages = ["difflib"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:386e12afcfd8964907c92dffd106860c0dedd71dbefae14397b77b724a13343b"
|
|
||||||
name = "github.com/pquerna/cachecontrol"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"cacheobject",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159"
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
packages = [
|
|
||||||
"assert",
|
|
||||||
"require",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
|
|
||||||
version = "v1.1.4"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:39630a0e2844fc4297c27caacb394a9fd342f869292284a62f856877adab65bc"
|
|
||||||
name = "github.com/yhat/wsutil"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "1d66fa95c997864ba4d8479f56609620fe542928"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:f6a006d27619a4d93bf9b66fe1999b8c8d1fa62bdc63af14f10fbe6fcaa2aa1a"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = [
|
|
||||||
"bcrypt",
|
|
||||||
"blowfish",
|
|
||||||
"ed25519",
|
|
||||||
"ed25519/internal/edwards25519",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:130b1bec86c62e121967ee0c69d9c263dc2d3ffe6c7c9a82aca4071c4d068861"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
packages = [
|
|
||||||
"context",
|
|
||||||
"context/ctxhttp",
|
|
||||||
"html",
|
|
||||||
"html/atom",
|
|
||||||
"html/charset",
|
|
||||||
"websocket",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:4a61176e8386727e4847b21a5a2625ce56b9c518bc543a28226503e701265db0"
|
|
||||||
name = "golang.org/x/oauth2"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"google",
|
|
||||||
"internal",
|
|
||||||
"jws",
|
|
||||||
"jwt",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:67a6e61e60283fd7dce50eba228080bff8805d9d69b2f121d7ec2260d120c4a8"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["unix"]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "ca7f33d4116e3a1f9425755d6a44e7ed9b4c97df"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:740b51a55815493a8d0f2b1e0d0ae48fe48953bf7eaf3fcc4198823bf67768c0"
|
|
||||||
name = "golang.org/x/text"
|
|
||||||
packages = [
|
|
||||||
"encoding",
|
|
||||||
"encoding/charmap",
|
|
||||||
"encoding/htmlindex",
|
|
||||||
"encoding/internal",
|
|
||||||
"encoding/internal/identifier",
|
|
||||||
"encoding/japanese",
|
|
||||||
"encoding/korean",
|
|
||||||
"encoding/simplifiedchinese",
|
|
||||||
"encoding/traditionalchinese",
|
|
||||||
"encoding/unicode",
|
|
||||||
"internal/gen",
|
|
||||||
"internal/language",
|
|
||||||
"internal/language/compact",
|
|
||||||
"internal/tag",
|
|
||||||
"internal/utf8internal",
|
|
||||||
"language",
|
|
||||||
"runes",
|
|
||||||
"transform",
|
|
||||||
"unicode/cldr",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
|
|
||||||
version = "v0.3.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:dc1fb726dbbe79c86369941eae1e3b431b8fc6f11dbd37f7899dc758a43cc3ed"
|
|
||||||
name = "google.golang.org/api"
|
|
||||||
packages = [
|
|
||||||
"admin/directory/v1",
|
|
||||||
"gensupport",
|
|
||||||
"googleapi",
|
|
||||||
"googleapi/internal/uritemplates",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:934fb8966f303ede63aa405e2c8d7f0a427a05ea8df335dfdc1833dd4d40756f"
|
|
||||||
name = "google.golang.org/appengine"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"internal",
|
|
||||||
"internal/app_identity",
|
|
||||||
"internal/base",
|
|
||||||
"internal/datastore",
|
|
||||||
"internal/log",
|
|
||||||
"internal/modules",
|
|
||||||
"internal/remote_api",
|
|
||||||
"internal/urlfetch",
|
|
||||||
"urlfetch",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:eb53021a8aa3f599d29c7102e65026242bdedce998a54837dc67f14b6a97c5fd"
|
|
||||||
name = "gopkg.in/fsnotify.v1"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
|
||||||
source = "https://github.com/fsnotify/fsnotify.git"
|
|
||||||
version = "v1.4.7"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:cb5b2a45a3dd41c01ff779c54ae4c8aab0271d6d3b3f734c8a8bd2c890299ef2"
|
|
||||||
name = "gopkg.in/fsnotify/fsnotify.v1"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "836bfd95fecc0f1511dd66bdbf2b5b61ab8b00b6"
|
|
||||||
version = "v1.2.11"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:11c58e19ff7ce22740423bb933f1ddca3bf575def40d5ac3437ec12871b1648b"
|
|
||||||
name = "gopkg.in/natefinch/lumberjack.v2"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "a96e63847dc3c67d17befa69c303767e2f84e54f"
|
|
||||||
version = "v2.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:be4ed0a2b15944dd777a663681a39260ed05f9c4e213017ed2e2255622c8820c"
|
|
||||||
name = "gopkg.in/square/go-jose.v2"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"cipher",
|
|
||||||
"json",
|
|
||||||
]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
|
|
||||||
version = "v2.1.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "v1"
|
|
||||||
digest = "1:a96d16bd088460f2e0685d46c39bcf1208ba46e0a977be2df49864ec7da447dd"
|
|
||||||
name = "gopkg.in/tomb.v1"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "dd632973f1e7218eb1089048e0798ec9ae7dceb8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:cedccf16b71e86db87a24f8d4c70b0a855872eb967cb906a66b95de56aefbd0d"
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = ""
|
|
||||||
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
|
|
||||||
version = "v2.2.2"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
input-imports = [
|
|
||||||
"github.com/BurntSushi/toml",
|
|
||||||
"github.com/bitly/go-simplejson",
|
|
||||||
"github.com/coreos/go-oidc",
|
|
||||||
"github.com/dgrijalva/jwt-go",
|
|
||||||
"github.com/mbland/hmacauth",
|
|
||||||
"github.com/mreiferson/go-options",
|
|
||||||
"github.com/onsi/ginkgo",
|
|
||||||
"github.com/onsi/gomega",
|
|
||||||
"github.com/stretchr/testify/assert",
|
|
||||||
"github.com/stretchr/testify/require",
|
|
||||||
"github.com/yhat/wsutil",
|
|
||||||
"golang.org/x/crypto/bcrypt",
|
|
||||||
"golang.org/x/net/websocket",
|
|
||||||
"golang.org/x/oauth2",
|
|
||||||
"golang.org/x/oauth2/google",
|
|
||||||
"google.golang.org/api/admin/directory/v1",
|
|
||||||
"google.golang.org/api/googleapi",
|
|
||||||
"gopkg.in/fsnotify/fsnotify.v1",
|
|
||||||
"gopkg.in/natefinch/lumberjack.v2",
|
|
||||||
"gopkg.in/square/go-jose.v2",
|
|
||||||
]
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
48
Gopkg.toml
48
Gopkg.toml
@ -1,48 +0,0 @@
|
|||||||
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/BurntSushi/toml"
|
|
||||||
version = "~0.3.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/bitly/go-simplejson"
|
|
||||||
version = "~0.5.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "v2"
|
|
||||||
name = "github.com/coreos/go-oidc"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/mreiferson/go-options"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
version = "~1.1.4"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/oauth2"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "google.golang.org/api"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "gopkg.in/fsnotify/fsnotify.v1"
|
|
||||||
version = "~1.2.0"
|
|
||||||
|
|
||||||
[[override]]
|
|
||||||
name = "gopkg.in/fsnotify.v1"
|
|
||||||
source = "https://github.com/fsnotify/fsnotify.git"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "gopkg.in/natefinch/lumberjack.v2"
|
|
||||||
version = "2.1.0"
|
|
3
MAINTAINERS
Normal file
3
MAINTAINERS
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Joel Speed <joel.speed@hotmail.co.uk> (@JoelSpeed)
|
||||||
|
Dan Bond (@syscll)
|
||||||
|
Henry Jenkins <henry@henryjenkins.name> (@steakunderscore)
|
73
Makefile
73
Makefile
@ -4,7 +4,7 @@ VERSION := $(shell git describe --always --dirty --tags 2>/dev/null || echo "und
|
|||||||
.NOTPARALLEL:
|
.NOTPARALLEL:
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: dep lint $(BINARY)
|
all: lint $(BINARY)
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@ -15,36 +15,15 @@ clean:
|
|||||||
distclean: clean
|
distclean: clean
|
||||||
rm -rf vendor
|
rm -rf vendor
|
||||||
|
|
||||||
BIN_DIR := $(GOPATH)/bin
|
|
||||||
GOMETALINTER := $(BIN_DIR)/gometalinter
|
|
||||||
|
|
||||||
$(GOMETALINTER):
|
|
||||||
$(GO) get -u github.com/alecthomas/gometalinter
|
|
||||||
gometalinter --install %> /dev/null
|
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: $(GOMETALINTER)
|
lint:
|
||||||
$(GOMETALINTER) --vendor --disable-all \
|
GO111MODULE=on $(GOLANGCILINT) run
|
||||||
--enable=vet \
|
|
||||||
--enable=vetshadow \
|
|
||||||
--enable=golint \
|
|
||||||
--enable=ineffassign \
|
|
||||||
--enable=goconst \
|
|
||||||
--enable=deadcode \
|
|
||||||
--enable=gofmt \
|
|
||||||
--enable=goimports \
|
|
||||||
--deadline=120s \
|
|
||||||
--tests ./...
|
|
||||||
|
|
||||||
.PHONY: dep
|
|
||||||
dep:
|
|
||||||
$(DEP) ensure --vendor-only
|
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: clean $(BINARY)
|
build: clean $(BINARY)
|
||||||
|
|
||||||
$(BINARY):
|
$(BINARY):
|
||||||
CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/pusher/oauth2_proxy
|
GO111MODULE=on CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/pusher/oauth2_proxy
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
docker:
|
docker:
|
||||||
@ -75,24 +54,34 @@ docker-push-all: docker-push
|
|||||||
docker push quay.io/pusher/oauth2_proxy:${VERSION}-armv6
|
docker push quay.io/pusher/oauth2_proxy:${VERSION}-armv6
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: dep lint
|
test: lint
|
||||||
$(GO) test -v -race ./...
|
GO111MODULE=on $(GO) test -v -race ./...
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release: lint test
|
release: lint test
|
||||||
mkdir release
|
mkdir release
|
||||||
GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-darwin-amd64 github.com/pusher/oauth2_proxy
|
mkdir release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-linux-amd64 github.com/pusher/oauth2_proxy
|
mkdir release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)
|
||||||
GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-linux-arm64 github.com/pusher/oauth2_proxy
|
mkdir release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)
|
||||||
GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-linux-armv6 github.com/pusher/oauth2_proxy
|
mkdir release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)
|
||||||
GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-windows-amd64 github.com/pusher/oauth2_proxy
|
mkdir release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)
|
||||||
shasum -a 256 release/$(BINARY)-darwin-amd64 > release/$(BINARY)-darwin-amd64-sha256sum.txt
|
GO111MODULE=on GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \
|
||||||
shasum -a 256 release/$(BINARY)-linux-amd64 > release/$(BINARY)-linux-amd64-sha256sum.txt
|
-o release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
|
||||||
shasum -a 256 release/$(BINARY)-linux-arm64 > release/$(BINARY)-linux-arm64-sha256sum.txt
|
GO111MODULE=on GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \
|
||||||
shasum -a 256 release/$(BINARY)-linux-armv6 > release/$(BINARY)-linux-armv6-sha256sum.txt
|
-o release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
|
||||||
shasum -a 256 release/$(BINARY)-windows-amd64 > release/$(BINARY)-windows-amd64-sha256sum.txt
|
GO111MODULE=on GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" \
|
||||||
tar -czvf release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-darwin-amd64
|
-o release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
|
||||||
tar -czvf release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-linux-amd64
|
GO111MODULE=on GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" \
|
||||||
tar -czvf release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION).tar.gz release/$(BINARY)-linux-arm64
|
-o release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
|
||||||
tar -czvf release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION).tar.gz release/$(BINARY)-linux-armv6
|
GO111MODULE=on GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \
|
||||||
tar -czvf release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-windows-amd64
|
-o release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
|
||||||
|
shasum -a 256 release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).darwin-amd64-sha256sum.txt
|
||||||
|
shasum -a 256 release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-amd64-sha256sum.txt
|
||||||
|
shasum -a 256 release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-arm64-sha256sum.txt
|
||||||
|
shasum -a 256 release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-armv6-sha256sum.txt
|
||||||
|
shasum -a 256 release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).windows-amd64-sha256sum.txt
|
||||||
|
tar -C release -czvf release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)
|
||||||
|
tar -C release -czvf release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)
|
||||||
|
tar -C release -czvf release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)
|
||||||
|
tar -C release -czvf release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)
|
||||||
|
tar -C release -czvf release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)
|
||||||
|
@ -15,7 +15,7 @@ A list of changes can be seen in the [CHANGELOG](CHANGELOG.md).
|
|||||||
|
|
||||||
1. Choose how to deploy:
|
1. Choose how to deploy:
|
||||||
|
|
||||||
a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v3.2.0`)
|
a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v4.0.0`)
|
||||||
|
|
||||||
b. Build with `$ go get github.com/pusher/oauth2_proxy` which will put the binary in `$GOROOT/bin`
|
b. Build with `$ go get github.com/pusher/oauth2_proxy` which will put the binary in `$GOROOT/bin`
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ Prebuilt binaries can be validated by extracting the file and verifying it again
|
|||||||
|
|
||||||
```
|
```
|
||||||
sha256sum -c sha256sum.txt 2>&1 | grep OK
|
sha256sum -c sha256sum.txt 2>&1 | grep OK
|
||||||
oauth2_proxy-3.2.0.linux-amd64: OK
|
oauth2_proxy-4.0.0.linux-amd64: OK
|
||||||
```
|
```
|
||||||
|
|
||||||
2. [Select a Provider and Register an OAuth Application with a Provider](https://pusher.github.io/oauth2_proxy/auth-configuration)
|
2. [Select a Provider and Register an OAuth Application with a Provider](https://pusher.github.io/oauth2_proxy/auth-configuration)
|
||||||
@ -38,6 +38,10 @@ Read the docs on our [Docs site](https://pusher.github.io/oauth2_proxy).
|
|||||||
|
|
||||||
![OAuth2 Proxy Architecture](https://cloud.githubusercontent.com/assets/45028/8027702/bd040b7a-0d6a-11e5-85b9-f8d953d04f39.png)
|
![OAuth2 Proxy Architecture](https://cloud.githubusercontent.com/assets/45028/8027702/bd040b7a-0d6a-11e5-85b9-f8d953d04f39.png)
|
||||||
|
|
||||||
|
## Getting Involved
|
||||||
|
|
||||||
|
If you would like to reach out to the maintainers, come talk to us in the `#oauth2_proxy` channel in the [Gophers slack](http://gophers.slack.com/).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please see our [Contributing](CONTRIBUTING.md) guidelines.
|
Please see our [Contributing](CONTRIBUTING.md) guidelines.
|
||||||
|
16
configure
vendored
16
configure
vendored
@ -13,13 +13,9 @@ for arg in "$@"; do
|
|||||||
"--with-go")
|
"--with-go")
|
||||||
desired[go]="${arg##*=}"
|
desired[go]="${arg##*=}"
|
||||||
;;
|
;;
|
||||||
"--with-dep")
|
|
||||||
desired[dep]="${arg##*=}"
|
|
||||||
;;
|
|
||||||
"--help")
|
"--help")
|
||||||
printf "${GREEN}$0${NC}\n"
|
printf "${GREEN}$0${NC}\n"
|
||||||
printf " available options:\n"
|
printf " available options:\n"
|
||||||
printf " --with-dep=${BLUE}<path_to_dep_binary>${NC}\n"
|
|
||||||
printf " --with-go=${BLUE}<path_to_go_binary>${NC}\n"
|
printf " --with-go=${BLUE}<path_to_go_binary>${NC}\n"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
@ -81,7 +77,7 @@ check_for() {
|
|||||||
check_go_version() {
|
check_go_version() {
|
||||||
echo -n "Checking go version... "
|
echo -n "Checking go version... "
|
||||||
GO_VERSION=$(${tools[go]} version | ${tools[awk]} '{where = match($0, /[0-9]\.[0-9]+\.[0-9]*/); if (where != 0) print substr($0, RSTART, RLENGTH)}')
|
GO_VERSION=$(${tools[go]} version | ${tools[awk]} '{where = match($0, /[0-9]\.[0-9]+\.[0-9]*/); if (where != 0) print substr($0, RSTART, RLENGTH)}')
|
||||||
vercomp $GO_VERSION 1.11
|
vercomp $GO_VERSION 1.12
|
||||||
case $? in
|
case $? in
|
||||||
0) ;&
|
0) ;&
|
||||||
1)
|
1)
|
||||||
@ -91,7 +87,7 @@ check_go_version() {
|
|||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
printf "${RED}"
|
printf "${RED}"
|
||||||
echo "$GO_VERSION < 1.11"
|
echo "$GO_VERSION < 1.12"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -116,16 +112,14 @@ check_go_env() {
|
|||||||
|
|
||||||
cd ${0%/*}
|
cd ${0%/*}
|
||||||
|
|
||||||
if [ ! -f .env ]; then
|
rm -fv .env
|
||||||
rm .env
|
|
||||||
fi
|
|
||||||
|
|
||||||
check_for make
|
check_for make
|
||||||
check_for awk
|
check_for awk
|
||||||
check_for go
|
check_for go
|
||||||
check_go_version
|
check_go_version
|
||||||
check_go_env
|
check_go_env
|
||||||
check_for dep
|
check_for golangci-lint
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
@ -133,7 +127,7 @@ cat <<- EOF > .env
|
|||||||
MAKE := "${tools[make]}"
|
MAKE := "${tools[make]}"
|
||||||
GO := "${tools[go]}"
|
GO := "${tools[go]}"
|
||||||
GO_VERSION := ${tools[go_version]}
|
GO_VERSION := ${tools[go_version]}
|
||||||
DEP := "${tools[dep]}"
|
GOLANGCILINT := "${tools[golangci-lint]}"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Environment configuration written to .env"
|
echo "Environment configuration written to .env"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
## OAuth2 Proxy Config File
|
## OAuth2 Proxy Config File
|
||||||
## https://github.com/bitly/oauth2_proxy
|
## https://github.com/pusher/oauth2_proxy
|
||||||
|
|
||||||
## <addr>:<port> to listen on for HTTP/HTTPS clients
|
## <addr>:<port> to listen on for HTTP/HTTPS clients
|
||||||
# http_address = "127.0.0.1:4180"
|
# http_address = "127.0.0.1:4180"
|
||||||
|
@ -12,7 +12,7 @@ to validate accounts by email, domain or group.
|
|||||||
|
|
||||||
**Note:** This repository was forked from [bitly/OAuth2_Proxy](https://github.com/bitly/oauth2_proxy) on 27/11/2018.
|
**Note:** This repository was forked from [bitly/OAuth2_Proxy](https://github.com/bitly/oauth2_proxy) on 27/11/2018.
|
||||||
Versions v3.0.0 and up are from this fork and will have diverged from any changes in the original fork.
|
Versions v3.0.0 and up are from this fork and will have diverged from any changes in the original fork.
|
||||||
A list of changes can be seen in the [CHANGELOG](CHANGELOG.md).
|
A list of changes can be seen in the [CHANGELOG]({{ site.gitweb }}/CHANGELOG.md).
|
||||||
|
|
||||||
[![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy)
|
[![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy)
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ Valid providers are :
|
|||||||
- [Azure](#azure-auth-provider)
|
- [Azure](#azure-auth-provider)
|
||||||
- [Facebook](#facebook-auth-provider)
|
- [Facebook](#facebook-auth-provider)
|
||||||
- [GitHub](#github-auth-provider)
|
- [GitHub](#github-auth-provider)
|
||||||
|
- [Keycloak](#keycloak-auth-provider)
|
||||||
- [GitLab](#gitlab-auth-provider)
|
- [GitLab](#gitlab-auth-provider)
|
||||||
- [LinkedIn](#linkedin-auth-provider)
|
- [LinkedIn](#linkedin-auth-provider)
|
||||||
- [login.gov](#logingov-provider)
|
- [login.gov](#logingov-provider)
|
||||||
@ -101,15 +102,31 @@ If you are using GitHub enterprise, make sure you set the following to the appro
|
|||||||
-redeem-url="http(s)://<enterprise github host>/login/oauth/access_token"
|
-redeem-url="http(s)://<enterprise github host>/login/oauth/access_token"
|
||||||
-validate-url="http(s)://<enterprise github host>/api/v3"
|
-validate-url="http(s)://<enterprise github host>/api/v3"
|
||||||
|
|
||||||
|
### Keycloak Auth Provider
|
||||||
|
|
||||||
|
1. Create new client in your Keycloak with **Access Type** 'confidental'.
|
||||||
|
2. Create a mapper with **Mapper Type** 'Group Membership'.
|
||||||
|
|
||||||
|
Make sure you set the following to the appropriate url:
|
||||||
|
|
||||||
|
-provider=keycloak
|
||||||
|
-client-id=<client you have created>
|
||||||
|
-client-secret=<your client's secret>
|
||||||
|
-login-url="http(s)://<keycloak host>/realms/<your realm>/protocol/openid-connect/auth"
|
||||||
|
-redeem-url="http(s)://<keycloak host>/realms/master/<your realm>/openid-connect/auth/token"
|
||||||
|
-validate-url="http(s)://<keycloak host>/realms/master/<your realm>/openid-connect/userinfo"
|
||||||
|
|
||||||
### GitLab Auth Provider
|
### GitLab Auth Provider
|
||||||
|
|
||||||
Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](http://doc.gitlab.com/ce/integration/oauth_provider.html)
|
Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](https://docs.gitlab.com/ce/integration/oauth_provider.html). Make sure to enable at least the `openid`, `profile` and `email` scopes.
|
||||||
|
|
||||||
|
Restricting by group membership is possible with the following option:
|
||||||
|
|
||||||
|
-gitlab-group="": restrict logins to members of any of these groups (slug), separated by a comma
|
||||||
|
|
||||||
If you are using self-hosted GitLab, make sure you set the following to the appropriate URL:
|
If you are using self-hosted GitLab, make sure you set the following to the appropriate URL:
|
||||||
|
|
||||||
-login-url="<your gitlab url>/oauth/authorize"
|
-oidc-issuer-url="<your gitlab url>"
|
||||||
-redeem-url="<your gitlab url>/oauth/token"
|
|
||||||
-validate-url="<your gitlab url>/api/v4/user"
|
|
||||||
|
|
||||||
### LinkedIn Auth Provider
|
### LinkedIn Auth Provider
|
||||||
|
|
||||||
@ -124,7 +141,7 @@ For LinkedIn, the registration steps are:
|
|||||||
|
|
||||||
### Microsoft Azure AD Provider
|
### Microsoft Azure AD Provider
|
||||||
|
|
||||||
For adding an application to the Microsoft Azure AD follow [these steps to add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/).
|
For adding an application to the Microsoft Azure AD follow [these steps to add an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app).
|
||||||
|
|
||||||
Take note of your `TenantId` if applicable for your situation. The `TenantId` can be used to override the default `common` authorization server with a tenant specific server.
|
Take note of your `TenantId` if applicable for your situation. The `TenantId` can be used to override the default `common` authorization server with a tenant specific server.
|
||||||
|
|
||||||
@ -144,6 +161,56 @@ OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many ma
|
|||||||
-cookie-secure=false
|
-cookie-secure=false
|
||||||
-email-domain example.com
|
-email-domain example.com
|
||||||
|
|
||||||
|
The OpenID Connect Provider (OIDC) can also be used to connect to other Identity Providers such as Okta. To configure the OIDC provider for Okta, perform
|
||||||
|
the following steps:
|
||||||
|
|
||||||
|
#### Configuring the OIDC Provider with Okta
|
||||||
|
|
||||||
|
1. Log in to Okta using an administrative account. It is suggested you try this in preview first, `example.oktapreview.com`
|
||||||
|
2. (OPTIONAL) If you want to configure authorization scopes and claims to be passed on to multiple applications,
|
||||||
|
you may wish to configure an authorization server for each application. Otherwise, the provided `default` will work.
|
||||||
|
* Navigate to **Security** then select **API**
|
||||||
|
* Click **Add Authorization Server**, if this option is not available you may require an additional license for a custom authorization server.
|
||||||
|
* Fill out the **Name** with something to describe the application you are protecting. e.g. 'Example App'.
|
||||||
|
* For **Audience**, pick the URL of the application you wish to protect: https://example.corp.com
|
||||||
|
* Fill out a **Description**
|
||||||
|
* Add any **Access Policies** you wish to configure to limit application access.
|
||||||
|
* The default settings will work for other options.
|
||||||
|
[See Okta documentation for more information on Authorization Servers](https://developer.okta.com/docs/guides/customize-authz-server/overview/)
|
||||||
|
3. Navigate to **Applications** then select **Add Application**.
|
||||||
|
* Select **Web** for the **Platform** setting.
|
||||||
|
* Select **OpenID Connect** and click **Create**
|
||||||
|
* Pick an **Application Name** such as `Example App`.
|
||||||
|
* Set the **Login redirect URI** to `https://example.corp.com`.
|
||||||
|
* Under **General** set the **Allowed grant types** to `Authorization Code` and `Refresh Token`.
|
||||||
|
* Leave the rest as default, taking note of the `Client ID` and `Client Secret`.
|
||||||
|
* Under **Assignments** select the users or groups you wish to access your application.
|
||||||
|
4. Create a configuration file like the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
provider = "oidc"
|
||||||
|
redirect_url = "https://example.corp.com"
|
||||||
|
oidc_issuer_url = "https://corp.okta.com/oauth2/abCd1234"
|
||||||
|
upstreams = [
|
||||||
|
"https://example.corp.com"
|
||||||
|
]
|
||||||
|
email_domains = [
|
||||||
|
"corp.com"
|
||||||
|
]
|
||||||
|
client_id = "XXXXX"
|
||||||
|
client_secret = "YYYYY"
|
||||||
|
pass_access_token = true
|
||||||
|
cookie_secret = "ZZZZZ"
|
||||||
|
skip_provider_button = true
|
||||||
|
```
|
||||||
|
|
||||||
|
The `oidc_issuer_url` is based on URL from your **Authorization Server**'s **Issuer** field in step 2, or simply https://corp.okta.com
|
||||||
|
The `client_id` and `client_secret` are configured in the application settings.
|
||||||
|
Generate a unique `client_secret` to encrypt the cookie.
|
||||||
|
|
||||||
|
Then you can start the oauth2_proxy with `./oauth2_proxy -config /etc/example.cfg`
|
||||||
|
|
||||||
|
|
||||||
### login.gov Provider
|
### login.gov Provider
|
||||||
|
|
||||||
login.gov is an OIDC provider for the US Government.
|
login.gov is an OIDC provider for the US Government.
|
||||||
@ -225,7 +292,7 @@ To authorize by email domain use `--email-domain=yourcompany.com`. To authorize
|
|||||||
|
|
||||||
## Adding a new Provider
|
## Adding a new Provider
|
||||||
|
|
||||||
Follow the examples in the [`providers` package](providers/) to define a new
|
Follow the examples in the [`providers` package]({{ site.gitweb }}/providers/) to define a new
|
||||||
`Provider` instance. Add a new `case` to
|
`Provider` instance. Add a new `case` to
|
||||||
[`providers.New()`](providers/providers.go) to allow `oauth2_proxy` to use the
|
[`providers.New()`]({{ site.gitweb }}/providers/providers.go) to allow `oauth2_proxy` to use the
|
||||||
new `Provider`.
|
new `Provider`.
|
||||||
|
@ -9,7 +9,7 @@ nav_order: 4
|
|||||||
|
|
||||||
There are two recommended configurations.
|
There are two recommended configurations.
|
||||||
|
|
||||||
1. Configure SSL Termination with OAuth2 Proxy by providing a `--tls-cert=/path/to/cert.pem` and `--tls-key=/path/to/cert.key`.
|
1. Configure SSL Termination with OAuth2 Proxy by providing a `--tls-cert-file=/path/to/cert.pem` and `--tls-key-file=/path/to/cert.key`.
|
||||||
|
|
||||||
The command line to run `oauth2_proxy` in this configuration would look like this:
|
The command line to run `oauth2_proxy` in this configuration would look like this:
|
||||||
|
|
||||||
@ -17,8 +17,8 @@ The command line to run `oauth2_proxy` in this configuration would look like thi
|
|||||||
./oauth2_proxy \
|
./oauth2_proxy \
|
||||||
--email-domain="yourcompany.com" \
|
--email-domain="yourcompany.com" \
|
||||||
--upstream=http://127.0.0.1:8080/ \
|
--upstream=http://127.0.0.1:8080/ \
|
||||||
--tls-cert=/path/to/cert.pem \
|
--tls-cert-file=/path/to/cert.pem \
|
||||||
--tls-key=/path/to/cert.key \
|
--tls-key-file=/path/to/cert.key \
|
||||||
--cookie-secret=... \
|
--cookie-secret=... \
|
||||||
--cookie-secure=true \
|
--cookie-secure=true \
|
||||||
--provider=... \
|
--provider=... \
|
||||||
|
@ -11,7 +11,7 @@ If `signature_key` is defined, proxied requests will be signed with the
|
|||||||
`GAP-Signature` header, which is a [Hash-based Message Authentication Code
|
`GAP-Signature` header, which is a [Hash-based Message Authentication Code
|
||||||
(HMAC)](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)
|
(HMAC)](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)
|
||||||
of selected request information and the request body [see `SIGNATURE_HEADERS`
|
of selected request information and the request body [see `SIGNATURE_HEADERS`
|
||||||
in `oauthproxy.go`](./oauthproxy.go).
|
in `oauthproxy.go`]({{ site.gitweb }}/oauthproxy.go).
|
||||||
|
|
||||||
`signature_key` must be of the form `algorithm:secretkey`, (ie: `signature_key = "sha1:secret0"`)
|
`signature_key` must be of the form `algorithm:secretkey`, (ie: `signature_key = "sha1:secret0"`)
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ GEM
|
|||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
minitest (5.11.3)
|
minitest (5.11.3)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nokogiri (1.10.1)
|
nokogiri (1.10.4)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
octokit (4.13.0)
|
octokit (4.13.0)
|
||||||
sawyer (~> 0.8.0, >= 0.5.3)
|
sawyer (~> 0.8.0, >= 0.5.3)
|
||||||
|
@ -18,6 +18,7 @@ description: >- # this means to ignore newlines until "baseurl:"
|
|||||||
OAuth2_Proxy documentation site
|
OAuth2_Proxy documentation site
|
||||||
baseurl: "/oauth2_proxy" # the subpath of your site, e.g. /blog
|
baseurl: "/oauth2_proxy" # the subpath of your site, e.g. /blog
|
||||||
url: "https://pusher.github.io" # the base hostname & protocol for your site, e.g. http://example.com
|
url: "https://pusher.github.io" # the base hostname & protocol for your site, e.g. http://example.com
|
||||||
|
gitweb: "https://github.com/pusher/oauth2_proxy/blob/master"
|
||||||
|
|
||||||
# Build settings
|
# Build settings
|
||||||
markdown: kramdown
|
markdown: kramdown
|
||||||
|
@ -14,89 +14,101 @@ To generate a strong cookie secret use `python -c 'import os,base64; print base6
|
|||||||
|
|
||||||
### Config File
|
### Config File
|
||||||
|
|
||||||
An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is in the contrib directory. It can be used by specifying `-config=/etc/oauth2_proxy.cfg`
|
An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example) config file is in the contrib directory. It can be used by specifying `-config=/etc/oauth2_proxy.cfg`
|
||||||
|
|
||||||
### Command Line Options
|
### Command Line Options
|
||||||
|
|
||||||
```
|
| Option | Type | Description | Default |
|
||||||
Usage of oauth2_proxy:
|
| ------ | ---- | ----------- | ------- |
|
||||||
-acr-values string: optional, used by login.gov (default "http://idmanagement.gov/ns/assurance/loa/1")
|
| `-acr-values` | string | optional, used by login.gov | `"http://idmanagement.gov/ns/assurance/loa/1"` |
|
||||||
-approval-prompt string: OAuth approval_prompt (default "force")
|
| `-approval-prompt` | string | OAuth approval_prompt | `"force"` |
|
||||||
-auth-logging: Log authentication attempts (default true)
|
| `-auth-logging` | bool | Log authentication attempts | true |
|
||||||
-auth-logging-format string: Template for authentication log lines (see "Logging Configuration" paragraph below)
|
| `-auth-logging-format` | string | Template for authentication log lines | see [Logging Configuration](#logging-configuration) |
|
||||||
-authenticated-emails-file string: authenticate against emails via file (one per line)
|
| `-authenticated-emails-file` | string | authenticate against emails via file (one per line) | |
|
||||||
-azure-tenant string: go to a tenant-specific or common (tenant-independent) endpoint. (default "common")
|
| `-azure-tenant string` | string | go to a tenant-specific or common (tenant-independent) endpoint. | `"common"` |
|
||||||
-basic-auth-password string: the password to set when passing the HTTP Basic Auth header
|
| `-basic-auth-password` | string | the password to set when passing the HTTP Basic Auth header | |
|
||||||
-client-id string: the OAuth Client ID: ie: "123456.apps.googleusercontent.com"
|
| `-client-id` | string | the OAuth Client ID: ie: `"123456.apps.googleusercontent.com"` | |
|
||||||
-client-secret string: the OAuth Client Secret
|
| `-client-secret` | string | the OAuth Client Secret | |
|
||||||
-config string: path to config file
|
| `-config` | string | path to config file | |
|
||||||
-cookie-domain string: an optional cookie domain to force cookies to (ie: .yourcompany.com)
|
| `-cookie-domain` | string | an optional cookie domain to force cookies to (ie: `.yourcompany.com`) | |
|
||||||
-cookie-expire duration: expire timeframe for cookie (default 168h0m0s)
|
| `-cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
|
||||||
-cookie-httponly: set HttpOnly cookie flag (default true)
|
| `-cookie-httponly` | bool | set HttpOnly cookie flag | true |
|
||||||
-cookie-name string: the name of the cookie that the oauth_proxy creates (default "_oauth2_proxy")
|
| `-cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
|
||||||
-cookie-path string: an optional cookie path to force cookies to (ie: /poc/)* (default "/")
|
| `-cookie-path` | string | an optional cookie path to force cookies to (ie: `/poc/`) | `"/"` |
|
||||||
-cookie-refresh duration: refresh the cookie after this duration; 0 to disable
|
| `-cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable | |
|
||||||
-cookie-secret string: the seed string for secure cookies (optionally base64 encoded)
|
| `-cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
|
||||||
-cookie-secure: set secure (HTTPS) cookie flag (default true)
|
| `-cookie-secure` | bool | set secure (HTTPS) cookie flag | true |
|
||||||
-custom-templates-dir string: path to custom html templates
|
| `-custom-templates-dir` | string | path to custom html templates | |
|
||||||
-display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true)
|
| `-display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true |
|
||||||
-email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
|
| `-email-domain` | string | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | |
|
||||||
-flush-interval: period between flushing response buffers when streaming responses (default "1s")
|
| `-extra-jwt-issuers` | string | if `-skip-jwt-bearer-tokens` is set, a list of extra JWT `issuer=audience` pairs (where the issuer URL has a `.well-known/openid-configuration` or a `.well-known/jwks.json`) | |
|
||||||
-footer string: custom footer string. Use "-" to disable default footer.
|
| `-exclude-logging-paths` | string | comma separated list of paths to exclude from logging, eg: `"/ping,/path2"` |`""` (no paths excluded) |
|
||||||
-gcp-healthchecks: will enable /liveness_check, /readiness_check, and / (with the proper user-agent) endpoints that will make it work well with GCP App Engine and GKE Ingresses (default false)
|
| `-flush-interval` | duration | period between flushing response buffers when streaming responses | `"1s"` |
|
||||||
-github-org string: restrict logins to members of this organisation
|
| `-banner` | string | custom banner string. Use `"-"` to disable default banner. | |
|
||||||
-github-team string: restrict logins to members of any of these teams (slug), separated by a comma
|
| `-footer` | string | custom footer string. Use `"-"` to disable default footer. | |
|
||||||
-google-admin-email string: the google admin to impersonate for api calls
|
| `-gcp-healthchecks` | bool | will enable `/liveness_check`, `/readiness_check`, and `/` (with the proper user-agent) endpoints that will make it work well with GCP App Engine and GKE Ingresses | false |
|
||||||
-google-group value: restrict logins to members of this google group (may be given multiple times).
|
| `-github-org` | string | restrict logins to members of this organisation | |
|
||||||
-google-service-account-json string: the path to the service account json credentials
|
| `-github-team` | string | restrict logins to members of any of these teams (slug), separated by a comma | |
|
||||||
-htpasswd-file string: additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
|
| `-gitlab-group` | string | restrict logins to members of any of these groups (slug), separated by a comma | |
|
||||||
-http-address string: [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients (default "127.0.0.1:4180")
|
| `-google-admin-email` | string | the google admin to impersonate for api calls | |
|
||||||
-https-address string: <addr>:<port> to listen on for HTTPS clients (default ":443")
|
| `-google-group` | string | restrict logins to members of this google group (may be given multiple times). | |
|
||||||
-logging-compress: Should rotated log files be compressed using gzip (default false)
|
| `-google-service-account-json` | string | the path to the service account json credentials | |
|
||||||
-logging-filename string: File to log requests to, empty for stdout (default to stdout)
|
| `-htpasswd-file` | string | additionally authenticate against a htpasswd file. Entries must be created with `htpasswd -s` for SHA encryption | |
|
||||||
-logging-local-time: If the time in log files and backup filenames are local or UTC time (default true)
|
| `-http-address` | string | `[http://]<addr>:<port>` or `unix://<path>` to listen on for HTTP clients | `"127.0.0.1:4180"` |
|
||||||
-logging-max-age int: Maximum number of days to retain old log files (default 7)
|
| `-https-address` | string | `<addr>:<port>` to listen on for HTTPS clients | `":443"` |
|
||||||
-logging-max-backups int: Maximum number of old log files to retain; 0 to disable (default 0)
|
| `-logging-compress` | bool | Should rotated log files be compressed using gzip | false |
|
||||||
-logging-max-size int: Maximum size in megabytes of the log file before rotation (default 100)
|
| `-logging-filename` | string | File to log requests to, empty for `stdout` | `""` (stdout) |
|
||||||
-jwt-key string: private key in PEM format used to sign JWT, so that you can say something like -jwt-key="${OAUTH2_PROXY_JWT_KEY}": required by login.gov
|
| `-logging-local-time` | bool | Use local time in log files and backup filenames instead of UTC | true (local time) |
|
||||||
-jwt-key-file string: path to the private key file in PEM format used to sign the JWT so that you can say something like -jwt-key-file=/etc/ssl/private/jwt_signing_key.pem: required by login.gov
|
| `-logging-max-age` | int | Maximum number of days to retain old log files | 7 |
|
||||||
-login-url string: Authentication endpoint
|
| `-logging-max-backups` | int | Maximum number of old log files to retain; 0 to disable | 0 |
|
||||||
-oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com"
|
| `-logging-max-size` | int | Maximum size in megabytes of the log file before rotation | 100 |
|
||||||
-oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled
|
| `-jwt-key` | string | private key in PEM format used to sign JWT, so that you can say something like `-jwt-key="${OAUTH2_PROXY_JWT_KEY}"`: required by login.gov | |
|
||||||
-pass-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header
|
| `-jwt-key-file` | string | path to the private key file in PEM format used to sign the JWT so that you can say something like `-jwt-key-file=/etc/ssl/private/jwt_signing_key.pem`: required by login.gov | |
|
||||||
-pass-authorization-header: pass OIDC IDToken to upstream via Authorization Bearer header
|
| `-login-url` | string | Authentication endpoint | |
|
||||||
-pass-basic-auth: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream (default true)
|
| `-insecure-oidc-allow-unverified-email` | bool | don't fail if an email address in an id_token is not verified | false |
|
||||||
-pass-host-header: pass the request Host Header to upstream (default true)
|
| `-oidc-issuer-url` | string | the OpenID Connect issuer URL. ie: `"https://accounts.google.com"` | |
|
||||||
-pass-user-headers: pass X-Forwarded-User and X-Forwarded-Email information to upstream (default true)
|
| `-oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | |
|
||||||
-profile-url string: Profile access endpoint
|
| `-pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header | false |
|
||||||
-provider string: OAuth provider (default "google")
|
| `-pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false |
|
||||||
-proxy-prefix string: the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) (default "/oauth2")
|
| `-pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream | true |
|
||||||
-proxy-websockets: enables WebSocket proxying (default true)
|
| `-pass-host-header` | bool | pass the request Host Header to upstream | true |
|
||||||
-pubjwk-url string: JWK pubkey access endpoint: required by login.gov
|
| `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true |
|
||||||
-redeem-url string: Token redemption endpoint
|
| `-profile-url` | string | Profile access endpoint | |
|
||||||
-redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
|
| `-provider` | string | OAuth provider | google |
|
||||||
-request-logging: Log requests to stdout (default true)
|
| `-ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
|
||||||
-request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below)
|
| `-proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`<oauth2>/sign_in`) | `"/oauth2"` |
|
||||||
-resource string: The resource that is protected (Azure AD only)
|
| `-proxy-websockets` | bool | enables WebSocket proxying | true |
|
||||||
-scope string: OAuth scope specification
|
| `-pubjwk-url` | string | JWK pubkey access endpoint: required by login.gov | |
|
||||||
-session-store-type: Session data storage backend (default: cookie)
|
| `-redeem-url` | string | Token redemption endpoint | |
|
||||||
-set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)
|
| `-redirect-url` | string | the OAuth Redirect URL. ie: `"https://internalapp.yourcompany.com/oauth2/callback"` | |
|
||||||
-set-authorization-header: set Authorization Bearer response header (useful in Nginx auth_request mode)
|
| `-redis-connection-url` | string | URL of redis server for redis session storage (eg: `redis://HOST[:PORT]`) | |
|
||||||
-signature-key string: GAP-Signature request signature key (algorithm:secretkey)
|
| `-redis-sentinel-master-name` | string | Redis sentinel master name. Used in conjunction with `--redis-use-sentinel` | |
|
||||||
-skip-auth-preflight: will skip authentication for OPTIONS requests
|
| `-redis-sentinel-connection-urls` | string \| list | List of Redis sentinel connection URLs (eg `redis://HOST[:PORT]`). Used in conjunction with `--redis-use-sentinel` | |
|
||||||
-skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times)
|
| `-redis-use-sentinel` | bool | Connect to redis via sentinels. Must set `--redis-sentinel-master-name` and `--redis-sentinel-connection-urls` to use this feature | false |
|
||||||
-skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case
|
| `-request-logging` | bool | Log requests | true |
|
||||||
-skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start
|
| `-request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) |
|
||||||
-ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS
|
| `-resource` | string | The resource that is protected (Azure AD only) | |
|
||||||
-standard-logging: Log standard runtime information (default true)
|
| `-scope` | string | OAuth scope specification | |
|
||||||
-standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below)
|
| `-session-store-type` | string | Session data storage backend | cookie |
|
||||||
-tls-cert string: path to certificate file
|
| `-set-xauthrequest` | bool | set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) | false |
|
||||||
-tls-key string: path to private key file
|
| `-set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false |
|
||||||
-upstream value: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
|
| `-signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
|
||||||
-validate-url string: Access token validation endpoint
|
| `-silence-ping-logging` | bool | disable logging of requests to ping endpoint | false |
|
||||||
-version: print version string
|
| `-skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false |
|
||||||
-whitelist-domain: allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)
|
| `-skip-auth-regex` | string | bypass authentication for requests paths that match (may be given multiple times) | |
|
||||||
```
|
| `-skip-jwt-bearer-tokens` | bool | will skip requests that have verified JWT bearer tokens | false |
|
||||||
|
| `-skip-oidc-discovery` | bool | bypass OIDC endpoint discovery. `-login-url`, `-redeem-url` and `-oidc-jwks-url` must be configured in this case | false |
|
||||||
|
| `-skip-provider-button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false |
|
||||||
|
| `-ssl-insecure-skip-verify` | bool | skip validation of certificates presented when using HTTPS providers | false |
|
||||||
|
| `-ssl-upstream-insecure-skip-verify` | bool | skip validation of certificates presented when using HTTPS upstreams | false |
|
||||||
|
| `-standard-logging` | bool | Log standard runtime information | true |
|
||||||
|
| `-standard-logging-format` | string | Template for standard log lines | see [Logging Configuration](#logging-configuration) |
|
||||||
|
| `-tls-cert-file` | string | path to certificate file | |
|
||||||
|
| `-tls-key-file` | string | path to private key file | |
|
||||||
|
| `-upstream` | string \| list | the http url(s) of the upstream endpoint or `file://` paths for static files. Routing is based on the path | |
|
||||||
|
| `-validate-url` | string | Access token validation endpoint | |
|
||||||
|
| `-version` | n/a | print version string | |
|
||||||
|
| `-whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (eg `.example.com`) | |
|
||||||
|
|
||||||
Note, when using the `whitelist-domain` option, any domain prefixed with a `.` will allow any subdomain of the specified domain as a valid redirect URL.
|
Note, when using the `whitelist-domain` option, any domain prefixed with a `.` will allow any subdomain of the specified domain as a valid redirect URL.
|
||||||
|
|
||||||
@ -112,17 +124,16 @@ Multiple upstreams can either be configured by supplying a comma separated list
|
|||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
The following environment variables can be used in place of the corresponding command-line arguments:
|
Every command line argument can be specified as an environment variable by
|
||||||
|
prefixing it with `OAUTH2_PROXY_`, capitalising it, and replacing hypens (`-`)
|
||||||
|
with underscores (`_`). If the argument can be specified multiple times, the
|
||||||
|
environment variable should be plural (trailing `S`).
|
||||||
|
|
||||||
- `OAUTH2_PROXY_CLIENT_ID`
|
This is particularly useful for storing secrets outside of a configuration file
|
||||||
- `OAUTH2_PROXY_CLIENT_SECRET`
|
or the command line.
|
||||||
- `OAUTH2_PROXY_COOKIE_NAME`
|
|
||||||
- `OAUTH2_PROXY_COOKIE_SECRET`
|
For example, the `--cookie-secret` flag becomes `OAUTH2_PROXY_COOKIE_SECRET`,
|
||||||
- `OAUTH2_PROXY_COOKIE_DOMAIN`
|
and the `--email-domain` flag becomes `OAUTH2_PROXY_EMAIL_DOMAINS`.
|
||||||
- `OAUTH2_PROXY_COOKIE_PATH`
|
|
||||||
- `OAUTH2_PROXY_COOKIE_EXPIRE`
|
|
||||||
- `OAUTH2_PROXY_COOKIE_REFRESH`
|
|
||||||
- `OAUTH2_PROXY_SIGNATURE_KEY`
|
|
||||||
|
|
||||||
## Logging Configuration
|
## Logging Configuration
|
||||||
|
|
||||||
@ -134,6 +145,8 @@ There are three different types of logging: standard, authentication, and HTTP r
|
|||||||
|
|
||||||
Each type of logging has their own configurable format and variables. By default these formats are similar to the Apache Combined Log.
|
Each type of logging has their own configurable format and variables. By default these formats are similar to the Apache Combined Log.
|
||||||
|
|
||||||
|
Logging of requests to the `/ping` endpoint can be disabled with `-silence-ping-logging` reducing log volume. This flag appends the `-ping-path` to `-exclude-logging-paths`.
|
||||||
|
|
||||||
### Auth Log Format
|
### Auth Log Format
|
||||||
Authentication logs are logs which are guaranteed to contain a username or email address of a user attempting to authenticate. These logs are output by default in the below format:
|
Authentication logs are logs which are guaranteed to contain a username or email address of a user attempting to authenticate. These logs are output by default in the below format:
|
||||||
|
|
||||||
@ -306,3 +319,5 @@ nginx.ingress.kubernetes.io/configuration-snippet: |
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You have to substitute *name* with the actual cookie name you configured via --cookie-name parameter. If you don't set a custom cookie name the variable should be "$upstream_cookie__oauth2_proxy_1" instead of "$upstream_cookie_name_1" and the new cookie-name should be "_oauth2_proxy_1=" instead of "name_1=".
|
||||||
|
@ -15,7 +15,8 @@ The OAuth2 Proxy uses a Cookie to track user sessions and will store the session
|
|||||||
data in one of the available session storage backends.
|
data in one of the available session storage backends.
|
||||||
|
|
||||||
At present the available backends are (as passed to `--session-store-type`):
|
At present the available backends are (as passed to `--session-store-type`):
|
||||||
- [cookie](cookie-storage) (deafult)
|
- [cookie](#cookie-storage) (default)
|
||||||
|
- [redis](#redis-storage)
|
||||||
|
|
||||||
### Cookie Storage
|
### Cookie Storage
|
||||||
|
|
||||||
@ -32,3 +33,35 @@ The following should be known when using this implementation:
|
|||||||
- Since multiple requests can be made concurrently to the OAuth2 Proxy, this session implementation
|
- Since multiple requests can be made concurrently to the OAuth2 Proxy, this session implementation
|
||||||
cannot lock sessions and while updating and refreshing sessions, there can be conflicts which force
|
cannot lock sessions and while updating and refreshing sessions, there can be conflicts which force
|
||||||
users to re-authenticate
|
users to re-authenticate
|
||||||
|
|
||||||
|
|
||||||
|
### Redis Storage
|
||||||
|
|
||||||
|
The Redis Storage backend stores sessions, encrypted, in redis. Instead sending all the information
|
||||||
|
back the the client for storage, as in the [Cookie storage](cookie-storage), a ticket is sent back
|
||||||
|
to the user as the cookie value instead.
|
||||||
|
|
||||||
|
A ticket is composed as the following:
|
||||||
|
|
||||||
|
`{CookieName}-{ticketID}.{secret}`
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
- The `CookieName` is the OAuth2 cookie name (_oauth2_proxy by default)
|
||||||
|
- The `ticketID` is a 128 bit random number, hex-encoded
|
||||||
|
- The `secret` is a 128 bit random number, base64url encoded (no padding). The secret is unique for every session.
|
||||||
|
- The pair of `{CookieName}-{ticketID}` comprises a ticket handle, and thus, the redis key
|
||||||
|
to which the session is stored. The encoded session is encrypted with the secret and stored
|
||||||
|
in redis via the `SETEX` command.
|
||||||
|
|
||||||
|
Encrypting every session uniquely protects the refresh/access/id tokens stored in the session from
|
||||||
|
disclosure.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
When using the redis store, specify `--session-store-type=redis` as well as the Redis connection URL, via
|
||||||
|
`--redis-connection-url=redis://host[:port][/db-number]`.
|
||||||
|
|
||||||
|
You may also configure the store for Redis Sentinel. In this case, you will want to use the
|
||||||
|
`--redis-use-sentinel=true` flag, as well as configure the flags `--redis-sentinel-master-name`
|
||||||
|
and `--redis-sentinel-connection-urls` appropriately.
|
||||||
|
87
go.mod
Normal file
87
go.mod
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
module github.com/pusher/oauth2_proxy
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go v0.41.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v0.3.1
|
||||||
|
github.com/OpenPeeDeeP/depguard v1.0.0 // indirect
|
||||||
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||||
|
github.com/alicebob/miniredis v0.0.0-20190417180845-3d7aa1333af5
|
||||||
|
github.com/bitly/go-simplejson v0.5.0
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||||
|
github.com/coreos/bbolt v1.3.3 // indirect
|
||||||
|
github.com/coreos/etcd v3.3.13+incompatible // indirect
|
||||||
|
github.com/coreos/go-oidc v2.0.0+incompatible
|
||||||
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a // indirect
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
|
github.com/fatih/color v1.7.0 // indirect
|
||||||
|
github.com/go-critic/go-critic v0.3.4 // indirect
|
||||||
|
github.com/go-kit/kit v0.9.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
|
||||||
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 // indirect
|
||||||
|
github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0 // indirect
|
||||||
|
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d // indirect
|
||||||
|
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 // indirect
|
||||||
|
github.com/golangci/golangci-lint v1.17.1 // indirect
|
||||||
|
github.com/golangci/gosec v0.0.0-20180901114220-8afd9cbb6cfb // indirect
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 // indirect
|
||||||
|
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 // indirect
|
||||||
|
github.com/gostaticanalysis/analysisutil v0.0.2 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.4 // indirect
|
||||||
|
github.com/kisielk/errcheck v1.2.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.7.2 // indirect
|
||||||
|
github.com/klauspost/cpuid v1.2.1 // indirect
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
|
github.com/kr/pty v1.1.8 // indirect
|
||||||
|
github.com/logrusorgru/aurora v0.0.0-20190428105938-cea283e61946 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||||
|
github.com/mbland/hmacauth v0.0.0-20170912224942-107c17adcc5e
|
||||||
|
github.com/mreiferson/go-options v0.0.0-20190302064952-20ba7d382d05
|
||||||
|
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect
|
||||||
|
github.com/onsi/ginkgo v1.8.0
|
||||||
|
github.com/onsi/gomega v1.5.0
|
||||||
|
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||||
|
github.com/pkg/errors v0.8.1 // indirect
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect
|
||||||
|
github.com/prometheus/common v0.6.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.0.3 // indirect
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0 // indirect
|
||||||
|
github.com/russross/blackfriday v2.0.0+incompatible // indirect
|
||||||
|
github.com/shirou/gopsutil v2.18.12+incompatible // indirect
|
||||||
|
github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||||
|
github.com/spf13/afero v1.2.2 // indirect
|
||||||
|
github.com/spf13/cobra v0.0.5 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/viper v1.4.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.2.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
|
github.com/timakin/bodyclose v0.0.0-20190713050349-d96ec0dee822 // indirect
|
||||||
|
github.com/ugorji/go v1.1.7 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.4.0 // indirect
|
||||||
|
github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
|
||||||
|
go.etcd.io/bbolt v1.3.3 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
||||||
|
golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f // indirect
|
||||||
|
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 // indirect
|
||||||
|
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||||
|
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
|
||||||
|
golang.org/x/tools v0.0.0-20190712213246-8b927904ee0d // indirect
|
||||||
|
google.golang.org/api v0.7.0
|
||||||
|
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 // indirect
|
||||||
|
google.golang.org/grpc v1.22.0 // indirect
|
||||||
|
gopkg.in/fsnotify/fsnotify.v1 v1.2.11
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3
|
||||||
|
gopkg.in/square/go-jose.v2 v2.1.3
|
||||||
|
mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe // indirect
|
||||||
|
sourcegraph.com/sqs/pbtypes v1.0.0 // indirect
|
||||||
|
)
|
531
go.sum
Normal file
531
go.sum
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
cloud.google.com/go v0.16.0 h1:alV/SO2XpH+lrvqjDl94dYez7FfeT8ptayazgWwHPIU=
|
||||||
|
cloud.google.com/go v0.16.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/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.41.0 h1:NFvqUTDnSNYPX5oReekmB+D+90jrJIcVImxQ3qrBVgM=
|
||||||
|
cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=
|
||||||
|
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
|
||||||
|
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||||
|
github.com/OpenPeeDeeP/depguard v0.0.0-20180806142446-a69c782687b2/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||||
|
github.com/OpenPeeDeeP/depguard v1.0.0 h1:k9QF73nrHT3nPLz3lu6G5s+3Hi8Je36ODr1F5gjAXXM=
|
||||||
|
github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||||
|
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
|
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/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
|
||||||
|
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
|
github.com/alicebob/miniredis v0.0.0-20190417180845-3d7aa1333af5 h1:+xnalaRl7JEs6xynGsLgGilz75ljDYZTFKuCadGquPY=
|
||||||
|
github.com/alicebob/miniredis v0.0.0-20190417180845-3d7aa1333af5/go.mod h1:8cBZ4R1fh1lx8l4UVit3jNxyybdDi+rjnukCwTYVQE0=
|
||||||
|
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 v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||||
|
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
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/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
|
github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5LbakNExNmZIg=
|
||||||
|
github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-semver v0.3.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-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
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/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-critic/go-critic v0.0.0-20181204210945-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||||
|
github.com/go-critic/go-critic v0.3.4 h1:FYaiaLjX0Nqei80KPhm4CyFQUBbmJwSrHxQ73taaGBc=
|
||||||
|
github.com/go-critic/go-critic v0.3.4/go.mod h1:AHR42Lk/E/aOznsrYdMYeIQS5RH10HZHSqP+rD6AJrc=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-lintpack/lintpack v0.5.1/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||||
|
github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0=
|
||||||
|
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||||
|
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-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||||
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-toolsmith/astcast v0.0.0-20181028201508-b7a89ed70af1/go.mod h1:TEo3Ghaj7PsZawQHxT/oBvo4HK/sl1RcuUHDKTTju+o=
|
||||||
|
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
|
||||||
|
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||||
|
github.com/go-toolsmith/astcopy v0.0.0-20180903214859-79b422d080c4/go.mod h1:c9CPdq2AzM8oPomdlPniEfPAC6g1s7NqZzODt8y6ib8=
|
||||||
|
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
|
||||||
|
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
|
||||||
|
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||||
|
github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ=
|
||||||
|
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||||
|
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
|
||||||
|
github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k=
|
||||||
|
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
|
||||||
|
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
|
||||||
|
github.com/go-toolsmith/astinfo v1.0.0/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
|
||||||
|
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
|
||||||
|
github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg=
|
||||||
|
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
|
||||||
|
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
|
||||||
|
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
|
||||||
|
github.com/go-toolsmith/strparse v0.0.0-20180903215201-830b6daa1241/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||||
|
github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4=
|
||||||
|
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||||
|
github.com/go-toolsmith/typep v0.0.0-20181030061450-d63dc7650676/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||||
|
github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA=
|
||||||
|
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||||
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.0.0/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/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 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
|
||||||
|
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||||
|
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM=
|
||||||
|
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
|
||||||
|
github.com/golangci/errcheck v0.0.0-20181003203344-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
|
||||||
|
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w=
|
||||||
|
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
|
||||||
|
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw=
|
||||||
|
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
|
||||||
|
github.com/golangci/go-tools v0.0.0-20180109140146-af6baa5dc196/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM=
|
||||||
|
github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0 h1:MRhC9XbUjE6XDOInSJ8pwHuPagqsyO89QDU9IdVhe3o=
|
||||||
|
github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM=
|
||||||
|
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8=
|
||||||
|
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
|
||||||
|
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||||
|
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d h1:pXTK/gkVNs7Zyy7WKgLXmpQ5bHTrq5GDsp8R9Qs67g0=
|
||||||
|
github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||||
|
github.com/golangci/gofmt v0.0.0-20181105071733-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||||
|
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiDKfK9XHgmc2wbDlRMD2hD8anAJHU=
|
||||||
|
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||||
|
github.com/golangci/golangci-lint v1.17.1 h1:lc8Hf9GPCjIr0hg3S/xhvFT1+Hydass8F1xchr8jkME=
|
||||||
|
github.com/golangci/golangci-lint v1.17.1/go.mod h1:+5sJSl2h3aly+fpmL2meSP8CaSKua2E4Twi9LPy7b1g=
|
||||||
|
github.com/golangci/gosec v0.0.0-20180901114220-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU=
|
||||||
|
github.com/golangci/gosec v0.0.0-20180901114220-8afd9cbb6cfb h1:Bi7BYmZVg4C+mKGi8LeohcP2GGUl2XJD4xCkJoZSaYc=
|
||||||
|
github.com/golangci/gosec v0.0.0-20180901114220-8afd9cbb6cfb/go.mod h1:ON/c2UR0VAAv6ZEAFKhjCLplESSmRFfZcDLASbI1GWo=
|
||||||
|
github.com/golangci/ineffassign v0.0.0-20180808204949-42439a7714cc h1:XRFao922N8F3EcIXBSNX8Iywk+GI0dxD/8FicMX2D/c=
|
||||||
|
github.com/golangci/ineffassign v0.0.0-20180808204949-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20180610141402-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4=
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||||
|
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA=
|
||||||
|
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
|
||||||
|
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk=
|
||||||
|
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
|
||||||
|
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us=
|
||||||
|
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
|
||||||
|
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||||
|
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 h1:XQKc8IYQOeRwVs36tDrEmTgDgP88d5iEURwpmtiAlOM=
|
||||||
|
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||||
|
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
|
||||||
|
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
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/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||||
|
github.com/gostaticanalysis/analysisutil v0.0.2 h1:OZ4/Q9Lt9bzdyyjAgAWzJfL5dSwPrbkN+6UOHwYeJDM=
|
||||||
|
github.com/gostaticanalysis/analysisutil v0.0.2/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||||
|
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/grpc-ecosystem/grpc-gateway v1.9.4/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 v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
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/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM=
|
||||||
|
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.7.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/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/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
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/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
|
github.com/logrusorgru/aurora v0.0.0-20190428105938-cea283e61946/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
|
github.com/magiconair/properties v1.7.6/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/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/mbland/hmacauth v0.0.0-20170912224942-107c17adcc5e h1:eMYU396eZUQ/ex49JNVJOEhShOhQe3Lf/opF61nFtlA=
|
||||||
|
github.com/mbland/hmacauth v0.0.0-20170912224942-107c17adcc5e/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||||
|
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||||
|
github.com/mreiferson/go-options v0.0.0-20190302064952-20ba7d382d05 h1:9cELXrXqZu2sczHBZHRpZ+84SR27+yXSKb1MBiUaPhA=
|
||||||
|
github.com/mreiferson/go-options v0.0.0-20190302064952-20ba7d382d05/go.mod h1:zHtCks/HQvOt8ATyfwVe3JJq2PPuImzXINPRTC03+9w=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||||
|
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||||
|
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E=
|
||||||
|
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||||
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||||
|
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/pelletier/go-toml v1.1.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 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM=
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||||
|
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_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
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/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||||
|
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/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/prometheus/tsdb v0.9.1/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4=
|
||||||
|
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.2.1/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
|
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
|
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||||
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs=
|
||||||
|
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/spf13/afero v1.1.0/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.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||||
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||||
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
|
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||||
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
|
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||||
|
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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ=
|
||||||
|
github.com/stretchr/testify v1.1.4/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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/timakin/bodyclose v0.0.0-20190407043127-4a873e97b2bb/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||||
|
github.com/timakin/bodyclose v0.0.0-20190713050349-d96ec0dee822 h1:uVnVN3IUKAVcB3xG26bThgwXkWaGFc9i5qFHYKy4TKc=
|
||||||
|
github.com/timakin/bodyclose v0.0.0-20190713050349-d96ec0dee822/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||||
|
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/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||||
|
github.com/valyala/fasthttp v1.4.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||||
|
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||||
|
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||||
|
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/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q=
|
||||||
|
github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997/go.mod h1:DIGbh/f5XMAessMV/uaIik81gkDVjUeQ9ApdaU7wRKE=
|
||||||
|
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583 h1:SZPG5w7Qxq7bMcMVl6e3Ht2X7f+AAGQdzjkbyOnNNZ8=
|
||||||
|
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||||
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
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-20171113213409-9f005a07e0d3 h1:f4/ZD59VsBOaJmWeI2yqtHvJhmRRPzi73C88ZtfhAIk=
|
||||||
|
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/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-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/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-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-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-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20171106152852-9ff8ebcc8e24 h1:nP0LlV1P7+z/qtbjHygz+Bba7QsbB4MqdhGJmAyicuI=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20171106152852-9ff8ebcc8e24/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-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 h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
|
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-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/sys v0.0.0-20171026204733-164713f0dfce/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-20180905080454-ebe1bf3edb33/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-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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190506115046-ca7f33d4116e h1:bq5BY1tGuaK8HxuwN6pT6kWgTVLeJ5KwuyBpsl1CZL4=
|
||||||
|
golang.org/x/sys v0.0.0-20190506115046-ca7f33d4116e/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-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
|
||||||
|
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
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-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
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-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/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-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190213192042-740235f6c0d8/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-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190712213246-8b927904ee0d h1:JZPFnINSinLUdC0BJDoKhrky8niIzXMPIY2oR07+I+E=
|
||||||
|
golang.org/x/tools v0.0.0-20190712213246-8b927904ee0d/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||||
|
google.golang.org/api v0.0.0-20171116170945-8791354e7ab1 h1:g6iAMpIfX2EaDmaU3Nm8KcWAuf9yDiM3uE5a7/9gZao=
|
||||||
|
google.golang.org/api v0.0.0-20171116170945-8791354e7ab1/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/appengine v1.0.0 h1:dN4LljjBKVChsv0XCSI+zbyzdqrkEwX5LQFUMRSGqOc=
|
||||||
|
google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
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/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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
|
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 h1:5pOB7se0B2+IssELuQUs6uoBgYJenkU2AQlvopc2sRw=
|
||||||
|
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
|
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/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
|
||||||
|
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||||
|
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/fsnotify/fsnotify.v1 v1.2.11 h1:m6l7lekIm1I7UEqyXifLaHywMLxwlWea1o3zUGDGQHs=
|
||||||
|
gopkg.in/fsnotify/fsnotify.v1 v1.2.11/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
|
||||||
|
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 h1:AFxeG48hTWHhDTQDk/m2gorfVHUEa9vo3tp3D7TzwjI=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.1.3 h1:/FoFBTvlJN6MTTVCe9plTOG+YydzkjvDGxiSPzIyoDM=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.1.3/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
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.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
|
||||||
|
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||||
|
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo=
|
||||||
|
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||||
|
mvdan.cc/unparam v0.0.0-20190124213536-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
|
||||||
|
mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe h1:Ekmnp+NcP2joadI9pbK4Bva87QKZSeY7le//oiMrc9g=
|
||||||
|
mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe/go.mod h1:BnhuWBAqxH3+J5bDybdxgw5ZfS+DsVd4iylsKQePN8o=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
|
sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o=
|
||||||
|
sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4=
|
@ -7,7 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
2
http.go
2
http.go
@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents an HTTP server
|
// Server represents an HTTP server
|
||||||
|
27
http_test.go
27
http_test.go
@ -8,6 +8,9 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const localhost = "127.0.0.1"
|
||||||
|
const host = "test-server"
|
||||||
|
|
||||||
func TestGCPHealthcheckLiveness(t *testing.T) {
|
func TestGCPHealthcheckLiveness(t *testing.T) {
|
||||||
handler := func(w http.ResponseWriter, req *http.Request) {
|
handler := func(w http.ResponseWriter, req *http.Request) {
|
||||||
w.Write([]byte("test"))
|
w.Write([]byte("test"))
|
||||||
@ -16,8 +19,8 @@ func TestGCPHealthcheckLiveness(t *testing.T) {
|
|||||||
h := gcpHealthcheck(http.HandlerFunc(handler))
|
h := gcpHealthcheck(http.HandlerFunc(handler))
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
r, _ := http.NewRequest("GET", "/liveness_check", nil)
|
r, _ := http.NewRequest("GET", "/liveness_check", nil)
|
||||||
r.RemoteAddr = "127.0.0.1"
|
r.RemoteAddr = localhost
|
||||||
r.Host = "test-server"
|
r.Host = host
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
|
|
||||||
assert.Equal(t, 200, rw.Code)
|
assert.Equal(t, 200, rw.Code)
|
||||||
@ -32,8 +35,8 @@ func TestGCPHealthcheckReadiness(t *testing.T) {
|
|||||||
h := gcpHealthcheck(http.HandlerFunc(handler))
|
h := gcpHealthcheck(http.HandlerFunc(handler))
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
r, _ := http.NewRequest("GET", "/readiness_check", nil)
|
r, _ := http.NewRequest("GET", "/readiness_check", nil)
|
||||||
r.RemoteAddr = "127.0.0.1"
|
r.RemoteAddr = localhost
|
||||||
r.Host = "test-server"
|
r.Host = host
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
|
|
||||||
assert.Equal(t, 200, rw.Code)
|
assert.Equal(t, 200, rw.Code)
|
||||||
@ -48,8 +51,8 @@ func TestGCPHealthcheckNotHealthcheck(t *testing.T) {
|
|||||||
h := gcpHealthcheck(http.HandlerFunc(handler))
|
h := gcpHealthcheck(http.HandlerFunc(handler))
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
r, _ := http.NewRequest("GET", "/not_any_check", nil)
|
r, _ := http.NewRequest("GET", "/not_any_check", nil)
|
||||||
r.RemoteAddr = "127.0.0.1"
|
r.RemoteAddr = localhost
|
||||||
r.Host = "test-server"
|
r.Host = host
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
|
|
||||||
assert.Equal(t, "test", rw.Body.String())
|
assert.Equal(t, "test", rw.Body.String())
|
||||||
@ -63,8 +66,8 @@ func TestGCPHealthcheckIngress(t *testing.T) {
|
|||||||
h := gcpHealthcheck(http.HandlerFunc(handler))
|
h := gcpHealthcheck(http.HandlerFunc(handler))
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
r, _ := http.NewRequest("GET", "/", nil)
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
r.RemoteAddr = "127.0.0.1"
|
r.RemoteAddr = localhost
|
||||||
r.Host = "test-server"
|
r.Host = host
|
||||||
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
|
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
|
|
||||||
@ -80,8 +83,8 @@ func TestGCPHealthcheckNotIngress(t *testing.T) {
|
|||||||
h := gcpHealthcheck(http.HandlerFunc(handler))
|
h := gcpHealthcheck(http.HandlerFunc(handler))
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
r, _ := http.NewRequest("GET", "/foo", nil)
|
r, _ := http.NewRequest("GET", "/foo", nil)
|
||||||
r.RemoteAddr = "127.0.0.1"
|
r.RemoteAddr = localhost
|
||||||
r.Host = "test-server"
|
r.Host = host
|
||||||
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
|
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
|
|
||||||
@ -96,8 +99,8 @@ func TestGCPHealthcheckNotIngressPut(t *testing.T) {
|
|||||||
h := gcpHealthcheck(http.HandlerFunc(handler))
|
h := gcpHealthcheck(http.HandlerFunc(handler))
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
r, _ := http.NewRequest("PUT", "/", nil)
|
r, _ := http.NewRequest("PUT", "/", nil)
|
||||||
r.RemoteAddr = "127.0.0.1"
|
r.RemoteAddr = localhost
|
||||||
r.Host = "test-server"
|
r.Host = host
|
||||||
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
|
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
|
// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
|
||||||
@ -75,18 +75,19 @@ func (l *responseLogger) Status() int {
|
|||||||
return l.status
|
return l.status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns teh response size
|
// Size returns the response size
|
||||||
func (l *responseLogger) Size() int {
|
func (l *responseLogger) Size() int {
|
||||||
return l.size
|
return l.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush sends any buffered data to the client
|
||||||
func (l *responseLogger) Flush() {
|
func (l *responseLogger) Flush() {
|
||||||
if flusher, ok := l.w.(http.Flusher); ok {
|
if flusher, ok := l.w.(http.Flusher); ok {
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
|
// loggingHandler is the http.Handler implementation for LoggingHandler
|
||||||
type loggingHandler struct {
|
type loggingHandler struct {
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoggingHandler_ServeHTTP(t *testing.T) {
|
func TestLoggingHandler_ServeHTTP(t *testing.T) {
|
||||||
@ -17,10 +17,23 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Format,
|
Format,
|
||||||
ExpectedLogMessage string
|
ExpectedLogMessage,
|
||||||
|
Path string
|
||||||
|
ExcludePaths []string
|
||||||
|
SilencePingLogging bool
|
||||||
}{
|
}{
|
||||||
{logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts))},
|
{logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{}, false},
|
||||||
{"{{.RequestMethod}}", "GET\n"},
|
{logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{}, true},
|
||||||
|
{logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{"/ping"}, false},
|
||||||
|
{logger.DefaultRequestLoggingFormat, "", "/foo/bar", []string{"/foo/bar"}, false},
|
||||||
|
{logger.DefaultRequestLoggingFormat, "", "/ping", []string{}, true},
|
||||||
|
{logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}, false},
|
||||||
|
{logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}, true},
|
||||||
|
{logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/foo/bar", "/ping"}, false},
|
||||||
|
{"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{}, true},
|
||||||
|
{"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{"/ping"}, false},
|
||||||
|
{"{{.RequestMethod}}", "GET\n", "/ping", []string{}, false},
|
||||||
|
{"{{.RequestMethod}}", "", "/ping", []string{"/ping"}, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -36,9 +49,13 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
|
|||||||
|
|
||||||
logger.SetOutput(buf)
|
logger.SetOutput(buf)
|
||||||
logger.SetReqTemplate(test.Format)
|
logger.SetReqTemplate(test.Format)
|
||||||
|
if test.SilencePingLogging {
|
||||||
|
test.ExcludePaths = append(test.ExcludePaths, "/ping")
|
||||||
|
}
|
||||||
|
logger.SetExcludePaths(test.ExcludePaths)
|
||||||
h := LoggingHandler(http.HandlerFunc(handler))
|
h := LoggingHandler(http.HandlerFunc(handler))
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", "/foo/bar", nil)
|
r, _ := http.NewRequest("GET", test.Path, nil)
|
||||||
r.RemoteAddr = "127.0.0.1"
|
r.RemoteAddr = "127.0.0.1"
|
||||||
r.Host = "test-server"
|
r.Host = "test-server"
|
||||||
|
|
||||||
|
34
main.go
34
main.go
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
options "github.com/mreiferson/go-options"
|
options "github.com/mreiferson/go-options"
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -23,15 +23,17 @@ func main() {
|
|||||||
whitelistDomains := StringArray{}
|
whitelistDomains := StringArray{}
|
||||||
upstreams := StringArray{}
|
upstreams := StringArray{}
|
||||||
skipAuthRegex := StringArray{}
|
skipAuthRegex := StringArray{}
|
||||||
|
jwtIssuers := StringArray{}
|
||||||
googleGroups := StringArray{}
|
googleGroups := StringArray{}
|
||||||
|
redisSentinelConnectionURLs := StringArray{}
|
||||||
|
|
||||||
config := flagSet.String("config", "", "path to config file")
|
config := flagSet.String("config", "", "path to config file")
|
||||||
showVersion := flagSet.Bool("version", false, "print version string")
|
showVersion := flagSet.Bool("version", false, "print version string")
|
||||||
|
|
||||||
flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients")
|
flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients")
|
||||||
flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
|
flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
|
||||||
flagSet.String("tls-cert", "", "path to certificate file")
|
flagSet.String("tls-cert-file", "", "path to certificate file")
|
||||||
flagSet.String("tls-key", "", "path to private key file")
|
flagSet.String("tls-key-file", "", "path to private key file")
|
||||||
flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
|
flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
|
||||||
flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
|
flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
|
||||||
flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path")
|
flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path")
|
||||||
@ -45,14 +47,21 @@ func main() {
|
|||||||
flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")
|
flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")
|
||||||
flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
|
flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
|
||||||
flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
|
flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
|
||||||
flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS")
|
flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
|
||||||
|
flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams")
|
||||||
flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
|
flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
|
||||||
|
flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)")
|
||||||
|
flagSet.Var(&jwtIssuers, "extra-jwt-issuers", "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
|
||||||
|
|
||||||
flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
|
flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
|
||||||
flagSet.Var(&whitelistDomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
|
flagSet.Var(&whitelistDomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
|
||||||
|
flagSet.String("keycloak-group", "", "restrict login to members of this group.")
|
||||||
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
|
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
|
||||||
|
flagSet.String("bitbucket-team", "", "restrict logins to members of this team")
|
||||||
|
flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository")
|
||||||
flagSet.String("github-org", "", "restrict logins to members of this organisation")
|
flagSet.String("github-org", "", "restrict logins to members of this organisation")
|
||||||
flagSet.String("github-team", "", "restrict logins to members of this team")
|
flagSet.String("github-team", "", "restrict logins to members of this team")
|
||||||
|
flagSet.String("gitlab-group", "", "restrict logins to members of this group")
|
||||||
flagSet.Var(&googleGroups, "google-group", "restrict logins to members of this google group (may be given multiple times).")
|
flagSet.Var(&googleGroups, "google-group", "restrict logins to members of this google group (may be given multiple times).")
|
||||||
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
|
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
|
||||||
flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
|
flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
|
||||||
@ -62,8 +71,10 @@ func main() {
|
|||||||
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption")
|
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -s\" for SHA encryption or \"htpasswd -B\" for bcrypt encryption")
|
||||||
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
|
flagSet.Bool("display-htpasswd-form", true, "display username / password login form if an htpasswd file is provided")
|
||||||
flagSet.String("custom-templates-dir", "", "path to custom html templates")
|
flagSet.String("custom-templates-dir", "", "path to custom html templates")
|
||||||
|
flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.")
|
||||||
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
|
flagSet.String("footer", "", "custom footer string. Use \"-\" to disable default footer.")
|
||||||
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
|
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
|
||||||
|
flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks")
|
||||||
flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
|
flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
|
||||||
|
|
||||||
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
|
flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
|
||||||
@ -76,6 +87,10 @@ func main() {
|
|||||||
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
|
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
|
||||||
|
|
||||||
flagSet.String("session-store-type", "cookie", "the session storage provider to use")
|
flagSet.String("session-store-type", "cookie", "the session storage provider to use")
|
||||||
|
flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
|
||||||
|
flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature")
|
||||||
|
flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel")
|
||||||
|
flagSet.Var(&redisSentinelConnectionURLs, "redis-sentinel-connection-urls", "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel")
|
||||||
|
|
||||||
flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
|
flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
|
||||||
flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
|
flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
|
||||||
@ -89,12 +104,15 @@ func main() {
|
|||||||
|
|
||||||
flagSet.Bool("request-logging", true, "Log HTTP requests")
|
flagSet.Bool("request-logging", true, "Log HTTP requests")
|
||||||
flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines")
|
flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines")
|
||||||
|
flagSet.String("exclude-logging-paths", "", "Exclude logging requests to paths (eg: '/path1,/path2,/path3')")
|
||||||
|
flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint")
|
||||||
|
|
||||||
flagSet.Bool("auth-logging", true, "Log authentication attempts")
|
flagSet.Bool("auth-logging", true, "Log authentication attempts")
|
||||||
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
|
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
|
||||||
|
|
||||||
flagSet.String("provider", "google", "OAuth provider")
|
flagSet.String("provider", "google", "OAuth provider")
|
||||||
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
|
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
|
||||||
|
flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
|
||||||
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
|
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
|
||||||
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
|
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
|
||||||
flagSet.String("login-url", "", "Authentication endpoint")
|
flagSet.String("login-url", "", "Authentication endpoint")
|
||||||
@ -140,7 +158,13 @@ func main() {
|
|||||||
validator := NewValidator(opts.EmailDomains, opts.AuthenticatedEmailsFile)
|
validator := NewValidator(opts.EmailDomains, opts.AuthenticatedEmailsFile)
|
||||||
oauthproxy := NewOAuthProxy(opts, validator)
|
oauthproxy := NewOAuthProxy(opts, validator)
|
||||||
|
|
||||||
if len(opts.EmailDomains) != 0 && opts.AuthenticatedEmailsFile == "" {
|
if len(opts.Banner) >= 1 {
|
||||||
|
if opts.Banner == "-" {
|
||||||
|
oauthproxy.SignInMessage = ""
|
||||||
|
} else {
|
||||||
|
oauthproxy.SignInMessage = opts.Banner
|
||||||
|
}
|
||||||
|
} else if len(opts.EmailDomains) != 0 && opts.AuthenticatedEmailsFile == "" {
|
||||||
if len(opts.EmailDomains) > 1 {
|
if len(opts.EmailDomains) > 1 {
|
||||||
oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using one of the following domains: %v", strings.Join(opts.EmailDomains, ", "))
|
oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using one of the following domains: %v", strings.Join(opts.EmailDomains, ", "))
|
||||||
} else if opts.EmailDomains[0] != "*" {
|
} else if opts.EmailDomains[0] != "*" {
|
||||||
|
481
oauthproxy.go
481
oauthproxy.go
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
b64 "encoding/base64"
|
b64 "encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -13,10 +15,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc"
|
||||||
"github.com/mbland/hmacauth"
|
"github.com/mbland/hmacauth"
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
"github.com/pusher/oauth2_proxy/providers"
|
"github.com/pusher/oauth2_proxy/providers"
|
||||||
"github.com/yhat/wsutil"
|
"github.com/yhat/wsutil"
|
||||||
)
|
)
|
||||||
@ -29,10 +32,6 @@ const (
|
|||||||
httpScheme = "http"
|
httpScheme = "http"
|
||||||
httpsScheme = "https"
|
httpsScheme = "https"
|
||||||
|
|
||||||
// Cookies are limited to 4kb including the length of the cookie name,
|
|
||||||
// the cookie name can be up to 256 bytes
|
|
||||||
maxCookieLength = 3840
|
|
||||||
|
|
||||||
applicationJSON = "application/json"
|
applicationJSON = "application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,6 +50,11 @@ var SignatureHeaders = []string{
|
|||||||
"Gap-Auth",
|
"Gap-Auth",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNeedsLogin means the user should be redirected to the login page
|
||||||
|
ErrNeedsLogin = errors.New("redirect to login page")
|
||||||
|
)
|
||||||
|
|
||||||
// OAuthProxy is the main authentication proxy
|
// OAuthProxy is the main authentication proxy
|
||||||
type OAuthProxy struct {
|
type OAuthProxy struct {
|
||||||
CookieSeed string
|
CookieSeed string
|
||||||
@ -75,6 +79,7 @@ type OAuthProxy struct {
|
|||||||
redirectURL *url.URL // the url to receive requests at
|
redirectURL *url.URL // the url to receive requests at
|
||||||
whitelistDomains []string
|
whitelistDomains []string
|
||||||
provider providers.Provider
|
provider providers.Provider
|
||||||
|
sessionStore sessionsapi.SessionStore
|
||||||
ProxyPrefix string
|
ProxyPrefix string
|
||||||
SignInMessage string
|
SignInMessage string
|
||||||
HtpasswdFile *HtpasswdFile
|
HtpasswdFile *HtpasswdFile
|
||||||
@ -88,11 +93,13 @@ type OAuthProxy struct {
|
|||||||
PassAccessToken bool
|
PassAccessToken bool
|
||||||
SetAuthorization bool
|
SetAuthorization bool
|
||||||
PassAuthorization bool
|
PassAuthorization bool
|
||||||
CookieCipher *cookie.Cipher
|
|
||||||
skipAuthRegex []string
|
skipAuthRegex []string
|
||||||
skipAuthPreflight bool
|
skipAuthPreflight bool
|
||||||
|
skipJwtBearerTokens bool
|
||||||
|
jwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||||
compiledRegex []*regexp.Regexp
|
compiledRegex []*regexp.Regexp
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
|
Banner string
|
||||||
Footer string
|
Footer string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,9 +129,14 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
|
// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
|
||||||
// servers
|
// servers
|
||||||
func NewReverseProxy(target *url.URL, flushInterval time.Duration) (proxy *httputil.ReverseProxy) {
|
func NewReverseProxy(target *url.URL, opts *Options) (proxy *httputil.ReverseProxy) {
|
||||||
proxy = httputil.NewSingleHostReverseProxy(target)
|
proxy = httputil.NewSingleHostReverseProxy(target)
|
||||||
proxy.FlushInterval = flushInterval
|
proxy.FlushInterval = opts.FlushInterval
|
||||||
|
if opts.SSLUpstreamInsecureSkipVerify {
|
||||||
|
proxy.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,9 +167,9 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
|
// NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
|
||||||
func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) (restProxy http.Handler) {
|
func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler {
|
||||||
u.Path = ""
|
u.Path = ""
|
||||||
proxy := NewReverseProxy(u, opts.FlushInterval)
|
proxy := NewReverseProxy(u, opts)
|
||||||
if !opts.PassHostHeader {
|
if !opts.PassHostHeader {
|
||||||
setProxyUpstreamHostHeader(proxy, u)
|
setProxyUpstreamHostHeader(proxy, u)
|
||||||
} else {
|
} else {
|
||||||
@ -171,7 +183,12 @@ func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.Hma
|
|||||||
wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
|
wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
|
||||||
wsProxy = wsutil.NewSingleHostReverseProxy(wsURL)
|
wsProxy = wsutil.NewSingleHostReverseProxy(wsURL)
|
||||||
}
|
}
|
||||||
return &UpstreamProxy{u.Host, proxy, wsProxy, auth}
|
return &UpstreamProxy{
|
||||||
|
upstream: u.Host,
|
||||||
|
handler: proxy,
|
||||||
|
wsHandler: wsProxy,
|
||||||
|
auth: auth,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
|
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
|
||||||
@ -196,7 +213,13 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
}
|
}
|
||||||
logger.Printf("mapping path %q => file system %q", path, u.Path)
|
logger.Printf("mapping path %q => file system %q", path, u.Path)
|
||||||
proxy := NewFileServer(path, u.Path)
|
proxy := NewFileServer(path, u.Path)
|
||||||
serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil})
|
uProxy := UpstreamProxy{
|
||||||
|
upstream: path,
|
||||||
|
handler: proxy,
|
||||||
|
wsHandler: nil,
|
||||||
|
auth: nil,
|
||||||
|
}
|
||||||
|
serveMux.Handle(path, &uProxy)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
|
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
|
||||||
}
|
}
|
||||||
@ -205,6 +228,12 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
logger.Printf("compiled skip-auth-regex => %q", u)
|
logger.Printf("compiled skip-auth-regex => %q", u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.SkipJwtBearerTokens {
|
||||||
|
logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.OIDCIssuerURL)
|
||||||
|
for _, issuer := range opts.ExtraJwtIssuers {
|
||||||
|
logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer)
|
||||||
|
}
|
||||||
|
}
|
||||||
redirectURL := opts.redirectURL
|
redirectURL := opts.redirectURL
|
||||||
if redirectURL.Path == "" {
|
if redirectURL.Path == "" {
|
||||||
redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
|
redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
|
||||||
@ -218,15 +247,6 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
|
|
||||||
logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
|
logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
|
||||||
|
|
||||||
var cipher *cookie.Cipher
|
|
||||||
if opts.PassAccessToken || opts.SetAuthorization || opts.PassAuthorization || (opts.CookieRefresh != time.Duration(0)) {
|
|
||||||
var err error
|
|
||||||
cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal("cookie-secret error: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &OAuthProxy{
|
return &OAuthProxy{
|
||||||
CookieName: opts.CookieName,
|
CookieName: opts.CookieName,
|
||||||
CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
|
CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
|
||||||
@ -240,7 +260,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
Validator: validator,
|
Validator: validator,
|
||||||
|
|
||||||
RobotsPath: "/robots.txt",
|
RobotsPath: "/robots.txt",
|
||||||
PingPath: "/ping",
|
PingPath: opts.PingPath,
|
||||||
SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
|
SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
|
||||||
SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),
|
SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),
|
||||||
OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
|
OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
|
||||||
@ -249,11 +269,14 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
|
|
||||||
ProxyPrefix: opts.ProxyPrefix,
|
ProxyPrefix: opts.ProxyPrefix,
|
||||||
provider: opts.provider,
|
provider: opts.provider,
|
||||||
|
sessionStore: opts.sessionStore,
|
||||||
serveMux: serveMux,
|
serveMux: serveMux,
|
||||||
redirectURL: redirectURL,
|
redirectURL: redirectURL,
|
||||||
whitelistDomains: opts.WhitelistDomains,
|
whitelistDomains: opts.WhitelistDomains,
|
||||||
skipAuthRegex: opts.SkipAuthRegex,
|
skipAuthRegex: opts.SkipAuthRegex,
|
||||||
skipAuthPreflight: opts.SkipAuthPreflight,
|
skipAuthPreflight: opts.SkipAuthPreflight,
|
||||||
|
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
||||||
|
jwtBearerVerifiers: opts.jwtBearerVerifiers,
|
||||||
compiledRegex: opts.CompiledRegex,
|
compiledRegex: opts.CompiledRegex,
|
||||||
SetXAuthRequest: opts.SetXAuthRequest,
|
SetXAuthRequest: opts.SetXAuthRequest,
|
||||||
PassBasicAuth: opts.PassBasicAuth,
|
PassBasicAuth: opts.PassBasicAuth,
|
||||||
@ -263,8 +286,8 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
SetAuthorization: opts.SetAuthorization,
|
SetAuthorization: opts.SetAuthorization,
|
||||||
PassAuthorization: opts.PassAuthorization,
|
PassAuthorization: opts.PassAuthorization,
|
||||||
SkipProviderButton: opts.SkipProviderButton,
|
SkipProviderButton: opts.SkipProviderButton,
|
||||||
CookieCipher: cipher,
|
|
||||||
templates: loadTemplates(opts.CustomTemplatesDir),
|
templates: loadTemplates(opts.CustomTemplatesDir),
|
||||||
|
Banner: opts.Banner,
|
||||||
Footer: opts.Footer,
|
Footer: opts.Footer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,7 +316,7 @@ func (p *OAuthProxy) displayCustomLoginForm() bool {
|
|||||||
return p.HtpasswdFile != nil && p.DisplayHtpasswdForm
|
return p.HtpasswdFile != nil && p.DisplayHtpasswdForm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *OAuthProxy) redeemCode(host, code string) (s *sessions.SessionState, err error) {
|
func (p *OAuthProxy) redeemCode(host, code string) (s *sessionsapi.SessionState, err error) {
|
||||||
if code == "" {
|
if code == "" {
|
||||||
return nil, errors.New("missing code")
|
return nil, errors.New("missing code")
|
||||||
}
|
}
|
||||||
@ -316,104 +339,6 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *sessions.SessionState, er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeSessionCookie creates an http.Cookie containing the authenticated user's
|
|
||||||
// authentication details
|
|
||||||
func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) []*http.Cookie {
|
|
||||||
if value != "" {
|
|
||||||
value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
|
|
||||||
}
|
|
||||||
c := p.makeCookie(req, p.CookieName, value, expiration, now)
|
|
||||||
if len(c.Value) > 4096-len(p.CookieName) {
|
|
||||||
return splitCookie(c)
|
|
||||||
}
|
|
||||||
return []*http.Cookie{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyCookie(c *http.Cookie) *http.Cookie {
|
|
||||||
return &http.Cookie{
|
|
||||||
Name: c.Name,
|
|
||||||
Value: c.Value,
|
|
||||||
Path: c.Path,
|
|
||||||
Domain: c.Domain,
|
|
||||||
Expires: c.Expires,
|
|
||||||
RawExpires: c.RawExpires,
|
|
||||||
MaxAge: c.MaxAge,
|
|
||||||
Secure: c.Secure,
|
|
||||||
HttpOnly: c.HttpOnly,
|
|
||||||
Raw: c.Raw,
|
|
||||||
Unparsed: c.Unparsed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// splitCookie reads the full cookie generated to store the session and splits
|
|
||||||
// it into a slice of cookies which fit within the 4kb cookie limit indexing
|
|
||||||
// the cookies from 0
|
|
||||||
func splitCookie(c *http.Cookie) []*http.Cookie {
|
|
||||||
if len(c.Value) < maxCookieLength {
|
|
||||||
return []*http.Cookie{c}
|
|
||||||
}
|
|
||||||
cookies := []*http.Cookie{}
|
|
||||||
valueBytes := []byte(c.Value)
|
|
||||||
count := 0
|
|
||||||
for len(valueBytes) > 0 {
|
|
||||||
new := copyCookie(c)
|
|
||||||
new.Name = fmt.Sprintf("%s_%d", c.Name, count)
|
|
||||||
count++
|
|
||||||
if len(valueBytes) < maxCookieLength {
|
|
||||||
new.Value = string(valueBytes)
|
|
||||||
valueBytes = []byte{}
|
|
||||||
} else {
|
|
||||||
newValue := valueBytes[:maxCookieLength]
|
|
||||||
valueBytes = valueBytes[maxCookieLength:]
|
|
||||||
new.Value = string(newValue)
|
|
||||||
}
|
|
||||||
cookies = append(cookies, new)
|
|
||||||
}
|
|
||||||
return cookies
|
|
||||||
}
|
|
||||||
|
|
||||||
// joinCookies takes a slice of cookies from the request and reconstructs the
|
|
||||||
// full session cookie
|
|
||||||
func joinCookies(cookies []*http.Cookie) (*http.Cookie, error) {
|
|
||||||
if len(cookies) == 0 {
|
|
||||||
return nil, fmt.Errorf("list of cookies must be > 0")
|
|
||||||
}
|
|
||||||
if len(cookies) == 1 {
|
|
||||||
return cookies[0], nil
|
|
||||||
}
|
|
||||||
c := copyCookie(cookies[0])
|
|
||||||
for i := 1; i < len(cookies); i++ {
|
|
||||||
c.Value += cookies[i].Value
|
|
||||||
}
|
|
||||||
c.Name = strings.TrimRight(c.Name, "_0")
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadCookie retreieves the sessions state cookie from the http request.
|
|
||||||
// If a single cookie is present this will be returned, otherwise it attempts
|
|
||||||
// to reconstruct a cookie split up by splitCookie
|
|
||||||
func loadCookie(req *http.Request, cookieName string) (*http.Cookie, error) {
|
|
||||||
c, err := req.Cookie(cookieName)
|
|
||||||
if err == nil {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
cookies := []*http.Cookie{}
|
|
||||||
err = nil
|
|
||||||
count := 0
|
|
||||||
for err == nil {
|
|
||||||
var c *http.Cookie
|
|
||||||
c, err = req.Cookie(fmt.Sprintf("%s_%d", cookieName, count))
|
|
||||||
if err == nil {
|
|
||||||
cookies = append(cookies, c)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(cookies) == 0 {
|
|
||||||
return nil, fmt.Errorf("Could not find cookie %s", cookieName)
|
|
||||||
}
|
|
||||||
return joinCookies(cookies)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeCSRFCookie creates a cookie for CSRF
|
// MakeCSRFCookie creates a cookie for CSRF
|
||||||
func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||||
return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
|
return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
|
||||||
@ -454,66 +379,18 @@ func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, va
|
|||||||
|
|
||||||
// ClearSessionCookie creates a cookie to unset the user's authentication cookie
|
// ClearSessionCookie creates a cookie to unset the user's authentication cookie
|
||||||
// stored in the user's session
|
// stored in the user's session
|
||||||
func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) error {
|
||||||
var cookies []*http.Cookie
|
return p.sessionStore.Clear(rw, req)
|
||||||
|
|
||||||
// matches CookieName, CookieName_<number>
|
|
||||||
var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", p.CookieName))
|
|
||||||
|
|
||||||
for _, c := range req.Cookies() {
|
|
||||||
if cookieNameRegex.MatchString(c.Name) {
|
|
||||||
clearCookie := p.makeCookie(req, c.Name, "", time.Hour*-1, time.Now())
|
|
||||||
|
|
||||||
http.SetCookie(rw, clearCookie)
|
|
||||||
cookies = append(cookies, clearCookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ugly hack because default domain changed
|
|
||||||
if p.CookieDomain == "" && len(cookies) > 0 {
|
|
||||||
clr2 := *cookies[0]
|
|
||||||
clr2.Domain = req.Host
|
|
||||||
http.SetCookie(rw, &clr2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSessionCookie adds the user's session cookie to the response
|
|
||||||
func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
|
||||||
for _, c := range p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()) {
|
|
||||||
http.SetCookie(rw, c)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadCookiedSession reads the user's authentication details from the request
|
// LoadCookiedSession reads the user's authentication details from the request
|
||||||
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessions.SessionState, time.Duration, error) {
|
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.SessionState, error) {
|
||||||
var age time.Duration
|
return p.sessionStore.Load(req)
|
||||||
c, err := loadCookie(req, p.CookieName)
|
|
||||||
if err != nil {
|
|
||||||
// always http.ErrNoCookie
|
|
||||||
return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName)
|
|
||||||
}
|
|
||||||
val, timestamp, ok := cookie.Validate(c, p.CookieSeed, p.CookieExpire)
|
|
||||||
if !ok {
|
|
||||||
return nil, age, errors.New("Cookie Signature not valid")
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := p.provider.SessionFromCookie(val, p.CookieCipher)
|
|
||||||
if err != nil {
|
|
||||||
return nil, age, err
|
|
||||||
}
|
|
||||||
|
|
||||||
age = time.Now().Truncate(time.Second).Sub(timestamp)
|
|
||||||
return session, age, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveSession creates a new session cookie value and sets this on the response
|
// SaveSession creates a new session cookie value and sets this on the response
|
||||||
func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
|
func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessionsapi.SessionState) error {
|
||||||
value, err := p.provider.CookieForSession(s, p.CookieCipher)
|
return p.sessionStore.Save(rw, req, s)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.SetSessionCookie(rw, req, value)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RobotsTxt disallows scraping pages from the OAuthProxy
|
// RobotsTxt disallows scraping pages from the OAuthProxy
|
||||||
@ -636,20 +513,19 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsWhitelistedRequest is used to check if auth should be skipped for this request
|
// IsWhitelistedRequest is used to check if auth should be skipped for this request
|
||||||
func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
|
func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool {
|
||||||
isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
|
isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
|
||||||
return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
|
return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWhitelistedPath is used to check if the request path is allowed without auth
|
// IsWhitelistedPath is used to check if the request path is allowed without auth
|
||||||
func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
|
func (p *OAuthProxy) IsWhitelistedPath(path string) bool {
|
||||||
for _, u := range p.compiledRegex {
|
for _, u := range p.compiledRegex {
|
||||||
ok = u.MatchString(path)
|
if u.MatchString(path) {
|
||||||
if ok {
|
return true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRemoteAddr(req *http.Request) (s string) {
|
func getRemoteAddr(req *http.Request) (s string) {
|
||||||
@ -694,7 +570,7 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
user, ok := p.ManualSignIn(rw, req)
|
user, ok := p.ManualSignIn(rw, req)
|
||||||
if ok {
|
if ok {
|
||||||
session := &sessions.SessionState{User: user}
|
session := &sessionsapi.SessionState{User: user}
|
||||||
p.SaveSession(rw, req, session)
|
p.SaveSession(rw, req, session)
|
||||||
http.Redirect(rw, req, redirect, 302)
|
http.Redirect(rw, req, redirect, 302)
|
||||||
} else {
|
} else {
|
||||||
@ -714,7 +590,7 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// OAuthStart starts the OAuth2 authentication flow
|
// OAuthStart starts the OAuth2 authentication flow
|
||||||
func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
|
||||||
nonce, err := cookie.Nonce()
|
nonce, err := encryption.Nonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("Error obtaining nonce: %s", err.Error())
|
logger.Printf("Error obtaining nonce: %s", err.Error())
|
||||||
p.ErrorPage(rw, 500, "Internal Error", err.Error())
|
p.ErrorPage(rw, 500, "Internal Error", err.Error())
|
||||||
@ -793,57 +669,89 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
http.Redirect(rw, req, redirect, 302)
|
http.Redirect(rw, req, redirect, 302)
|
||||||
} else {
|
} else {
|
||||||
logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Invalid authentication via OAuth2: unauthorized")
|
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized")
|
||||||
p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
|
p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticateOnly checks whether the user is currently logged in
|
// AuthenticateOnly checks whether the user is currently logged in
|
||||||
func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
|
||||||
status := p.Authenticate(rw, req)
|
session, err := p.getAuthenticatedSession(rw, req)
|
||||||
if status == http.StatusAccepted {
|
if err != nil {
|
||||||
rw.WriteHeader(http.StatusAccepted)
|
|
||||||
} else {
|
|
||||||
http.Error(rw, "unauthorized request", http.StatusUnauthorized)
|
http.Error(rw, "unauthorized request", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we are authenticated
|
||||||
|
p.addHeadersForProxying(rw, req, session)
|
||||||
|
rw.WriteHeader(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy proxies the user request if the user is authenticated else it prompts
|
// Proxy proxies the user request if the user is authenticated else it prompts
|
||||||
// them to authenticate
|
// them to authenticate
|
||||||
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
||||||
status := p.Authenticate(rw, req)
|
session, err := p.getAuthenticatedSession(rw, req)
|
||||||
if status == http.StatusInternalServerError {
|
switch err {
|
||||||
p.ErrorPage(rw, http.StatusInternalServerError,
|
case nil:
|
||||||
"Internal Error", "Internal Error")
|
// we are authenticated
|
||||||
} else if status == http.StatusForbidden {
|
p.addHeadersForProxying(rw, req, session)
|
||||||
|
p.serveMux.ServeHTTP(rw, req)
|
||||||
|
|
||||||
|
case ErrNeedsLogin:
|
||||||
|
// we need to send the user to a login screen
|
||||||
|
if isAjax(req) {
|
||||||
|
// no point redirecting an AJAX request
|
||||||
|
p.ErrorJSON(rw, http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if p.SkipProviderButton {
|
if p.SkipProviderButton {
|
||||||
p.OAuthStart(rw, req)
|
p.OAuthStart(rw, req)
|
||||||
} else {
|
} else {
|
||||||
p.SignInPage(rw, req, http.StatusForbidden)
|
p.SignInPage(rw, req, http.StatusForbidden)
|
||||||
}
|
}
|
||||||
} else if status == http.StatusUnauthorized {
|
|
||||||
p.ErrorJSON(rw, status)
|
default:
|
||||||
} else {
|
// unknown error
|
||||||
p.serveMux.ServeHTTP(rw, req)
|
logger.Printf("Unexpected internal error: %s", err)
|
||||||
}
|
p.ErrorPage(rw, http.StatusInternalServerError,
|
||||||
|
"Internal Error", "Internal Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate checks whether a user is authenticated
|
}
|
||||||
func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
|
|
||||||
|
// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so
|
||||||
|
// Returns nil, ErrNeedsLogin if user needs to login.
|
||||||
|
// Set-Cookie headers may be set on the response as a side-effect of calling this method.
|
||||||
|
func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) {
|
||||||
|
var session *sessionsapi.SessionState
|
||||||
|
var err error
|
||||||
var saveSession, clearSession, revalidated bool
|
var saveSession, clearSession, revalidated bool
|
||||||
remoteAddr := getRemoteAddr(req)
|
|
||||||
|
|
||||||
session, sessionAge, err := p.LoadCookiedSession(req)
|
if p.skipJwtBearerTokens && req.Header.Get("Authorization") != "" {
|
||||||
|
session, err = p.GetJwtSession(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("Error retrieving session from token in Authorization header: %s", err)
|
||||||
|
}
|
||||||
|
if session != nil {
|
||||||
|
saveSession = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteAddr := getRemoteAddr(req)
|
||||||
|
if session == nil {
|
||||||
|
session, err = p.LoadCookiedSession(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("Error loading cookied session: %s", err)
|
logger.Printf("Error loading cookied session: %s", err)
|
||||||
}
|
}
|
||||||
if session != nil && sessionAge > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
|
|
||||||
logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", sessionAge, session, p.CookieRefresh)
|
if session != nil {
|
||||||
|
if session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
|
||||||
|
logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh)
|
||||||
saveSession = true
|
saveSession = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
if ok, err := p.provider.RefreshSessionIfNeeded(session); err != nil {
|
||||||
if ok, err = p.provider.RefreshSessionIfNeeded(session); err != nil {
|
|
||||||
logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
|
logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
|
||||||
clearSession = true
|
clearSession = true
|
||||||
session = nil
|
session = nil
|
||||||
@ -851,6 +759,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
|
|||||||
saveSession = true
|
saveSession = true
|
||||||
revalidated = true
|
revalidated = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if session != nil && session.IsExpired() {
|
if session != nil && session.IsExpired() {
|
||||||
logger.Printf("Removing session: token expired %s", session)
|
logger.Printf("Removing session: token expired %s", session)
|
||||||
@ -868,18 +778,20 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if session != nil && session.Email != "" && !p.Validator(session.Email) {
|
if session != nil && session.Email != "" {
|
||||||
|
if !p.Validator(session.Email) || !p.provider.ValidateGroup(session.Email) {
|
||||||
logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session)
|
logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session)
|
||||||
session = nil
|
session = nil
|
||||||
saveSession = false
|
saveSession = false
|
||||||
clearSession = true
|
clearSession = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if saveSession && session != nil {
|
if saveSession && session != nil {
|
||||||
err = p.SaveSession(rw, req, session)
|
err = p.SaveSession(rw, req, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err)
|
logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err)
|
||||||
return http.StatusInternalServerError
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,57 +807,83 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if session == nil {
|
if session == nil {
|
||||||
// Check if is an ajax request and return unauthorized to avoid a redirect
|
return nil, ErrNeedsLogin
|
||||||
// to the login page
|
|
||||||
if p.isAjax(req) {
|
|
||||||
return http.StatusUnauthorized
|
|
||||||
}
|
|
||||||
return http.StatusForbidden
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, the user is authenticated. proxy normally
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addHeadersForProxying adds the appropriate headers the request / response for proxying
|
||||||
|
func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
|
||||||
if p.PassBasicAuth {
|
if p.PassBasicAuth {
|
||||||
req.SetBasicAuth(session.User, p.BasicAuthPassword)
|
req.SetBasicAuth(session.User, p.BasicAuthPassword)
|
||||||
req.Header["X-Forwarded-User"] = []string{session.User}
|
req.Header["X-Forwarded-User"] = []string{session.User}
|
||||||
if session.Email != "" {
|
if session.Email != "" {
|
||||||
req.Header["X-Forwarded-Email"] = []string{session.Email}
|
req.Header["X-Forwarded-Email"] = []string{session.Email}
|
||||||
|
} else {
|
||||||
|
req.Header.Del("X-Forwarded-Email")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.PassUserHeaders {
|
if p.PassUserHeaders {
|
||||||
req.Header["X-Forwarded-User"] = []string{session.User}
|
req.Header["X-Forwarded-User"] = []string{session.User}
|
||||||
if session.Email != "" {
|
if session.Email != "" {
|
||||||
req.Header["X-Forwarded-Email"] = []string{session.Email}
|
req.Header["X-Forwarded-Email"] = []string{session.Email}
|
||||||
|
} else {
|
||||||
|
req.Header.Del("X-Forwarded-Email")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.SetXAuthRequest {
|
if p.SetXAuthRequest {
|
||||||
rw.Header().Set("X-Auth-Request-User", session.User)
|
rw.Header().Set("X-Auth-Request-User", session.User)
|
||||||
if session.Email != "" {
|
if session.Email != "" {
|
||||||
rw.Header().Set("X-Auth-Request-Email", session.Email)
|
rw.Header().Set("X-Auth-Request-Email", session.Email)
|
||||||
|
} else {
|
||||||
|
rw.Header().Del("X-Auth-Request-Email")
|
||||||
}
|
}
|
||||||
if p.PassAccessToken && session.AccessToken != "" {
|
|
||||||
|
if p.PassAccessToken {
|
||||||
|
if session.AccessToken != "" {
|
||||||
rw.Header().Set("X-Auth-Request-Access-Token", session.AccessToken)
|
rw.Header().Set("X-Auth-Request-Access-Token", session.AccessToken)
|
||||||
|
} else {
|
||||||
|
rw.Header().Del("X-Auth-Request-Access-Token")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.PassAccessToken && session.AccessToken != "" {
|
}
|
||||||
|
|
||||||
|
if p.PassAccessToken {
|
||||||
|
if session.AccessToken != "" {
|
||||||
req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
|
req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
|
||||||
|
} else {
|
||||||
|
req.Header.Del("X-Forwarded-Access-Token")
|
||||||
}
|
}
|
||||||
if p.PassAuthorization && session.IDToken != "" {
|
}
|
||||||
|
|
||||||
|
if p.PassAuthorization {
|
||||||
|
if session.IDToken != "" {
|
||||||
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)}
|
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)}
|
||||||
|
} else {
|
||||||
|
req.Header.Del("Authorization")
|
||||||
}
|
}
|
||||||
if p.SetAuthorization && session.IDToken != "" {
|
}
|
||||||
|
if p.SetAuthorization {
|
||||||
|
if session.IDToken != "" {
|
||||||
rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
|
rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
|
||||||
|
} else {
|
||||||
|
rw.Header().Del("Authorization")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if session.Email == "" {
|
if session.Email == "" {
|
||||||
rw.Header().Set("GAP-Auth", session.User)
|
rw.Header().Set("GAP-Auth", session.User)
|
||||||
} else {
|
} else {
|
||||||
rw.Header().Set("GAP-Auth", session.Email)
|
rw.Header().Set("GAP-Auth", session.Email)
|
||||||
}
|
}
|
||||||
return http.StatusAccepted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckBasicAuth checks the requests Authorization header for basic auth
|
// CheckBasicAuth checks the requests Authorization header for basic auth
|
||||||
// credentials and authenticates these against the proxies HtpasswdFile
|
// credentials and authenticates these against the proxies HtpasswdFile
|
||||||
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessions.SessionState, error) {
|
func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
|
||||||
if p.HtpasswdFile == nil {
|
if p.HtpasswdFile == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -967,14 +905,14 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessions.SessionState,
|
|||||||
}
|
}
|
||||||
if p.HtpasswdFile.Validate(pair[0], pair[1]) {
|
if p.HtpasswdFile.Validate(pair[0], pair[1]) {
|
||||||
logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
|
logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
|
||||||
return &sessions.SessionState{User: pair[0]}, nil
|
return &sessionsapi.SessionState{User: pair[0]}, nil
|
||||||
}
|
}
|
||||||
logger.PrintAuthf(pair[0], req, logger.AuthFailure, "Invalid authentication via basic auth: not in Htpasswd File")
|
logger.PrintAuthf(pair[0], req, logger.AuthFailure, "Invalid authentication via basic auth: not in Htpasswd File")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAjax checks if a request is an ajax request
|
// isAjax checks if a request is an ajax request
|
||||||
func (p *OAuthProxy) isAjax(req *http.Request) bool {
|
func isAjax(req *http.Request) bool {
|
||||||
acceptValues, ok := req.Header["accept"]
|
acceptValues, ok := req.Header["accept"]
|
||||||
if !ok {
|
if !ok {
|
||||||
acceptValues = req.Header["Accept"]
|
acceptValues = req.Header["Accept"]
|
||||||
@ -988,8 +926,97 @@ func (p *OAuthProxy) isAjax(req *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorJSON returns the error code witht an application/json mime type
|
// ErrorJSON returns the error code with an application/json mime type
|
||||||
func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) {
|
func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) {
|
||||||
rw.Header().Set("Content-Type", applicationJSON)
|
rw.Header().Set("Content-Type", applicationJSON)
|
||||||
rw.WriteHeader(code)
|
rw.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetJwtSession loads a session based on a JWT token in the authorization header.
|
||||||
|
func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState, error) {
|
||||||
|
rawBearerToken, err := p.findBearerToken(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
var session *sessionsapi.SessionState
|
||||||
|
for _, verifier := range p.jwtBearerVerifiers {
|
||||||
|
bearerToken, err := verifier.Verify(ctx, rawBearerToken)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed to verify bearer token: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims struct {
|
||||||
|
Subject string `json:"sub"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Verified *bool `json:"email_verified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bearerToken.Claims(&claims); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse bearer token claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims.Email == "" {
|
||||||
|
claims.Email = claims.Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims.Verified != nil && !*claims.Verified {
|
||||||
|
return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
session = &sessionsapi.SessionState{
|
||||||
|
AccessToken: rawBearerToken,
|
||||||
|
IDToken: rawBearerToken,
|
||||||
|
RefreshToken: "",
|
||||||
|
ExpiresOn: bearerToken.Expiry,
|
||||||
|
Email: claims.Email,
|
||||||
|
User: claims.Email,
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to verify jwt token %s", req.Header.Get("Authorization"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// findBearerToken finds a valid JWT token from the Authorization header of a given request.
|
||||||
|
func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) {
|
||||||
|
auth := req.Header.Get("Authorization")
|
||||||
|
s := strings.SplitN(auth, " ", 2)
|
||||||
|
if len(s) != 2 {
|
||||||
|
return "", fmt.Errorf("invalid authorization header %s", auth)
|
||||||
|
}
|
||||||
|
jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`)
|
||||||
|
var rawBearerToken string
|
||||||
|
if s[0] == "Bearer" && jwtRegex.MatchString(s[1]) {
|
||||||
|
rawBearerToken = s[1]
|
||||||
|
} else if s[0] == "Basic" {
|
||||||
|
// Check if we have a Bearer token masquerading in Basic
|
||||||
|
b, err := b64.StdEncoding.DecodeString(s[1])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pair := strings.SplitN(string(b), ":", 2)
|
||||||
|
if len(pair) != 2 {
|
||||||
|
return "", fmt.Errorf("invalid format %s", b)
|
||||||
|
}
|
||||||
|
user, password := pair[0], pair[1]
|
||||||
|
|
||||||
|
// check user, user+password, or just password for a token
|
||||||
|
if jwtRegex.MatchString(user) {
|
||||||
|
// Support blank passwords or magic `x-oauth-basic` passwords - nothing else
|
||||||
|
if password == "" || password == "x-oauth-basic" {
|
||||||
|
rawBearerToken = user
|
||||||
|
}
|
||||||
|
} else if jwtRegex.MatchString(password) {
|
||||||
|
// support passwords and ignore user
|
||||||
|
rawBearerToken = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rawBearerToken == "" {
|
||||||
|
return "", fmt.Errorf("no valid bearer token found in authorization header")
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawBearerToken, nil
|
||||||
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@ -14,9 +16,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc"
|
||||||
"github.com/mbland/hmacauth"
|
"github.com/mbland/hmacauth"
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
||||||
"github.com/pusher/oauth2_proxy/providers"
|
"github.com/pusher/oauth2_proxy/providers"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -118,7 +122,7 @@ func TestNewReverseProxy(t *testing.T) {
|
|||||||
backendHost := net.JoinHostPort(backendHostname, backendPort)
|
backendHost := net.JoinHostPort(backendHostname, backendPort)
|
||||||
proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
|
proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
|
||||||
|
|
||||||
proxyHandler := NewReverseProxy(proxyURL, time.Second)
|
proxyHandler := NewReverseProxy(proxyURL, &Options{FlushInterval: time.Second})
|
||||||
setProxyUpstreamHostHeader(proxyHandler, proxyURL)
|
setProxyUpstreamHostHeader(proxyHandler, proxyURL)
|
||||||
frontend := httptest.NewServer(proxyHandler)
|
frontend := httptest.NewServer(proxyHandler)
|
||||||
defer frontend.Close()
|
defer frontend.Close()
|
||||||
@ -140,7 +144,7 @@ func TestEncodedSlashes(t *testing.T) {
|
|||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
|
|
||||||
b, _ := url.Parse(backend.URL)
|
b, _ := url.Parse(backend.URL)
|
||||||
proxyHandler := NewReverseProxy(b, time.Second)
|
proxyHandler := NewReverseProxy(b, &Options{FlushInterval: time.Second})
|
||||||
setProxyDirector(proxyHandler)
|
setProxyDirector(proxyHandler)
|
||||||
frontend := httptest.NewServer(proxyHandler)
|
frontend := httptest.NewServer(proxyHandler)
|
||||||
defer frontend.Close()
|
defer frontend.Close()
|
||||||
@ -159,9 +163,9 @@ func TestEncodedSlashes(t *testing.T) {
|
|||||||
|
|
||||||
func TestRobotsTxt(t *testing.T) {
|
func TestRobotsTxt(t *testing.T) {
|
||||||
opts := NewOptions()
|
opts := NewOptions()
|
||||||
opts.ClientID = "bazquux"
|
opts.ClientID = "asdlkjx"
|
||||||
opts.ClientSecret = "foobar"
|
opts.ClientSecret = "alkgks"
|
||||||
opts.CookieSecret = "xyzzyplugh"
|
opts.CookieSecret = "asdkugkj"
|
||||||
opts.Validate()
|
opts.Validate()
|
||||||
|
|
||||||
proxy := NewOAuthProxy(opts, func(string) bool { return true })
|
proxy := NewOAuthProxy(opts, func(string) bool { return true })
|
||||||
@ -174,9 +178,9 @@ func TestRobotsTxt(t *testing.T) {
|
|||||||
|
|
||||||
func TestIsValidRedirect(t *testing.T) {
|
func TestIsValidRedirect(t *testing.T) {
|
||||||
opts := NewOptions()
|
opts := NewOptions()
|
||||||
opts.ClientID = "bazquux"
|
opts.ClientID = "skdlfj"
|
||||||
opts.ClientSecret = "foobar"
|
opts.ClientSecret = "fgkdsgj"
|
||||||
opts.CookieSecret = "xyzzyplugh"
|
opts.CookieSecret = "ljgiogbj"
|
||||||
// Should match domains that are exactly foo.bar and any subdomain of bar.foo
|
// Should match domains that are exactly foo.bar and any subdomain of bar.foo
|
||||||
opts.WhitelistDomains = []string{"foo.bar", ".bar.foo"}
|
opts.WhitelistDomains = []string{"foo.bar", ".bar.foo"}
|
||||||
opts.Validate()
|
opts.Validate()
|
||||||
@ -227,6 +231,7 @@ type TestProvider struct {
|
|||||||
*providers.ProviderData
|
*providers.ProviderData
|
||||||
EmailAddress string
|
EmailAddress string
|
||||||
ValidToken bool
|
ValidToken bool
|
||||||
|
GroupValidator func(string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
|
func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
|
||||||
@ -251,6 +256,9 @@ func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
|
|||||||
Scope: "profile.email",
|
Scope: "profile.email",
|
||||||
},
|
},
|
||||||
EmailAddress: emailAddress,
|
EmailAddress: emailAddress,
|
||||||
|
GroupValidator: func(s string) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,6 +270,13 @@ func (tp *TestProvider) ValidateSessionState(session *sessions.SessionState) boo
|
|||||||
return tp.ValidToken
|
return tp.ValidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tp *TestProvider) ValidateGroup(email string) bool {
|
||||||
|
if tp.GroupValidator != nil {
|
||||||
|
return tp.GroupValidator(email)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func TestBasicAuthPassword(t *testing.T) {
|
func TestBasicAuthPassword(t *testing.T) {
|
||||||
providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
logger.Printf("%#v", r)
|
logger.Printf("%#v", r)
|
||||||
@ -283,8 +298,8 @@ func TestBasicAuthPassword(t *testing.T) {
|
|||||||
// The CookieSecret must be 32 bytes in order to create the AES
|
// The CookieSecret must be 32 bytes in order to create the AES
|
||||||
// cipher.
|
// cipher.
|
||||||
opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
|
opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
|
||||||
opts.ClientID = "bazquux"
|
opts.ClientID = "dlgkj"
|
||||||
opts.ClientSecret = "foobar"
|
opts.ClientSecret = "alkgret"
|
||||||
opts.CookieSecure = false
|
opts.CookieSecure = false
|
||||||
opts.PassBasicAuth = true
|
opts.PassBasicAuth = true
|
||||||
opts.PassUserHeaders = true
|
opts.PassUserHeaders = true
|
||||||
@ -377,8 +392,8 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
|
|||||||
// The CookieSecret must be 32 bytes in order to create the AES
|
// The CookieSecret must be 32 bytes in order to create the AES
|
||||||
// cipher.
|
// cipher.
|
||||||
t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
|
t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
|
||||||
t.opts.ClientID = "bazquux"
|
t.opts.ClientID = "slgkj"
|
||||||
t.opts.ClientSecret = "foobar"
|
t.opts.ClientSecret = "gfjgojl"
|
||||||
t.opts.CookieSecure = false
|
t.opts.CookieSecure = false
|
||||||
t.opts.PassAccessToken = opts.PassAccessToken
|
t.opts.PassAccessToken = opts.PassAccessToken
|
||||||
t.opts.Validate()
|
t.opts.Validate()
|
||||||
@ -503,9 +518,9 @@ func NewSignInPageTest(skipProvider bool) *SignInPageTest {
|
|||||||
var sipTest SignInPageTest
|
var sipTest SignInPageTest
|
||||||
|
|
||||||
sipTest.opts = NewOptions()
|
sipTest.opts = NewOptions()
|
||||||
sipTest.opts.CookieSecret = "foobar"
|
sipTest.opts.CookieSecret = "adklsj2"
|
||||||
sipTest.opts.ClientID = "bazquux"
|
sipTest.opts.ClientID = "lkdgj"
|
||||||
sipTest.opts.ClientSecret = "xyzzyplugh"
|
sipTest.opts.ClientSecret = "sgiufgoi"
|
||||||
sipTest.opts.SkipProviderButton = skipProvider
|
sipTest.opts.SkipProviderButton = skipProvider
|
||||||
sipTest.opts.Validate()
|
sipTest.opts.Validate()
|
||||||
|
|
||||||
@ -600,12 +615,17 @@ type ProcessCookieTestOpts struct {
|
|||||||
providerValidateCookieResponse bool
|
providerValidateCookieResponse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
|
type OptionsModifier func(*Options)
|
||||||
|
|
||||||
|
func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
|
||||||
var pcTest ProcessCookieTest
|
var pcTest ProcessCookieTest
|
||||||
|
|
||||||
pcTest.opts = NewOptions()
|
pcTest.opts = NewOptions()
|
||||||
pcTest.opts.ClientID = "bazquux"
|
for _, modifier := range modifiers {
|
||||||
pcTest.opts.ClientSecret = "xyzzyplugh"
|
modifier(pcTest.opts)
|
||||||
|
}
|
||||||
|
pcTest.opts.ClientID = "asdfljk"
|
||||||
|
pcTest.opts.ClientSecret = "lkjfdsig"
|
||||||
pcTest.opts.CookieSecret = "0123456789abcdefabcd"
|
pcTest.opts.CookieSecret = "0123456789abcdefabcd"
|
||||||
// First, set the CookieRefresh option so proxy.AesCipher is created,
|
// First, set the CookieRefresh option so proxy.AesCipher is created,
|
||||||
// needed to encrypt the access_token.
|
// needed to encrypt the access_token.
|
||||||
@ -634,32 +654,34 @@ func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) []*http.Cookie {
|
func NewProcessCookieTestWithOptionsModifiers(modifiers ...OptionsModifier) *ProcessCookieTest {
|
||||||
return p.proxy.MakeSessionCookie(p.req, value, p.opts.CookieExpire, ref)
|
return NewProcessCookieTest(ProcessCookieTestOpts{
|
||||||
|
providerValidateCookieResponse: true,
|
||||||
|
}, modifiers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState, ref time.Time) error {
|
func (p *ProcessCookieTest) SaveSession(s *sessions.SessionState) error {
|
||||||
value, err := p.proxy.provider.CookieForSession(s, p.proxy.CookieCipher)
|
err := p.proxy.SaveSession(p.rw, p.req, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, c := range p.proxy.MakeSessionCookie(p.req, value, p.proxy.CookieExpire, ref) {
|
for _, cookie := range p.rw.Result().Cookies() {
|
||||||
p.req.AddCookie(c)
|
p.req.AddCookie(cookie)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, time.Duration, error) {
|
func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error) {
|
||||||
return p.proxy.LoadCookiedSession(p.req)
|
return p.proxy.LoadCookiedSession(p.req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadCookiedSession(t *testing.T) {
|
func TestLoadCookiedSession(t *testing.T) {
|
||||||
pcTest := NewProcessCookieTestWithDefaults()
|
pcTest := NewProcessCookieTestWithDefaults()
|
||||||
|
|
||||||
startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token"}
|
startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: time.Now()}
|
||||||
pcTest.SaveSession(startSession, time.Now())
|
pcTest.SaveSession(startSession)
|
||||||
|
|
||||||
session, _, err := pcTest.LoadCookiedSession()
|
session, err := pcTest.LoadCookiedSession()
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Equal(t, startSession.Email, session.Email)
|
assert.Equal(t, startSession.Email, session.Email)
|
||||||
assert.Equal(t, "john.doe@example.com", session.User)
|
assert.Equal(t, "john.doe@example.com", session.User)
|
||||||
@ -669,7 +691,7 @@ func TestLoadCookiedSession(t *testing.T) {
|
|||||||
func TestProcessCookieNoCookieError(t *testing.T) {
|
func TestProcessCookieNoCookieError(t *testing.T) {
|
||||||
pcTest := NewProcessCookieTestWithDefaults()
|
pcTest := NewProcessCookieTestWithDefaults()
|
||||||
|
|
||||||
session, _, err := pcTest.LoadCookiedSession()
|
session, err := pcTest.LoadCookiedSession()
|
||||||
assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
|
assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
|
||||||
if session != nil {
|
if session != nil {
|
||||||
t.Errorf("expected nil session. got %#v", session)
|
t.Errorf("expected nil session. got %#v", session)
|
||||||
@ -677,29 +699,31 @@ func TestProcessCookieNoCookieError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessCookieRefreshNotSet(t *testing.T) {
|
func TestProcessCookieRefreshNotSet(t *testing.T) {
|
||||||
pcTest := NewProcessCookieTestWithDefaults()
|
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
|
||||||
pcTest.proxy.CookieExpire = time.Duration(23) * time.Hour
|
opts.CookieExpire = time.Duration(23) * time.Hour
|
||||||
|
})
|
||||||
reference := time.Now().Add(time.Duration(-2) * time.Hour)
|
reference := time.Now().Add(time.Duration(-2) * time.Hour)
|
||||||
|
|
||||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||||
pcTest.SaveSession(startSession, reference)
|
pcTest.SaveSession(startSession)
|
||||||
|
|
||||||
session, age, err := pcTest.LoadCookiedSession()
|
session, err := pcTest.LoadCookiedSession()
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
if age < time.Duration(-2)*time.Hour {
|
if session.Age() < time.Duration(-2)*time.Hour {
|
||||||
t.Errorf("cookie too young %v", age)
|
t.Errorf("cookie too young %v", session.Age())
|
||||||
}
|
}
|
||||||
assert.Equal(t, startSession.Email, session.Email)
|
assert.Equal(t, startSession.Email, session.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessCookieFailIfCookieExpired(t *testing.T) {
|
func TestProcessCookieFailIfCookieExpired(t *testing.T) {
|
||||||
pcTest := NewProcessCookieTestWithDefaults()
|
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
|
||||||
pcTest.proxy.CookieExpire = time.Duration(24) * time.Hour
|
opts.CookieExpire = time.Duration(24) * time.Hour
|
||||||
|
})
|
||||||
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
||||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||||
pcTest.SaveSession(startSession, reference)
|
pcTest.SaveSession(startSession)
|
||||||
|
|
||||||
session, _, err := pcTest.LoadCookiedSession()
|
session, err := pcTest.LoadCookiedSession()
|
||||||
assert.NotEqual(t, nil, err)
|
assert.NotEqual(t, nil, err)
|
||||||
if session != nil {
|
if session != nil {
|
||||||
t.Errorf("expected nil session %#v", session)
|
t.Errorf("expected nil session %#v", session)
|
||||||
@ -707,22 +731,23 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
|
func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
|
||||||
pcTest := NewProcessCookieTestWithDefaults()
|
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
|
||||||
pcTest.proxy.CookieExpire = time.Duration(24) * time.Hour
|
opts.CookieExpire = time.Duration(24) * time.Hour
|
||||||
|
})
|
||||||
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
||||||
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||||
pcTest.SaveSession(startSession, reference)
|
pcTest.SaveSession(startSession)
|
||||||
|
|
||||||
pcTest.proxy.CookieRefresh = time.Hour
|
pcTest.proxy.CookieRefresh = time.Hour
|
||||||
session, _, err := pcTest.LoadCookiedSession()
|
session, err := pcTest.LoadCookiedSession()
|
||||||
assert.NotEqual(t, nil, err)
|
assert.NotEqual(t, nil, err)
|
||||||
if session != nil {
|
if session != nil {
|
||||||
t.Errorf("expected nil session %#v", session)
|
t.Errorf("expected nil session %#v", session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthOnlyEndpointTest() *ProcessCookieTest {
|
func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
|
||||||
pcTest := NewProcessCookieTestWithDefaults()
|
pcTest := NewProcessCookieTestWithOptionsModifiers(modifiers...)
|
||||||
pcTest.req, _ = http.NewRequest("GET",
|
pcTest.req, _ = http.NewRequest("GET",
|
||||||
pcTest.opts.ProxyPrefix+"/auth", nil)
|
pcTest.opts.ProxyPrefix+"/auth", nil)
|
||||||
return pcTest
|
return pcTest
|
||||||
@ -731,8 +756,8 @@ func NewAuthOnlyEndpointTest() *ProcessCookieTest {
|
|||||||
func TestAuthOnlyEndpointAccepted(t *testing.T) {
|
func TestAuthOnlyEndpointAccepted(t *testing.T) {
|
||||||
test := NewAuthOnlyEndpointTest()
|
test := NewAuthOnlyEndpointTest()
|
||||||
startSession := &sessions.SessionState{
|
startSession := &sessions.SessionState{
|
||||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
|
||||||
test.SaveSession(startSession, time.Now())
|
test.SaveSession(startSession)
|
||||||
|
|
||||||
test.proxy.ServeHTTP(test.rw, test.req)
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
assert.Equal(t, http.StatusAccepted, test.rw.Code)
|
assert.Equal(t, http.StatusAccepted, test.rw.Code)
|
||||||
@ -750,12 +775,13 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
|
func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
|
||||||
test := NewAuthOnlyEndpointTest()
|
test := NewAuthOnlyEndpointTest(func(opts *Options) {
|
||||||
test.proxy.CookieExpire = time.Duration(24) * time.Hour
|
opts.CookieExpire = time.Duration(24) * time.Hour
|
||||||
|
})
|
||||||
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
|
||||||
startSession := &sessions.SessionState{
|
startSession := &sessions.SessionState{
|
||||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
|
||||||
test.SaveSession(startSession, reference)
|
test.SaveSession(startSession)
|
||||||
|
|
||||||
test.proxy.ServeHTTP(test.rw, test.req)
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
|
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
|
||||||
@ -766,8 +792,8 @@ func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
|
|||||||
func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
|
func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
|
||||||
test := NewAuthOnlyEndpointTest()
|
test := NewAuthOnlyEndpointTest()
|
||||||
startSession := &sessions.SessionState{
|
startSession := &sessions.SessionState{
|
||||||
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
|
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
|
||||||
test.SaveSession(startSession, time.Now())
|
test.SaveSession(startSession)
|
||||||
test.validateUser = false
|
test.validateUser = false
|
||||||
|
|
||||||
test.proxy.ServeHTTP(test.rw, test.req)
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
@ -776,6 +802,25 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
|
|||||||
assert.Equal(t, "unauthorized request\n", string(bodyBytes))
|
assert.Equal(t, "unauthorized request\n", string(bodyBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthOnlyEndpointUnauthorizedOnProviderGroupValidationFailure(t *testing.T) {
|
||||||
|
test := NewAuthOnlyEndpointTest()
|
||||||
|
startSession := &sessions.SessionState{
|
||||||
|
Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
|
||||||
|
test.SaveSession(startSession)
|
||||||
|
provider := &TestProvider{
|
||||||
|
ValidToken: true,
|
||||||
|
GroupValidator: func(s string) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
test.proxy.provider = provider
|
||||||
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
|
||||||
|
assert.Equal(t, "unauthorized request\n", string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
||||||
var pcTest ProcessCookieTest
|
var pcTest ProcessCookieTest
|
||||||
|
|
||||||
@ -797,8 +842,8 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
|||||||
pcTest.opts.ProxyPrefix+"/auth", nil)
|
pcTest.opts.ProxyPrefix+"/auth", nil)
|
||||||
|
|
||||||
startSession := &sessions.SessionState{
|
startSession := &sessions.SessionState{
|
||||||
User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token"}
|
User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
|
||||||
pcTest.SaveSession(startSession, time.Now())
|
pcTest.SaveSession(startSession)
|
||||||
|
|
||||||
pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
|
pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
|
||||||
assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
|
assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
|
||||||
@ -815,9 +860,9 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
|
|||||||
|
|
||||||
opts := NewOptions()
|
opts := NewOptions()
|
||||||
opts.Upstreams = append(opts.Upstreams, upstream.URL)
|
opts.Upstreams = append(opts.Upstreams, upstream.URL)
|
||||||
opts.ClientID = "bazquux"
|
opts.ClientID = "aljsal"
|
||||||
opts.ClientSecret = "foobar"
|
opts.ClientSecret = "jglkfsdgj"
|
||||||
opts.CookieSecret = "xyzzyplugh"
|
opts.CookieSecret = "dkfjgdls"
|
||||||
opts.SkipAuthPreflight = true
|
opts.SkipAuthPreflight = true
|
||||||
opts.Validate()
|
opts.Validate()
|
||||||
|
|
||||||
@ -930,11 +975,11 @@ func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
|
|||||||
|
|
||||||
state := &sessions.SessionState{
|
state := &sessions.SessionState{
|
||||||
Email: "mbland@acm.org", AccessToken: "my_access_token"}
|
Email: "mbland@acm.org", AccessToken: "my_access_token"}
|
||||||
value, err := proxy.provider.CookieForSession(state, proxy.CookieCipher)
|
err = proxy.SaveSession(st.rw, req, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, c := range proxy.MakeSessionCookie(req, value, proxy.CookieExpire, time.Now()) {
|
for _, c := range st.rw.Result().Cookies() {
|
||||||
req.AddCookie(c)
|
req.AddCookie(c)
|
||||||
}
|
}
|
||||||
// This is used by the upstream to validate the signature.
|
// This is used by the upstream to validate the signature.
|
||||||
@ -954,8 +999,8 @@ func TestNoRequestSignature(t *testing.T) {
|
|||||||
func TestRequestSignatureGetRequest(t *testing.T) {
|
func TestRequestSignatureGetRequest(t *testing.T) {
|
||||||
st := NewSignatureTest()
|
st := NewSignatureTest()
|
||||||
defer st.Close()
|
defer st.Close()
|
||||||
st.opts.SignatureKey = "sha1:foobar"
|
st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d"
|
||||||
st.MakeRequestWithExpectedKey("GET", "", "foobar")
|
st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d")
|
||||||
assert.Equal(t, 200, st.rw.Code)
|
assert.Equal(t, 200, st.rw.Code)
|
||||||
assert.Equal(t, st.rw.Body.String(), "signatures match")
|
assert.Equal(t, st.rw.Body.String(), "signatures match")
|
||||||
}
|
}
|
||||||
@ -963,9 +1008,9 @@ func TestRequestSignatureGetRequest(t *testing.T) {
|
|||||||
func TestRequestSignaturePostRequest(t *testing.T) {
|
func TestRequestSignaturePostRequest(t *testing.T) {
|
||||||
st := NewSignatureTest()
|
st := NewSignatureTest()
|
||||||
defer st.Close()
|
defer st.Close()
|
||||||
st.opts.SignatureKey = "sha1:foobar"
|
st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61"
|
||||||
payload := `{ "hello": "world!" }`
|
payload := `{ "hello": "world!" }`
|
||||||
st.MakeRequestWithExpectedKey("POST", payload, "foobar")
|
st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61")
|
||||||
assert.Equal(t, 200, st.rw.Code)
|
assert.Equal(t, 200, st.rw.Code)
|
||||||
assert.Equal(t, st.rw.Body.String(), "signatures match")
|
assert.Equal(t, st.rw.Body.String(), "signatures match")
|
||||||
}
|
}
|
||||||
@ -1011,9 +1056,9 @@ type ajaxRequestTest struct {
|
|||||||
func newAjaxRequestTest() *ajaxRequestTest {
|
func newAjaxRequestTest() *ajaxRequestTest {
|
||||||
test := &ajaxRequestTest{}
|
test := &ajaxRequestTest{}
|
||||||
test.opts = NewOptions()
|
test.opts = NewOptions()
|
||||||
test.opts.CookieSecret = "foobar"
|
test.opts.CookieSecret = "sdflsw"
|
||||||
test.opts.ClientID = "bazquux"
|
test.opts.ClientID = "gkljfdl"
|
||||||
test.opts.ClientSecret = "xyzzyplugh"
|
test.opts.ClientSecret = "sdflkjs"
|
||||||
test.opts.Validate()
|
test.opts.Validate()
|
||||||
test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
|
test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
|
||||||
return true
|
return true
|
||||||
@ -1068,7 +1113,12 @@ func TestAjaxForbiddendRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClearSplitCookie(t *testing.T) {
|
func TestClearSplitCookie(t *testing.T) {
|
||||||
p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
|
opts := NewOptions()
|
||||||
|
opts.CookieName = "oauth2"
|
||||||
|
opts.CookieDomain = "abc"
|
||||||
|
store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
|
||||||
var rw = httptest.NewRecorder()
|
var rw = httptest.NewRecorder()
|
||||||
req := httptest.NewRequest("get", "/", nil)
|
req := httptest.NewRequest("get", "/", nil)
|
||||||
|
|
||||||
@ -1092,7 +1142,12 @@ func TestClearSplitCookie(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClearSingleCookie(t *testing.T) {
|
func TestClearSingleCookie(t *testing.T) {
|
||||||
p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
|
opts := NewOptions()
|
||||||
|
opts.CookieName = "oauth2"
|
||||||
|
opts.CookieDomain = "abc"
|
||||||
|
store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
p := OAuthProxy{CookieName: opts.CookieName, CookieDomain: opts.CookieDomain, sessionStore: store}
|
||||||
var rw = httptest.NewRecorder()
|
var rw = httptest.NewRecorder()
|
||||||
req := httptest.NewRequest("get", "/", nil)
|
req := httptest.NewRequest("get", "/", nil)
|
||||||
|
|
||||||
@ -1110,3 +1165,173 @@ func TestClearSingleCookie(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
|
assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NoOpKeySet struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NoOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
|
||||||
|
splitStrings := strings.Split(jwt, ".")
|
||||||
|
payloadString := splitStrings[1]
|
||||||
|
jsonString, err := base64.RawURLEncoding.DecodeString(payloadString)
|
||||||
|
return []byte(jsonString), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetJwtSession(t *testing.T) {
|
||||||
|
/* token payload:
|
||||||
|
{
|
||||||
|
"sub": "1234567890",
|
||||||
|
"aud": "https://test.myapp.com",
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"iss": "https://issuer.example.com",
|
||||||
|
"iat": 1553691215,
|
||||||
|
"exp": 1912151821
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
|
||||||
|
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
|
||||||
|
"WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
|
||||||
|
"E1LCJleHAiOjE5MTIxNTE4MjF9." +
|
||||||
|
"rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
|
||||||
|
"OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
|
||||||
|
|
||||||
|
keyset := NoOpKeySet{}
|
||||||
|
verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
|
||||||
|
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
|
||||||
|
|
||||||
|
test := NewAuthOnlyEndpointTest(func(opts *Options) {
|
||||||
|
opts.PassAuthorization = true
|
||||||
|
opts.SetAuthorization = true
|
||||||
|
opts.SetXAuthRequest = true
|
||||||
|
opts.SkipJwtBearerTokens = true
|
||||||
|
opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier)
|
||||||
|
})
|
||||||
|
tp, _ := test.proxy.provider.(*TestProvider)
|
||||||
|
tp.GroupValidator = func(s string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader := fmt.Sprintf("Bearer %s", goodJwt)
|
||||||
|
test.req.Header = map[string][]string{
|
||||||
|
"Authorization": {authHeader},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bearer
|
||||||
|
session, _ := test.proxy.GetJwtSession(test.req)
|
||||||
|
assert.Equal(t, session.User, "john@example.com")
|
||||||
|
assert.Equal(t, session.Email, "john@example.com")
|
||||||
|
assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0))
|
||||||
|
assert.Equal(t, session.IDToken, goodJwt)
|
||||||
|
|
||||||
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
|
if test.rw.Code >= 400 {
|
||||||
|
t.Fatalf("expected 3xx got %d", test.rw.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check PassAuthorization, should overwrite Basic header
|
||||||
|
assert.Equal(t, test.req.Header.Get("Authorization"), authHeader)
|
||||||
|
assert.Equal(t, test.req.Header.Get("X-Forwarded-User"), "john@example.com")
|
||||||
|
assert.Equal(t, test.req.Header.Get("X-Forwarded-Email"), "john@example.com")
|
||||||
|
|
||||||
|
// SetAuthorization and SetXAuthRequest
|
||||||
|
assert.Equal(t, test.rw.Header().Get("Authorization"), authHeader)
|
||||||
|
assert.Equal(t, test.rw.Header().Get("X-Auth-Request-User"), "john@example.com")
|
||||||
|
assert.Equal(t, test.rw.Header().Get("X-Auth-Request-Email"), "john@example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJwtUnauthorizedOnGroupValidationFailure(t *testing.T) {
|
||||||
|
goodJwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." +
|
||||||
|
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly90ZXN0Lm15YXBwLmNvbSIsIm5hbWUiOiJKb2huIERvZSIsImVtY" +
|
||||||
|
"WlsIjoiam9obkBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNTUzNjkxMj" +
|
||||||
|
"E1LCJleHAiOjE5MTIxNTE4MjF9." +
|
||||||
|
"rLVyzOnEldUq_pNkfa-WiV8TVJYWyZCaM2Am_uo8FGg11zD7l-qmz3x1seTvqpH6Y0Ty00fmv6dJnGnC8WMnPXQiodRTfhBSe" +
|
||||||
|
"OKZMu0HkMD2sg52zlKkbfLTO6ic5VnbVgwjjrB8am_Ta6w7kyFUaB5C1BsIrrLMldkWEhynbb8"
|
||||||
|
|
||||||
|
keyset := NoOpKeySet{}
|
||||||
|
verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
|
||||||
|
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
|
||||||
|
|
||||||
|
test := NewAuthOnlyEndpointTest(func(opts *Options) {
|
||||||
|
opts.PassAuthorization = true
|
||||||
|
opts.SetAuthorization = true
|
||||||
|
opts.SetXAuthRequest = true
|
||||||
|
opts.SkipJwtBearerTokens = true
|
||||||
|
opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier)
|
||||||
|
})
|
||||||
|
tp, _ := test.proxy.provider.(*TestProvider)
|
||||||
|
// Verify ValidateGroup fails JWT authorization
|
||||||
|
tp.GroupValidator = func(s string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader := fmt.Sprintf("Bearer %s", goodJwt)
|
||||||
|
test.req.Header = map[string][]string{
|
||||||
|
"Authorization": {authHeader},
|
||||||
|
}
|
||||||
|
test.proxy.ServeHTTP(test.rw, test.req)
|
||||||
|
if test.rw.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("expected 401 got %d", test.rw.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindJwtBearerToken(t *testing.T) {
|
||||||
|
p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"}
|
||||||
|
getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}}
|
||||||
|
|
||||||
|
validToken := "eyJfoobar.eyJfoobar.12345asdf"
|
||||||
|
var token string
|
||||||
|
|
||||||
|
// Bearer
|
||||||
|
getReq.Header = map[string][]string{
|
||||||
|
"Authorization": {fmt.Sprintf("Bearer %s", validToken)},
|
||||||
|
}
|
||||||
|
|
||||||
|
token, _ = p.findBearerToken(getReq)
|
||||||
|
assert.Equal(t, validToken, token)
|
||||||
|
|
||||||
|
// Basic - no password
|
||||||
|
getReq.SetBasicAuth(token, "")
|
||||||
|
token, _ = p.findBearerToken(getReq)
|
||||||
|
assert.Equal(t, validToken, token)
|
||||||
|
|
||||||
|
// Basic - sentinel password
|
||||||
|
getReq.SetBasicAuth(token, "x-oauth-basic")
|
||||||
|
token, _ = p.findBearerToken(getReq)
|
||||||
|
assert.Equal(t, validToken, token)
|
||||||
|
|
||||||
|
// Basic - any username, password matching jwt pattern
|
||||||
|
getReq.SetBasicAuth("any-username-you-could-wish-for", token)
|
||||||
|
token, _ = p.findBearerToken(getReq)
|
||||||
|
assert.Equal(t, validToken, token)
|
||||||
|
|
||||||
|
failures := []string{
|
||||||
|
// Too many parts
|
||||||
|
"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
|
||||||
|
// Not enough parts
|
||||||
|
"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA",
|
||||||
|
// Invalid encrypted key
|
||||||
|
"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.//////.dGVzdA.dGVzdA.dGVzdA",
|
||||||
|
// Invalid IV
|
||||||
|
"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.//////.dGVzdA.dGVzdA",
|
||||||
|
// Invalid ciphertext
|
||||||
|
"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.//////.dGVzdA",
|
||||||
|
// Invalid tag
|
||||||
|
"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.dGVzdA.dGVzdA.dGVzdA.//////",
|
||||||
|
// Invalid header
|
||||||
|
"W10.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
|
||||||
|
// Invalid header
|
||||||
|
"######.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
|
||||||
|
// Missing alc/enc params
|
||||||
|
"e30.dGVzdA.dGVzdA.dGVzdA.dGVzdA",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, failure := range failures {
|
||||||
|
getReq.Header = map[string][]string{
|
||||||
|
"Authorization": {fmt.Sprintf("Bearer %s", failure)},
|
||||||
|
}
|
||||||
|
_, err := p.findBearerToken(getReq)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s", token)
|
||||||
|
}
|
||||||
|
181
options.go
181
options.go
@ -17,8 +17,11 @@ import (
|
|||||||
oidc "github.com/coreos/go-oidc"
|
oidc "github.com/coreos/go-oidc"
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
"github.com/mbland/hmacauth"
|
"github.com/mbland/hmacauth"
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
||||||
|
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/sessions"
|
||||||
"github.com/pusher/oauth2_proxy/providers"
|
"github.com/pusher/oauth2_proxy/providers"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
)
|
)
|
||||||
@ -26,28 +29,34 @@ import (
|
|||||||
// Options holds Configuration Options that can be set by Command Line Flag,
|
// Options holds Configuration Options that can be set by Command Line Flag,
|
||||||
// or Config File
|
// or Config File
|
||||||
type Options struct {
|
type Options struct {
|
||||||
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy-prefix" env:"OAUTH2_PROXY_PROXY_PREFIX"`
|
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix" env:"OAUTH2_PROXY_PROXY_PREFIX"`
|
||||||
|
PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"`
|
||||||
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"`
|
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"`
|
||||||
HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"`
|
HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"`
|
||||||
HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"`
|
HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"`
|
||||||
RedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"`
|
RedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"`
|
||||||
ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
|
ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
|
||||||
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
|
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
|
||||||
TLSCertFile string `flag:"tls-cert" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"`
|
TLSCertFile string `flag:"tls-cert-file" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"`
|
||||||
TLSKeyFile string `flag:"tls-key" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"`
|
TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"`
|
||||||
|
|
||||||
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"`
|
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"`
|
||||||
|
KeycloakGroup string `flag:"keycloak-group" cfg:"keycloak_group" env:"OAUTH2_PROXY_KEYCLOAK_GROUP"`
|
||||||
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"`
|
AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"`
|
||||||
|
BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team" env:"OAUTH2_PROXY_BITBUCKET_TEAM"`
|
||||||
|
BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository" env:"OAUTH2_PROXY_BITBUCKET_REPOSITORY"`
|
||||||
EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"`
|
EmailDomains []string `flag:"email-domain" cfg:"email_domains" env:"OAUTH2_PROXY_EMAIL_DOMAINS"`
|
||||||
WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"`
|
WhitelistDomains []string `flag:"whitelist-domain" cfg:"whitelist_domains" env:"OAUTH2_PROXY_WHITELIST_DOMAINS"`
|
||||||
GitHubOrg string `flag:"github-org" cfg:"github_org" env:"OAUTH2_PROXY_GITHUB_ORG"`
|
GitHubOrg string `flag:"github-org" cfg:"github_org" env:"OAUTH2_PROXY_GITHUB_ORG"`
|
||||||
GitHubTeam string `flag:"github-team" cfg:"github_team" env:"OAUTH2_PROXY_GITHUB_TEAM"`
|
GitHubTeam string `flag:"github-team" cfg:"github_team" env:"OAUTH2_PROXY_GITHUB_TEAM"`
|
||||||
|
GitLabGroup string `flag:"gitlab-group" cfg:"gitlab_group" env:"OAUTH2_PROXY_GITLAB_GROUP"`
|
||||||
GoogleGroups []string `flag:"google-group" cfg:"google_group" env:"OAUTH2_PROXY_GOOGLE_GROUPS"`
|
GoogleGroups []string `flag:"google-group" cfg:"google_group" env:"OAUTH2_PROXY_GOOGLE_GROUPS"`
|
||||||
GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email" env:"OAUTH2_PROXY_GOOGLE_ADMIN_EMAIL"`
|
GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email" env:"OAUTH2_PROXY_GOOGLE_ADMIN_EMAIL"`
|
||||||
GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json" env:"OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON"`
|
GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json" env:"OAUTH2_PROXY_GOOGLE_SERVICE_ACCOUNT_JSON"`
|
||||||
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE"`
|
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file" env:"OAUTH2_PROXY_HTPASSWD_FILE"`
|
||||||
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM"`
|
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form" env:"OAUTH2_PROXY_DISPLAY_HTPASSWD_FORM"`
|
||||||
CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR"`
|
CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir" env:"OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR"`
|
||||||
|
Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
|
||||||
Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
|
Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
|
||||||
|
|
||||||
// Embed CookieOptions
|
// Embed CookieOptions
|
||||||
@ -58,6 +67,8 @@ type Options struct {
|
|||||||
|
|
||||||
Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
|
Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
|
||||||
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
|
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
|
||||||
|
SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
|
||||||
|
ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
|
||||||
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
|
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
|
||||||
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
|
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
|
||||||
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
|
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
|
||||||
@ -65,6 +76,7 @@ type Options struct {
|
|||||||
SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
|
SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
|
||||||
PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
|
PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
|
||||||
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
|
SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
|
||||||
|
SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_UPSTREAM_INSECURE_SKIP_VERIFY"`
|
||||||
SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
|
SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
|
||||||
SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
|
SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
|
||||||
PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
|
PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
|
||||||
@ -75,8 +87,9 @@ type Options struct {
|
|||||||
// potential overrides.
|
// potential overrides.
|
||||||
Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
|
Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
|
||||||
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
|
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
|
||||||
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"`
|
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"`
|
||||||
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_URL"`
|
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"`
|
||||||
|
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_PROXY_OIDC_JWKS_URL"`
|
||||||
LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"`
|
LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"`
|
||||||
RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"`
|
RedeemURL string `flag:"redeem-url" cfg:"redeem_url" env:"OAUTH2_PROXY_REDEEM_URL"`
|
||||||
ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"`
|
ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"`
|
||||||
@ -86,19 +99,20 @@ type Options struct {
|
|||||||
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"`
|
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"`
|
||||||
|
|
||||||
// Configuration values for logging
|
// Configuration values for logging
|
||||||
LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"`
|
LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_PROXY_LOGGING_FILENAME"`
|
||||||
LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_LOGGING_MAX_SIZE"`
|
LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE"`
|
||||||
LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_LOGGING_MAX_AGE"`
|
LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE"`
|
||||||
LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_LOGGING_MAX_BACKUPS"`
|
LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS"`
|
||||||
LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_LOGGING_LOCAL_TIME"`
|
LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME"`
|
||||||
LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_LOGGING_COMPRESS"`
|
LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS"`
|
||||||
StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_STANDARD_LOGGING"`
|
StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING"`
|
||||||
StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_STANDARD_LOGGING_FORMAT"`
|
StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"`
|
||||||
RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_REQUEST_LOGGING"`
|
RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"`
|
||||||
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_REQUEST_LOGGING_FORMAT"`
|
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"`
|
||||||
AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_LOGGING_AUTH_LOGGING"`
|
ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS"`
|
||||||
AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_AUTH_LOGGING_FORMAT"`
|
SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"`
|
||||||
|
AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"`
|
||||||
|
AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"`
|
||||||
SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
|
SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
|
||||||
AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
|
AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"`
|
||||||
JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
|
JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"`
|
||||||
@ -111,8 +125,10 @@ type Options struct {
|
|||||||
proxyURLs []*url.URL
|
proxyURLs []*url.URL
|
||||||
CompiledRegex []*regexp.Regexp
|
CompiledRegex []*regexp.Regexp
|
||||||
provider providers.Provider
|
provider providers.Provider
|
||||||
|
sessionStore sessionsapi.SessionStore
|
||||||
signatureData *SignatureData
|
signatureData *SignatureData
|
||||||
oidcVerifier *oidc.IDTokenVerifier
|
oidcVerifier *oidc.IDTokenVerifier
|
||||||
|
jwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignatureData holds hmacauth signature hash and key
|
// SignatureData holds hmacauth signature hash and key
|
||||||
@ -125,6 +141,7 @@ type SignatureData struct {
|
|||||||
func NewOptions() *Options {
|
func NewOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
ProxyPrefix: "/oauth2",
|
ProxyPrefix: "/oauth2",
|
||||||
|
PingPath: "/ping",
|
||||||
ProxyWebSockets: true,
|
ProxyWebSockets: true,
|
||||||
HTTPAddress: "127.0.0.1:4180",
|
HTTPAddress: "127.0.0.1:4180",
|
||||||
HTTPSAddress: ":443",
|
HTTPSAddress: ":443",
|
||||||
@ -136,6 +153,9 @@ func NewOptions() *Options {
|
|||||||
CookieExpire: time.Duration(168) * time.Hour,
|
CookieExpire: time.Duration(168) * time.Hour,
|
||||||
CookieRefresh: time.Duration(0),
|
CookieRefresh: time.Duration(0),
|
||||||
},
|
},
|
||||||
|
SessionOptions: options.SessionOptions{
|
||||||
|
Type: "cookie",
|
||||||
|
},
|
||||||
SetXAuthRequest: false,
|
SetXAuthRequest: false,
|
||||||
SkipAuthPreflight: false,
|
SkipAuthPreflight: false,
|
||||||
PassBasicAuth: true,
|
PassBasicAuth: true,
|
||||||
@ -145,6 +165,7 @@ func NewOptions() *Options {
|
|||||||
SetAuthorization: false,
|
SetAuthorization: false,
|
||||||
PassAuthorization: false,
|
PassAuthorization: false,
|
||||||
ApprovalPrompt: "force",
|
ApprovalPrompt: "force",
|
||||||
|
InsecureOIDCAllowUnverifiedEmail: false,
|
||||||
SkipOIDCDiscovery: false,
|
SkipOIDCDiscovery: false,
|
||||||
LoggingFilename: "",
|
LoggingFilename: "",
|
||||||
LoggingMaxSize: 100,
|
LoggingMaxSize: 100,
|
||||||
@ -152,6 +173,8 @@ func NewOptions() *Options {
|
|||||||
LoggingMaxBackups: 0,
|
LoggingMaxBackups: 0,
|
||||||
LoggingLocalTime: true,
|
LoggingLocalTime: true,
|
||||||
LoggingCompress: false,
|
LoggingCompress: false,
|
||||||
|
ExcludeLoggingPaths: "",
|
||||||
|
SilencePingLogging: false,
|
||||||
StandardLogging: true,
|
StandardLogging: true,
|
||||||
StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
|
StandardLoggingFormat: logger.DefaultStandardLoggingFormat,
|
||||||
RequestLogging: true,
|
RequestLogging: true,
|
||||||
@ -161,6 +184,12 @@ func NewOptions() *Options {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
|
||||||
|
type jwtIssuer struct {
|
||||||
|
issuerURI string
|
||||||
|
audience string
|
||||||
|
}
|
||||||
|
|
||||||
func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
|
func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
|
||||||
parsed, err := url.Parse(toParse)
|
parsed, err := url.Parse(toParse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -237,6 +266,25 @@ func (o *Options) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.SkipJwtBearerTokens {
|
||||||
|
// If we are using an oidc provider, go ahead and add that provider to the list
|
||||||
|
if o.oidcVerifier != nil {
|
||||||
|
o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, o.oidcVerifier)
|
||||||
|
}
|
||||||
|
// Configure extra issuers
|
||||||
|
if len(o.ExtraJwtIssuers) > 0 {
|
||||||
|
var jwtIssuers []jwtIssuer
|
||||||
|
jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs)
|
||||||
|
for _, jwtIssuer := range jwtIssuers {
|
||||||
|
verifier, err := newVerifierFromJwtIssuer(jwtIssuer)
|
||||||
|
if err != nil {
|
||||||
|
msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err))
|
||||||
|
}
|
||||||
|
o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
|
o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
|
||||||
|
|
||||||
for _, u := range o.Upstreams {
|
for _, u := range o.Upstreams {
|
||||||
@ -261,7 +309,8 @@ func (o *Options) Validate() error {
|
|||||||
}
|
}
|
||||||
msgs = parseProviderInfo(o, msgs)
|
msgs = parseProviderInfo(o, msgs)
|
||||||
|
|
||||||
if o.PassAccessToken || (o.CookieRefresh != time.Duration(0)) {
|
var cipher *encryption.Cipher
|
||||||
|
if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.CookieRefresh != time.Duration(0)) {
|
||||||
validCookieSecretSize := false
|
validCookieSecretSize := false
|
||||||
for _, i := range []int{16, 24, 32} {
|
for _, i := range []int{16, 24, 32} {
|
||||||
if len(secretBytes(o.CookieSecret)) == i {
|
if len(secretBytes(o.CookieSecret)) == i {
|
||||||
@ -283,8 +332,22 @@ func (o *Options) Validate() error {
|
|||||||
"pass_access_token == true or "+
|
"pass_access_token == true or "+
|
||||||
"cookie_refresh != 0, but is %d bytes.%s",
|
"cookie_refresh != 0, but is %d bytes.%s",
|
||||||
len(secretBytes(o.CookieSecret)), suffix))
|
len(secretBytes(o.CookieSecret)), suffix))
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
cipher, err = encryption.NewCipher(secretBytes(o.CookieSecret))
|
||||||
|
if err != nil {
|
||||||
|
msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o.SessionOptions.Cipher = cipher
|
||||||
|
sessionStore, err := sessions.NewSessionStore(&o.SessionOptions, &o.CookieOptions)
|
||||||
|
if err != nil {
|
||||||
|
msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
|
||||||
|
} else {
|
||||||
|
o.sessionStore = sessionStore
|
||||||
|
}
|
||||||
|
|
||||||
if o.CookieRefresh >= o.CookieExpire {
|
if o.CookieRefresh >= o.CookieExpire {
|
||||||
msgs = append(msgs, fmt.Sprintf(
|
msgs = append(msgs, fmt.Sprintf(
|
||||||
@ -336,6 +399,8 @@ func parseProviderInfo(o *Options, msgs []string) []string {
|
|||||||
p.Configure(o.AzureTenant)
|
p.Configure(o.AzureTenant)
|
||||||
case *providers.GitHubProvider:
|
case *providers.GitHubProvider:
|
||||||
p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
|
p.SetOrgTeam(o.GitHubOrg, o.GitHubTeam)
|
||||||
|
case *providers.KeycloakProvider:
|
||||||
|
p.SetGroup(o.KeycloakGroup)
|
||||||
case *providers.GoogleProvider:
|
case *providers.GoogleProvider:
|
||||||
if o.GoogleServiceAccountJSON != "" {
|
if o.GoogleServiceAccountJSON != "" {
|
||||||
file, err := os.Open(o.GoogleServiceAccountJSON)
|
file, err := os.Open(o.GoogleServiceAccountJSON)
|
||||||
@ -345,12 +410,39 @@ func parseProviderInfo(o *Options, msgs []string) []string {
|
|||||||
p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file)
|
p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case *providers.BitbucketProvider:
|
||||||
|
p.SetTeam(o.BitbucketTeam)
|
||||||
|
p.SetRepository(o.BitbucketRepository)
|
||||||
case *providers.OIDCProvider:
|
case *providers.OIDCProvider:
|
||||||
|
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
|
||||||
if o.oidcVerifier == nil {
|
if o.oidcVerifier == nil {
|
||||||
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
||||||
} else {
|
} else {
|
||||||
p.Verifier = o.oidcVerifier
|
p.Verifier = o.oidcVerifier
|
||||||
}
|
}
|
||||||
|
case *providers.GitLabProvider:
|
||||||
|
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
|
||||||
|
p.Group = o.GitLabGroup
|
||||||
|
p.EmailDomains = o.EmailDomains
|
||||||
|
|
||||||
|
if o.oidcVerifier != nil {
|
||||||
|
p.Verifier = o.oidcVerifier
|
||||||
|
} else {
|
||||||
|
// Initialize with default verifier for gitlab.com
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
provider, err := oidc.NewProvider(ctx, "https://gitlab.com")
|
||||||
|
if err != nil {
|
||||||
|
msgs = append(msgs, "failed to initialize oidc provider for gitlab.com")
|
||||||
|
} else {
|
||||||
|
p.Verifier = provider.Verifier(&oidc.Config{
|
||||||
|
ClientID: o.ClientID,
|
||||||
|
})
|
||||||
|
|
||||||
|
p.LoginURL, msgs = parseURL(provider.Endpoint().AuthURL, "login", msgs)
|
||||||
|
p.RedeemURL, msgs = parseURL(provider.Endpoint().TokenURL, "redeem", msgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
case *providers.LoginGovProvider:
|
case *providers.LoginGovProvider:
|
||||||
p.AcrValues = o.AcrValues
|
p.AcrValues = o.AcrValues
|
||||||
p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
|
p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs)
|
||||||
@ -404,10 +496,49 @@ func parseSignatureKey(o *Options, msgs []string) []string {
|
|||||||
return append(msgs, "unsupported signature hash algorithm: "+
|
return append(msgs, "unsupported signature hash algorithm: "+
|
||||||
o.SignatureKey)
|
o.SignatureKey)
|
||||||
}
|
}
|
||||||
o.signatureData = &SignatureData{hash, secretKey}
|
o.signatureData = &SignatureData{hash: hash, key: secretKey}
|
||||||
return msgs
|
return msgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseJwtIssuers takes in an array of strings in the form of issuer=audience
|
||||||
|
// and parses to an array of jwtIssuer structs.
|
||||||
|
func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
|
||||||
|
var parsedIssuers []jwtIssuer
|
||||||
|
for _, jwtVerifier := range issuers {
|
||||||
|
components := strings.Split(jwtVerifier, "=")
|
||||||
|
if len(components) < 2 {
|
||||||
|
msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
uri, audience := components[0], strings.Join(components[1:], "=")
|
||||||
|
parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience})
|
||||||
|
}
|
||||||
|
return parsedIssuers, msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
|
||||||
|
// a verifier for that issuer.
|
||||||
|
func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) {
|
||||||
|
config := &oidc.Config{
|
||||||
|
ClientID: jwtIssuer.audience,
|
||||||
|
}
|
||||||
|
// Try as an OpenID Connect Provider first
|
||||||
|
var verifier *oidc.IDTokenVerifier
|
||||||
|
provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI)
|
||||||
|
if err != nil {
|
||||||
|
// Try as JWKS URI
|
||||||
|
jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
|
||||||
|
_, err := http.NewRequest("GET", jwksURI, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
|
||||||
|
} else {
|
||||||
|
verifier = provider.Verifier(config)
|
||||||
|
}
|
||||||
|
return verifier, nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateCookieName(o *Options, msgs []string) []string {
|
func validateCookieName(o *Options, msgs []string) []string {
|
||||||
cookie := &http.Cookie{Name: o.CookieName}
|
cookie := &http.Cookie{Name: o.CookieName}
|
||||||
if cookie.String() == "" {
|
if cookie.String() == "" {
|
||||||
@ -478,6 +609,14 @@ func setupLogger(o *Options, msgs []string) []string {
|
|||||||
logger.SetAuthTemplate(o.AuthLoggingFormat)
|
logger.SetAuthTemplate(o.AuthLoggingFormat)
|
||||||
logger.SetReqTemplate(o.RequestLoggingFormat)
|
logger.SetReqTemplate(o.RequestLoggingFormat)
|
||||||
|
|
||||||
|
excludePaths := make([]string, 0)
|
||||||
|
excludePaths = append(excludePaths, strings.Split(o.ExcludeLoggingPaths, ",")...)
|
||||||
|
if o.SilencePingLogging {
|
||||||
|
excludePaths = append(excludePaths, o.PingPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.SetExcludePaths(excludePaths)
|
||||||
|
|
||||||
if !o.LoggingLocalTime {
|
if !o.LoggingLocalTime {
|
||||||
logger.SetFlags(logger.Flags() | logger.LUTC)
|
logger.SetFlags(logger.Flags() | logger.LUTC)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
|
import "github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
|
|
||||||
// SessionOptions contains configuration options for the SessionStore providers.
|
// SessionOptions contains configuration options for the SessionStore providers.
|
||||||
type SessionOptions struct {
|
type SessionOptions struct {
|
||||||
Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"`
|
Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"`
|
||||||
|
Cipher *encryption.Cipher
|
||||||
CookieStoreOptions
|
CookieStoreOptions
|
||||||
|
RedisStoreOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// CookieSessionStoreType is used to indicate the CookieSessionStore should be
|
// CookieSessionStoreType is used to indicate the CookieSessionStore should be
|
||||||
@ -12,3 +16,15 @@ var CookieSessionStoreType = "cookie"
|
|||||||
|
|
||||||
// CookieStoreOptions contains configuration options for the CookieSessionStore.
|
// CookieStoreOptions contains configuration options for the CookieSessionStore.
|
||||||
type CookieStoreOptions struct{}
|
type CookieStoreOptions struct{}
|
||||||
|
|
||||||
|
// RedisSessionStoreType is used to indicate the RedisSessionStore should be
|
||||||
|
// used for storing sessions.
|
||||||
|
var RedisSessionStoreType = "redis"
|
||||||
|
|
||||||
|
// RedisStoreOptions contains configuration options for the RedisSessionStore.
|
||||||
|
type RedisStoreOptions struct {
|
||||||
|
RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"`
|
||||||
|
UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"`
|
||||||
|
SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"`
|
||||||
|
SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"`
|
||||||
|
}
|
||||||
|
@ -7,13 +7,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SessionState is used to store information about the currently authenticated user session
|
// SessionState is used to store information about the currently authenticated user session
|
||||||
type SessionState struct {
|
type SessionState struct {
|
||||||
AccessToken string `json:",omitempty"`
|
AccessToken string `json:",omitempty"`
|
||||||
IDToken string `json:",omitempty"`
|
IDToken string `json:",omitempty"`
|
||||||
|
CreatedAt time.Time `json:"-"`
|
||||||
ExpiresOn time.Time `json:"-"`
|
ExpiresOn time.Time `json:"-"`
|
||||||
RefreshToken string `json:",omitempty"`
|
RefreshToken string `json:",omitempty"`
|
||||||
Email string `json:",omitempty"`
|
Email string `json:",omitempty"`
|
||||||
@ -23,6 +24,7 @@ type SessionState struct {
|
|||||||
// SessionStateJSON is used to encode SessionState into JSON without exposing time.Time zero value
|
// SessionStateJSON is used to encode SessionState into JSON without exposing time.Time zero value
|
||||||
type SessionStateJSON struct {
|
type SessionStateJSON struct {
|
||||||
*SessionState
|
*SessionState
|
||||||
|
CreatedAt *time.Time `json:",omitempty"`
|
||||||
ExpiresOn *time.Time `json:",omitempty"`
|
ExpiresOn *time.Time `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +36,14 @@ func (s *SessionState) IsExpired() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Age returns the age of a session
|
||||||
|
func (s *SessionState) Age() time.Duration {
|
||||||
|
if !s.CreatedAt.IsZero() {
|
||||||
|
return time.Now().Truncate(time.Second).Sub(s.CreatedAt)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// String constructs a summary of the session state
|
// String constructs a summary of the session state
|
||||||
func (s *SessionState) String() string {
|
func (s *SessionState) String() string {
|
||||||
o := fmt.Sprintf("Session{email:%s user:%s", s.Email, s.User)
|
o := fmt.Sprintf("Session{email:%s user:%s", s.Email, s.User)
|
||||||
@ -43,6 +53,9 @@ func (s *SessionState) String() string {
|
|||||||
if s.IDToken != "" {
|
if s.IDToken != "" {
|
||||||
o += " id_token:true"
|
o += " id_token:true"
|
||||||
}
|
}
|
||||||
|
if !s.CreatedAt.IsZero() {
|
||||||
|
o += fmt.Sprintf(" created:%s", s.CreatedAt)
|
||||||
|
}
|
||||||
if !s.ExpiresOn.IsZero() {
|
if !s.ExpiresOn.IsZero() {
|
||||||
o += fmt.Sprintf(" expires:%s", s.ExpiresOn)
|
o += fmt.Sprintf(" expires:%s", s.ExpiresOn)
|
||||||
}
|
}
|
||||||
@ -53,7 +66,7 @@ func (s *SessionState) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EncodeSessionState returns string representation of the current session
|
// EncodeSessionState returns string representation of the current session
|
||||||
func (s *SessionState) EncodeSessionState(c *cookie.Cipher) (string, error) {
|
func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error) {
|
||||||
var ss SessionState
|
var ss SessionState
|
||||||
if c == nil {
|
if c == nil {
|
||||||
// Store only Email and User when cipher is unavailable
|
// Store only Email and User when cipher is unavailable
|
||||||
@ -95,6 +108,9 @@ func (s *SessionState) EncodeSessionState(c *cookie.Cipher) (string, error) {
|
|||||||
}
|
}
|
||||||
// Embed SessionState and ExpiresOn pointer into SessionStateJSON
|
// Embed SessionState and ExpiresOn pointer into SessionStateJSON
|
||||||
ssj := &SessionStateJSON{SessionState: &ss}
|
ssj := &SessionStateJSON{SessionState: &ss}
|
||||||
|
if !ss.CreatedAt.IsZero() {
|
||||||
|
ssj.CreatedAt = &ss.CreatedAt
|
||||||
|
}
|
||||||
if !ss.ExpiresOn.IsZero() {
|
if !ss.ExpiresOn.IsZero() {
|
||||||
ssj.ExpiresOn = &ss.ExpiresOn
|
ssj.ExpiresOn = &ss.ExpiresOn
|
||||||
}
|
}
|
||||||
@ -117,7 +133,7 @@ func legacyDecodeSessionStatePlain(v string) (*SessionState, error) {
|
|||||||
|
|
||||||
// legacyDecodeSessionState attempts to decode the session state string
|
// legacyDecodeSessionState attempts to decode the session state string
|
||||||
// generated by v3.1.0 or older
|
// generated by v3.1.0 or older
|
||||||
func legacyDecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) {
|
func legacyDecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) {
|
||||||
chunks := strings.Split(v, "|")
|
chunks := strings.Split(v, "|")
|
||||||
|
|
||||||
if c == nil {
|
if c == nil {
|
||||||
@ -160,13 +176,16 @@ func legacyDecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DecodeSessionState decodes the session cookie string into a SessionState
|
// DecodeSessionState decodes the session cookie string into a SessionState
|
||||||
func DecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) {
|
func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) {
|
||||||
var ssj SessionStateJSON
|
var ssj SessionStateJSON
|
||||||
var ss *SessionState
|
var ss *SessionState
|
||||||
err := json.Unmarshal([]byte(v), &ssj)
|
err := json.Unmarshal([]byte(v), &ssj)
|
||||||
if err == nil && ssj.SessionState != nil {
|
if err == nil && ssj.SessionState != nil {
|
||||||
// Extract SessionState and ExpiresOn value from SessionStateJSON
|
// Extract SessionState and CreatedAt,ExpiresOn value from SessionStateJSON
|
||||||
ss = ssj.SessionState
|
ss = ssj.SessionState
|
||||||
|
if ssj.CreatedAt != nil {
|
||||||
|
ss.CreatedAt = *ssj.CreatedAt
|
||||||
|
}
|
||||||
if ssj.ExpiresOn != nil {
|
if ssj.ExpiresOn != nil {
|
||||||
ss.ExpiresOn = *ssj.ExpiresOn
|
ss.ExpiresOn = *ssj.ExpiresOn
|
||||||
}
|
}
|
||||||
@ -184,14 +203,14 @@ func DecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) {
|
|||||||
User: ss.User,
|
User: ss.User,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Backward compatibility with using unecrypted Email
|
// Backward compatibility with using unencrypted Email
|
||||||
if ss.Email != "" {
|
if ss.Email != "" {
|
||||||
decryptedEmail, errEmail := c.Decrypt(ss.Email)
|
decryptedEmail, errEmail := c.Decrypt(ss.Email)
|
||||||
if errEmail == nil {
|
if errEmail == nil {
|
||||||
ss.Email = decryptedEmail
|
ss.Email = decryptedEmail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Backward compatibility with using unecrypted User
|
// Backward compatibility with using unencrypted User
|
||||||
if ss.User != "" {
|
if ss.User != "" {
|
||||||
decryptedUser, errUser := c.Decrypt(ss.User)
|
decryptedUser, errUser := c.Decrypt(ss.User)
|
||||||
if errUser == nil {
|
if errUser == nil {
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,14 +14,15 @@ const secret = "0123456789abcdefghijklmnopqrstuv"
|
|||||||
const altSecret = "0000000000abcdefghijklmnopqrstuv"
|
const altSecret = "0000000000abcdefghijklmnopqrstuv"
|
||||||
|
|
||||||
func TestSessionStateSerialization(t *testing.T) {
|
func TestSessionStateSerialization(t *testing.T) {
|
||||||
c, err := cookie.NewCipher([]byte(secret))
|
c, err := encryption.NewCipher([]byte(secret))
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
c2, err := cookie.NewCipher([]byte(altSecret))
|
c2, err := encryption.NewCipher([]byte(altSecret))
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
s := &sessions.SessionState{
|
s := &sessions.SessionState{
|
||||||
Email: "user@domain.com",
|
Email: "user@domain.com",
|
||||||
AccessToken: "token1234",
|
AccessToken: "token1234",
|
||||||
IDToken: "rawtoken1234",
|
IDToken: "rawtoken1234",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
||||||
RefreshToken: "refresh4321",
|
RefreshToken: "refresh4321",
|
||||||
}
|
}
|
||||||
@ -35,6 +36,7 @@ func TestSessionStateSerialization(t *testing.T) {
|
|||||||
assert.Equal(t, s.Email, ss.Email)
|
assert.Equal(t, s.Email, ss.Email)
|
||||||
assert.Equal(t, s.AccessToken, ss.AccessToken)
|
assert.Equal(t, s.AccessToken, ss.AccessToken)
|
||||||
assert.Equal(t, s.IDToken, ss.IDToken)
|
assert.Equal(t, s.IDToken, ss.IDToken)
|
||||||
|
assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
|
||||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||||
assert.Equal(t, s.RefreshToken, ss.RefreshToken)
|
assert.Equal(t, s.RefreshToken, ss.RefreshToken)
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ func TestSessionStateSerialization(t *testing.T) {
|
|||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.NotEqual(t, "user@domain.com", ss.User)
|
assert.NotEqual(t, "user@domain.com", ss.User)
|
||||||
assert.NotEqual(t, s.Email, ss.Email)
|
assert.NotEqual(t, s.Email, ss.Email)
|
||||||
|
assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
|
||||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||||
assert.NotEqual(t, s.AccessToken, ss.AccessToken)
|
assert.NotEqual(t, s.AccessToken, ss.AccessToken)
|
||||||
assert.NotEqual(t, s.IDToken, ss.IDToken)
|
assert.NotEqual(t, s.IDToken, ss.IDToken)
|
||||||
@ -51,14 +54,15 @@ func TestSessionStateSerialization(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStateSerializationWithUser(t *testing.T) {
|
func TestSessionStateSerializationWithUser(t *testing.T) {
|
||||||
c, err := cookie.NewCipher([]byte(secret))
|
c, err := encryption.NewCipher([]byte(secret))
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
c2, err := cookie.NewCipher([]byte(altSecret))
|
c2, err := encryption.NewCipher([]byte(altSecret))
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
s := &sessions.SessionState{
|
s := &sessions.SessionState{
|
||||||
User: "just-user",
|
User: "just-user",
|
||||||
Email: "user@domain.com",
|
Email: "user@domain.com",
|
||||||
AccessToken: "token1234",
|
AccessToken: "token1234",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
||||||
RefreshToken: "refresh4321",
|
RefreshToken: "refresh4321",
|
||||||
}
|
}
|
||||||
@ -71,6 +75,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
|
|||||||
assert.Equal(t, s.User, ss.User)
|
assert.Equal(t, s.User, ss.User)
|
||||||
assert.Equal(t, s.Email, ss.Email)
|
assert.Equal(t, s.Email, ss.Email)
|
||||||
assert.Equal(t, s.AccessToken, ss.AccessToken)
|
assert.Equal(t, s.AccessToken, ss.AccessToken)
|
||||||
|
assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
|
||||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||||
assert.Equal(t, s.RefreshToken, ss.RefreshToken)
|
assert.Equal(t, s.RefreshToken, ss.RefreshToken)
|
||||||
|
|
||||||
@ -80,6 +85,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
|
|||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.NotEqual(t, s.User, ss.User)
|
assert.NotEqual(t, s.User, ss.User)
|
||||||
assert.NotEqual(t, s.Email, ss.Email)
|
assert.NotEqual(t, s.Email, ss.Email)
|
||||||
|
assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
|
||||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||||
assert.NotEqual(t, s.AccessToken, ss.AccessToken)
|
assert.NotEqual(t, s.AccessToken, ss.AccessToken)
|
||||||
assert.NotEqual(t, s.RefreshToken, ss.RefreshToken)
|
assert.NotEqual(t, s.RefreshToken, ss.RefreshToken)
|
||||||
@ -89,6 +95,7 @@ func TestSessionStateSerializationNoCipher(t *testing.T) {
|
|||||||
s := &sessions.SessionState{
|
s := &sessions.SessionState{
|
||||||
Email: "user@domain.com",
|
Email: "user@domain.com",
|
||||||
AccessToken: "token1234",
|
AccessToken: "token1234",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
||||||
RefreshToken: "refresh4321",
|
RefreshToken: "refresh4321",
|
||||||
}
|
}
|
||||||
@ -109,6 +116,7 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) {
|
|||||||
User: "just-user",
|
User: "just-user",
|
||||||
Email: "user@domain.com",
|
Email: "user@domain.com",
|
||||||
AccessToken: "token1234",
|
AccessToken: "token1234",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
||||||
RefreshToken: "refresh4321",
|
RefreshToken: "refresh4321",
|
||||||
}
|
}
|
||||||
@ -138,7 +146,7 @@ func TestExpired(t *testing.T) {
|
|||||||
type testCase struct {
|
type testCase struct {
|
||||||
sessions.SessionState
|
sessions.SessionState
|
||||||
Encoded string
|
Encoded string
|
||||||
Cipher *cookie.Cipher
|
Cipher *encryption.Cipher
|
||||||
Error bool
|
Error bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +155,7 @@ type testCase struct {
|
|||||||
// Currently only tests without cipher here because we have no way to mock
|
// Currently only tests without cipher here because we have no way to mock
|
||||||
// the random generator used in EncodeSessionState.
|
// the random generator used in EncodeSessionState.
|
||||||
func TestEncodeSessionState(t *testing.T) {
|
func TestEncodeSessionState(t *testing.T) {
|
||||||
|
c := time.Now()
|
||||||
e := time.Now().Add(time.Duration(1) * time.Hour)
|
e := time.Now().Add(time.Duration(1) * time.Hour)
|
||||||
|
|
||||||
testCases := []testCase{
|
testCases := []testCase{
|
||||||
@ -163,6 +172,7 @@ func TestEncodeSessionState(t *testing.T) {
|
|||||||
User: "just-user",
|
User: "just-user",
|
||||||
AccessToken: "token1234",
|
AccessToken: "token1234",
|
||||||
IDToken: "rawtoken1234",
|
IDToken: "rawtoken1234",
|
||||||
|
CreatedAt: c,
|
||||||
ExpiresOn: e,
|
ExpiresOn: e,
|
||||||
RefreshToken: "refresh4321",
|
RefreshToken: "refresh4321",
|
||||||
},
|
},
|
||||||
@ -185,12 +195,15 @@ func TestEncodeSessionState(t *testing.T) {
|
|||||||
|
|
||||||
// TestDecodeSessionState testssessions.DecodeSessionState with the test vector
|
// TestDecodeSessionState testssessions.DecodeSessionState with the test vector
|
||||||
func TestDecodeSessionState(t *testing.T) {
|
func TestDecodeSessionState(t *testing.T) {
|
||||||
|
created := time.Now()
|
||||||
|
createdJSON, _ := created.MarshalJSON()
|
||||||
|
createdString := string(createdJSON)
|
||||||
e := time.Now().Add(time.Duration(1) * time.Hour)
|
e := time.Now().Add(time.Duration(1) * time.Hour)
|
||||||
eJSON, _ := e.MarshalJSON()
|
eJSON, _ := e.MarshalJSON()
|
||||||
eString := string(eJSON)
|
eString := string(eJSON)
|
||||||
eUnix := e.Unix()
|
eUnix := e.Unix()
|
||||||
|
|
||||||
c, err := cookie.NewCipher([]byte(secret))
|
c, err := encryption.NewCipher([]byte(secret))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
testCases := []testCase{
|
testCases := []testCase{
|
||||||
@ -219,7 +232,7 @@ func TestDecodeSessionState(t *testing.T) {
|
|||||||
Email: "user@domain.com",
|
Email: "user@domain.com",
|
||||||
User: "just-user",
|
User: "just-user",
|
||||||
},
|
},
|
||||||
Encoded: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","ExpiresOn":%s}`, eString),
|
Encoded: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SessionState: sessions.SessionState{
|
SessionState: sessions.SessionState{
|
||||||
@ -227,10 +240,11 @@ func TestDecodeSessionState(t *testing.T) {
|
|||||||
User: "just-user",
|
User: "just-user",
|
||||||
AccessToken: "token1234",
|
AccessToken: "token1234",
|
||||||
IDToken: "rawtoken1234",
|
IDToken: "rawtoken1234",
|
||||||
|
CreatedAt: created,
|
||||||
ExpiresOn: e,
|
ExpiresOn: e,
|
||||||
RefreshToken: "refresh4321",
|
RefreshToken: "refresh4321",
|
||||||
},
|
},
|
||||||
Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","ExpiresOn":%s}`, eString),
|
Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
|
||||||
Cipher: c,
|
Cipher: c,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -316,3 +330,14 @@ func TestDecodeSessionState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionStateAge(t *testing.T) {
|
||||||
|
ss := &sessions.SessionState{}
|
||||||
|
|
||||||
|
// Created at unset so should be 0
|
||||||
|
assert.Equal(t, time.Duration(0), ss.Age())
|
||||||
|
|
||||||
|
// Set CreatedAt to 1 hour ago
|
||||||
|
ss.CreatedAt = time.Now().Add(-1 * time.Hour)
|
||||||
|
assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
|
||||||
|
}
|
||||||
|
@ -6,7 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MakeCookie constructs a cookie from the given parameters,
|
// MakeCookie constructs a cookie from the given parameters,
|
||||||
@ -32,3 +33,9 @@ func MakeCookie(req *http.Request, name string, value string, path string, domai
|
|||||||
Expires: now.Add(expiration),
|
Expires: now.Add(expiration),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeCookieFromOptions constructs a cookie based on the givemn *options.CookieOptions,
|
||||||
|
// value and creation time
|
||||||
|
func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
|
||||||
|
return MakeCookie(req, name, value, opts.CookiePath, opts.CookieDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now)
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cookie
|
package encryption
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
@ -1,4 +1,4 @@
|
|||||||
package cookie
|
package encryption
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
@ -1,4 +1,4 @@
|
|||||||
package cookie
|
package encryption
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
@ -88,6 +88,7 @@ type Logger struct {
|
|||||||
stdEnabled bool
|
stdEnabled bool
|
||||||
authEnabled bool
|
authEnabled bool
|
||||||
reqEnabled bool
|
reqEnabled bool
|
||||||
|
excludePaths map[string]struct{}
|
||||||
stdLogTemplate *template.Template
|
stdLogTemplate *template.Template
|
||||||
authTemplate *template.Template
|
authTemplate *template.Template
|
||||||
reqTemplate *template.Template
|
reqTemplate *template.Template
|
||||||
@ -101,6 +102,7 @@ func New(flag int) *Logger {
|
|||||||
stdEnabled: true,
|
stdEnabled: true,
|
||||||
authEnabled: true,
|
authEnabled: true,
|
||||||
reqEnabled: true,
|
reqEnabled: true,
|
||||||
|
excludePaths: nil,
|
||||||
stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)),
|
stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)),
|
||||||
authTemplate: template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)),
|
authTemplate: template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)),
|
||||||
reqTemplate: template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)),
|
reqTemplate: template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)),
|
||||||
@ -177,6 +179,10 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := l.excludePaths[url.Path]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
duration := float64(time.Now().Sub(ts)) / float64(time.Second)
|
duration := float64(time.Now().Sub(ts)) / float64(time.Second)
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
@ -302,6 +308,16 @@ func (l *Logger) SetReqEnabled(e bool) {
|
|||||||
l.reqEnabled = e
|
l.reqEnabled = e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetExcludePaths sets the paths to exclude from logging.
|
||||||
|
func (l *Logger) SetExcludePaths(s []string) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.excludePaths = make(map[string]struct{})
|
||||||
|
for _, p := range s {
|
||||||
|
l.excludePaths[p] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetStandardTemplate sets the template for standard logging.
|
// SetStandardTemplate sets the template for standard logging.
|
||||||
func (l *Logger) SetStandardTemplate(t string) {
|
func (l *Logger) SetStandardTemplate(t string) {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
@ -365,6 +381,11 @@ func SetReqEnabled(e bool) {
|
|||||||
std.SetReqEnabled(e)
|
std.SetReqEnabled(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetExcludePaths sets the path to exclude from logging, eg: health checks
|
||||||
|
func SetExcludePaths(s []string) {
|
||||||
|
std.SetExcludePaths(s)
|
||||||
|
}
|
||||||
|
|
||||||
// SetStandardTemplate sets the template for standard logging for
|
// SetStandardTemplate sets the template for standard logging for
|
||||||
// the standard logger.
|
// the standard logger.
|
||||||
func SetStandardTemplate(t string) {
|
func SetStandardTemplate(t string) {
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package requests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/bitly/go-simplejson"
|
"github.com/bitly/go-simplejson"
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request parses the request body into a simplejson.Json object
|
// Request parses the request body into a simplejson.Json object
|
@ -1,4 +1,4 @@
|
|||||||
package api
|
package requests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
@ -8,10 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/cookies"
|
"github.com/pusher/oauth2_proxy/pkg/cookies"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/sessions/utils"
|
"github.com/pusher/oauth2_proxy/pkg/sessions/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,36 +27,33 @@ var _ sessions.SessionStore = &SessionStore{}
|
|||||||
// SessionStore is an implementation of the sessions.SessionStore
|
// SessionStore is an implementation of the sessions.SessionStore
|
||||||
// interface that stores sessions in client side cookies
|
// interface that stores sessions in client side cookies
|
||||||
type SessionStore struct {
|
type SessionStore struct {
|
||||||
CookieCipher *cookie.Cipher
|
CookieOptions *options.CookieOptions
|
||||||
CookieDomain string
|
CookieCipher *encryption.Cipher
|
||||||
CookieExpire time.Duration
|
|
||||||
CookieHTTPOnly bool
|
|
||||||
CookieName string
|
|
||||||
CookiePath string
|
|
||||||
CookieSecret string
|
|
||||||
CookieSecure bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save takes a sessions.SessionState and stores the information from it
|
// Save takes a sessions.SessionState and stores the information from it
|
||||||
// within Cookies set on the HTTP response writer
|
// within Cookies set on the HTTP response writer
|
||||||
func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
|
func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
|
||||||
|
if ss.CreatedAt.IsZero() {
|
||||||
|
ss.CreatedAt = time.Now()
|
||||||
|
}
|
||||||
value, err := utils.CookieForSession(ss, s.CookieCipher)
|
value, err := utils.CookieForSession(ss, s.CookieCipher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.setSessionCookie(rw, req, value)
|
s.setSessionCookie(rw, req, value, ss.CreatedAt)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reads sessions.SessionState information from Cookies within the
|
// Load reads sessions.SessionState information from Cookies within the
|
||||||
// HTTP request object
|
// HTTP request object
|
||||||
func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
|
func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
|
||||||
c, err := loadCookie(req, s.CookieName)
|
c, err := loadCookie(req, s.CookieOptions.CookieName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// always http.ErrNoCookie
|
// always http.ErrNoCookie
|
||||||
return nil, fmt.Errorf("Cookie %q not present", s.CookieName)
|
return nil, fmt.Errorf("Cookie %q not present", s.CookieOptions.CookieName)
|
||||||
}
|
}
|
||||||
val, _, ok := cookie.Validate(c, s.CookieSecret, s.CookieExpire)
|
val, _, ok := encryption.Validate(c, s.CookieOptions.CookieSecret, s.CookieOptions.CookieExpire)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("Cookie Signature not valid")
|
return nil, errors.New("Cookie Signature not valid")
|
||||||
}
|
}
|
||||||
@ -74,11 +71,11 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
|
|||||||
var cookies []*http.Cookie
|
var cookies []*http.Cookie
|
||||||
|
|
||||||
// matches CookieName, CookieName_<number>
|
// matches CookieName, CookieName_<number>
|
||||||
var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", s.CookieName))
|
var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", s.CookieOptions.CookieName))
|
||||||
|
|
||||||
for _, c := range req.Cookies() {
|
for _, c := range req.Cookies() {
|
||||||
if cookieNameRegex.MatchString(c.Name) {
|
if cookieNameRegex.MatchString(c.Name) {
|
||||||
clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1)
|
clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1, time.Now())
|
||||||
|
|
||||||
http.SetCookie(rw, clearCookie)
|
http.SetCookie(rw, clearCookie)
|
||||||
cookies = append(cookies, clearCookie)
|
cookies = append(cookies, clearCookie)
|
||||||
@ -89,60 +86,42 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setSessionCookie adds the user's session cookie to the response
|
// setSessionCookie adds the user's session cookie to the response
|
||||||
func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
|
func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Request, val string, created time.Time) {
|
||||||
for _, c := range s.makeSessionCookie(req, val, s.CookieExpire, time.Now()) {
|
for _, c := range s.makeSessionCookie(req, val, created) {
|
||||||
http.SetCookie(rw, c)
|
http.SetCookie(rw, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeSessionCookie creates an http.Cookie containing the authenticated user's
|
// makeSessionCookie creates an http.Cookie containing the authenticated user's
|
||||||
// authentication details
|
// authentication details
|
||||||
func (s *SessionStore) makeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) []*http.Cookie {
|
func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie {
|
||||||
if value != "" {
|
if value != "" {
|
||||||
value = cookie.SignedValue(s.CookieSecret, s.CookieName, value, now)
|
value = encryption.SignedValue(s.CookieOptions.CookieSecret, s.CookieOptions.CookieName, value, now)
|
||||||
}
|
}
|
||||||
c := s.makeCookie(req, s.CookieName, value, expiration)
|
c := s.makeCookie(req, s.CookieOptions.CookieName, value, s.CookieOptions.CookieExpire, now)
|
||||||
if len(c.Value) > 4096-len(s.CookieName) {
|
if len(c.Value) > 4096-len(s.CookieOptions.CookieName) {
|
||||||
return splitCookie(c)
|
return splitCookie(c)
|
||||||
}
|
}
|
||||||
return []*http.Cookie{c}
|
return []*http.Cookie{c}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration) *http.Cookie {
|
func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||||
return cookies.MakeCookie(
|
return cookies.MakeCookieFromOptions(
|
||||||
req,
|
req,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
s.CookiePath,
|
s.CookieOptions,
|
||||||
s.CookieDomain,
|
|
||||||
s.CookieHTTPOnly,
|
|
||||||
s.CookieSecure,
|
|
||||||
expiration,
|
expiration,
|
||||||
time.Now(),
|
now,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCookieSessionStore initialises a new instance of the SessionStore from
|
// NewCookieSessionStore initialises a new instance of the SessionStore from
|
||||||
// the configuration given
|
// the configuration given
|
||||||
func NewCookieSessionStore(opts options.CookieStoreOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
|
func NewCookieSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
|
||||||
var cipher *cookie.Cipher
|
|
||||||
if len(cookieOpts.CookieSecret) > 0 {
|
|
||||||
var err error
|
|
||||||
cipher, err = cookie.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create cipher: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SessionStore{
|
return &SessionStore{
|
||||||
CookieCipher: cipher,
|
CookieCipher: opts.Cipher,
|
||||||
CookieDomain: cookieOpts.CookieDomain,
|
CookieOptions: cookieOpts,
|
||||||
CookieExpire: cookieOpts.CookieExpire,
|
|
||||||
CookieHTTPOnly: cookieOpts.CookieHTTPOnly,
|
|
||||||
CookieName: cookieOpts.CookieName,
|
|
||||||
CookiePath: cookieOpts.CookiePath,
|
|
||||||
CookieSecret: cookieOpts.CookieSecret,
|
|
||||||
CookieSecure: cookieOpts.CookieSecure,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
305
pkg/sessions/redis/redis_store.go
Normal file
305
pkg/sessions/redis/redis_store.go
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/cookies"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TicketData is a structure representing the ticket used in server session storage
|
||||||
|
type TicketData struct {
|
||||||
|
TicketID string
|
||||||
|
Secret []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionStore is an implementation of the sessions.SessionStore
|
||||||
|
// interface that stores sessions in redis
|
||||||
|
type SessionStore struct {
|
||||||
|
CookieCipher *encryption.Cipher
|
||||||
|
CookieOptions *options.CookieOptions
|
||||||
|
Client *redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedisSessionStore initialises a new instance of the SessionStore from
|
||||||
|
// the configuration given
|
||||||
|
func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
|
||||||
|
client, err := newRedisClient(opts.RedisStoreOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error constructing redis client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := &SessionStore{
|
||||||
|
Client: client,
|
||||||
|
CookieCipher: opts.Cipher,
|
||||||
|
CookieOptions: cookieOpts,
|
||||||
|
}
|
||||||
|
return rs, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRedisClient(opts options.RedisStoreOptions) (*redis.Client, error) {
|
||||||
|
if opts.UseSentinel {
|
||||||
|
client := redis.NewFailoverClient(&redis.FailoverOptions{
|
||||||
|
MasterName: opts.SentinelMasterName,
|
||||||
|
SentinelAddrs: opts.SentinelConnectionURLs,
|
||||||
|
})
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
opt, err := redis.ParseURL(opts.RedisConnectionURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse redis url: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := redis.NewClient(opt)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save takes a sessions.SessionState and stores the information from it
|
||||||
|
// to redies, and adds a new ticket cookie on the HTTP response writer
|
||||||
|
func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
|
||||||
|
if s.CreatedAt.IsZero() {
|
||||||
|
s.CreatedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old sessions that we are refreshing would have a request cookie
|
||||||
|
// New sessions don't, so we ignore the error. storeValue will check requestCookie
|
||||||
|
requestCookie, _ := req.Cookie(store.CookieOptions.CookieName)
|
||||||
|
value, err := s.EncodeSessionState(store.CookieCipher)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ticketString, err := store.storeValue(value, store.CookieOptions.CookieExpire, requestCookie)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ticketCookie := store.makeCookie(
|
||||||
|
req,
|
||||||
|
ticketString,
|
||||||
|
store.CookieOptions.CookieExpire,
|
||||||
|
s.CreatedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
http.SetCookie(rw, ticketCookie)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads sessions.SessionState information from a ticket
|
||||||
|
// cookie within the HTTP request object
|
||||||
|
func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
|
||||||
|
requestCookie, err := req.Cookie(store.CookieOptions.CookieName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error loading session: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Cookie Signature not valid")
|
||||||
|
}
|
||||||
|
session, err := store.loadSessionFromString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error loading session: %s", err)
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSessionFromString loads the session based on the ticket value
|
||||||
|
func (store *SessionStore) loadSessionFromString(value string) (*sessions.SessionState, error) {
|
||||||
|
ticket, err := decodeTicket(store.CookieOptions.CookieName, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := store.Client.Get(ticket.asHandle(store.CookieOptions.CookieName)).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resultBytes := []byte(result)
|
||||||
|
block, err := aes.NewCipher(ticket.Secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Use secret as the IV too, because each entry has it's own key
|
||||||
|
stream := cipher.NewCFBDecrypter(block, ticket.Secret)
|
||||||
|
stream.XORKeyStream(resultBytes, resultBytes)
|
||||||
|
|
||||||
|
session, err := sessions.DecodeSessionState(string(resultBytes), store.CookieCipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear clears any saved session information for a given ticket cookie
|
||||||
|
// from redis, and then clears the session
|
||||||
|
func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
|
||||||
|
// We go ahead and clear the cookie first, always.
|
||||||
|
clearCookie := store.makeCookie(
|
||||||
|
req,
|
||||||
|
"",
|
||||||
|
time.Hour*-1,
|
||||||
|
time.Now(),
|
||||||
|
)
|
||||||
|
http.SetCookie(rw, clearCookie)
|
||||||
|
|
||||||
|
// If there was an existing cookie we should clear the session in redis
|
||||||
|
requestCookie, err := req.Cookie(store.CookieOptions.CookieName)
|
||||||
|
if err != nil && err == http.ErrNoCookie {
|
||||||
|
// No existing cookie so can't clear redis
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("error retrieving cookie: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Cookie Signature not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only return an error if we had an issue with redis
|
||||||
|
// If there's an issue decoding the ticket, ignore it
|
||||||
|
ticket, _ := decodeTicket(store.CookieOptions.CookieName, val)
|
||||||
|
if ticket != nil {
|
||||||
|
_, err := store.Client.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error clearing cookie from redis: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCookie makes a cookie, signing the value if present
|
||||||
|
func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
|
||||||
|
if value != "" {
|
||||||
|
value = encryption.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now)
|
||||||
|
}
|
||||||
|
return cookies.MakeCookieFromOptions(
|
||||||
|
req,
|
||||||
|
store.CookieOptions.CookieName,
|
||||||
|
value,
|
||||||
|
store.CookieOptions,
|
||||||
|
expires,
|
||||||
|
now,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *SessionStore) storeValue(value string, expiration time.Duration, requestCookie *http.Cookie) (string, error) {
|
||||||
|
ticket, err := store.getTicket(requestCookie)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error getting ticket: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := make([]byte, len(value))
|
||||||
|
block, err := aes.NewCipher(ticket.Secret)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error initiating cipher block %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use secret as the Initialization Vector too, because each entry has it's own key
|
||||||
|
stream := cipher.NewCFBEncrypter(block, ticket.Secret)
|
||||||
|
stream.XORKeyStream(ciphertext, []byte(value))
|
||||||
|
|
||||||
|
handle := ticket.asHandle(store.CookieOptions.CookieName)
|
||||||
|
err = store.Client.Set(handle, ciphertext, expiration).Err()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ticket.encodeTicket(store.CookieOptions.CookieName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTicket retrieves an existing ticket from the cookie if present,
|
||||||
|
// or creates a new ticket
|
||||||
|
func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, error) {
|
||||||
|
if requestCookie == nil {
|
||||||
|
return newTicket()
|
||||||
|
}
|
||||||
|
|
||||||
|
// An existing cookie exists, try to retrieve the ticket
|
||||||
|
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
|
||||||
|
if !ok {
|
||||||
|
// Cookie is invalid, create a new ticket
|
||||||
|
return newTicket()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid cookie, decode the ticket
|
||||||
|
ticket, err := decodeTicket(store.CookieOptions.CookieName, val)
|
||||||
|
if err != nil {
|
||||||
|
// If we can't decode the ticket we have to create a new one
|
||||||
|
return newTicket()
|
||||||
|
}
|
||||||
|
return ticket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTicket() (*TicketData, error) {
|
||||||
|
rawID := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, rawID); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create new ticket ID %s", err)
|
||||||
|
}
|
||||||
|
// ticketID is hex encoded
|
||||||
|
ticketID := fmt.Sprintf("%x", rawID)
|
||||||
|
|
||||||
|
secret := make([]byte, aes.BlockSize)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, secret); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create initialization vector %s", err)
|
||||||
|
}
|
||||||
|
ticket := &TicketData{
|
||||||
|
TicketID: ticketID,
|
||||||
|
Secret: secret,
|
||||||
|
}
|
||||||
|
return ticket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ticket *TicketData) asHandle(prefix string) string {
|
||||||
|
return fmt.Sprintf("%s-%s", prefix, ticket.TicketID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeTicket(cookieName string, ticketString string) (*TicketData, error) {
|
||||||
|
prefix := cookieName + "-"
|
||||||
|
if !strings.HasPrefix(ticketString, prefix) {
|
||||||
|
return nil, fmt.Errorf("failed to decode ticket handle")
|
||||||
|
}
|
||||||
|
trimmedTicket := strings.TrimPrefix(ticketString, prefix)
|
||||||
|
|
||||||
|
ticketParts := strings.Split(trimmedTicket, ".")
|
||||||
|
if len(ticketParts) != 2 {
|
||||||
|
return nil, fmt.Errorf("failed to decode ticket")
|
||||||
|
}
|
||||||
|
ticketID, secretBase64 := ticketParts[0], ticketParts[1]
|
||||||
|
|
||||||
|
// ticketID must be a hexadecimal string
|
||||||
|
_, err := hex.DecodeString(ticketID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("server ticket failed sanity checks")
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := base64.RawURLEncoding.DecodeString(secretBase64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode initialization vector %s", err)
|
||||||
|
}
|
||||||
|
ticketData := &TicketData{
|
||||||
|
TicketID: ticketID,
|
||||||
|
Secret: secret,
|
||||||
|
}
|
||||||
|
return ticketData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ticket *TicketData) encodeTicket(prefix string) string {
|
||||||
|
handle := ticket.asHandle(prefix)
|
||||||
|
ticketString := handle + "." + base64.RawURLEncoding.EncodeToString(ticket.Secret)
|
||||||
|
return ticketString
|
||||||
|
}
|
@ -6,13 +6,16 @@ import (
|
|||||||
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
"github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/sessions/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSessionStore creates a SessionStore from the provided configuration
|
// NewSessionStore creates a SessionStore from the provided configuration
|
||||||
func NewSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
|
func NewSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
|
||||||
switch opts.Type {
|
switch opts.Type {
|
||||||
case options.CookieSessionStoreType:
|
case options.CookieSessionStoreType:
|
||||||
return cookie.NewCookieSessionStore(opts.CookieStoreOptions, cookieOpts)
|
return cookie.NewCookieSessionStore(opts, cookieOpts)
|
||||||
|
case options.RedisSessionStoreType:
|
||||||
|
return redis.NewRedisSessionStore(opts, cookieOpts)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown session store type '%s'", opts.Type)
|
return nil, fmt.Errorf("unknown session store type '%s'", opts.Type)
|
||||||
}
|
}
|
||||||
|
@ -5,16 +5,22 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
"github.com/pusher/oauth2_proxy/pkg/apis/options"
|
||||||
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/cookies"
|
"github.com/pusher/oauth2_proxy/pkg/cookies"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/sessions"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/sessions/redis"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/sessions/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSessionStore(t *testing.T) {
|
func TestSessionStore(t *testing.T) {
|
||||||
@ -30,6 +36,7 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
var response *httptest.ResponseRecorder
|
var response *httptest.ResponseRecorder
|
||||||
var session *sessionsapi.SessionState
|
var session *sessionsapi.SessionState
|
||||||
var ss sessionsapi.SessionStore
|
var ss sessionsapi.SessionStore
|
||||||
|
var mr *miniredis.Miniredis
|
||||||
|
|
||||||
CheckCookieOptions := func() {
|
CheckCookieOptions := func() {
|
||||||
Context("the cookies returned", func() {
|
Context("the cookies returned", func() {
|
||||||
@ -72,11 +79,67 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("have a signature timestamp matching session.CreatedAt", func() {
|
||||||
|
for _, cookie := range cookies {
|
||||||
|
if cookie.Value != "" {
|
||||||
|
parts := strings.Split(cookie.Value, "|")
|
||||||
|
Expect(parts).To(HaveLen(3))
|
||||||
|
Expect(parts[1]).To(Equal(strconv.Itoa(int(session.CreatedAt.Unix()))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionStoreInterfaceTests := func() {
|
// The following should only be for server stores
|
||||||
|
PersistentSessionStoreTests := func() {
|
||||||
|
Context("when Clear is called on a persistent store", func() {
|
||||||
|
var resultCookies []*http.Cookie
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||||
|
saveResp := httptest.NewRecorder()
|
||||||
|
err := ss.Save(saveResp, req, session)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
resultCookies = saveResp.Result().Cookies()
|
||||||
|
for _, c := range resultCookies {
|
||||||
|
request.AddCookie(c)
|
||||||
|
}
|
||||||
|
err = ss.Clear(response, request)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("attempting to Load", func() {
|
||||||
|
var loadedAfterClear *sessionsapi.SessionState
|
||||||
|
var loadErr error
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
loadReq := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||||
|
for _, c := range resultCookies {
|
||||||
|
loadReq.AddCookie(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedAfterClear, loadErr = ss.Load(loadReq)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns an empty session", func() {
|
||||||
|
Expect(loadedAfterClear).To(BeNil())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns an error", func() {
|
||||||
|
Expect(loadErr).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
CheckCookieOptions()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionStoreInterfaceTests := func(persistent bool) {
|
||||||
Context("when Save is called", func() {
|
Context("when Save is called", func() {
|
||||||
|
Context("with no existing session", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
err := ss.Save(response, request, session)
|
err := ss.Save(response, request, session)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
@ -86,23 +149,72 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
|
Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Ensures the session CreatedAt is not zero", func() {
|
||||||
|
Expect(session.CreatedAt.IsZero()).To(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with a broken session", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
By("Using a valid cookie with a different providers session encoding")
|
||||||
|
broken := "BrokenSessionFromADifferentSessionImplementation"
|
||||||
|
value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
|
||||||
|
cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
|
||||||
|
request.AddCookie(cookie)
|
||||||
|
|
||||||
|
err := ss.Save(response, request, session)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("sets a `set-cookie` header in the response", func() {
|
||||||
|
Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Ensures the session CreatedAt is not zero", func() {
|
||||||
|
Expect(session.CreatedAt.IsZero()).To(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with an expired saved session", func() {
|
||||||
|
var err error
|
||||||
|
BeforeEach(func() {
|
||||||
|
By("saving a session")
|
||||||
|
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||||
|
saveResp := httptest.NewRecorder()
|
||||||
|
err = ss.Save(saveResp, req, session)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
By("and clearing the session")
|
||||||
|
for _, c := range saveResp.Result().Cookies() {
|
||||||
|
request.AddCookie(c)
|
||||||
|
}
|
||||||
|
clearResp := httptest.NewRecorder()
|
||||||
|
err = ss.Clear(clearResp, request)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
By("then saving a request with the cleared session")
|
||||||
|
err = ss.Save(response, request, session)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("no error should occur", func() {
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
CheckCookieOptions()
|
CheckCookieOptions()
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("when Clear is called", func() {
|
Context("when Clear is called", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
cookie := cookies.MakeCookie(request,
|
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||||
cookieOpts.CookieName,
|
saveResp := httptest.NewRecorder()
|
||||||
"foo",
|
err := ss.Save(saveResp, req, session)
|
||||||
cookieOpts.CookiePath,
|
Expect(err).ToNot(HaveOccurred())
|
||||||
cookieOpts.CookieDomain,
|
|
||||||
cookieOpts.CookieHTTPOnly,
|
for _, c := range saveResp.Result().Cookies() {
|
||||||
cookieOpts.CookieSecure,
|
request.AddCookie(c)
|
||||||
cookieOpts.CookieExpire,
|
}
|
||||||
time.Now(),
|
err = ss.Clear(response, request)
|
||||||
)
|
|
||||||
request.AddCookie(cookie)
|
|
||||||
err := ss.Clear(response, request)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -114,16 +226,10 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Context("when Load is called", func() {
|
Context("when Load is called", func() {
|
||||||
|
LoadSessionTests := func() {
|
||||||
var loadedSession *sessionsapi.SessionState
|
var loadedSession *sessionsapi.SessionState
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
var err error
|
||||||
resp := httptest.NewRecorder()
|
|
||||||
err := ss.Save(resp, req, session)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
for _, cookie := range resp.Result().Cookies() {
|
|
||||||
request.AddCookie(cookie)
|
|
||||||
}
|
|
||||||
loadedSession, err = ss.Load(request)
|
loadedSession, err = ss.Load(request)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
@ -138,19 +244,80 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
|
|
||||||
// Can't compare time.Time using Equal() so remove ExpiresOn from sessions
|
// Can't compare time.Time using Equal() so remove ExpiresOn from sessions
|
||||||
l := *loadedSession
|
l := *loadedSession
|
||||||
|
l.CreatedAt = time.Time{}
|
||||||
l.ExpiresOn = time.Time{}
|
l.ExpiresOn = time.Time{}
|
||||||
s := *session
|
s := *session
|
||||||
|
s.CreatedAt = time.Time{}
|
||||||
s.ExpiresOn = time.Time{}
|
s.ExpiresOn = time.Time{}
|
||||||
Expect(l).To(Equal(s))
|
Expect(l).To(Equal(s))
|
||||||
|
|
||||||
// Compare time.Time separately
|
// Compare time.Time separately
|
||||||
|
Expect(loadedSession.CreatedAt.Equal(session.CreatedAt)).To(BeTrue())
|
||||||
Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue())
|
Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RunSessionTests := func() {
|
BeforeEach(func() {
|
||||||
|
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
err := ss.Save(resp, req, session)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
for _, cookie := range resp.Result().Cookies() {
|
||||||
|
request.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("before the refresh period", func() {
|
||||||
|
LoadSessionTests()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test TTLs and cleanup of persistent session storage
|
||||||
|
// For non-persistent we rely on the browser cookie lifecycle
|
||||||
|
if persistent {
|
||||||
|
Context("after the refresh period, but before the cookie expire period", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
switch ss.(type) {
|
||||||
|
case *redis.SessionStore:
|
||||||
|
mr.FastForward(cookieOpts.CookieRefresh + time.Minute)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
LoadSessionTests()
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("after the cookie expire period", func() {
|
||||||
|
var loadedSession *sessionsapi.SessionState
|
||||||
|
var err error
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
switch ss.(type) {
|
||||||
|
case *redis.SessionStore:
|
||||||
|
mr.FastForward(cookieOpts.CookieExpire + time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedSession, err = ss.Load(request)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns an error loading the session", func() {
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns an empty session", func() {
|
||||||
|
Expect(loadedSession).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if persistent {
|
||||||
|
PersistentSessionStoreTests()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RunSessionTests := func(persistent bool) {
|
||||||
Context("with default options", func() {
|
Context("with default options", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
var err error
|
var err error
|
||||||
@ -158,7 +325,7 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
SessionStoreInterfaceTests()
|
SessionStoreInterfaceTests(persistent)
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("with non-default options", func() {
|
Context("with non-default options", func() {
|
||||||
@ -167,7 +334,7 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
CookieName: "_cookie_name",
|
CookieName: "_cookie_name",
|
||||||
CookiePath: "/path",
|
CookiePath: "/path",
|
||||||
CookieExpire: time.Duration(72) * time.Hour,
|
CookieExpire: time.Duration(72) * time.Hour,
|
||||||
CookieRefresh: time.Duration(3600),
|
CookieRefresh: time.Duration(2) * time.Hour,
|
||||||
CookieSecure: false,
|
CookieSecure: false,
|
||||||
CookieHTTPOnly: false,
|
CookieHTTPOnly: false,
|
||||||
CookieDomain: "example.com",
|
CookieDomain: "example.com",
|
||||||
@ -178,21 +345,25 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
SessionStoreInterfaceTests()
|
SessionStoreInterfaceTests(persistent)
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("with a cookie-secret set", func() {
|
Context("with a cipher", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
secret := make([]byte, 32)
|
secret := make([]byte, 32)
|
||||||
_, err := rand.Read(secret)
|
_, err := rand.Read(secret)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
cookieOpts.CookieSecret = base64.URLEncoding.EncodeToString(secret)
|
cookieOpts.CookieSecret = base64.URLEncoding.EncodeToString(secret)
|
||||||
|
cipher, err := encryption.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(cipher).ToNot(BeNil())
|
||||||
|
opts.Cipher = cipher
|
||||||
|
|
||||||
ss, err = sessions.NewSessionStore(opts, cookieOpts)
|
ss, err = sessions.NewSessionStore(opts, cookieOpts)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
SessionStoreInterfaceTests()
|
SessionStoreInterfaceTests(persistent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +376,7 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
CookieName: "_oauth2_proxy",
|
CookieName: "_oauth2_proxy",
|
||||||
CookiePath: "/",
|
CookiePath: "/",
|
||||||
CookieExpire: time.Duration(168) * time.Hour,
|
CookieExpire: time.Duration(168) * time.Hour,
|
||||||
CookieRefresh: time.Duration(0),
|
CookieRefresh: time.Duration(1) * time.Hour,
|
||||||
CookieSecure: true,
|
CookieSecure: true,
|
||||||
CookieHTTPOnly: true,
|
CookieHTTPOnly: true,
|
||||||
}
|
}
|
||||||
@ -231,11 +402,35 @@ var _ = Describe("NewSessionStore", func() {
|
|||||||
It("creates a cookie.SessionStore", func() {
|
It("creates a cookie.SessionStore", func() {
|
||||||
ss, err := sessions.NewSessionStore(opts, cookieOpts)
|
ss, err := sessions.NewSessionStore(opts, cookieOpts)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(ss).To(BeAssignableToTypeOf(&cookie.SessionStore{}))
|
Expect(ss).To(BeAssignableToTypeOf(&sessionscookie.SessionStore{}))
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("the cookie.SessionStore", func() {
|
Context("the cookie.SessionStore", func() {
|
||||||
RunSessionTests()
|
RunSessionTests(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with type 'redis'", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
mr, err = miniredis.Run()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
opts.Type = options.RedisSessionStoreType
|
||||||
|
opts.RedisConnectionURL = "redis://" + mr.Addr()
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
mr.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("creates a redis.SessionStore", func() {
|
||||||
|
ss, err := sessions.NewSessionStore(opts, cookieOpts)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(ss).To(BeAssignableToTypeOf(&redis.SessionStore{}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("the redis.SessionStore", func() {
|
||||||
|
RunSessionTests(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3,17 +3,17 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CookieForSession serializes a session state for storage in a cookie
|
// CookieForSession serializes a session state for storage in a cookie
|
||||||
func CookieForSession(s *sessions.SessionState, c *cookie.Cipher) (string, error) {
|
func CookieForSession(s *sessions.SessionState, c *encryption.Cipher) (string, error) {
|
||||||
return s.EncodeSessionState(c)
|
return s.EncodeSessionState(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionFromCookie deserializes a session from a cookie value
|
// SessionFromCookie deserializes a session from a cookie value
|
||||||
func SessionFromCookie(v string, c *cookie.Cipher) (s *sessions.SessionState, err error) {
|
func SessionFromCookie(v string, c *encryption.Cipher) (s *sessions.SessionState, err error) {
|
||||||
return sessions.DecodeSessionState(v, c)
|
return sessions.DecodeSessionState(v, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/bitly/go-simplejson"
|
"github.com/bitly/go-simplejson"
|
||||||
"github.com/pusher/oauth2_proxy/api"
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/requests"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AzureProvider represents an Azure based Identity Provider
|
// AzureProvider represents an Azure based Identity Provider
|
||||||
@ -102,7 +102,7 @@ func (p *AzureProvider) GetEmailAddress(s *sessions.SessionState) (string, error
|
|||||||
}
|
}
|
||||||
req.Header = getAzureHeader(s.AccessToken)
|
req.Header = getAzureHeader(s.AccessToken)
|
||||||
|
|
||||||
json, err := api.Request(req)
|
json, err := requests.Request(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
163
providers/bitbucket.go
Normal file
163
providers/bitbucket.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/requests"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BitbucketProvider represents an Bitbucket based Identity Provider
|
||||||
|
type BitbucketProvider struct {
|
||||||
|
*ProviderData
|
||||||
|
Team string
|
||||||
|
Repository string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBitbucketProvider initiates a new BitbucketProvider
|
||||||
|
func NewBitbucketProvider(p *ProviderData) *BitbucketProvider {
|
||||||
|
p.ProviderName = "Bitbucket"
|
||||||
|
if p.LoginURL == nil || p.LoginURL.String() == "" {
|
||||||
|
p.LoginURL = &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "bitbucket.org",
|
||||||
|
Path: "/site/oauth2/authorize",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.RedeemURL == nil || p.RedeemURL.String() == "" {
|
||||||
|
p.RedeemURL = &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "bitbucket.org",
|
||||||
|
Path: "/site/oauth2/access_token",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ValidateURL == nil || p.ValidateURL.String() == "" {
|
||||||
|
p.ValidateURL = &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "api.bitbucket.org",
|
||||||
|
Path: "/2.0/user/emails",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Scope == "" {
|
||||||
|
p.Scope = "email"
|
||||||
|
}
|
||||||
|
return &BitbucketProvider{ProviderData: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTeam defines the Bitbucket team the user must be part of
|
||||||
|
func (p *BitbucketProvider) SetTeam(team string) {
|
||||||
|
p.Team = team
|
||||||
|
if !strings.Contains(p.Scope, "team") {
|
||||||
|
p.Scope += " team"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRepository defines the repository the user must have access to
|
||||||
|
func (p *BitbucketProvider) SetRepository(repository string) {
|
||||||
|
p.Repository = repository
|
||||||
|
if !strings.Contains(p.Scope, "repository") {
|
||||||
|
p.Scope += " repository"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailAddress returns the email of the authenticated user
|
||||||
|
func (p *BitbucketProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
|
||||||
|
|
||||||
|
var emails struct {
|
||||||
|
Values []struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Primary bool `json:"is_primary"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var teams struct {
|
||||||
|
Values []struct {
|
||||||
|
Name string `json:"username"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var repositories struct {
|
||||||
|
Values []struct {
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("GET",
|
||||||
|
p.ValidateURL.String()+"?access_token="+s.AccessToken, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed building request %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = requests.RequestJSON(req, &emails)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed making request %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Team != "" {
|
||||||
|
teamURL := &url.URL{}
|
||||||
|
*teamURL = *p.ValidateURL
|
||||||
|
teamURL.Path = "/2.0/teams"
|
||||||
|
req, err = http.NewRequest("GET",
|
||||||
|
teamURL.String()+"?role=member&access_token="+s.AccessToken, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed building request %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = requests.RequestJSON(req, &teams)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed requesting teams membership %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var found = false
|
||||||
|
for _, team := range teams.Values {
|
||||||
|
if p.Team == team.Name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found != true {
|
||||||
|
logger.Print("team membership test failed, access denied")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Repository != "" {
|
||||||
|
repositoriesURL := &url.URL{}
|
||||||
|
*repositoriesURL = *p.ValidateURL
|
||||||
|
repositoriesURL.Path = "/2.0/repositories/" + strings.Split(p.Repository, "/")[0]
|
||||||
|
req, err = http.NewRequest("GET",
|
||||||
|
repositoriesURL.String()+"?role=contributor"+
|
||||||
|
"&q=full_name="+url.QueryEscape("\""+p.Repository+"\"")+
|
||||||
|
"&access_token="+s.AccessToken,
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed building request %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = requests.RequestJSON(req, &repositories)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed checking repository access %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var found = false
|
||||||
|
for _, repository := range repositories.Values {
|
||||||
|
if p.Repository == repository.FullName {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found != true {
|
||||||
|
logger.Print("repository access test failed, access denied")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, email := range emails.Values {
|
||||||
|
if email.Primary {
|
||||||
|
return email.Email, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
170
providers/bitbucket_test.go
Normal file
170
providers/bitbucket_test.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testBitbucketProvider(hostname, team string, repository string) *BitbucketProvider {
|
||||||
|
p := NewBitbucketProvider(
|
||||||
|
&ProviderData{
|
||||||
|
ProviderName: "",
|
||||||
|
LoginURL: &url.URL{},
|
||||||
|
RedeemURL: &url.URL{},
|
||||||
|
ProfileURL: &url.URL{},
|
||||||
|
ValidateURL: &url.URL{},
|
||||||
|
Scope: ""})
|
||||||
|
|
||||||
|
if team != "" {
|
||||||
|
p.SetTeam(team)
|
||||||
|
}
|
||||||
|
|
||||||
|
if repository != "" {
|
||||||
|
p.SetRepository(repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostname != "" {
|
||||||
|
updateURL(p.Data().LoginURL, hostname)
|
||||||
|
updateURL(p.Data().RedeemURL, hostname)
|
||||||
|
updateURL(p.Data().ProfileURL, hostname)
|
||||||
|
updateURL(p.Data().ValidateURL, hostname)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBitbucketBackend(payload string) *httptest.Server {
|
||||||
|
paths := map[string]bool{
|
||||||
|
"/2.0/user/emails": true,
|
||||||
|
"/2.0/teams": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return httptest.NewServer(http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
url := r.URL
|
||||||
|
if !paths[url.Path] {
|
||||||
|
log.Printf("%s not in %+v\n", url.Path, paths)
|
||||||
|
w.WriteHeader(404)
|
||||||
|
} else if r.URL.Query().Get("access_token") != "imaginary_access_token" {
|
||||||
|
w.WriteHeader(403)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(payload))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketProviderDefaults(t *testing.T) {
|
||||||
|
p := testBitbucketProvider("", "", "")
|
||||||
|
assert.NotEqual(t, nil, p)
|
||||||
|
assert.Equal(t, "Bitbucket", p.Data().ProviderName)
|
||||||
|
assert.Equal(t, "https://bitbucket.org/site/oauth2/authorize",
|
||||||
|
p.Data().LoginURL.String())
|
||||||
|
assert.Equal(t, "https://bitbucket.org/site/oauth2/access_token",
|
||||||
|
p.Data().RedeemURL.String())
|
||||||
|
assert.Equal(t, "https://api.bitbucket.org/2.0/user/emails",
|
||||||
|
p.Data().ValidateURL.String())
|
||||||
|
assert.Equal(t, "email", p.Data().Scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketProviderScopeAdjustForTeam(t *testing.T) {
|
||||||
|
p := testBitbucketProvider("", "test-team", "")
|
||||||
|
assert.NotEqual(t, nil, p)
|
||||||
|
assert.Equal(t, "email team", p.Data().Scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketProviderScopeAdjustForRepository(t *testing.T) {
|
||||||
|
p := testBitbucketProvider("", "", "rest-repo")
|
||||||
|
assert.NotEqual(t, nil, p)
|
||||||
|
assert.Equal(t, "email repository", p.Data().Scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketProviderOverrides(t *testing.T) {
|
||||||
|
p := NewBitbucketProvider(
|
||||||
|
&ProviderData{
|
||||||
|
LoginURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "/oauth/auth"},
|
||||||
|
RedeemURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "/oauth/token"},
|
||||||
|
ValidateURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "/api/v3/user"},
|
||||||
|
Scope: "profile"})
|
||||||
|
assert.NotEqual(t, nil, p)
|
||||||
|
assert.Equal(t, "Bitbucket", p.Data().ProviderName)
|
||||||
|
assert.Equal(t, "https://example.com/oauth/auth",
|
||||||
|
p.Data().LoginURL.String())
|
||||||
|
assert.Equal(t, "https://example.com/oauth/token",
|
||||||
|
p.Data().RedeemURL.String())
|
||||||
|
assert.Equal(t, "https://example.com/api/v3/user",
|
||||||
|
p.Data().ValidateURL.String())
|
||||||
|
assert.Equal(t, "profile", p.Data().Scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketProviderGetEmailAddress(t *testing.T) {
|
||||||
|
b := testBitbucketBackend("{\"values\": [ { \"email\": \"michael.bland@gsa.gov\", \"is_primary\": true } ] }")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testBitbucketProvider(bURL.Host, "", "")
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "michael.bland@gsa.gov", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketProviderGetEmailAddressAndGroup(t *testing.T) {
|
||||||
|
b := testBitbucketBackend("{\"values\": [ { \"email\": \"michael.bland@gsa.gov\", \"is_primary\": true, \"username\": \"bioinformatics\" } ] }")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testBitbucketProvider(bURL.Host, "bioinformatics", "")
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "michael.bland@gsa.gov", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that trying to trigger the "failed building request" case is not
|
||||||
|
// practical, since the only way it can fail is if the URL fails to parse.
|
||||||
|
func TestBitbucketProviderGetEmailAddressFailedRequest(t *testing.T) {
|
||||||
|
b := testBitbucketBackend("unused payload")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testBitbucketProvider(bURL.Host, "", "")
|
||||||
|
|
||||||
|
// We'll trigger a request failure by using an unexpected access
|
||||||
|
// token. Alternatively, we could allow the parsing of the payload as
|
||||||
|
// JSON to fail.
|
||||||
|
session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
assert.Equal(t, "", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitbucketProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
|
||||||
|
b := testBitbucketBackend("{\"foo\": \"bar\"}")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testBitbucketProvider(bURL.Host, "", "")
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.Equal(t, "", email)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
}
|
@ -6,8 +6,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/api"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/requests"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FacebookProvider represents an Facebook based Identity Provider
|
// FacebookProvider represents an Facebook based Identity Provider
|
||||||
@ -69,7 +69,7 @@ func (p *FacebookProvider) GetEmailAddress(s *sessions.SessionState) (string, er
|
|||||||
Email string
|
Email string
|
||||||
}
|
}
|
||||||
var r result
|
var r result
|
||||||
err = api.RequestJSON(req, &r)
|
err = requests.RequestJSON(req, &r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GitHubProvider represents an GitHub based Identity Provider
|
// GitHubProvider represents an GitHub based Identity Provider
|
||||||
|
@ -1,62 +1,258 @@
|
|||||||
package providers
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/api"
|
oidc "github.com/coreos/go-oidc"
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GitLabProvider represents an GitLab based Identity Provider
|
// GitLabProvider represents a GitLab based Identity Provider
|
||||||
type GitLabProvider struct {
|
type GitLabProvider struct {
|
||||||
*ProviderData
|
*ProviderData
|
||||||
|
|
||||||
|
Group string
|
||||||
|
EmailDomains []string
|
||||||
|
|
||||||
|
Verifier *oidc.IDTokenVerifier
|
||||||
|
AllowUnverifiedEmail bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGitLabProvider initiates a new GitLabProvider
|
// NewGitLabProvider initiates a new GitLabProvider
|
||||||
func NewGitLabProvider(p *ProviderData) *GitLabProvider {
|
func NewGitLabProvider(p *ProviderData) *GitLabProvider {
|
||||||
p.ProviderName = "GitLab"
|
p.ProviderName = "GitLab"
|
||||||
if p.LoginURL == nil || p.LoginURL.String() == "" {
|
|
||||||
p.LoginURL = &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "gitlab.com",
|
|
||||||
Path: "/oauth/authorize",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.RedeemURL == nil || p.RedeemURL.String() == "" {
|
|
||||||
p.RedeemURL = &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "gitlab.com",
|
|
||||||
Path: "/oauth/token",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.ValidateURL == nil || p.ValidateURL.String() == "" {
|
|
||||||
p.ValidateURL = &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "gitlab.com",
|
|
||||||
Path: "/api/v4/user",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.Scope == "" {
|
if p.Scope == "" {
|
||||||
p.Scope = "read_user"
|
p.Scope = "openid email"
|
||||||
}
|
}
|
||||||
|
|
||||||
return &GitLabProvider{ProviderData: p}
|
return &GitLabProvider{ProviderData: p}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redeem exchanges the OAuth2 authentication token for an ID token
|
||||||
|
func (p *GitLabProvider) Redeem(redirectURL, code string) (s *sessions.SessionState, err error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := oauth2.Config{
|
||||||
|
ClientID: p.ClientID,
|
||||||
|
ClientSecret: p.ClientSecret,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
TokenURL: p.RedeemURL.String(),
|
||||||
|
},
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
}
|
||||||
|
token, err := c.Exchange(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("token exchange: %v", err)
|
||||||
|
}
|
||||||
|
s, err = p.createSessionState(ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to update session: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshSessionIfNeeded checks if the session has expired and uses the
|
||||||
|
// RefreshToken to fetch a new ID token if required
|
||||||
|
func (p *GitLabProvider) RefreshSessionIfNeeded(s *sessions.SessionState) (bool, error) {
|
||||||
|
if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
origExpiration := s.ExpiresOn
|
||||||
|
|
||||||
|
err := p.redeemRefreshToken(s)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to redeem refresh token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("refreshed id token %s (expired on %s)\n", s, origExpiration)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GitLabProvider) redeemRefreshToken(s *sessions.SessionState) (err error) {
|
||||||
|
c := oauth2.Config{
|
||||||
|
ClientID: p.ClientID,
|
||||||
|
ClientSecret: p.ClientSecret,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
TokenURL: p.RedeemURL.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
t := &oauth2.Token{
|
||||||
|
RefreshToken: s.RefreshToken,
|
||||||
|
Expiry: time.Now().Add(-time.Hour),
|
||||||
|
}
|
||||||
|
token, err := c.TokenSource(ctx, t).Token()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get token: %v", err)
|
||||||
|
}
|
||||||
|
newSession, err := p.createSessionState(ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to update session: %v", err)
|
||||||
|
}
|
||||||
|
s.AccessToken = newSession.AccessToken
|
||||||
|
s.IDToken = newSession.IDToken
|
||||||
|
s.RefreshToken = newSession.RefreshToken
|
||||||
|
s.CreatedAt = newSession.CreatedAt
|
||||||
|
s.ExpiresOn = newSession.ExpiresOn
|
||||||
|
s.Email = newSession.Email
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type gitlabUserInfo struct {
|
||||||
|
Username string `json:"nickname"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GitLabProvider) getUserInfo(s *sessions.SessionState) (*gitlabUserInfo, error) {
|
||||||
|
// Retrieve user info JSON
|
||||||
|
// https://docs.gitlab.com/ee/integration/openid_connect_provider.html#shared-information
|
||||||
|
|
||||||
|
// Build user info url from login url of GitLab instance
|
||||||
|
userInfoURL := *p.LoginURL
|
||||||
|
userInfoURL.Path = "/oauth/userinfo"
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", userInfoURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create user info request: %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+s.AccessToken)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to perform user info request: %v", err)
|
||||||
|
}
|
||||||
|
var body []byte
|
||||||
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read user info response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("got %d during user info request: %s", resp.StatusCode, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo gitlabUserInfo
|
||||||
|
err = json.Unmarshal(body, &userInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse user info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GitLabProvider) verifyGroupMembership(userInfo *gitlabUserInfo) error {
|
||||||
|
if p.Group == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect user group memberships
|
||||||
|
membershipSet := make(map[string]bool)
|
||||||
|
for _, group := range userInfo.Groups {
|
||||||
|
membershipSet[group] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a valid group that they are a member of
|
||||||
|
validGroups := strings.Split(p.Group, " ")
|
||||||
|
for _, validGroup := range validGroups {
|
||||||
|
if _, ok := membershipSet[validGroup]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("user is not a member of '%s'", p.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GitLabProvider) verifyEmailDomain(userInfo *gitlabUserInfo) error {
|
||||||
|
if len(p.EmailDomains) == 0 || p.EmailDomains[0] == "*" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range p.EmailDomains {
|
||||||
|
if strings.HasSuffix(userInfo.Email, domain) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("user email is not one of the valid domains '%v'", p.EmailDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GitLabProvider) createSessionState(ctx context.Context, token *oauth2.Token) (*sessions.SessionState, error) {
|
||||||
|
rawIDToken, ok := token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("token response did not contain an id_token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and verify ID Token payload.
|
||||||
|
idToken, err := p.Verifier.Verify(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not verify id_token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sessions.SessionState{
|
||||||
|
AccessToken: token.AccessToken,
|
||||||
|
IDToken: rawIDToken,
|
||||||
|
RefreshToken: token.RefreshToken,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
ExpiresOn: idToken.Expiry,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateSessionState checks that the session's IDToken is still valid
|
||||||
|
func (p *GitLabProvider) ValidateSessionState(s *sessions.SessionState) bool {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := p.Verifier.Verify(ctx, s.IDToken)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// GetEmailAddress returns the Account email address
|
// GetEmailAddress returns the Account email address
|
||||||
func (p *GitLabProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
|
func (p *GitLabProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
|
||||||
|
// Retrieve user info
|
||||||
|
userInfo, err := p.getUserInfo(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to retrieve user info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET",
|
// Check if email is verified
|
||||||
p.ValidateURL.String()+"?access_token="+s.AccessToken, nil)
|
if !p.AllowUnverifiedEmail && !userInfo.EmailVerified {
|
||||||
|
return "", fmt.Errorf("user email is not verified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if email has valid domain
|
||||||
|
err = p.verifyEmailDomain(userInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("failed building request %s", err)
|
return "", fmt.Errorf("email domain check failed: %v", err)
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
json, err := api.Request(req)
|
|
||||||
|
// Check group membership
|
||||||
|
err = p.verifyGroupMembership(userInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("failed making request %s", err)
|
return "", fmt.Errorf("group membership check failed: %v", err)
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
return json.Get("email").String()
|
|
||||||
|
return userInfo.Email, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserName returns the Account user name
|
||||||
|
func (p *GitLabProvider) GetUserName(s *sessions.SessionState) (string, error) {
|
||||||
|
userInfo, err := p.getUserInfo(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to retrieve user info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo.Username, nil
|
||||||
}
|
}
|
||||||
|
@ -25,104 +25,142 @@ func testGitLabProvider(hostname string) *GitLabProvider {
|
|||||||
updateURL(p.Data().ProfileURL, hostname)
|
updateURL(p.Data().ProfileURL, hostname)
|
||||||
updateURL(p.Data().ValidateURL, hostname)
|
updateURL(p.Data().ValidateURL, hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGitLabBackend(payload string) *httptest.Server {
|
func testGitLabBackend() *httptest.Server {
|
||||||
path := "/api/v4/user"
|
userInfo := `
|
||||||
query := "access_token=imaginary_access_token"
|
{
|
||||||
|
"nickname": "FooBar",
|
||||||
|
"email": "foo@bar.com",
|
||||||
|
"email_verified": false,
|
||||||
|
"groups": ["foo", "bar"]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
authHeader := "Bearer gitlab_access_token"
|
||||||
|
|
||||||
return httptest.NewServer(http.HandlerFunc(
|
return httptest.NewServer(http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != path || r.URL.RawQuery != query {
|
if r.URL.Path == "/oauth/userinfo" {
|
||||||
w.WriteHeader(404)
|
if r.Header["Authorization"][0] == authHeader {
|
||||||
} else {
|
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
w.Write([]byte(payload))
|
w.Write([]byte(userInfo))
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(401)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(404)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitLabProviderDefaults(t *testing.T) {
|
func TestGitLabProviderBadToken(t *testing.T) {
|
||||||
p := testGitLabProvider("")
|
b := testGitLabBackend()
|
||||||
assert.NotEqual(t, nil, p)
|
|
||||||
assert.Equal(t, "GitLab", p.Data().ProviderName)
|
|
||||||
assert.Equal(t, "https://gitlab.com/oauth/authorize",
|
|
||||||
p.Data().LoginURL.String())
|
|
||||||
assert.Equal(t, "https://gitlab.com/oauth/token",
|
|
||||||
p.Data().RedeemURL.String())
|
|
||||||
assert.Equal(t, "https://gitlab.com/api/v4/user",
|
|
||||||
p.Data().ValidateURL.String())
|
|
||||||
assert.Equal(t, "read_user", p.Data().Scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitLabProviderOverrides(t *testing.T) {
|
|
||||||
p := NewGitLabProvider(
|
|
||||||
&ProviderData{
|
|
||||||
LoginURL: &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "example.com",
|
|
||||||
Path: "/oauth/auth"},
|
|
||||||
RedeemURL: &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "example.com",
|
|
||||||
Path: "/oauth/token"},
|
|
||||||
ValidateURL: &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "example.com",
|
|
||||||
Path: "/api/v4/user"},
|
|
||||||
Scope: "profile"})
|
|
||||||
assert.NotEqual(t, nil, p)
|
|
||||||
assert.Equal(t, "GitLab", p.Data().ProviderName)
|
|
||||||
assert.Equal(t, "https://example.com/oauth/auth",
|
|
||||||
p.Data().LoginURL.String())
|
|
||||||
assert.Equal(t, "https://example.com/oauth/token",
|
|
||||||
p.Data().RedeemURL.String())
|
|
||||||
assert.Equal(t, "https://example.com/api/v4/user",
|
|
||||||
p.Data().ValidateURL.String())
|
|
||||||
assert.Equal(t, "profile", p.Data().Scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitLabProviderGetEmailAddress(t *testing.T) {
|
|
||||||
b := testGitLabBackend("{\"email\": \"michael.bland@gsa.gov\"}")
|
|
||||||
defer b.Close()
|
defer b.Close()
|
||||||
|
|
||||||
bURL, _ := url.Parse(b.URL)
|
bURL, _ := url.Parse(b.URL)
|
||||||
p := testGitLabProvider(bURL.Host)
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
|
||||||
session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
|
session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"}
|
||||||
|
_, err := p.GetEmailAddress(session)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitLabProviderUnverifiedEmailDenied(t *testing.T) {
|
||||||
|
b := testGitLabBackend()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
|
||||||
|
_, err := p.GetEmailAddress(session)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitLabProviderUnverifiedEmailAllowed(t *testing.T) {
|
||||||
|
b := testGitLabBackend()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
p.AllowUnverifiedEmail = true
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
|
||||||
email, err := p.GetEmailAddress(session)
|
email, err := p.GetEmailAddress(session)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Equal(t, "michael.bland@gsa.gov", email)
|
assert.Equal(t, "foo@bar.com", email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that trying to trigger the "failed building request" case is not
|
func TestGitLabProviderUsername(t *testing.T) {
|
||||||
// practical, since the only way it can fail is if the URL fails to parse.
|
b := testGitLabBackend()
|
||||||
func TestGitLabProviderGetEmailAddressFailedRequest(t *testing.T) {
|
|
||||||
b := testGitLabBackend("unused payload")
|
|
||||||
defer b.Close()
|
defer b.Close()
|
||||||
|
|
||||||
bURL, _ := url.Parse(b.URL)
|
bURL, _ := url.Parse(b.URL)
|
||||||
p := testGitLabProvider(bURL.Host)
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
p.AllowUnverifiedEmail = true
|
||||||
|
|
||||||
// We'll trigger a request failure by using an unexpected access
|
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
|
||||||
// token. Alternatively, we could allow the parsing of the payload as
|
username, err := p.GetUserName(session)
|
||||||
// JSON to fail.
|
assert.Equal(t, nil, err)
|
||||||
session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
|
assert.Equal(t, "FooBar", username)
|
||||||
email, err := p.GetEmailAddress(session)
|
|
||||||
assert.NotEqual(t, nil, err)
|
|
||||||
assert.Equal(t, "", email)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitLabProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
|
func TestGitLabProviderGroupMembershipValid(t *testing.T) {
|
||||||
b := testGitLabBackend("{\"foo\": \"bar\"}")
|
b := testGitLabBackend()
|
||||||
defer b.Close()
|
defer b.Close()
|
||||||
|
|
||||||
bURL, _ := url.Parse(b.URL)
|
bURL, _ := url.Parse(b.URL)
|
||||||
p := testGitLabProvider(bURL.Host)
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
p.AllowUnverifiedEmail = true
|
||||||
|
p.Group = "foo"
|
||||||
|
|
||||||
session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
|
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
|
||||||
email, err := p.GetEmailAddress(session)
|
email, err := p.GetEmailAddress(session)
|
||||||
assert.NotEqual(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Equal(t, "", email)
|
assert.Equal(t, "foo@bar.com", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitLabProviderGroupMembershipMissing(t *testing.T) {
|
||||||
|
b := testGitLabBackend()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
p.AllowUnverifiedEmail = true
|
||||||
|
p.Group = "baz"
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
|
||||||
|
_, err := p.GetEmailAddress(session)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitLabProviderEmailDomainValid(t *testing.T) {
|
||||||
|
b := testGitLabBackend()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
p.AllowUnverifiedEmail = true
|
||||||
|
p.EmailDomains = []string{"bar.com"}
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "foo@bar.com", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitLabProviderEmailDomainInvalid(t *testing.T) {
|
||||||
|
b := testGitLabBackend()
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testGitLabProvider(bURL.Host)
|
||||||
|
p.AllowUnverifiedEmail = true
|
||||||
|
p.EmailDomains = []string{"baz.com"}
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: "gitlab_access_token"}
|
||||||
|
_, err := p.GetEmailAddress(session)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
admin "google.golang.org/api/admin/directory/v1"
|
admin "google.golang.org/api/admin/directory/v1"
|
||||||
@ -149,6 +149,7 @@ func (p *GoogleProvider) Redeem(redirectURL, code string) (s *sessions.SessionSt
|
|||||||
s = &sessions.SessionState{
|
s = &sessions.SessionState{
|
||||||
AccessToken: jsonResponse.AccessToken,
|
AccessToken: jsonResponse.AccessToken,
|
||||||
IDToken: jsonResponse.IDToken,
|
IDToken: jsonResponse.IDToken,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
|
ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
|
||||||
RefreshToken: jsonResponse.RefreshToken,
|
RefreshToken: jsonResponse.RefreshToken,
|
||||||
Email: c.Email,
|
Email: c.Email,
|
||||||
@ -188,67 +189,42 @@ func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userInGroup(service *admin.Service, groups []string, email string) bool {
|
func userInGroup(service *admin.Service, groups []string, email string) bool {
|
||||||
user, err := fetchUser(service, email)
|
|
||||||
if err != nil {
|
|
||||||
logger.Printf("error fetching user: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
id := user.Id
|
|
||||||
custID := user.CustomerId
|
|
||||||
|
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
members, err := fetchGroupMembers(service, group)
|
// Use the HasMember API to checking for the user's presence in each group or nested subgroups
|
||||||
if err != nil {
|
req := service.Members.HasMember(group, email)
|
||||||
if err, ok := err.(*googleapi.Error); ok && err.Code == 404 {
|
|
||||||
logger.Printf("error fetching members for group %s: group does not exist", group)
|
|
||||||
} else {
|
|
||||||
logger.Printf("error fetching group members: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, member := range members {
|
|
||||||
switch member.Type {
|
|
||||||
case "CUSTOMER":
|
|
||||||
if member.Id == custID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case "USER":
|
|
||||||
if member.Id == id {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchUser(service *admin.Service, email string) (*admin.User, error) {
|
|
||||||
user, err := service.Users.Get(email).Do()
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchGroupMembers(service *admin.Service, group string) ([]*admin.Member, error) {
|
|
||||||
members := []*admin.Member{}
|
|
||||||
pageToken := ""
|
|
||||||
for {
|
|
||||||
req := service.Members.List(group)
|
|
||||||
if pageToken != "" {
|
|
||||||
req.PageToken(pageToken)
|
|
||||||
}
|
|
||||||
r, err := req.Do()
|
r, err := req.Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
err, ok := err.(*googleapi.Error)
|
||||||
|
if ok && err.Code == 404 {
|
||||||
|
logger.Printf("error checking membership in group %s: group does not exist", group)
|
||||||
|
} else if ok && err.Code == 400 {
|
||||||
|
// It is possible for Members.HasMember to return false even if the email is a group member.
|
||||||
|
// One case that can cause this is if the user email is from a different domain than the group,
|
||||||
|
// e.g. "member@otherdomain.com" in the group "group@mydomain.com" will result in a 400 error
|
||||||
|
// from the HasMember API. In that case, attempt to query the member object directly from the group.
|
||||||
|
req := service.Members.Get(group, email)
|
||||||
|
r, err := req.Do()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("error using get API to check member %s of google group %s: user not in the group", email, group)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
for _, member := range r.Members {
|
|
||||||
members = append(members, member)
|
// If the non-domain user is found within the group, still verify that they are "ACTIVE".
|
||||||
|
// Do not count the user as belonging to a group if they have another status ("ARCHIVED", "SUSPENDED", or "UNKNOWN").
|
||||||
|
if r.Status == "ACTIVE" {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if r.NextPageToken == "" {
|
} else {
|
||||||
break
|
logger.Printf("error checking group membership: %v", err)
|
||||||
}
|
}
|
||||||
pageToken = r.NextPageToken
|
continue
|
||||||
}
|
}
|
||||||
return members, nil
|
if r.IsMember {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateGroup validates that the provided email exists in the configured Google
|
// ValidateGroup validates that the provided email exists in the configured Google
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
package providers
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
admin "google.golang.org/api/admin/directory/v1"
|
||||||
|
option "google.golang.org/api/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRedeemServer(body []byte) (*url.URL, *httptest.Server) {
|
func newRedeemServer(body []byte) (*url.URL, *httptest.Server) {
|
||||||
@ -179,3 +184,56 @@ func TestGoogleProviderGetEmailAddressEmailMissing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGoogleProviderUserInGroup(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/groups/group@example.com/hasMember/member-in-domain@example.com" {
|
||||||
|
fmt.Fprintln(w, `{"isMember": true}`)
|
||||||
|
} else if r.URL.Path == "/groups/group@example.com/hasMember/non-member-in-domain@example.com" {
|
||||||
|
fmt.Fprintln(w, `{"isMember": false}`)
|
||||||
|
} else if r.URL.Path == "/groups/group@example.com/hasMember/member-out-of-domain@otherexample.com" {
|
||||||
|
http.Error(
|
||||||
|
w,
|
||||||
|
`{"error": {"errors": [{"domain": "global","reason": "invalid","message": "Invalid Input: memberKey"}],"code": 400,"message": "Invalid Input: memberKey"}}`,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
)
|
||||||
|
} else if r.URL.Path == "/groups/group@example.com/hasMember/non-member-out-of-domain@otherexample.com" {
|
||||||
|
http.Error(
|
||||||
|
w,
|
||||||
|
`{"error": {"errors": [{"domain": "global","reason": "invalid","message": "Invalid Input: memberKey"}],"code": 400,"message": "Invalid Input: memberKey"}}`,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
)
|
||||||
|
} else if r.URL.Path == "/groups/group@example.com/members/non-member-out-of-domain@otherexample.com" {
|
||||||
|
// note that the client currently doesn't care what this response text or code is - any error here results in failure to match the group
|
||||||
|
http.Error(
|
||||||
|
w,
|
||||||
|
`{"error": {"errors": [{"domain": "global","reason": "notFound","message": "Resource Not Found: memberKey"}],"code": 404,"message": "Resource Not Found: memberKey"}}`,
|
||||||
|
http.StatusNotFound,
|
||||||
|
)
|
||||||
|
} else if r.URL.Path == "/groups/group@example.com/members/member-out-of-domain@otherexample.com" {
|
||||||
|
fmt.Fprintln(w,
|
||||||
|
`{"kind": "admin#directory#member","etag":"12345","id":"1234567890","email": "member-out-of-domain@otherexample.com","role": "MEMBER","type": "USER","status": "ACTIVE","delivery_settings": "ALL_MAIL"}}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := ts.Client()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
service, err := admin.NewService(ctx, option.WithHTTPClient(client))
|
||||||
|
service.BasePath = ts.URL
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
result := userInGroup(service, []string{"group@example.com"}, "member-in-domain@example.com")
|
||||||
|
assert.True(t, result)
|
||||||
|
|
||||||
|
result = userInGroup(service, []string{"group@example.com"}, "member-out-of-domain@otherexample.com")
|
||||||
|
assert.True(t, result)
|
||||||
|
|
||||||
|
result = userInGroup(service, []string{"group@example.com"}, "non-member-in-domain@example.com")
|
||||||
|
assert.False(t, result)
|
||||||
|
|
||||||
|
result = userInGroup(service, []string{"group@example.com"}, "non-member-out-of-domain@otherexample.com")
|
||||||
|
assert.False(t, result)
|
||||||
|
}
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/api"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/requests"
|
||||||
)
|
)
|
||||||
|
|
||||||
// stripToken is a helper function to obfuscate "access_token"
|
// stripToken is a helper function to obfuscate "access_token"
|
||||||
@ -47,7 +47,7 @@ func stripParam(param, endpoint string) string {
|
|||||||
|
|
||||||
// validateToken returns true if token is valid
|
// validateToken returns true if token is valid
|
||||||
func validateToken(p Provider, accessToken string, header http.Header) bool {
|
func validateToken(p Provider, accessToken string, header http.Header) bool {
|
||||||
if accessToken == "" || p.Data().ValidateURL == nil {
|
if accessToken == "" || p.Data().ValidateURL == nil || p.Data().ValidateURL.String() == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
endpoint := p.Data().ValidateURL.String()
|
endpoint := p.Data().ValidateURL.String()
|
||||||
@ -55,7 +55,7 @@ func validateToken(p Provider, accessToken string, header http.Header) bool {
|
|||||||
params := url.Values{"access_token": {accessToken}}
|
params := url.Values{"access_token": {accessToken}}
|
||||||
endpoint = endpoint + "?" + params.Encode()
|
endpoint = endpoint + "?" + params.Encode()
|
||||||
}
|
}
|
||||||
resp, err := api.RequestUnparsedResponse(endpoint, header)
|
resp, err := requests.RequestUnparsedResponse(endpoint, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("GET %s", stripToken(endpoint))
|
logger.Printf("GET %s", stripToken(endpoint))
|
||||||
logger.Printf("token validation request failed: %s", err)
|
logger.Printf("token validation request failed: %s", err)
|
||||||
|
86
providers/keycloak.go
Normal file
86
providers/keycloak.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/requests"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeycloakProvider struct {
|
||||||
|
*ProviderData
|
||||||
|
Group string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeycloakProvider(p *ProviderData) *KeycloakProvider {
|
||||||
|
p.ProviderName = "Keycloak"
|
||||||
|
if p.LoginURL == nil || p.LoginURL.String() == "" {
|
||||||
|
p.LoginURL = &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "keycloak.org",
|
||||||
|
Path: "/oauth/authorize",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.RedeemURL == nil || p.RedeemURL.String() == "" {
|
||||||
|
p.RedeemURL = &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "keycloak.org",
|
||||||
|
Path: "/oauth/token",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ValidateURL == nil || p.ValidateURL.String() == "" {
|
||||||
|
p.ValidateURL = &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "keycloak.org",
|
||||||
|
Path: "/api/v3/user",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Scope == "" {
|
||||||
|
p.Scope = "api"
|
||||||
|
}
|
||||||
|
return &KeycloakProvider{ProviderData: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *KeycloakProvider) SetGroup(group string) {
|
||||||
|
p.Group = group
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *KeycloakProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", p.ValidateURL.String(), nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+s.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed building request %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
json, err := requests.Request(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("failed making request %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Group != "" {
|
||||||
|
var groups, err = json.Get("groups").Array()
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("groups not found %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var found = false
|
||||||
|
for i := range groups {
|
||||||
|
if groups[i].(string) == p.Group {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found != true {
|
||||||
|
logger.Printf("group not found, access denied")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Get("email").String()
|
||||||
|
}
|
151
providers/keycloak_test.go
Normal file
151
providers/keycloak_test.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bmizerany/assert"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
const imaginaryAccessToken = "imaginary_access_token"
|
||||||
|
const bearerAccessToken = "Bearer " + imaginaryAccessToken
|
||||||
|
|
||||||
|
func testKeycloakProvider(hostname, group string) *KeycloakProvider {
|
||||||
|
p := NewKeycloakProvider(
|
||||||
|
&ProviderData{
|
||||||
|
ProviderName: "",
|
||||||
|
LoginURL: &url.URL{},
|
||||||
|
RedeemURL: &url.URL{},
|
||||||
|
ProfileURL: &url.URL{},
|
||||||
|
ValidateURL: &url.URL{},
|
||||||
|
Scope: ""})
|
||||||
|
|
||||||
|
if group != "" {
|
||||||
|
p.SetGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostname != "" {
|
||||||
|
updateURL(p.Data().LoginURL, hostname)
|
||||||
|
updateURL(p.Data().RedeemURL, hostname)
|
||||||
|
updateURL(p.Data().ProfileURL, hostname)
|
||||||
|
updateURL(p.Data().ValidateURL, hostname)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func testKeycloakBackend(payload string) *httptest.Server {
|
||||||
|
path := "/api/v3/user"
|
||||||
|
|
||||||
|
return httptest.NewServer(http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
url := r.URL
|
||||||
|
if url.Path != path {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
} else if r.Header.Get("Authorization") != bearerAccessToken {
|
||||||
|
w.WriteHeader(403)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(payload))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeycloakProviderDefaults(t *testing.T) {
|
||||||
|
p := testKeycloakProvider("", "")
|
||||||
|
assert.NotEqual(t, nil, p)
|
||||||
|
assert.Equal(t, "Keycloak", p.Data().ProviderName)
|
||||||
|
assert.Equal(t, "https://keycloak.org/oauth/authorize",
|
||||||
|
p.Data().LoginURL.String())
|
||||||
|
assert.Equal(t, "https://keycloak.org/oauth/token",
|
||||||
|
p.Data().RedeemURL.String())
|
||||||
|
assert.Equal(t, "https://keycloak.org/api/v3/user",
|
||||||
|
p.Data().ValidateURL.String())
|
||||||
|
assert.Equal(t, "api", p.Data().Scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeycloakProviderOverrides(t *testing.T) {
|
||||||
|
p := NewKeycloakProvider(
|
||||||
|
&ProviderData{
|
||||||
|
LoginURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "/oauth/auth"},
|
||||||
|
RedeemURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "/oauth/token"},
|
||||||
|
ValidateURL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "/api/v3/user"},
|
||||||
|
Scope: "profile"})
|
||||||
|
assert.NotEqual(t, nil, p)
|
||||||
|
assert.Equal(t, "Keycloak", p.Data().ProviderName)
|
||||||
|
assert.Equal(t, "https://example.com/oauth/auth",
|
||||||
|
p.Data().LoginURL.String())
|
||||||
|
assert.Equal(t, "https://example.com/oauth/token",
|
||||||
|
p.Data().RedeemURL.String())
|
||||||
|
assert.Equal(t, "https://example.com/api/v3/user",
|
||||||
|
p.Data().ValidateURL.String())
|
||||||
|
assert.Equal(t, "profile", p.Data().Scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeycloakProviderGetEmailAddress(t *testing.T) {
|
||||||
|
b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\"}")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testKeycloakProvider(bURL.Host, "")
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: imaginaryAccessToken}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "michael.bland@gsa.gov", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeycloakProviderGetEmailAddressAndGroup(t *testing.T) {
|
||||||
|
b := testKeycloakBackend("{\"email\": \"michael.bland@gsa.gov\", \"groups\": [\"test-grp1\", \"test-grp2\"]}")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testKeycloakProvider(bURL.Host, "test-grp1")
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: imaginaryAccessToken}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "michael.bland@gsa.gov", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that trying to trigger the "failed building request" case is not
|
||||||
|
// practical, since the only way it can fail is if the URL fails to parse.
|
||||||
|
func TestKeycloakProviderGetEmailAddressFailedRequest(t *testing.T) {
|
||||||
|
b := testKeycloakBackend("unused payload")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testKeycloakProvider(bURL.Host, "")
|
||||||
|
|
||||||
|
// We'll trigger a request failure by using an unexpected access
|
||||||
|
// token. Alternatively, we could allow the parsing of the payload as
|
||||||
|
// JSON to fail.
|
||||||
|
session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
assert.Equal(t, "", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeycloakProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
|
||||||
|
b := testKeycloakBackend("{\"foo\": \"bar\"}")
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
bURL, _ := url.Parse(b.URL)
|
||||||
|
p := testKeycloakProvider(bURL.Host, "")
|
||||||
|
|
||||||
|
session := &sessions.SessionState{AccessToken: imaginaryAccessToken}
|
||||||
|
email, err := p.GetEmailAddress(session)
|
||||||
|
assert.NotEqual(t, nil, err)
|
||||||
|
assert.Equal(t, "", email)
|
||||||
|
}
|
@ -6,8 +6,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/api"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/requests"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinkedInProvider represents an LinkedIn based Identity Provider
|
// LinkedInProvider represents an LinkedIn based Identity Provider
|
||||||
@ -61,7 +61,7 @@ func (p *LinkedInProvider) GetEmailAddress(s *sessions.SessionState) (string, er
|
|||||||
}
|
}
|
||||||
req.Header = getLinkedInHeader(s.AccessToken)
|
req.Header = getLinkedInHeader(s.AccessToken)
|
||||||
|
|
||||||
json, err := api.Request(req)
|
json, err := requests.Request(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -252,6 +252,7 @@ func (p *LoginGovProvider) Redeem(redirectURL, code string) (s *sessions.Session
|
|||||||
s = &sessions.SessionState{
|
s = &sessions.SessionState{
|
||||||
AccessToken: jsonResponse.AccessToken,
|
AccessToken: jsonResponse.AccessToken,
|
||||||
IDToken: jsonResponse.IDToken,
|
IDToken: jsonResponse.IDToken,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
|
ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
|
||||||
Email: email,
|
Email: email,
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ package providers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
oidc "github.com/coreos/go-oidc"
|
oidc "github.com/coreos/go-oidc"
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/requests"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +18,7 @@ type OIDCProvider struct {
|
|||||||
*ProviderData
|
*ProviderData
|
||||||
|
|
||||||
Verifier *oidc.IDTokenVerifier
|
Verifier *oidc.IDTokenVerifier
|
||||||
|
AllowUnverifiedEmail bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOIDCProvider initiates a new OIDCProvider
|
// NewOIDCProvider initiates a new OIDCProvider
|
||||||
@ -87,6 +91,7 @@ func (p *OIDCProvider) redeemRefreshToken(s *sessions.SessionState) (err error)
|
|||||||
s.AccessToken = newSession.AccessToken
|
s.AccessToken = newSession.AccessToken
|
||||||
s.IDToken = newSession.IDToken
|
s.IDToken = newSession.IDToken
|
||||||
s.RefreshToken = newSession.RefreshToken
|
s.RefreshToken = newSession.RefreshToken
|
||||||
|
s.CreatedAt = newSession.CreatedAt
|
||||||
s.ExpiresOn = newSession.ExpiresOn
|
s.ExpiresOn = newSession.ExpiresOn
|
||||||
s.Email = newSession.Email
|
s.Email = newSession.Email
|
||||||
return
|
return
|
||||||
@ -115,10 +120,33 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
|
|||||||
}
|
}
|
||||||
|
|
||||||
if claims.Email == "" {
|
if claims.Email == "" {
|
||||||
// TODO: Try getting email from /userinfo before falling back to Subject
|
if p.ProfileURL.String() == "" {
|
||||||
claims.Email = claims.Subject
|
return nil, fmt.Errorf("id_token did not contain an email")
|
||||||
}
|
}
|
||||||
if claims.Verified != nil && !*claims.Verified {
|
|
||||||
|
// If the userinfo endpoint profileURL is defined, then there is a chance the userinfo
|
||||||
|
// contents at the profileURL contains the email.
|
||||||
|
// Make a query to the userinfo endpoint, and attempt to locate the email from there.
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", p.ProfileURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header = getOIDCHeader(token.AccessToken)
|
||||||
|
|
||||||
|
respJSON, err := requests.Request(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
email, err := respJSON.Get("email").String()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Neither id_token nor userinfo endpoint contained an email")
|
||||||
|
}
|
||||||
|
|
||||||
|
claims.Email = email
|
||||||
|
}
|
||||||
|
if !p.AllowUnverifiedEmail && claims.Verified != nil && !*claims.Verified {
|
||||||
return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
|
return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +154,8 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
|
|||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
IDToken: rawIDToken,
|
IDToken: rawIDToken,
|
||||||
RefreshToken: token.RefreshToken,
|
RefreshToken: token.RefreshToken,
|
||||||
ExpiresOn: token.Expiry,
|
CreatedAt: time.Now(),
|
||||||
|
ExpiresOn: idToken.Expiry,
|
||||||
Email: claims.Email,
|
Email: claims.Email,
|
||||||
User: claims.Subject,
|
User: claims.Subject,
|
||||||
}, nil
|
}, nil
|
||||||
@ -142,3 +171,10 @@ func (p *OIDCProvider) ValidateSessionState(s *sessions.SessionState) bool {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOIDCHeader(accessToken string) http.Header {
|
||||||
|
header := make(http.Header)
|
||||||
|
header.Set("Accept", "application/json")
|
||||||
|
header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
@ -8,9 +8,10 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redeem provides a default implementation of the OAuth2 token redemption process
|
// Redeem provides a default implementation of the OAuth2 token redemption process
|
||||||
@ -72,7 +73,7 @@ func (p *ProviderData) Redeem(redirectURL, code string) (s *sessions.SessionStat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if a := v.Get("access_token"); a != "" {
|
if a := v.Get("access_token"); a != "" {
|
||||||
s = &sessions.SessionState{AccessToken: a}
|
s = &sessions.SessionState{AccessToken: a, CreatedAt: time.Now()}
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("no access token found %s", body)
|
err = fmt.Errorf("no access token found %s", body)
|
||||||
}
|
}
|
||||||
@ -95,12 +96,12 @@ func (p *ProviderData) GetLoginURL(redirectURI, state string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CookieForSession serializes a session state for storage in a cookie
|
// CookieForSession serializes a session state for storage in a cookie
|
||||||
func (p *ProviderData) CookieForSession(s *sessions.SessionState, c *cookie.Cipher) (string, error) {
|
func (p *ProviderData) CookieForSession(s *sessions.SessionState, c *encryption.Cipher) (string, error) {
|
||||||
return s.EncodeSessionState(c)
|
return s.EncodeSessionState(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionFromCookie deserializes a session from a cookie value
|
// SessionFromCookie deserializes a session from a cookie value
|
||||||
func (p *ProviderData) SessionFromCookie(v string, c *cookie.Cipher) (s *sessions.SessionState, err error) {
|
func (p *ProviderData) SessionFromCookie(v string, c *encryption.Cipher) (s *sessions.SessionState, err error) {
|
||||||
return sessions.DecodeSessionState(v, c)
|
return sessions.DecodeSessionState(v, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package providers
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pusher/oauth2_proxy/cookie"
|
|
||||||
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
|
||||||
|
"github.com/pusher/oauth2_proxy/pkg/encryption"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider represents an upstream identity provider implementation
|
// Provider represents an upstream identity provider implementation
|
||||||
@ -15,8 +15,8 @@ type Provider interface {
|
|||||||
ValidateSessionState(*sessions.SessionState) bool
|
ValidateSessionState(*sessions.SessionState) bool
|
||||||
GetLoginURL(redirectURI, finalRedirect string) string
|
GetLoginURL(redirectURI, finalRedirect string) string
|
||||||
RefreshSessionIfNeeded(*sessions.SessionState) (bool, error)
|
RefreshSessionIfNeeded(*sessions.SessionState) (bool, error)
|
||||||
SessionFromCookie(string, *cookie.Cipher) (*sessions.SessionState, error)
|
SessionFromCookie(string, *encryption.Cipher) (*sessions.SessionState, error)
|
||||||
CookieForSession(*sessions.SessionState, *cookie.Cipher) (string, error)
|
CookieForSession(*sessions.SessionState, *encryption.Cipher) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New provides a new Provider based on the configured provider string
|
// New provides a new Provider based on the configured provider string
|
||||||
@ -28,6 +28,8 @@ func New(provider string, p *ProviderData) Provider {
|
|||||||
return NewFacebookProvider(p)
|
return NewFacebookProvider(p)
|
||||||
case "github":
|
case "github":
|
||||||
return NewGitHubProvider(p)
|
return NewGitHubProvider(p)
|
||||||
|
case "keycloak":
|
||||||
|
return NewKeycloakProvider(p)
|
||||||
case "azure":
|
case "azure":
|
||||||
return NewAzureProvider(p)
|
return NewAzureProvider(p)
|
||||||
case "gitlab":
|
case "gitlab":
|
||||||
@ -36,6 +38,8 @@ func New(provider string, p *ProviderData) Provider {
|
|||||||
return NewOIDCProvider(p)
|
return NewOIDCProvider(p)
|
||||||
case "login.gov":
|
case "login.gov":
|
||||||
return NewLoginGovProvider(p)
|
return NewLoginGovProvider(p)
|
||||||
|
case "bitbucket":
|
||||||
|
return NewBitbucketProvider(p)
|
||||||
default:
|
default:
|
||||||
return NewGoogleProvider(p)
|
return NewGoogleProvider(p)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadTemplates(dir string) *template.Template {
|
func loadTemplates(dir string) *template.Template {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserMap holds information from the authenticated emails file
|
// UserMap holds information from the authenticated emails file
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pusher/oauth2_proxy/logger"
|
"github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
fsnotify "gopkg.in/fsnotify/fsnotify.v1"
|
fsnotify "gopkg.in/fsnotify/fsnotify.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/pusher/oauth2_proxy/logger"
|
import "github.com/pusher/oauth2_proxy/pkg/logger"
|
||||||
|
|
||||||
func WatchForUpdates(filename string, done <-chan bool, action func()) {
|
func WatchForUpdates(filename string, done <-chan bool, action func()) {
|
||||||
logger.Printf("file watching not implemented on this platform")
|
logger.Printf("file watching not implemented on this platform")
|
||||||
|
Loading…
Reference in New Issue
Block a user