Compare commits

..

25 Commits

Author SHA1 Message Date
Joel Speed
131206cf41
Ensure SessionStores can handle recieving cookies for the wrong implementation 2019-05-30 11:55:42 +01:00
Joel Speed
6d7f0ab57d
Make sure the cookie exists before we clear the session in redis 2019-05-30 10:53:53 +01:00
Joel Speed
66bbf146ec
Fix ticket retrieval with an invalid ticket 2019-05-30 10:10:28 +01:00
Joel Speed
48edce3003
Ensure sessions are refreshable in redis session store 2019-05-29 15:25:56 +01:00
Joel Speed
9dc1a96d81
Check SaveSession works when an existing session is present 2019-05-29 11:59:58 +01:00
Joel Speed
ff36b61f8c
Add Redis sentinel compatibility 2019-05-24 17:32:55 +01:00
Joel Speed
09d0a25ced
Add redis-connection-url flag 2019-05-20 12:58:43 +02:00
Joel Speed
ec04cf43d8
Refactor persistent tests with more Context 2019-05-20 12:58:42 +02:00
Joel Speed
a9e2d1bfa7
Stop miniredis after each test 2019-05-20 12:58:42 +02:00
Joel Speed
bf72731bcb
Run persistent tests with multiple option groups 2019-05-20 12:58:41 +02:00
Joel Speed
b770aeca63
Clean up persistent SessionStore tests 2019-05-20 12:58:40 +02:00
Joel Speed
0ca05bd05b
Tranfser all cookies in tests 2019-05-20 12:58:39 +02:00
Joel Speed
e179741b21
Remove spurious comment 2019-05-20 12:58:38 +02:00
Joel Speed
22b42848b8
Rename expire -> expiration 2019-05-20 12:58:37 +02:00
Joel Speed
d461812444
More obvious comment on CFB 2019-05-20 12:58:36 +02:00
Joel Speed
cf1a9c819d
Make loadSessionFromString private 2019-05-20 12:58:35 +02:00
Joel Speed
bae865f880
Fix comments on Redis options 2019-05-20 12:58:34 +02:00
Joel Speed
b791312bec
Fix go-redis version pin 2019-05-20 12:58:33 +02:00
Joel Speed
a21dc34282
Sign cookies in the Redis Session store 2019-05-20 12:58:32 +02:00
Joel Speed
febd026ffe
Use session CreatedAt for cookie timings 2019-05-20 12:58:31 +02:00
Joel Speed
8e7f98e461
Simplify redis store options 2019-05-20 12:58:30 +02:00
Brian Van Klaveren
94945238b2
Pin version of go-redis 2019-05-20 12:58:29 +02:00
Brian Van Klaveren
89a06a0104
Check cookie error and doc on cookie handling 2019-05-20 12:58:28 +02:00
Brian Van Klaveren
1f0cb4ae44
Add support for a redis session store 2019-05-20 12:58:27 +02:00
Brian Van Klaveren
3afb196a57
Fix session_state type 2019-05-20 12:58:24 +02:00
67 changed files with 1164 additions and 2946 deletions

10
.github/CODEOWNERS vendored
View File

@ -1,6 +1,6 @@
# Default owner should be a Pusher cloud-team member or another maintainer # Default owner should be a Pusher cloud-team member unless overridden by later
# unless overridden by later rules in this file # rules in this file
* @pusher/cloud-team @syscll @steakunderscore * @pusher/cloud-team
# 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,7 +10,3 @@
# 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

View File

@ -1,13 +0,0 @@
run:
deadline: 120s
linters:
enable:
- govet
- golint
- ineffassign
- goconst
- deadcode
- gofmt
- goimports
enable-all: false
disable-all: true

View File

@ -1,12 +1,16 @@
language: go language: go
go: go:
- 1.11.x
- 1.12.x - 1.12.x
install: install:
# Fetch dependencies # Fetch dependencies
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.17.1 - wget -O dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
- GO111MODULE=on go mod download - chmod +x dep
- mv dep $GOPATH/bin/dep
script: script:
- ./configure && make test - ./configure
# Run tests
- make test
sudo: false sudo: false
notifications: notifications:
email: false email: false

View File

@ -1,85 +1,17 @@
# 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
- [#231](https://github.com/pusher/oauth2_proxy/pull/231) Rework GitLab provider - [#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)
- 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
- [#234](https://github.com/pusher/oauth2_proxy/pull/234) Added option `-ssl-upstream-insecure-skip-validation` to skip validation of upstream SSL certificates (@jansinger) - [#148](https://github.com/pusher/outh2_proxy/pull/148) Implement SessionStore interface within proxy (@JoelSpeed)
- [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant) - [#147](https://github.com/pusher/outh2_proxy/pull/147) Add SessionStore interfaces and initial implementation (@JoelSpeed)
- [#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
@ -101,21 +33,8 @@ reconfigure their proxies. Please read the Breaking Changes below thoroughly.
- 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

View File

@ -1,17 +1,15 @@
FROM golang:1.12-stretch AS builder FROM golang:1.12-stretch AS builder
# Download tools # Download tools
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
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
COPY go.mod go.sum ./ RUN dep ensure --vendor-only
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
@ -22,7 +20,7 @@ COPY . .
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.10 FROM alpine:3.8
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

View File

@ -1,17 +1,15 @@
FROM golang:1.12-stretch AS builder FROM golang:1.12-stretch AS builder
# Download tools # Download tools
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
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
COPY go.mod go.sum ./ RUN dep ensure --vendor-only
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
@ -22,7 +20,7 @@ COPY . .
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.10 FROM arm64v8/alpine:3.8
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

View File

@ -1,17 +1,15 @@
FROM golang:1.12-stretch AS builder FROM golang:1.12-stretch AS builder
# Download tools # Download tools
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 RUN wget -O $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
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
COPY go.mod go.sum ./ RUN dep ensure --vendor-only
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
@ -22,7 +20,7 @@ COPY . .
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.10 FROM arm32v6/alpine:3.8
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

426
Gopkg.lock generated Normal file
View File

@ -0,0 +1,426 @@
# 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]]
branch = "master"
digest = "1:3cce78d5d0090e3f1162945fba60ba74e72e8422e8e41bb9c701afb67237bb65"
name = "github.com/alicebob/gopher-json"
packages = ["."]
pruneopts = ""
revision = "5a6b3ba71ee69b77cf64febf8b5a7526ca5eaef0"
[[projects]]
digest = "1:18a07506ddaa87b1612bfd69eef03f510faf122398df3da774d46dcfe751a060"
name = "github.com/alicebob/miniredis"
packages = [
".",
"server",
]
pruneopts = ""
revision = "3d7aa1333af56ab862d446678d93aaa6803e0938"
version = "v2.7.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]]
digest = "1:8c7410dae63c74bd92db09bf33af7e0698b635ab6a397fd8e9e10dfcce3138ac"
name = "github.com/go-redis/redis"
packages = [
".",
"internal",
"internal/consistenthash",
"internal/hashtag",
"internal/pool",
"internal/proto",
"internal/util",
]
pruneopts = ""
revision = "d22fde8721cc915a55aeb6b00944a76a92bfeb6e"
version = "v6.15.2"
[[projects]]
branch = "master"
digest = "1:3b760d3b93f994df8eb1d9ebfad17d3e9e37edcb7f7efaa15b427c0d7a64f4e4"
name = "github.com/golang/protobuf"
packages = ["proto"]
pruneopts = ""
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
[[projects]]
digest = "1:dcf8316121302735c0ac84e05f4686e3b34e284444435e9a206da48d8be18cb1"
name = "github.com/gomodule/redigo"
packages = [
"internal",
"redis",
]
pruneopts = ""
revision = "9c11da706d9b7902c6da69c592f75637793fe121"
version = "v2.0.0"
[[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:378d29a839ff770e9d9150580b4c01ff0a513a296b0487558a7af7c18adab98e"
name = "github.com/yuin/gopher-lua"
packages = [
".",
"ast",
"parse",
"pm",
]
pruneopts = ""
revision = "8bfc7677f583b35a5663a9dd934c08f3b5774bbb"
[[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/alicebob/miniredis",
"github.com/bitly/go-simplejson",
"github.com/coreos/go-oidc",
"github.com/dgrijalva/jwt-go",
"github.com/go-redis/redis",
"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

56
Gopkg.toml Normal file
View File

@ -0,0 +1,56 @@
# 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"
[[constraint]]
name = "github.com/go-redis/redis"
version = "v6.15.2"
[[constraint]]
name = "github.com/alicebob/miniredis"
version = "2.7.0"

View File

@ -1,3 +0,0 @@
Joel Speed <joel.speed@hotmail.co.uk> (@JoelSpeed)
Dan Bond (@syscll)
Henry Jenkins <henry@henryjenkins.name> (@steakunderscore)

View File

@ -4,7 +4,7 @@ VERSION := $(shell git describe --always --dirty --tags 2>/dev/null || echo "und
.NOTPARALLEL: .NOTPARALLEL:
.PHONY: all .PHONY: all
all: lint $(BINARY) all: dep lint $(BINARY)
.PHONY: clean .PHONY: clean
clean: clean:
@ -15,15 +15,36 @@ 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: lint: $(GOMETALINTER)
GO111MODULE=on $(GOLANGCILINT) run $(GOMETALINTER) --vendor --disable-all \
--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):
GO111MODULE=on CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/pusher/oauth2_proxy 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:
@ -54,34 +75,24 @@ 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: lint test: dep lint
GO111MODULE=on $(GO) test -v -race ./... $(GO) test -v -race ./...
.PHONY: release .PHONY: release
release: lint test release: lint test
mkdir release mkdir release
mkdir release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION) 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).linux-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-arm64.$(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-armv6.$(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).windows-amd64.$(GO_VERSION) GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-windows-amd64 github.com/pusher/oauth2_proxy
GO111MODULE=on GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ shasum -a 256 release/$(BINARY)-darwin-amd64 > release/$(BINARY)-darwin-amd64-sha256sum.txt
-o release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy shasum -a 256 release/$(BINARY)-linux-amd64 > release/$(BINARY)-linux-amd64-sha256sum.txt
GO111MODULE=on GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ shasum -a 256 release/$(BINARY)-linux-arm64 > release/$(BINARY)-linux-arm64-sha256sum.txt
-o release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy shasum -a 256 release/$(BINARY)-linux-armv6 > release/$(BINARY)-linux-armv6-sha256sum.txt
GO111MODULE=on GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" \ shasum -a 256 release/$(BINARY)-windows-amd64 > release/$(BINARY)-windows-amd64-sha256sum.txt
-o release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy tar -czvf release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-darwin-amd64
GO111MODULE=on GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" \ tar -czvf release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-linux-amd64
-o release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy tar -czvf release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION).tar.gz release/$(BINARY)-linux-arm64
GO111MODULE=on GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ tar -czvf release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION).tar.gz release/$(BINARY)-linux-armv6
-o release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy tar -czvf release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-windows-amd64
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)

View File

@ -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 `v4.0.0`) a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v3.2.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-4.0.0.linux-amd64: OK oauth2_proxy-3.2.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,10 +38,6 @@ 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.

View File

@ -1,4 +1,4 @@
package requests package api
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/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
) )
// Request parses the request body into a simplejson.Json object // Request parses the request body into a simplejson.Json object

View File

@ -1,4 +1,4 @@
package requests package api
import ( import (
"io/ioutil" "io/ioutil"

16
configure vendored
View File

@ -13,9 +13,13 @@ 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
;; ;;
@ -77,7 +81,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.12 vercomp $GO_VERSION 1.11
case $? in case $? in
0) ;& 0) ;&
1) 1)
@ -87,7 +91,7 @@ check_go_version() {
;; ;;
2) 2)
printf "${RED}" printf "${RED}"
echo "$GO_VERSION < 1.12" echo "$GO_VERSION < 1.11"
exit 1 exit 1
;; ;;
esac esac
@ -112,14 +116,16 @@ check_go_env() {
cd ${0%/*} cd ${0%/*}
rm -fv .env if [ ! -f .env ]; then
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 golangci-lint check_for dep
echo echo
@ -127,7 +133,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]}
GOLANGCILINT := "${tools[golangci-lint]}" DEP := "${tools[dep]}"
EOF EOF
echo "Environment configuration written to .env" echo "Environment configuration written to .env"

View File

@ -1,5 +1,5 @@
## OAuth2 Proxy Config File ## OAuth2 Proxy Config File
## https://github.com/pusher/oauth2_proxy ## https://github.com/bitly/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"

View File

@ -1,4 +1,4 @@
package encryption package cookie
import ( import (
"crypto/aes" "crypto/aes"

View File

@ -1,4 +1,4 @@
package encryption package cookie
import ( import (
"encoding/base64" "encoding/base64"

View File

@ -1,4 +1,4 @@
package encryption package cookie
import ( import (
"crypto/rand" "crypto/rand"

View File

@ -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]({{ site.gitweb }}/CHANGELOG.md). A list of changes can be seen in the [CHANGELOG](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)

View File

@ -15,7 +15,6 @@ 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)
@ -102,31 +101,15 @@ 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](https://docs.gitlab.com/ce/integration/oauth_provider.html). Make sure to enable at least the `openid`, `profile` and `email` scopes. 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)
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:
-oidc-issuer-url="<your gitlab url>" -login-url="<your gitlab url>/oauth/authorize"
-redeem-url="<your gitlab url>/oauth/token"
-validate-url="<your gitlab url>/api/v4/user"
### LinkedIn Auth Provider ### LinkedIn Auth Provider
@ -141,7 +124,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://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app). 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/).
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.
@ -161,56 +144,6 @@ 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.
@ -292,7 +225,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]({{ site.gitweb }}/providers/) to define a new Follow the examples in the [`providers` package](providers/) to define a new
`Provider` instance. Add a new `case` to `Provider` instance. Add a new `case` to
[`providers.New()`]({{ site.gitweb }}/providers/providers.go) to allow `oauth2_proxy` to use the [`providers.New()`](providers/providers.go) to allow `oauth2_proxy` to use the
new `Provider`. new `Provider`.

View File

@ -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-file=/path/to/cert.pem` and `--tls-key-file=/path/to/cert.key`. 1. Configure SSL Termination with OAuth2 Proxy by providing a `--tls-cert=/path/to/cert.pem` and `--tls-key=/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 @@ There are two recommended configurations.
./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-file=/path/to/cert.pem \ --tls-cert=/path/to/cert.pem \
--tls-key-file=/path/to/cert.key \ --tls-key=/path/to/cert.key \
--cookie-secret=... \ --cookie-secret=... \
--cookie-secure=true \ --cookie-secure=true \
--provider=... \ --provider=... \

View File

@ -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`]({{ site.gitweb }}/oauthproxy.go). in `oauthproxy.go`](./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"`)

View File

@ -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.4) nokogiri (1.10.1)
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)

View File

@ -18,7 +18,6 @@ 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

View File

@ -14,101 +14,89 @@ To generate a strong cookie secret use `python -c 'import os,base64; print base6
### Config File ### Config File
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` 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`
### Command Line Options ### Command Line Options
| Option | Type | Description | Default | ```
| ------ | ---- | ----------- | ------- | Usage of oauth2_proxy:
| `-acr-values` | string | optional, used by login.gov | `"http://idmanagement.gov/ns/assurance/loa/1"` | -acr-values string: optional, used by login.gov (default "http://idmanagement.gov/ns/assurance/loa/1")
| `-approval-prompt` | string | OAuth approval_prompt | `"force"` | -approval-prompt string: OAuth approval_prompt (default "force")
| `-auth-logging` | bool | Log authentication attempts | true | -auth-logging: Log authentication attempts (default true)
| `-auth-logging-format` | string | Template for authentication log lines | see [Logging Configuration](#logging-configuration) | -auth-logging-format string: Template for authentication log lines (see "Logging Configuration" paragraph below)
| `-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` | string | go to a tenant-specific or common (tenant-independent) endpoint. | `"common"` | -azure-tenant string: go to a tenant-specific or common (tenant-independent) endpoint. (default "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 | 168h0m0s | -cookie-expire duration: expire timeframe for cookie (default 168h0m0s)
| `-cookie-httponly` | bool | set HttpOnly cookie flag | true | -cookie-httponly: set HttpOnly cookie flag (default true)
| `-cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` | -cookie-name string: the name of the cookie that the oauth_proxy creates (default "_oauth2_proxy")
| `-cookie-path` | string | an optional cookie path to force cookies to (ie: `/poc/`) | `"/"` | -cookie-path string: an optional cookie path to force cookies to (ie: /poc/)* (default "/")
| `-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` | bool | set secure (HTTPS) cookie flag | true | -cookie-secure: set secure (HTTPS) cookie flag (default true)
| `-custom-templates-dir` | string | path to custom html templates | | -custom-templates-dir string: path to custom html templates
| `-display-htpasswd-form` | bool | display username / password login form if an htpasswd file is provided | true | -display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true)
| `-email-domain` | string | authenticate emails with the specified domain (may be given multiple times). Use `*` to authenticate any email | | -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
| `-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`) | | -flush-interval: period between flushing response buffers when streaming responses (default "1s")
| `-exclude-logging-paths` | string | comma separated list of paths to exclude from logging, eg: `"/ping,/path2"` |`""` (no paths excluded) | -footer string: custom footer string. Use "-" to disable default footer.
| `-flush-interval` | duration | period between flushing response buffers when streaming responses | `"1s"` | -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)
| `-banner` | string | custom banner string. Use `"-"` to disable default banner. | | -github-org string: restrict logins to members of this organisation
| `-footer` | string | custom footer string. Use `"-"` to disable default footer. | | -github-team string: restrict logins to members of any of these teams (slug), separated by a comma
| `-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-admin-email string: the google admin to impersonate for api calls
| `-github-org` | string | restrict logins to members of this organisation | | -google-group value: restrict logins to members of this google group (may be given multiple times).
| `-github-team` | string | restrict logins to members of any of these teams (slug), separated by a comma | | -google-service-account-json string: the path to the service account json credentials
| `-gitlab-group` | string | restrict logins to members of any of these groups (slug), separated by a comma | | -htpasswd-file string: additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
| `-google-admin-email` | string | the google admin to impersonate for api calls | | -http-address string: [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients (default "127.0.0.1:4180")
| `-google-group` | string | restrict logins to members of this google group (may be given multiple times). | | -https-address string: <addr>:<port> to listen on for HTTPS clients (default ":443")
| `-google-service-account-json` | string | the path to the service account json credentials | | -logging-compress: Should rotated log files be compressed using gzip (default false)
| `-htpasswd-file` | string | additionally authenticate against a htpasswd file. Entries must be created with `htpasswd -s` for SHA encryption | | -logging-filename string: File to log requests to, empty for stdout (default to stdout)
| `-http-address` | string | `[http://]<addr>:<port>` or `unix://<path>` to listen on for HTTP clients | `"127.0.0.1:4180"` | -logging-local-time: If the time in log files and backup filenames are local or UTC time (default true)
| `-https-address` | string | `<addr>:<port>` to listen on for HTTPS clients | `":443"` | -logging-max-age int: Maximum number of days to retain old log files (default 7)
| `-logging-compress` | bool | Should rotated log files be compressed using gzip | false | -logging-max-backups int: Maximum number of old log files to retain; 0 to disable (default 0)
| `-logging-filename` | string | File to log requests to, empty for `stdout` | `""` (stdout) | -logging-max-size int: Maximum size in megabytes of the log file before rotation (default 100)
| `-logging-local-time` | bool | Use local time in log files and backup filenames instead of UTC | true (local time) | -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-max-age` | int | Maximum number of days to retain old log files | 7 | -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-backups` | int | Maximum number of old log files to retain; 0 to disable | 0 | -login-url string: Authentication endpoint
| `-logging-max-size` | int | Maximum size in megabytes of the log file before rotation | 100 | -oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com"
| `-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 | | -oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled
| `-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-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header
| `-login-url` | string | Authentication endpoint | | -pass-authorization-header: pass OIDC IDToken to upstream via Authorization Bearer header
| `-insecure-oidc-allow-unverified-email` | bool | don't fail if an email address in an id_token is not verified | false | -pass-basic-auth: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream (default true)
| `-oidc-issuer-url` | string | the OpenID Connect issuer URL. ie: `"https://accounts.google.com"` | | -pass-host-header: pass the request Host Header to upstream (default true)
| `-oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | | -pass-user-headers: pass X-Forwarded-User and X-Forwarded-Email information to upstream (default true)
| `-pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header | false | -profile-url string: Profile access endpoint
| `-pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false | -provider string: OAuth provider (default "google")
| `-pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream | true | -proxy-prefix string: the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) (default "/oauth2")
| `-pass-host-header` | bool | pass the request Host Header to upstream | true | -proxy-websockets: enables WebSocket proxying (default true)
| `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true | -pubjwk-url string: JWK pubkey access endpoint: required by login.gov
| `-profile-url` | string | Profile access endpoint | | -redeem-url string: Token redemption endpoint
| `-provider` | string | OAuth provider | google | -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
| `-ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` | -request-logging: Log requests to stdout (default true)
| `-proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`<oauth2>/sign_in`) | `"/oauth2"` | -request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below)
| `-proxy-websockets` | bool | enables WebSocket proxying | true | -resource string: The resource that is protected (Azure AD only)
| `-pubjwk-url` | string | JWK pubkey access endpoint: required by login.gov | | -scope string: OAuth scope specification
| `-redeem-url` | string | Token redemption endpoint | | -session-store-type: Session data storage backend (default: cookie)
| `-redirect-url` | string | the OAuth Redirect URL. ie: `"https://internalapp.yourcompany.com/oauth2/callback"` | | -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)
| `-redis-connection-url` | string | URL of redis server for redis session storage (eg: `redis://HOST[:PORT]`) | | -set-authorization-header: set Authorization Bearer response header (useful in Nginx auth_request mode)
| `-redis-sentinel-master-name` | string | Redis sentinel master name. Used in conjunction with `--redis-use-sentinel` | | -signature-key string: GAP-Signature request signature key (algorithm:secretkey)
| `-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-preflight: will skip authentication for OPTIONS requests
| `-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-auth-regex value: bypass authentication for requests path's that match (may be given multiple times)
| `-request-logging` | bool | Log requests | true | -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case
| `-request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) | -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start
| `-resource` | string | The resource that is protected (Azure AD only) | | -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS
| `-scope` | string | OAuth scope specification | | -standard-logging: Log standard runtime information (default true)
| `-session-store-type` | string | Session data storage backend | cookie | -standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below)
| `-set-xauthrequest` | bool | set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) | false | -tls-cert string: path to certificate file
| `-set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false | -tls-key string: path to private key file
| `-signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | | -upstream value: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
| `-silence-ping-logging` | bool | disable logging of requests to ping endpoint | false | -validate-url string: Access token validation endpoint
| `-skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false | -version: print version string
| `-skip-auth-regex` | string | bypass authentication for requests paths that match (may be given multiple times) | | -whitelist-domain: allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)
| `-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.
@ -124,16 +112,17 @@ Multiple upstreams can either be configured by supplying a comma separated list
### Environment variables ### Environment variables
Every command line argument can be specified as an environment variable by The following environment variables can be used in place of the corresponding command-line arguments:
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`).
This is particularly useful for storing secrets outside of a configuration file - `OAUTH2_PROXY_CLIENT_ID`
or the command line. - `OAUTH2_PROXY_CLIENT_SECRET`
- `OAUTH2_PROXY_COOKIE_NAME`
For example, the `--cookie-secret` flag becomes `OAUTH2_PROXY_COOKIE_SECRET`, - `OAUTH2_PROXY_COOKIE_SECRET`
and the `--email-domain` flag becomes `OAUTH2_PROXY_EMAIL_DOMAINS`. - `OAUTH2_PROXY_COOKIE_DOMAIN`
- `OAUTH2_PROXY_COOKIE_PATH`
- `OAUTH2_PROXY_COOKIE_EXPIRE`
- `OAUTH2_PROXY_COOKIE_REFRESH`
- `OAUTH2_PROXY_SIGNATURE_KEY`
## Logging Configuration ## Logging Configuration
@ -145,8 +134,6 @@ 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:
@ -319,5 +306,3 @@ 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=".

View File

@ -15,8 +15,7 @@ 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) (default) - [cookie](cookie-storage) (deafult)
- [redis](#redis-storage)
### Cookie Storage ### Cookie Storage
@ -33,35 +32,3 @@ 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
View File

@ -1,87 +0,0 @@
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
View File

@ -1,531 +0,0 @@
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=

View File

@ -7,7 +7,7 @@ import (
"io" "io"
"os" "os"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
) )
// Server represents an HTTP server // Server represents an HTTP server

View File

@ -8,9 +8,6 @@ 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"))
@ -19,8 +16,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 = localhost r.RemoteAddr = "127.0.0.1"
r.Host = host r.Host = "test-server"
h.ServeHTTP(rw, r) h.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code) assert.Equal(t, 200, rw.Code)
@ -35,8 +32,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 = localhost r.RemoteAddr = "127.0.0.1"
r.Host = host r.Host = "test-server"
h.ServeHTTP(rw, r) h.ServeHTTP(rw, r)
assert.Equal(t, 200, rw.Code) assert.Equal(t, 200, rw.Code)
@ -51,8 +48,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 = localhost r.RemoteAddr = "127.0.0.1"
r.Host = host r.Host = "test-server"
h.ServeHTTP(rw, r) h.ServeHTTP(rw, r)
assert.Equal(t, "test", rw.Body.String()) assert.Equal(t, "test", rw.Body.String())
@ -66,8 +63,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 = localhost r.RemoteAddr = "127.0.0.1"
r.Host = host r.Host = "test-server"
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
h.ServeHTTP(rw, r) h.ServeHTTP(rw, r)
@ -83,8 +80,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 = localhost r.RemoteAddr = "127.0.0.1"
r.Host = host r.Host = "test-server"
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
h.ServeHTTP(rw, r) h.ServeHTTP(rw, r)
@ -99,8 +96,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 = localhost r.RemoteAddr = "127.0.0.1"
r.Host = host r.Host = "test-server"
r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) r.Header.Set(userAgentHeader, googleHealthCheckUserAgent)
h.ServeHTTP(rw, r) h.ServeHTTP(rw, r)

View File

@ -88,7 +88,6 @@ 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
@ -102,7 +101,6 @@ 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)),
@ -179,10 +177,6 @@ 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 == "" {
@ -308,16 +302,6 @@ 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()
@ -381,11 +365,6 @@ 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) {

View File

@ -10,7 +10,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/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,19 +75,18 @@ func (l *responseLogger) Status() int {
return l.status return l.status
} }
// Size returns the response size // Size returns teh 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 LoggingHandler // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
type loggingHandler struct { type loggingHandler struct {
handler http.Handler handler http.Handler
} }

View File

@ -9,7 +9,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
) )
func TestLoggingHandler_ServeHTTP(t *testing.T) { func TestLoggingHandler_ServeHTTP(t *testing.T) {
@ -17,23 +17,10 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) {
tests := []struct { tests := []struct {
Format, Format,
ExpectedLogMessage, ExpectedLogMessage string
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)), "/foo/bar", []string{}, false}, {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{}, true}, {"{{.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{"/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 {
@ -49,13 +36,9 @@ 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", test.Path, nil) r, _ := http.NewRequest("GET", "/foo/bar", nil)
r.RemoteAddr = "127.0.0.1" r.RemoteAddr = "127.0.0.1"
r.Host = "test-server" r.Host = "test-server"

35
main.go
View File

@ -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/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
) )
func main() { func main() {
@ -23,7 +23,6 @@ func main() {
whitelistDomains := StringArray{} whitelistDomains := StringArray{}
upstreams := StringArray{} upstreams := StringArray{}
skipAuthRegex := StringArray{} skipAuthRegex := StringArray{}
jwtIssuers := StringArray{}
googleGroups := StringArray{} googleGroups := StringArray{}
redisSentinelConnectionURLs := StringArray{} redisSentinelConnectionURLs := StringArray{}
@ -32,8 +31,8 @@ func main() {
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-file", "", "path to certificate file") flagSet.String("tls-cert", "", "path to certificate file")
flagSet.String("tls-key-file", "", "path to private key file") flagSet.String("tls-key", "", "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")
@ -47,21 +46,14 @@ 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 providers") flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS")
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")
@ -71,10 +63,8 @@ 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")
@ -88,9 +78,9 @@ func main() {
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.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.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-conneciton-urls to use this feature")
flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel") flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjuction 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.Var(&redisSentinelConnectionURLs, "redis-sentinel-connection-urls", "List of Redis sentinel conneciton URLs (eg redis://HOST[:PORT]). Used in conjuction 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")
@ -104,15 +94,12 @@ 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")
@ -158,13 +145,7 @@ 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.Banner) >= 1 { if len(opts.EmailDomains) != 0 && opts.AuthenticatedEmailsFile == "" {
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] != "*" {

View File

@ -1,8 +1,6 @@
package main package main
import ( import (
"context"
"crypto/tls"
b64 "encoding/base64" b64 "encoding/base64"
"errors" "errors"
"fmt" "fmt"
@ -15,11 +13,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/coreos/go-oidc"
"github.com/mbland/hmacauth" "github.com/mbland/hmacauth"
"github.com/pusher/oauth2_proxy/cookie"
"github.com/pusher/oauth2_proxy/logger"
sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" 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/providers" "github.com/pusher/oauth2_proxy/providers"
"github.com/yhat/wsutil" "github.com/yhat/wsutil"
) )
@ -50,11 +47,6 @@ 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
@ -95,11 +87,8 @@ type OAuthProxy struct {
PassAuthorization bool PassAuthorization bool
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
} }
@ -129,14 +118,9 @@ 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, opts *Options) (proxy *httputil.ReverseProxy) { func NewReverseProxy(target *url.URL, flushInterval time.Duration) (proxy *httputil.ReverseProxy) {
proxy = httputil.NewSingleHostReverseProxy(target) proxy = httputil.NewSingleHostReverseProxy(target)
proxy.FlushInterval = opts.FlushInterval proxy.FlushInterval = flushInterval
if opts.SSLUpstreamInsecureSkipVerify {
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
return proxy return proxy
} }
@ -167,9 +151,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) http.Handler { func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) (restProxy http.Handler) {
u.Path = "" u.Path = ""
proxy := NewReverseProxy(u, opts) proxy := NewReverseProxy(u, opts.FlushInterval)
if !opts.PassHostHeader { if !opts.PassHostHeader {
setProxyUpstreamHostHeader(proxy, u) setProxyUpstreamHostHeader(proxy, u)
} else { } else {
@ -183,12 +167,7 @@ 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{ return &UpstreamProxy{u.Host, proxy, wsProxy, auth}
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
@ -213,13 +192,7 @@ 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)
uProxy := UpstreamProxy{ serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil})
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))
} }
@ -228,12 +201,6 @@ 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)
@ -260,7 +227,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
Validator: validator, Validator: validator,
RobotsPath: "/robots.txt", RobotsPath: "/robots.txt",
PingPath: opts.PingPath, PingPath: "/ping",
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),
@ -275,8 +242,6 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
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,
@ -287,7 +252,6 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
PassAuthorization: opts.PassAuthorization, PassAuthorization: opts.PassAuthorization,
SkipProviderButton: opts.SkipProviderButton, SkipProviderButton: opts.SkipProviderButton,
templates: loadTemplates(opts.CustomTemplatesDir), templates: loadTemplates(opts.CustomTemplatesDir),
Banner: opts.Banner,
Footer: opts.Footer, Footer: opts.Footer,
} }
} }
@ -513,19 +477,20 @@ 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) bool { func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok 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) bool { func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
for _, u := range p.compiledRegex { for _, u := range p.compiledRegex {
if u.MatchString(path) { ok = u.MatchString(path)
return true if ok {
return
} }
} }
return false return
} }
func getRemoteAddr(req *http.Request) (s string) { func getRemoteAddr(req *http.Request) (s string) {
@ -590,7 +555,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 := encryption.Nonce() nonce, err := cookie.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())
@ -669,89 +634,57 @@ 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.AuthFailure, "Invalid authentication via OAuth2: unauthorized") logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "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) {
session, err := p.getAuthenticatedSession(rw, req) status := p.Authenticate(rw, req)
if err != nil { if status == http.StatusAccepted {
http.Error(rw, "unauthorized request", http.StatusUnauthorized)
return
}
// we are authenticated
p.addHeadersForProxying(rw, req, session)
rw.WriteHeader(http.StatusAccepted) rw.WriteHeader(http.StatusAccepted)
} else {
http.Error(rw, "unauthorized request", http.StatusUnauthorized)
}
} }
// 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) {
session, err := p.getAuthenticatedSession(rw, req) status := p.Authenticate(rw, req)
switch err { if status == http.StatusInternalServerError {
case nil: p.ErrorPage(rw, http.StatusInternalServerError,
// we are authenticated "Internal Error", "Internal Error")
p.addHeadersForProxying(rw, req, session) } else if status == http.StatusForbidden {
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 {
default: p.ErrorJSON(rw, status)
// unknown error } else {
logger.Printf("Unexpected internal error: %s", err) p.serveMux.ServeHTTP(rw, req)
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
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) remoteAddr := getRemoteAddr(req)
if session == nil {
session, err = p.LoadCookiedSession(req) 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 && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
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) logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh)
saveSession = true saveSession = true
} }
if ok, err := p.provider.RefreshSessionIfNeeded(session); err != nil { var ok bool
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
@ -759,8 +692,6 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
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)
@ -778,20 +709,18 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
} }
} }
if session != nil && session.Email != "" { if session != nil && session.Email != "" && !p.Validator(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 nil, err return http.StatusInternalServerError
} }
} }
@ -807,78 +736,52 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
} }
if session == nil { if session == nil {
return nil, ErrNeedsLogin // Check if is an ajax request and return unauthorized to avoid a redirect
// to the login page
if p.isAjax(req) {
return http.StatusUnauthorized
}
return http.StatusForbidden
} }
return session, nil // At this point, the user is authenticated. proxy normally
}
// 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
@ -912,7 +815,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionStat
} }
// isAjax checks if a request is an ajax request // isAjax checks if a request is an ajax request
func isAjax(req *http.Request) bool { func (p *OAuthProxy) 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"]
@ -926,97 +829,8 @@ func isAjax(req *http.Request) bool {
return false return false
} }
// ErrorJSON returns the error code with an application/json mime type // ErrorJSON returns the error code witht 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
}

View File

@ -1,10 +1,8 @@
package main package main
import ( import (
"context"
"crypto" "crypto"
"encoding/base64" "encoding/base64"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@ -16,10 +14,9 @@ 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/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"
@ -122,7 +119,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, &Options{FlushInterval: time.Second}) proxyHandler := NewReverseProxy(proxyURL, time.Second)
setProxyUpstreamHostHeader(proxyHandler, proxyURL) setProxyUpstreamHostHeader(proxyHandler, proxyURL)
frontend := httptest.NewServer(proxyHandler) frontend := httptest.NewServer(proxyHandler)
defer frontend.Close() defer frontend.Close()
@ -144,7 +141,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, &Options{FlushInterval: time.Second}) proxyHandler := NewReverseProxy(b, time.Second)
setProxyDirector(proxyHandler) setProxyDirector(proxyHandler)
frontend := httptest.NewServer(proxyHandler) frontend := httptest.NewServer(proxyHandler)
defer frontend.Close() defer frontend.Close()
@ -163,9 +160,9 @@ func TestEncodedSlashes(t *testing.T) {
func TestRobotsTxt(t *testing.T) { func TestRobotsTxt(t *testing.T) {
opts := NewOptions() opts := NewOptions()
opts.ClientID = "asdlkjx" opts.ClientID = "bazquux"
opts.ClientSecret = "alkgks" opts.ClientSecret = "foobar"
opts.CookieSecret = "asdkugkj" opts.CookieSecret = "xyzzyplugh"
opts.Validate() opts.Validate()
proxy := NewOAuthProxy(opts, func(string) bool { return true }) proxy := NewOAuthProxy(opts, func(string) bool { return true })
@ -178,9 +175,9 @@ func TestRobotsTxt(t *testing.T) {
func TestIsValidRedirect(t *testing.T) { func TestIsValidRedirect(t *testing.T) {
opts := NewOptions() opts := NewOptions()
opts.ClientID = "skdlfj" opts.ClientID = "bazquux"
opts.ClientSecret = "fgkdsgj" opts.ClientSecret = "foobar"
opts.CookieSecret = "ljgiogbj" opts.CookieSecret = "xyzzyplugh"
// 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()
@ -231,7 +228,6 @@ 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 {
@ -256,9 +252,6 @@ 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
},
} }
} }
@ -270,13 +263,6 @@ 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)
@ -298,8 +284,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 = "dlgkj" opts.ClientID = "bazquux"
opts.ClientSecret = "alkgret" opts.ClientSecret = "foobar"
opts.CookieSecure = false opts.CookieSecure = false
opts.PassBasicAuth = true opts.PassBasicAuth = true
opts.PassUserHeaders = true opts.PassUserHeaders = true
@ -392,8 +378,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 = "slgkj" t.opts.ClientID = "bazquux"
t.opts.ClientSecret = "gfjgojl" t.opts.ClientSecret = "foobar"
t.opts.CookieSecure = false t.opts.CookieSecure = false
t.opts.PassAccessToken = opts.PassAccessToken t.opts.PassAccessToken = opts.PassAccessToken
t.opts.Validate() t.opts.Validate()
@ -518,9 +504,9 @@ func NewSignInPageTest(skipProvider bool) *SignInPageTest {
var sipTest SignInPageTest var sipTest SignInPageTest
sipTest.opts = NewOptions() sipTest.opts = NewOptions()
sipTest.opts.CookieSecret = "adklsj2" sipTest.opts.CookieSecret = "foobar"
sipTest.opts.ClientID = "lkdgj" sipTest.opts.ClientID = "bazquux"
sipTest.opts.ClientSecret = "sgiufgoi" sipTest.opts.ClientSecret = "xyzzyplugh"
sipTest.opts.SkipProviderButton = skipProvider sipTest.opts.SkipProviderButton = skipProvider
sipTest.opts.Validate() sipTest.opts.Validate()
@ -624,8 +610,8 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
for _, modifier := range modifiers { for _, modifier := range modifiers {
modifier(pcTest.opts) modifier(pcTest.opts)
} }
pcTest.opts.ClientID = "asdfljk" pcTest.opts.ClientID = "bazquux"
pcTest.opts.ClientSecret = "lkjfdsig" pcTest.opts.ClientSecret = "xyzzyplugh"
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.
@ -802,25 +788,6 @@ 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
@ -860,9 +827,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 = "aljsal" opts.ClientID = "bazquux"
opts.ClientSecret = "jglkfsdgj" opts.ClientSecret = "foobar"
opts.CookieSecret = "dkfjgdls" opts.CookieSecret = "xyzzyplugh"
opts.SkipAuthPreflight = true opts.SkipAuthPreflight = true
opts.Validate() opts.Validate()
@ -999,8 +966,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:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d" st.opts.SignatureKey = "sha1:foobar"
st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d") st.MakeRequestWithExpectedKey("GET", "", "foobar")
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")
} }
@ -1008,9 +975,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:d90df39e2d19282840252612dd7c81421a372f61" st.opts.SignatureKey = "sha1:foobar"
payload := `{ "hello": "world!" }` payload := `{ "hello": "world!" }`
st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61") st.MakeRequestWithExpectedKey("POST", payload, "foobar")
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")
} }
@ -1056,9 +1023,9 @@ type ajaxRequestTest struct {
func newAjaxRequestTest() *ajaxRequestTest { func newAjaxRequestTest() *ajaxRequestTest {
test := &ajaxRequestTest{} test := &ajaxRequestTest{}
test.opts = NewOptions() test.opts = NewOptions()
test.opts.CookieSecret = "sdflsw" test.opts.CookieSecret = "foobar"
test.opts.ClientID = "gkljfdl" test.opts.ClientID = "bazquux"
test.opts.ClientSecret = "sdflkjs" test.opts.ClientSecret = "xyzzyplugh"
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
@ -1165,173 +1132,3 @@ 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)
}

View File

@ -17,10 +17,10 @@ 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/cookie"
"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" 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/pkg/sessions"
"github.com/pusher/oauth2_proxy/providers" "github.com/pusher/oauth2_proxy/providers"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
@ -29,34 +29,28 @@ 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-file" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"` TLSCertFile string `flag:"tls-cert" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"`
TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"` TLSKeyFile string `flag:"tls-key" 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
@ -67,8 +61,6 @@ 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"`
@ -76,7 +68,6 @@ 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"`
@ -87,9 +78,8 @@ 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"`
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"` SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"`
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_OIDC_JWKS_URL"`
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"`
@ -99,20 +89,19 @@ 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_PROXY_LOGGING_FILENAME"` LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"`
LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE"` LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_LOGGING_MAX_SIZE"`
LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE"` LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_LOGGING_MAX_AGE"`
LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS"` LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_LOGGING_MAX_BACKUPS"`
LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME"` LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_LOGGING_LOCAL_TIME"`
LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS"` LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_LOGGING_COMPRESS"`
StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING"` StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_STANDARD_LOGGING"`
StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"` StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_STANDARD_LOGGING_FORMAT"`
RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"` RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_REQUEST_LOGGING"`
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"` RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_REQUEST_LOGGING_FORMAT"`
ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS"` AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_LOGGING_AUTH_LOGGING"`
SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"` AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_AUTH_LOGGING_FORMAT"`
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"`
@ -128,7 +117,6 @@ type Options struct {
sessionStore sessionsapi.SessionStore 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
@ -141,7 +129,6 @@ 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",
@ -165,7 +152,6 @@ 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,
@ -173,8 +159,6 @@ 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,
@ -184,12 +168,6 @@ 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 {
@ -266,25 +244,6 @@ 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 {
@ -309,7 +268,7 @@ func (o *Options) Validate() error {
} }
msgs = parseProviderInfo(o, msgs) msgs = parseProviderInfo(o, msgs)
var cipher *encryption.Cipher var cipher *cookie.Cipher
if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.CookieRefresh != time.Duration(0)) { 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} {
@ -334,7 +293,7 @@ func (o *Options) Validate() error {
len(secretBytes(o.CookieSecret)), suffix)) len(secretBytes(o.CookieSecret)), suffix))
} else { } else {
var err error var err error
cipher, err = encryption.NewCipher(secretBytes(o.CookieSecret)) cipher, err = cookie.NewCipher(secretBytes(o.CookieSecret))
if err != nil { if err != nil {
msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err)) msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
} }
@ -399,8 +358,6 @@ 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)
@ -410,39 +367,12 @@ 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)
@ -496,49 +426,10 @@ 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: hash, key: secretKey} o.signatureData = &SignatureData{hash, 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() == "" {
@ -609,14 +500,6 @@ 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)
} }

View File

@ -1,11 +1,13 @@
package options package options
import "github.com/pusher/oauth2_proxy/pkg/encryption" import (
"github.com/pusher/oauth2_proxy/cookie"
)
// 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 Cipher *cookie.Cipher
CookieStoreOptions CookieStoreOptions
RedisStoreOptions RedisStoreOptions
} }

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/pusher/oauth2_proxy/pkg/encryption" "github.com/pusher/oauth2_proxy/cookie"
) )
// SessionState is used to store information about the currently authenticated user session // SessionState is used to store information about the currently authenticated user session
@ -66,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 *encryption.Cipher) (string, error) { func (s *SessionState) EncodeSessionState(c *cookie.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
@ -133,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 *encryption.Cipher) (*SessionState, error) { func legacyDecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) {
chunks := strings.Split(v, "|") chunks := strings.Split(v, "|")
if c == nil { if c == nil {
@ -176,7 +176,7 @@ func legacyDecodeSessionState(v string, c *encryption.Cipher) (*SessionState, er
} }
// DecodeSessionState decodes the session cookie string into a SessionState // DecodeSessionState decodes the session cookie string into a SessionState
func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { func DecodeSessionState(v string, c *cookie.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)

View File

@ -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,9 +14,9 @@ const secret = "0123456789abcdefghijklmnopqrstuv"
const altSecret = "0000000000abcdefghijklmnopqrstuv" const altSecret = "0000000000abcdefghijklmnopqrstuv"
func TestSessionStateSerialization(t *testing.T) { func TestSessionStateSerialization(t *testing.T) {
c, err := encryption.NewCipher([]byte(secret)) c, err := cookie.NewCipher([]byte(secret))
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
c2, err := encryption.NewCipher([]byte(altSecret)) c2, err := cookie.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",
@ -54,9 +54,9 @@ func TestSessionStateSerialization(t *testing.T) {
} }
func TestSessionStateSerializationWithUser(t *testing.T) { func TestSessionStateSerializationWithUser(t *testing.T) {
c, err := encryption.NewCipher([]byte(secret)) c, err := cookie.NewCipher([]byte(secret))
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
c2, err := encryption.NewCipher([]byte(altSecret)) c2, err := cookie.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",
@ -146,7 +146,7 @@ func TestExpired(t *testing.T) {
type testCase struct { type testCase struct {
sessions.SessionState sessions.SessionState
Encoded string Encoded string
Cipher *encryption.Cipher Cipher *cookie.Cipher
Error bool Error bool
} }
@ -203,7 +203,7 @@ func TestDecodeSessionState(t *testing.T) {
eString := string(eJSON) eString := string(eJSON)
eUnix := e.Unix() eUnix := e.Unix()
c, err := encryption.NewCipher([]byte(secret)) c, err := cookie.NewCipher([]byte(secret))
assert.NoError(t, err) assert.NoError(t, err)
testCases := []testCase{ testCases := []testCase{

View File

@ -6,8 +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/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,

View File

@ -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"
) )
@ -28,7 +28,7 @@ var _ sessions.SessionStore = &SessionStore{}
// interface that stores sessions in client side cookies // interface that stores sessions in client side cookies
type SessionStore struct { type SessionStore struct {
CookieOptions *options.CookieOptions CookieOptions *options.CookieOptions
CookieCipher *encryption.Cipher CookieCipher *cookie.Cipher
} }
// Save takes a sessions.SessionState and stores the information from it // Save takes a sessions.SessionState and stores the information from it
@ -53,7 +53,7 @@ func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
// always http.ErrNoCookie // always http.ErrNoCookie
return nil, fmt.Errorf("Cookie %q not present", s.CookieOptions.CookieName) return nil, fmt.Errorf("Cookie %q not present", s.CookieOptions.CookieName)
} }
val, _, ok := encryption.Validate(c, s.CookieOptions.CookieSecret, s.CookieOptions.CookieExpire) val, _, ok := cookie.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")
} }
@ -96,7 +96,7 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques
// authentication details // authentication details
func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie { func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie {
if value != "" { if value != "" {
value = encryption.SignedValue(s.CookieOptions.CookieSecret, s.CookieOptions.CookieName, value, now) value = cookie.SignedValue(s.CookieOptions.CookieSecret, s.CookieOptions.CookieName, value, now)
} }
c := s.makeCookie(req, s.CookieOptions.CookieName, value, s.CookieOptions.CookieExpire, now) c := s.makeCookie(req, s.CookieOptions.CookieName, value, s.CookieOptions.CookieExpire, now)
if len(c.Value) > 4096-len(s.CookieOptions.CookieName) { if len(c.Value) > 4096-len(s.CookieOptions.CookieName) {

View File

@ -13,10 +13,10 @@ import (
"time" "time"
"github.com/go-redis/redis" "github.com/go-redis/redis"
"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"
) )
// TicketData is a structure representing the ticket used in server session storage // TicketData is a structure representing the ticket used in server session storage
@ -28,7 +28,7 @@ type TicketData struct {
// SessionStore is an implementation of the sessions.SessionStore // SessionStore is an implementation of the sessions.SessionStore
// interface that stores sessions in redis // interface that stores sessions in redis
type SessionStore struct { type SessionStore struct {
CookieCipher *encryption.Cipher CookieCipher *cookie.Cipher
CookieOptions *options.CookieOptions CookieOptions *options.CookieOptions
Client *redis.Client Client *redis.Client
} }
@ -106,7 +106,7 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro
return nil, fmt.Errorf("error loading session: %s", err) return nil, fmt.Errorf("error loading session: %s", err)
} }
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
if !ok { if !ok {
return nil, fmt.Errorf("Cookie Signature not valid") return nil, fmt.Errorf("Cookie Signature not valid")
} }
@ -166,7 +166,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
return fmt.Errorf("error retrieving cookie: %v", err) return fmt.Errorf("error retrieving cookie: %v", err)
} }
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
if !ok { if !ok {
return fmt.Errorf("Cookie Signature not valid") return fmt.Errorf("Cookie Signature not valid")
} }
@ -186,7 +186,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
// makeCookie makes a cookie, signing the value if present // 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 { func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
if value != "" { if value != "" {
value = encryption.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now) value = cookie.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now)
} }
return cookies.MakeCookieFromOptions( return cookies.MakeCookieFromOptions(
req, req,
@ -230,7 +230,7 @@ func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, e
} }
// An existing cookie exists, try to retrieve the ticket // An existing cookie exists, try to retrieve the ticket
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
if !ok { if !ok {
// Cookie is invalid, create a new ticket // Cookie is invalid, create a new ticket
return newTicket() return newTicket()

View File

@ -13,10 +13,10 @@ import (
"github.com/alicebob/miniredis" "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/cookie"
"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"
sessionscookie "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/redis"
@ -158,7 +158,7 @@ var _ = Describe("NewSessionStore", func() {
BeforeEach(func() { BeforeEach(func() {
By("Using a valid cookie with a different providers session encoding") By("Using a valid cookie with a different providers session encoding")
broken := "BrokenSessionFromADifferentSessionImplementation" broken := "BrokenSessionFromADifferentSessionImplementation"
value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now()) value := cookie.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now()) cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
request.AddCookie(cookie) request.AddCookie(cookie)
@ -354,7 +354,7 @@ var _ = Describe("NewSessionStore", func() {
_, 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)) cipher, err := cookie.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret))
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(cipher).ToNot(BeNil()) Expect(cipher).ToNot(BeNil())
opts.Cipher = cipher opts.Cipher = cipher

View File

@ -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 *encryption.Cipher) (string, error) { func CookieForSession(s *sessions.SessionState, c *cookie.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 *encryption.Cipher) (s *sessions.SessionState, err error) { func SessionFromCookie(v string, c *cookie.Cipher) (s *sessions.SessionState, err error) {
return sessions.DecodeSessionState(v, c) return sessions.DecodeSessionState(v, c)
} }

View File

@ -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 := requests.Request(req) json, err := api.Request(req)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -1,163 +0,0 @@
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
}

View File

@ -1,170 +0,0 @@
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)
}

View File

@ -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 = requests.RequestJSON(req, &r) err = api.RequestJSON(req, &r)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -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

View File

@ -1,258 +1,62 @@
package providers package providers
import ( import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http" "net/http"
"strings" "net/url"
"time"
oidc "github.com/coreos/go-oidc" "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"
"golang.org/x/oauth2"
) )
// GitLabProvider represents a GitLab based Identity Provider // GitLabProvider represents an 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 = "openid email" p.Scope = "read_user"
} }
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) req, err := http.NewRequest("GET",
p.ValidateURL.String()+"?access_token="+s.AccessToken, nil)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to retrieve user info: %v", err) logger.Printf("failed building request %s", err)
return "", err
} }
json, err := api.Request(req)
// Check if email is verified
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 {
return "", fmt.Errorf("email domain check failed: %v", err) logger.Printf("failed making request %s", err)
return "", err
} }
return json.Get("email").String()
// Check group membership
err = p.verifyGroupMembership(userInfo)
if err != nil {
return "", fmt.Errorf("group membership check failed: %v", err)
}
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
} }

View File

@ -25,142 +25,104 @@ 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() *httptest.Server { func testGitLabBackend(payload string) *httptest.Server {
userInfo := ` path := "/api/v4/user"
{ 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 == "/oauth/userinfo" { if r.URL.Path != path || r.URL.RawQuery != query {
if r.Header["Authorization"][0] == authHeader {
w.WriteHeader(200)
w.Write([]byte(userInfo))
} else {
w.WriteHeader(401)
}
} else {
w.WriteHeader(404) w.WriteHeader(404)
} else {
w.WriteHeader(200)
w.Write([]byte(payload))
} }
})) }))
} }
func TestGitLabProviderBadToken(t *testing.T) { func TestGitLabProviderDefaults(t *testing.T) {
b := testGitLabBackend() p := testGitLabProvider("")
defer b.Close() assert.NotEqual(t, nil, p)
assert.Equal(t, "GitLab", p.Data().ProviderName)
bURL, _ := url.Parse(b.URL) assert.Equal(t, "https://gitlab.com/oauth/authorize",
p := testGitLabProvider(bURL.Host) p.Data().LoginURL.String())
assert.Equal(t, "https://gitlab.com/oauth/token",
session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"} p.Data().RedeemURL.String())
_, err := p.GetEmailAddress(session) assert.Equal(t, "https://gitlab.com/api/v4/user",
assert.NotEqual(t, nil, err) p.Data().ValidateURL.String())
assert.Equal(t, "read_user", p.Data().Scope)
} }
func TestGitLabProviderUnverifiedEmailDenied(t *testing.T) { func TestGitLabProviderOverrides(t *testing.T) {
b := testGitLabBackend() p := NewGitLabProvider(
defer b.Close() &ProviderData{
LoginURL: &url.URL{
bURL, _ := url.Parse(b.URL) Scheme: "https",
p := testGitLabProvider(bURL.Host) Host: "example.com",
Path: "/oauth/auth"},
session := &sessions.SessionState{AccessToken: "gitlab_access_token"} RedeemURL: &url.URL{
_, err := p.GetEmailAddress(session) Scheme: "https",
assert.NotEqual(t, nil, err) 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 TestGitLabProviderUnverifiedEmailAllowed(t *testing.T) { func TestGitLabProviderGetEmailAddress(t *testing.T) {
b := testGitLabBackend() 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)
p.AllowUnverifiedEmail = true
session := &sessions.SessionState{AccessToken: "gitlab_access_token"} session := &sessions.SessionState{AccessToken: "imaginary_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, "foo@bar.com", email) assert.Equal(t, "michael.bland@gsa.gov", email)
} }
func TestGitLabProviderUsername(t *testing.T) { // Note that trying to trigger the "failed building request" case is not
b := testGitLabBackend() // practical, since the only way it can fail is if the URL fails to parse.
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
session := &sessions.SessionState{AccessToken: "gitlab_access_token"} // We'll trigger a request failure by using an unexpected access
username, err := p.GetUserName(session) // token. Alternatively, we could allow the parsing of the payload as
assert.Equal(t, nil, err) // JSON to fail.
assert.Equal(t, "FooBar", username) session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
}
func TestGitLabProviderGroupMembershipValid(t *testing.T) {
b := testGitLabBackend()
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitLabProvider(bURL.Host)
p.AllowUnverifiedEmail = true
p.Group = "foo"
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, "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) assert.NotEqual(t, nil, err)
assert.Equal(t, "", email)
} }
func TestGitLabProviderEmailDomainValid(t *testing.T) { func TestGitLabProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
b := testGitLabBackend() b := testGitLabBackend("{\"foo\": \"bar\"}")
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.EmailDomains = []string{"bar.com"}
session := &sessions.SessionState{AccessToken: "gitlab_access_token"} session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
email, err := p.GetEmailAddress(session) 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) assert.NotEqual(t, nil, err)
assert.Equal(t, "", email)
} }

View File

@ -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"
@ -189,44 +189,69 @@ 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 {
// Use the HasMember API to checking for the user's presence in each group or nested subgroups members, err := fetchGroupMembers(service, group)
req := service.Members.HasMember(group, email)
r, err := req.Do()
if err != nil { if err != nil {
err, ok := err.(*googleapi.Error) if err, ok := err.(*googleapi.Error); ok && err.Code == 404 {
if ok && err.Code == 404 { logger.Printf("error fetching members for group %s: group does not exist", group)
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
}
// 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
}
} else { } else {
logger.Printf("error checking group membership: %v", err) logger.Printf("error fetching group members: %v", err)
return false
} }
continue
} }
if r.IsMember {
for _, member := range members {
switch member.Type {
case "CUSTOMER":
if member.Id == custID {
return true return true
} }
case "USER":
if member.Id == id {
return true
}
}
}
} }
return false 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()
if err != nil {
return nil, err
}
for _, member := range r.Members {
members = append(members, member)
}
if r.NextPageToken == "" {
break
}
pageToken = r.NextPageToken
}
return members, nil
}
// ValidateGroup validates that the provided email exists in the configured Google // ValidateGroup validates that the provided email exists in the configured Google
// group(s). // group(s).
func (p *GoogleProvider) ValidateGroup(email string) bool { func (p *GoogleProvider) ValidateGroup(email string) bool {

View File

@ -1,19 +1,14 @@
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) {
@ -184,56 +179,3 @@ 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)
}

View File

@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/api"
"github.com/pusher/oauth2_proxy/pkg/requests" "github.com/pusher/oauth2_proxy/logger"
) )
// 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 || p.Data().ValidateURL.String() == "" { if accessToken == "" || p.Data().ValidateURL == nil {
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 := requests.RequestUnparsedResponse(endpoint, header) resp, err := api.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)

View File

@ -1,86 +0,0 @@
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()
}

View File

@ -1,151 +0,0 @@
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)
}

View File

@ -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 := requests.Request(req) json, err := api.Request(req)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -3,13 +3,10 @@ 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"
) )
@ -18,7 +15,6 @@ type OIDCProvider struct {
*ProviderData *ProviderData
Verifier *oidc.IDTokenVerifier Verifier *oidc.IDTokenVerifier
AllowUnverifiedEmail bool
} }
// NewOIDCProvider initiates a new OIDCProvider // NewOIDCProvider initiates a new OIDCProvider
@ -120,33 +116,10 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
} }
if claims.Email == "" { if claims.Email == "" {
if p.ProfileURL.String() == "" { // TODO: Try getting email from /userinfo before falling back to Subject
return nil, fmt.Errorf("id_token did not contain an email") claims.Email = claims.Subject
} }
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)
} }
@ -155,7 +128,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
IDToken: rawIDToken, IDToken: rawIDToken,
RefreshToken: token.RefreshToken, RefreshToken: token.RefreshToken,
CreatedAt: time.Now(), CreatedAt: time.Now(),
ExpiresOn: idToken.Expiry, ExpiresOn: token.Expiry,
Email: claims.Email, Email: claims.Email,
User: claims.Subject, User: claims.Subject,
}, nil }, nil
@ -171,10 +144,3 @@ 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
}

View File

@ -10,8 +10,8 @@ import (
"net/url" "net/url"
"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"
) )
// Redeem provides a default implementation of the OAuth2 token redemption process // Redeem provides a default implementation of the OAuth2 token redemption process
@ -96,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 *encryption.Cipher) (string, error) { func (p *ProviderData) CookieForSession(s *sessions.SessionState, c *cookie.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 *encryption.Cipher) (s *sessions.SessionState, err error) { func (p *ProviderData) SessionFromCookie(v string, c *cookie.Cipher) (s *sessions.SessionState, err error) {
return sessions.DecodeSessionState(v, c) return sessions.DecodeSessionState(v, c)
} }

View File

@ -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, *encryption.Cipher) (*sessions.SessionState, error) SessionFromCookie(string, *cookie.Cipher) (*sessions.SessionState, error)
CookieForSession(*sessions.SessionState, *encryption.Cipher) (string, error) CookieForSession(*sessions.SessionState, *cookie.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,8 +28,6 @@ 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":
@ -38,8 +36,6 @@ 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)
} }

View File

@ -4,7 +4,7 @@ import (
"html/template" "html/template"
"path" "path"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
) )
func loadTemplates(dir string) *template.Template { func loadTemplates(dir string) *template.Template {

View File

@ -8,7 +8,7 @@ import (
"sync/atomic" "sync/atomic"
"unsafe" "unsafe"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
) )
// UserMap holds information from the authenticated emails file // UserMap holds information from the authenticated emails file

View File

@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/logger"
fsnotify "gopkg.in/fsnotify/fsnotify.v1" fsnotify "gopkg.in/fsnotify/fsnotify.v1"
) )

View File

@ -2,7 +2,7 @@
package main package main
import "github.com/pusher/oauth2_proxy/pkg/logger" import "github.com/pusher/oauth2_proxy/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")