From 3f2fab10e62b13f804d6a54af72539a95fcf1885 Mon Sep 17 00:00:00 2001 From: Benjamin Chess Date: Tue, 16 Apr 2019 14:57:09 -0700 Subject: [PATCH 001/128] check google group based on email address --- CHANGELOG.md | 3 +++ providers/google.go | 14 +++++++++----- providers/google_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f892f6b..9da760b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ - [#111](https://github.com/pusher/oauth2_proxy/pull/111) Add option for telling where to find a login.gov JWT key file (@timothy-spencer) +- [#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. + # v3.2.0 ## Release highlights diff --git a/providers/google.go b/providers/google.go index f031bfc..7b8815f 100644 --- a/providers/google.go +++ b/providers/google.go @@ -187,10 +187,8 @@ 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 + user = nil } - id := user.Id - custID := user.CustomerId for _, group := range groups { members, err := fetchGroupMembers(service, group) @@ -204,13 +202,19 @@ func userInGroup(service *admin.Service, groups []string, email string) bool { } for _, member := range members { + if member.Email == email { + return true + } + if user == nil { + continue + } switch member.Type { case "CUSTOMER": - if member.Id == custID { + if member.Id == user.CustomerId { return true } case "USER": - if member.Id == id { + if member.Id == user.Id { return true } } diff --git a/providers/google_test.go b/providers/google_test.go index 25b375a..0c1725b 100644 --- a/providers/google_test.go +++ b/providers/google_test.go @@ -3,12 +3,15 @@ package providers import ( "encoding/base64" "encoding/json" + "fmt" "net/http" "net/http/httptest" "net/url" "testing" "github.com/stretchr/testify/assert" + + admin "google.golang.org/api/admin/directory/v1" ) func newRedeemServer(body []byte) (*url.URL, *httptest.Server) { @@ -179,3 +182,37 @@ 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 == "/users/member-by-email@example.com" { + fmt.Fprintln(w, "{}") + } else if r.URL.Path == "/users/non-member-by-email@example.com" { + fmt.Fprintln(w, "{}") + } else if r.URL.Path == "/users/member-by-id@example.com" { + fmt.Fprintln(w, "{\"id\": \"member-id\"}") + } else if r.URL.Path == "/users/non-member-by-id@example.com" { + fmt.Fprintln(w, "{\"id\": \"non-member-id\"}") + } else if r.URL.Path == "/groups/group@example.com/members" { + fmt.Fprintln(w, "{\"members\": [{\"email\": \"member-by-email@example.com\"}, {\"id\": \"member-id\", \"type\": \"USER\"}]}") + } + })) + defer ts.Close() + + client := ts.Client() + service, err := admin.New(client) + service.BasePath = ts.URL + assert.Equal(t, nil, err) + + result := userInGroup(service, []string{"group@example.com"}, "member-by-email@example.com") + assert.True(t, result) + + result = userInGroup(service, []string{"group@example.com"}, "member-by-id@example.com") + assert.True(t, result) + + result = userInGroup(service, []string{"group@example.com"}, "non-member-by-id@example.com") + assert.False(t, result) + + result = userInGroup(service, []string{"group@example.com"}, "non-member-by-email@example.com") + assert.False(t, result) +} From 7179c5796aca5798f90e6962fedcd9b3c6577cee Mon Sep 17 00:00:00 2001 From: Benjamin Chess Date: Wed, 8 May 2019 08:29:38 -0700 Subject: [PATCH 002/128] make unable to fetch user a warning message --- providers/google.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/google.go b/providers/google.go index 7b8815f..02fd35f 100644 --- a/providers/google.go +++ b/providers/google.go @@ -186,7 +186,7 @@ func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Serv 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) + logger.Printf("Warning: unable to fetch user: %v", err) user = nil } From 076484297e3550a953787816ff0189b0973ed728 Mon Sep 17 00:00:00 2001 From: Chris Hofstaedtler Date: Fri, 31 May 2019 10:24:45 +0200 Subject: [PATCH 003/128] Make release tarballs look like bitly's Fixes #162 --- CHANGELOG.md | 5 +++++ Makefile | 40 +++++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b34f7b..ba12df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ 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 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 @@ -35,6 +39,7 @@ - `-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) +- [#170](https://github.com/pusher/oauth2_proxy/pull/170) Restore binary tarball contents to be compatible with bitlys original tarballs (@zeha) # v3.2.0 diff --git a/Makefile b/Makefile index 0dca566..0ff4260 100644 --- a/Makefile +++ b/Makefile @@ -81,18 +81,28 @@ test: dep lint .PHONY: release release: lint test mkdir release - GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-darwin-amd64 github.com/pusher/oauth2_proxy - GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-linux-amd64 github.com/pusher/oauth2_proxy - GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-linux-arm64 github.com/pusher/oauth2_proxy - GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-linux-armv6 github.com/pusher/oauth2_proxy - GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-windows-amd64 github.com/pusher/oauth2_proxy - shasum -a 256 release/$(BINARY)-darwin-amd64 > release/$(BINARY)-darwin-amd64-sha256sum.txt - shasum -a 256 release/$(BINARY)-linux-amd64 > release/$(BINARY)-linux-amd64-sha256sum.txt - shasum -a 256 release/$(BINARY)-linux-arm64 > release/$(BINARY)-linux-arm64-sha256sum.txt - shasum -a 256 release/$(BINARY)-linux-armv6 > release/$(BINARY)-linux-armv6-sha256sum.txt - shasum -a 256 release/$(BINARY)-windows-amd64 > release/$(BINARY)-windows-amd64-sha256sum.txt - tar -czvf release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-darwin-amd64 - tar -czvf release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-linux-amd64 - tar -czvf release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION).tar.gz release/$(BINARY)-linux-arm64 - tar -czvf release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION).tar.gz release/$(BINARY)-linux-armv6 - tar -czvf release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION).tar.gz release/$(BINARY)-windows-amd64 + mkdir release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION) + mkdir release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION) + mkdir release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION) + mkdir release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION) + mkdir release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION) + GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ + -o release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy + GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ + -o release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy + GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" \ + -o release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy + GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" \ + -o release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy + GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ + -o release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy + shasum -a 256 release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).darwin-amd64-sha256sum.txt + shasum -a 256 release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-amd64-sha256sum.txt + shasum -a 256 release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-arm64-sha256sum.txt + shasum -a 256 release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-armv6-sha256sum.txt + shasum -a 256 release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).windows-amd64-sha256sum.txt + tar -C release -czvf release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION) + tar -C release -czvf release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION) + tar -C release -czvf release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION) + tar -C release -czvf release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION) + tar -C release -czvf release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION) From b05eb71adf77e3e2c879316316bd405587997e85 Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Mon, 3 Jun 2019 17:25:48 +1000 Subject: [PATCH 004/128] Stop assuming that GOPATH is always set, and is a single directory As of I think go1.8 GOPATH is by default $HOME/go so it is incorrect to assume that it is set. If not set, then the Makefile assumes gometalinter will be in /bin/gometalinter, which it likely is not, and thus fails. We could change configure to set GOPATH in the .env, however then we would be assuming that GOPATH is a single entry - whereas like other paths, it can contain more than one value. So instead this commit stops trying to install gometalinter, and like dep, it assumes that it is installed prior. (and since the current behaviour of the Makefile is affecting state external to the project, that seems more logical) --- Makefile | 9 +-------- configure | 2 ++ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 0dca566..09ff1bb 100644 --- a/Makefile +++ b/Makefile @@ -15,15 +15,8 @@ clean: distclean: clean 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 -lint: $(GOMETALINTER) +lint: $(GOMETALINTER) --vendor --disable-all \ --enable=vet \ --enable=vetshadow \ diff --git a/configure b/configure index 24790ab..9d93215 100755 --- a/configure +++ b/configure @@ -126,6 +126,7 @@ check_for go check_go_version check_go_env check_for dep +check_for gometalinter echo @@ -134,6 +135,7 @@ cat <<- EOF > .env GO := "${tools[go]}" GO_VERSION := ${tools[go_version]} DEP := "${tools[dep]}" + GOMETALINTER := "${tools[gometalinter]}" EOF echo "Environment configuration written to .env" From e7d29590cd718e68fb2397c0416f0e7eabe884da Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Mon, 3 Jun 2019 17:47:51 +1000 Subject: [PATCH 005/128] Fix travis so that if "configure" fails, it doesn't try to run make --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5cdc33..ac0f582 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,7 @@ install: - chmod +x dep - mv dep $GOPATH/bin/dep script: - - ./configure - # Run tests - - make test + - ./configure && make test sudo: false notifications: email: false From 37475637cdd632405d42776a4820208badfa641f Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Mon, 3 Jun 2019 17:50:22 +1000 Subject: [PATCH 006/128] Install gometalinter in travis instead --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ac0f582..fcdfbff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ install: - wget -O dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 - chmod +x dep - mv dep $GOPATH/bin/dep + - go get github.com/alecthomas/gometalinter + - gometalinter --install script: - ./configure && make test sudo: false From 29fb71fac54a8fed30036d164c15483e43d739be Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 30 May 2019 14:56:22 +0100 Subject: [PATCH 007/128] Drop Go 1.11 from Travis CI --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c5cdc33..d3ea1a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: go go: - - 1.11.x - 1.12.x install: # Fetch dependencies From f0b6f1525bdd7b6eaa6c31afe09ab3b7c290008b Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 30 May 2019 14:58:22 +0100 Subject: [PATCH 008/128] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 529ab4d..445c3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## Changes since v3.2.0 +- [#168](https://github.com/pusher/outh2_proxy/pull/168) Drop Go 1.11 support in Travis (@JoelSpeed) - [#169](https://github.com/pusher/outh2_proxy/pull/169) Update Alpine to 3.9 (@kskewes) - [#148](https://github.com/pusher/outh2_proxy/pull/148) Implement SessionStore interface within proxy (@JoelSpeed) - [#147](https://github.com/pusher/outh2_proxy/pull/147) Add SessionStore interfaces and initial implementation (@JoelSpeed) From 006322562dc55579bb9b36cc92cb72a31775fbd7 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Mon, 3 Jun 2019 14:58:39 +0100 Subject: [PATCH 009/128] Bump go version in configure to check for go 1.12 --- configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 24790ab..fa80a9f 100755 --- a/configure +++ b/configure @@ -81,7 +81,7 @@ check_for() { check_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)}') - vercomp $GO_VERSION 1.11 + vercomp $GO_VERSION 1.12 case $? in 0) ;& 1) @@ -91,7 +91,7 @@ check_go_version() { ;; 2) printf "${RED}" - echo "$GO_VERSION < 1.11" + echo "$GO_VERSION < 1.12" exit 1 ;; esac From b6c60f52ee77f07990442887a1c1cd5fff9e3b16 Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Mon, 3 Jun 2019 14:54:54 +1000 Subject: [PATCH 010/128] Bump go-oidc --- CHANGELOG.md | 2 ++ Gopkg.lock | 6 +++--- Gopkg.toml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3c69b..7b4e7a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ ## Changes since v3.2.0 +- [#175](https://github.com/pusher/outh2_proxy/pull/175) Bump go-oidc to v2.0.0 (@aeijdenberg). + - Includes fix for potential signature checking issue when OIDC discovery is skipped. - [#168](https://github.com/pusher/outh2_proxy/pull/168) Drop Go 1.11 support in Travis (@JoelSpeed) - [#169](https://github.com/pusher/outh2_proxy/pull/169) Update Alpine to 3.9 (@kskewes) - [#148](https://github.com/pusher/outh2_proxy/pull/148) Implement SessionStore interface within proxy (@JoelSpeed) diff --git a/Gopkg.lock b/Gopkg.lock index 01af4d2..ffbd76a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -26,12 +26,12 @@ version = "v0.5.0" [[projects]] - branch = "v2" - digest = "1:e5a238f8fa890e529d7e493849bbae8988c9e70344e4630cc4f9a11b00516afb" + digest = "1:d8ee1b165eb7f4fd9ada718e1e7eeb0bc1fd462592d0bd823df694443f448681" name = "github.com/coreos/go-oidc" packages = ["."] pruneopts = "" - revision = "77e7f2010a464ade7338597afe650dfcffbe2ca8" + revision = "1180514eaf4d9f38d0d19eef639a1d695e066e72" + version = "v2.0.0" [[projects]] digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b" diff --git a/Gopkg.toml b/Gopkg.toml index 732bbcb..cfb10a7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -12,7 +12,7 @@ version = "~0.5.0" [[constraint]] - branch = "v2" + version = "^2" name = "github.com/coreos/go-oidc" [[constraint]] From e881612ea622dfed39265796076973ae72acfe9b Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 8 May 2019 12:35:15 -0700 Subject: [PATCH 011/128] Fix session_state type --- pkg/apis/sessions/session_state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/apis/sessions/session_state.go b/pkg/apis/sessions/session_state.go index e085976..01789ff 100644 --- a/pkg/apis/sessions/session_state.go +++ b/pkg/apis/sessions/session_state.go @@ -203,14 +203,14 @@ func DecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) { User: ss.User, } } else { - // Backward compatibility with using unecrypted Email + // Backward compatibility with using unencrypted Email if ss.Email != "" { decryptedEmail, errEmail := c.Decrypt(ss.Email) if errEmail == nil { ss.Email = decryptedEmail } } - // Backward compatibility with using unecrypted User + // Backward compatibility with using unencrypted User if ss.User != "" { decryptedUser, errUser := c.Decrypt(ss.User) if errUser == nil { From b1bd3280dbf080cde82e1c2e832125bc21e05494 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 9 May 2019 16:09:22 -0700 Subject: [PATCH 012/128] Add support for a redis session store --- Gopkg.lock | 61 +++++++ Gopkg.toml | 8 + pkg/apis/options/sessions.go | 10 ++ pkg/sessions/redis/redis_store.go | 271 +++++++++++++++++++++++++++++ pkg/sessions/session_store.go | 3 + pkg/sessions/session_store_test.go | 88 ++++++++-- 6 files changed, 427 insertions(+), 14 deletions(-) create mode 100644 pkg/sessions/redis/redis_store.go diff --git a/Gopkg.lock b/Gopkg.lock index 01af4d2..e8bb95e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -17,6 +17,25 @@ 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" @@ -49,6 +68,22 @@ 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" @@ -57,6 +92,17 @@ 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" @@ -173,6 +219,19 @@ 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" @@ -341,9 +400,11 @@ 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", diff --git a/Gopkg.toml b/Gopkg.toml index 732bbcb..7c90984 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -46,3 +46,11 @@ [[constraint]] name = "gopkg.in/natefinch/lumberjack.v2" version = "2.1.0" + +[[constraint]] + branch = "master" + name = "github.com/go-redis/redis" + +[[constraint]] + name = "github.com/alicebob/miniredis" + version = "2.7.0" diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 7d33f14..591cc37 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -9,6 +9,7 @@ type SessionOptions struct { Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"` Cipher *cookie.Cipher CookieStoreOptions + RedisStoreOptions } // CookieSessionStoreType is used to indicate the CookieSessionStore should be @@ -17,3 +18,12 @@ var CookieSessionStoreType = "cookie" // CookieStoreOptions contains configuration options for the CookieSessionStore. type CookieStoreOptions struct{} + +// RedisSessionStoreType is used to indicate the CookieSessionStore should be +// used for storing sessions. +var RedisSessionStoreType = "redis" + +// RedisStoreOptions contains configuration options for the CookieSessionStore. +type RedisStoreOptions struct { + RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"` +} diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go new file mode 100644 index 0000000..74cb1c0 --- /dev/null +++ b/pkg/sessions/redis/redis_store.go @@ -0,0 +1,271 @@ +package redis + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/go-redis/redis" + "github.com/pusher/oauth2_proxy/cookie" + "github.com/pusher/oauth2_proxy/pkg/apis/options" + "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/cookies" + "github.com/pusher/oauth2_proxy/pkg/sessions/utils" +) + +// TicketData is a structure representing a used in server session storage +type TicketData struct { + TicketID string + Secret []byte +} + +// SessionStore is an implementation of the sessions.SessionStore +// interface that stores sessions in redis +type SessionStore struct { + CookieCipher *cookie.Cipher + CookieDomain string + CookieExpire time.Duration + CookieHTTPOnly bool + CookieName string + CookiePath string + CookieSecret string + CookieSecure bool + Client *redis.Client +} + +// NewRedisSessionStore initialises a new instance of the SessionStore from +// the configuration given +func NewRedisSessionStore(opts options.RedisStoreOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) { + opt, err := redis.ParseURL(opts.RedisConnectionURL) + if err != nil { + return nil, fmt.Errorf("unable to parse redis url: %s", err) + } + + var cookieCipher *cookie.Cipher + if len(cookieOpts.CookieSecret) > 0 { + var err error + cookieCipher, err = cookie.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret)) + if err != nil { + return nil, fmt.Errorf("unable to create cookieCipher: %v", err) + } + } + + client := redis.NewClient(opt) + + rs := &SessionStore{ + Client: client, + CookieCipher: cookieCipher, + CookieDomain: cookieOpts.CookieDomain, + CookieExpire: cookieOpts.CookieExpire, + CookieHTTPOnly: cookieOpts.CookieHTTPOnly, + CookieName: cookieOpts.CookieName, + CookiePath: cookieOpts.CookiePath, + CookieSecret: cookieOpts.CookieSecret, + CookieSecure: cookieOpts.CookieSecure, + } + return rs, nil + +} + +// Save takes a sessions.SessionState and stores the information from it +// to redies, and adds a new ticket cookie on the HTTP response writer +func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error { + requestCookie, _ := req.Cookie(store.CookieName) + value, err := s.EncodeSessionState(store.CookieCipher) + if err != nil { + return err + } + ticketString, err := store.storeValue(value, s.ExpiresOn, requestCookie) + if err != nil { + return err + } + + ticketCookie := cookies.MakeCookie( + req, + store.CookieName, + ticketString, + store.CookiePath, + store.CookieDomain, + store.CookieHTTPOnly, + store.CookieSecure, + store.CookieExpire, + time.Now(), + ) + + http.SetCookie(rw, ticketCookie) + return nil +} + +// Load reads sessions.SessionState information from a ticket +// cookie within the HTTP request object +func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) { + requestCookie, _ := req.Cookie(store.CookieName) + // No cookie validation necessary + session, err := store.LoadSessionFromString(requestCookie.Value) + if err != nil { + return nil, fmt.Errorf("error loading session: %s", err) + } + return session, nil +} + +// LoadSessionFromString loads the session based on the ticket value +func (store *SessionStore) LoadSessionFromString(value string) (*sessions.SessionState, error) { + ticket, err := decodeTicket(store.CookieName, value) + if err != nil { + return nil, err + } + + result, err := store.Client.Get(ticket.asHandle(store.CookieName)).Result() + if err != nil { + return nil, err + } + + resultBytes := []byte(result) + block, err := aes.NewCipher(ticket.Secret) + if err != nil { + return nil, err + } + // Use secret as the IV too, because each entry has it's own key + stream := cipher.NewCFBDecrypter(block, ticket.Secret) + stream.XORKeyStream(resultBytes, resultBytes) + + session, err := sessions.DecodeSessionState(string(resultBytes), store.CookieCipher) + if err != nil { + return nil, err + } + return session, nil +} + +// Clear clears any saved session information for a given ticket cookie +// from redis, and then clears the session +func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error { + requestCookie, _ := req.Cookie(store.CookieName) + + // We go ahead and clear the cookie first, always. + clearCookie := cookies.MakeCookie( + req, + store.CookieName, + "", + store.CookiePath, + store.CookieDomain, + store.CookieHTTPOnly, + store.CookieSecure, + time.Hour*-1, + time.Now(), + ) + http.SetCookie(rw, clearCookie) + + // We only return an error if we had an issue with redis + // If there's an issue decoding the ticket, ignore it + ticket, _ := decodeTicket(store.CookieName, requestCookie.Value) + if ticket != nil { + deleted, err := store.Client.Del(ticket.asHandle(store.CookieName)).Result() + fmt.Println("delted %n", deleted) + if err != nil { + return fmt.Errorf("error clearing cookie from redis: %s", err) + } + } + return nil +} + +func (store *SessionStore) storeValue(value string, expiresOn time.Time, requestCookie *http.Cookie) (string, error) { + var ticket *TicketData + if requestCookie != nil { + var err error + ticket, err = decodeTicket(store.CookieName, requestCookie.Value) + if err != nil { + return "", err + } + } else { + var err error + ticket, err = newTicket() + if err != nil { + return "", fmt.Errorf("error creating new ticket: %s", err) + } + } + + ciphertext := make([]byte, len(value)) + block, err := aes.NewCipher(ticket.Secret) + if err != nil { + return "", fmt.Errorf("error initiating cipher block %s", err) + } + + // Use secret as the IV too, because each entry has it's own key + stream := cipher.NewCFBEncrypter(block, ticket.Secret) + stream.XORKeyStream(ciphertext, []byte(value)) + + handle := ticket.asHandle(store.CookieName) + expires := expiresOn.Sub(time.Now()) + err = store.Client.Set(handle, ciphertext, expires).Err() + if err != nil { + return "", err + } + return ticket.encodeTicket(store.CookieName), nil +} + +func newTicket() (*TicketData, error) { + rawID := make([]byte, 16) + if _, err := io.ReadFull(rand.Reader, rawID); err != nil { + return nil, fmt.Errorf("failed to create new ticket ID %s", err) + } + // ticketID is hex encoded + ticketID := fmt.Sprintf("%x", rawID) + + secret := make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, secret); err != nil { + return nil, fmt.Errorf("failed to create initialization vector %s", err) + } + ticket := &TicketData{ + TicketID: ticketID, + Secret: secret, + } + return ticket, nil +} + +func (ticket *TicketData) asHandle(prefix string) string { + return fmt.Sprintf("%s-%s", prefix, ticket.TicketID) +} + +func decodeTicket(cookieName string, ticketString string) (*TicketData, error) { + prefix := cookieName + "-" + if !strings.HasPrefix(ticketString, prefix) { + return nil, fmt.Errorf("failed to decode ticket handle") + } + trimmedTicket := strings.TrimPrefix(ticketString, prefix) + + ticketParts := strings.Split(trimmedTicket, ".") + if len(ticketParts) != 2 { + return nil, fmt.Errorf("failed to decode ticket") + } + ticketID, secretBase64 := ticketParts[0], ticketParts[1] + + // ticketID must be a hexadecimal string + _, err := hex.DecodeString(ticketID) + if err != nil { + return nil, fmt.Errorf("server ticket failed sanity checks") + // s is not a valid + } + + secret, err := base64.RawURLEncoding.DecodeString(secretBase64) + if err != nil { + return nil, fmt.Errorf("failed to decode initialization vector %s", err) + } + ticketData := &TicketData{ + TicketID: ticketID, + Secret: secret, + } + return ticketData, nil +} + +func (ticket *TicketData) encodeTicket(prefix string) string { + handle := ticket.asHandle(prefix) + ticketString := handle + "." + base64.RawURLEncoding.EncodeToString(ticket.Secret) + return ticketString +} diff --git a/pkg/sessions/session_store.go b/pkg/sessions/session_store.go index ec84dd7..e8e229a 100644 --- a/pkg/sessions/session_store.go +++ b/pkg/sessions/session_store.go @@ -6,6 +6,7 @@ import ( "github.com/pusher/oauth2_proxy/pkg/apis/options" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" + "github.com/pusher/oauth2_proxy/pkg/sessions/redis" ) // NewSessionStore creates a SessionStore from the provided configuration @@ -13,6 +14,8 @@ func NewSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOpt switch opts.Type { case options.CookieSessionStoreType: return cookie.NewCookieSessionStore(opts, cookieOpts) + case options.RedisSessionStoreType: + return redis.NewRedisSessionStore(opts.RedisStoreOptions, cookieOpts) default: return nil, fmt.Errorf("unknown session store type '%s'", opts.Type) } diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index b407d58..47780f6 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -10,14 +10,15 @@ import ( "testing" "time" + "github.com/alicebob/miniredis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/pkg/apis/options" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" - "github.com/pusher/oauth2_proxy/pkg/cookies" "github.com/pusher/oauth2_proxy/pkg/sessions" sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" + "github.com/pusher/oauth2_proxy/pkg/sessions/redis" "github.com/pusher/oauth2_proxy/pkg/sessions/utils" ) @@ -109,22 +110,18 @@ var _ = Describe("NewSessionStore", func() { Context("when Clear is called", func() { BeforeEach(func() { - cookie := cookies.MakeCookie(request, - cookieOpts.CookieName, - "foo", - cookieOpts.CookiePath, - cookieOpts.CookieDomain, - cookieOpts.CookieHTTPOnly, - cookieOpts.CookieSecure, - cookieOpts.CookieExpire, - time.Now(), - ) - request.AddCookie(cookie) - err := ss.Clear(response, request) + req := httptest.NewRequest("GET", "http://example.com/", nil) + saveResp := httptest.NewRecorder() + err := ss.Save(saveResp, req, session) + Expect(err).ToNot(HaveOccurred()) + + resultCookie := saveResp.Result().Cookies()[0] + request.AddCookie(resultCookie) + err = ss.Clear(response, request) Expect(err).ToNot(HaveOccurred()) }) - It("sets a `set-cookie` header in the response", func() { + It("sets a `set-cookie` header in the response and session to not be loadable after clear", func() { Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) }) @@ -171,6 +168,35 @@ var _ = Describe("NewSessionStore", func() { }) } + PersistentSessionStoreTests := func() { + Context("when Clear is called the session should not be recoverable", func() { + var loadedAfterClear *sessionsapi.SessionState + BeforeEach(func() { + req := httptest.NewRequest("GET", "http://example.com/", nil) + saveResp := httptest.NewRecorder() + err := ss.Save(saveResp, req, session) + Expect(err).ToNot(HaveOccurred()) + + resultCookie := saveResp.Result().Cookies()[0] + request.AddCookie(resultCookie) + err = ss.Clear(response, request) + Expect(err).ToNot(HaveOccurred()) + + // The following should only be for server stores + loadReq := httptest.NewRequest("GET", "http://example.com/", nil) + loadReq.AddCookie(resultCookie) + loadedAfterClear, err = ss.Load(loadReq) + }) + + It("sets a `set-cookie` header in the response and session to not be loadable after clear", func() { + Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) + Expect(loadedAfterClear).To(BeNil()) + }) + + CheckCookieOptions() + }) + } + RunSessionTests := func() { Context("with default options", func() { BeforeEach(func() { @@ -221,6 +247,18 @@ var _ = Describe("NewSessionStore", func() { }) } + RunPersistentSessionStoreTests := func() { + Context("with default options", func() { + BeforeEach(func() { + var err error + ss, err = sessions.NewSessionStore(opts, cookieOpts) + Expect(err).ToNot(HaveOccurred()) + }) + + PersistentSessionStoreTests() + }) + } + BeforeEach(func() { ss = nil opts = &options.SessionOptions{} @@ -264,6 +302,28 @@ var _ = Describe("NewSessionStore", func() { }) }) + Context("with type 'redis'", func() { + BeforeEach(func() { + mr, err := miniredis.Run() + if err != nil { + panic(err) + } + opts.Type = options.RedisSessionStoreType + opts.RedisConnectionURL = "redis://" + mr.Addr() + }) + + It("creates a redis.SessionStore", func() { + ss, err := sessions.NewSessionStore(opts, cookieOpts) + Expect(err).NotTo(HaveOccurred()) + Expect(ss).To(BeAssignableToTypeOf(&redis.SessionStore{})) + }) + + Context("the redis.SessionStore", func() { + RunSessionTests() + RunPersistentSessionStoreTests() + }) + }) + Context("with an invalid type", func() { BeforeEach(func() { opts.Type = "invalid-type" From 42731f061702dbba5dea52c4c81bb822afd48054 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 13 May 2019 11:54:06 -0700 Subject: [PATCH 013/128] Check cookie error and doc on cookie handling --- pkg/sessions/redis/redis_store.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 74cb1c0..c240071 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -20,7 +20,7 @@ import ( "github.com/pusher/oauth2_proxy/pkg/sessions/utils" ) -// TicketData is a structure representing a used in server session storage +// TicketData is a structure representing the ticket used in server session storage type TicketData struct { TicketID string Secret []byte @@ -77,6 +77,8 @@ func NewRedisSessionStore(opts options.RedisStoreOptions, cookieOpts *options.Co // Save takes a sessions.SessionState and stores the information from it // to redies, and adds a new ticket cookie on the HTTP response writer func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error { + // Old sessions that we are refreshing would have a request cookie + // New sessions don't, so we ignore the error. storeValue will check requestCookie requestCookie, _ := req.Cookie(store.CookieName) value, err := s.EncodeSessionState(store.CookieCipher) if err != nil { @@ -106,7 +108,10 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se // Load reads sessions.SessionState information from a ticket // cookie within the HTTP request object func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) { - requestCookie, _ := req.Cookie(store.CookieName) + requestCookie, err := req.Cookie(store.CookieName) + if err != nil { + return nil, fmt.Errorf("error loading session: %s", err) + } // No cookie validation necessary session, err := store.LoadSessionFromString(requestCookie.Value) if err != nil { From f2562e897362398e677b96d554dc69e4250956c5 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 13 May 2019 11:54:22 -0700 Subject: [PATCH 014/128] Pin version of go-redis --- Gopkg.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Gopkg.toml b/Gopkg.toml index 7c90984..bdc0ae9 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -50,6 +50,7 @@ [[constraint]] branch = "master" name = "github.com/go-redis/redis" + version = "v6.15.2" [[constraint]] name = "github.com/alicebob/miniredis" From 296d989e5871be8e0274f2cb8540d53c497ee6e8 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 15 May 2019 17:06:05 +0100 Subject: [PATCH 015/128] Simplify redis store options --- pkg/sessions/redis/redis_store.go | 78 ++++++++++--------------------- pkg/sessions/session_store.go | 2 +- 2 files changed, 26 insertions(+), 54 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index c240071..ec55a03 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -17,7 +17,6 @@ import ( "github.com/pusher/oauth2_proxy/pkg/apis/options" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/cookies" - "github.com/pusher/oauth2_proxy/pkg/sessions/utils" ) // TicketData is a structure representing the ticket used in server session storage @@ -29,46 +28,25 @@ type TicketData struct { // SessionStore is an implementation of the sessions.SessionStore // interface that stores sessions in redis type SessionStore struct { - CookieCipher *cookie.Cipher - CookieDomain string - CookieExpire time.Duration - CookieHTTPOnly bool - CookieName string - CookiePath string - CookieSecret string - CookieSecure bool - Client *redis.Client + CookieCipher *cookie.Cipher + CookieOptions *options.CookieOptions + Client *redis.Client } // NewRedisSessionStore initialises a new instance of the SessionStore from // the configuration given -func NewRedisSessionStore(opts options.RedisStoreOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) { - opt, err := redis.ParseURL(opts.RedisConnectionURL) +func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) { + opt, err := redis.ParseURL(opts.RedisStoreOptions.RedisConnectionURL) if err != nil { return nil, fmt.Errorf("unable to parse redis url: %s", err) } - var cookieCipher *cookie.Cipher - if len(cookieOpts.CookieSecret) > 0 { - var err error - cookieCipher, err = cookie.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret)) - if err != nil { - return nil, fmt.Errorf("unable to create cookieCipher: %v", err) - } - } - client := redis.NewClient(opt) rs := &SessionStore{ - Client: client, - CookieCipher: cookieCipher, - CookieDomain: cookieOpts.CookieDomain, - CookieExpire: cookieOpts.CookieExpire, - CookieHTTPOnly: cookieOpts.CookieHTTPOnly, - CookieName: cookieOpts.CookieName, - CookiePath: cookieOpts.CookiePath, - CookieSecret: cookieOpts.CookieSecret, - CookieSecure: cookieOpts.CookieSecure, + Client: client, + CookieCipher: opts.Cipher, + CookieOptions: cookieOpts, } return rs, nil @@ -79,7 +57,7 @@ func NewRedisSessionStore(opts options.RedisStoreOptions, cookieOpts *options.Co func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error { // Old sessions that we are refreshing would have a request cookie // New sessions don't, so we ignore the error. storeValue will check requestCookie - requestCookie, _ := req.Cookie(store.CookieName) + requestCookie, _ := req.Cookie(store.CookieOptions.CookieName) value, err := s.EncodeSessionState(store.CookieCipher) if err != nil { return err @@ -89,15 +67,12 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se return err } - ticketCookie := cookies.MakeCookie( + ticketCookie := cookies.MakeCookieFromOptions( req, - store.CookieName, + store.CookieOptions.CookieName, ticketString, - store.CookiePath, - store.CookieDomain, - store.CookieHTTPOnly, - store.CookieSecure, - store.CookieExpire, + store.CookieOptions, + store.CookieOptions.CookieExpire, time.Now(), ) @@ -108,7 +83,7 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se // Load reads sessions.SessionState information from a ticket // cookie within the HTTP request object func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) { - requestCookie, err := req.Cookie(store.CookieName) + requestCookie, err := req.Cookie(store.CookieOptions.CookieName) if err != nil { return nil, fmt.Errorf("error loading session: %s", err) } @@ -122,12 +97,12 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro // LoadSessionFromString loads the session based on the ticket value func (store *SessionStore) LoadSessionFromString(value string) (*sessions.SessionState, error) { - ticket, err := decodeTicket(store.CookieName, value) + ticket, err := decodeTicket(store.CookieOptions.CookieName, value) if err != nil { return nil, err } - result, err := store.Client.Get(ticket.asHandle(store.CookieName)).Result() + result, err := store.Client.Get(ticket.asHandle(store.CookieOptions.CookieName)).Result() if err != nil { return nil, err } @@ -151,17 +126,14 @@ func (store *SessionStore) LoadSessionFromString(value string) (*sessions.Sessio // Clear clears any saved session information for a given ticket cookie // from redis, and then clears the session func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error { - requestCookie, _ := req.Cookie(store.CookieName) + requestCookie, _ := req.Cookie(store.CookieOptions.CookieName) // We go ahead and clear the cookie first, always. - clearCookie := cookies.MakeCookie( + clearCookie := cookies.MakeCookieFromOptions( req, - store.CookieName, + store.CookieOptions.CookieName, "", - store.CookiePath, - store.CookieDomain, - store.CookieHTTPOnly, - store.CookieSecure, + store.CookieOptions, time.Hour*-1, time.Now(), ) @@ -169,9 +141,9 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro // We only return an error if we had an issue with redis // If there's an issue decoding the ticket, ignore it - ticket, _ := decodeTicket(store.CookieName, requestCookie.Value) + ticket, _ := decodeTicket(store.CookieOptions.CookieName, requestCookie.Value) if ticket != nil { - deleted, err := store.Client.Del(ticket.asHandle(store.CookieName)).Result() + deleted, err := store.Client.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result() fmt.Println("delted %n", deleted) if err != nil { return fmt.Errorf("error clearing cookie from redis: %s", err) @@ -184,7 +156,7 @@ func (store *SessionStore) storeValue(value string, expiresOn time.Time, request var ticket *TicketData if requestCookie != nil { var err error - ticket, err = decodeTicket(store.CookieName, requestCookie.Value) + ticket, err = decodeTicket(store.CookieOptions.CookieName, requestCookie.Value) if err != nil { return "", err } @@ -206,13 +178,13 @@ func (store *SessionStore) storeValue(value string, expiresOn time.Time, request stream := cipher.NewCFBEncrypter(block, ticket.Secret) stream.XORKeyStream(ciphertext, []byte(value)) - handle := ticket.asHandle(store.CookieName) + handle := ticket.asHandle(store.CookieOptions.CookieName) expires := expiresOn.Sub(time.Now()) err = store.Client.Set(handle, ciphertext, expires).Err() if err != nil { return "", err } - return ticket.encodeTicket(store.CookieName), nil + return ticket.encodeTicket(store.CookieOptions.CookieName), nil } func newTicket() (*TicketData, error) { diff --git a/pkg/sessions/session_store.go b/pkg/sessions/session_store.go index e8e229a..17ef21c 100644 --- a/pkg/sessions/session_store.go +++ b/pkg/sessions/session_store.go @@ -15,7 +15,7 @@ func NewSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOpt case options.CookieSessionStoreType: return cookie.NewCookieSessionStore(opts, cookieOpts) case options.RedisSessionStoreType: - return redis.NewRedisSessionStore(opts.RedisStoreOptions, cookieOpts) + return redis.NewRedisSessionStore(opts, cookieOpts) default: return nil, fmt.Errorf("unknown session store type '%s'", opts.Type) } From 2c566a5f5b65c3c6294a848ed51ddc7451a77d20 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 15 May 2019 17:08:15 +0100 Subject: [PATCH 016/128] Use session CreatedAt for cookie timings --- pkg/sessions/redis/redis_store.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index ec55a03..01b3053 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -55,6 +55,10 @@ func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cook // Save takes a sessions.SessionState and stores the information from it // to redies, and adds a new ticket cookie on the HTTP response writer func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error { + if s.CreatedAt.IsZero() { + s.CreatedAt = time.Now() + } + // Old sessions that we are refreshing would have a request cookie // New sessions don't, so we ignore the error. storeValue will check requestCookie requestCookie, _ := req.Cookie(store.CookieOptions.CookieName) @@ -73,7 +77,7 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se ticketString, store.CookieOptions, store.CookieOptions.CookieExpire, - time.Now(), + s.CreatedAt, ) http.SetCookie(rw, ticketCookie) From b255ed56ef400fdd9b9e17439aa3463c1869bfe7 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 15 May 2019 17:20:32 +0100 Subject: [PATCH 017/128] Sign cookies in the Redis Session store --- pkg/sessions/redis/redis_store.go | 41 ++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 01b3053..05ff5ec 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -71,11 +71,9 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se return err } - ticketCookie := cookies.MakeCookieFromOptions( + ticketCookie := store.makeCookie( req, - store.CookieOptions.CookieName, ticketString, - store.CookieOptions, store.CookieOptions.CookieExpire, s.CreatedAt, ) @@ -91,8 +89,12 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro if err != nil { return nil, fmt.Errorf("error loading session: %s", err) } - // No cookie validation necessary - session, err := store.LoadSessionFromString(requestCookie.Value) + + val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + if !ok { + return nil, fmt.Errorf("Cookie Signature not valid") + } + session, err := store.LoadSessionFromString(val) if err != nil { return nil, fmt.Errorf("error loading session: %s", err) } @@ -132,12 +134,15 @@ func (store *SessionStore) LoadSessionFromString(value string) (*sessions.Sessio func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error { requestCookie, _ := req.Cookie(store.CookieOptions.CookieName) + val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + if !ok { + return fmt.Errorf("Cookie Signature not valid") + } + // We go ahead and clear the cookie first, always. - clearCookie := cookies.MakeCookieFromOptions( + clearCookie := store.makeCookie( req, - store.CookieOptions.CookieName, "", - store.CookieOptions, time.Hour*-1, time.Now(), ) @@ -145,10 +150,9 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro // We only return an error if we had an issue with redis // If there's an issue decoding the ticket, ignore it - ticket, _ := decodeTicket(store.CookieOptions.CookieName, requestCookie.Value) + ticket, _ := decodeTicket(store.CookieOptions.CookieName, val) if ticket != nil { - deleted, err := store.Client.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result() - fmt.Println("delted %n", deleted) + _, err := store.Client.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result() if err != nil { return fmt.Errorf("error clearing cookie from redis: %s", err) } @@ -156,6 +160,21 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro return nil } +// makeCookie makes a cookie, signing the value if present +func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie { + if value != "" { + value = cookie.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now) + } + return cookies.MakeCookieFromOptions( + req, + store.CookieOptions.CookieName, + value, + store.CookieOptions, + expires, + now, + ) +} + func (store *SessionStore) storeValue(value string, expiresOn time.Time, requestCookie *http.Cookie) (string, error) { var ticket *TicketData if requestCookie != nil { From 7a1fc52e3375ced61d7bbf4b066827dcd0b9535c Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 15 May 2019 17:24:06 +0100 Subject: [PATCH 018/128] Fix go-redis version pin --- Gopkg.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Gopkg.toml b/Gopkg.toml index bdc0ae9..6778c97 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -48,7 +48,6 @@ version = "2.1.0" [[constraint]] - branch = "master" name = "github.com/go-redis/redis" version = "v6.15.2" From 130d03758d0cdb8be52d792174c4c15503ed48b7 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:03:38 +0100 Subject: [PATCH 019/128] Fix comments on Redis options --- pkg/apis/options/sessions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 591cc37..6787ec9 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -19,11 +19,11 @@ var CookieSessionStoreType = "cookie" // CookieStoreOptions contains configuration options for the CookieSessionStore. type CookieStoreOptions struct{} -// RedisSessionStoreType is used to indicate the CookieSessionStore should be +// RedisSessionStoreType is used to indicate the RedisSessionStore should be // used for storing sessions. var RedisSessionStoreType = "redis" -// RedisStoreOptions contains configuration options for the CookieSessionStore. +// RedisStoreOptions contains configuration options for the RedisSessionStore. type RedisStoreOptions struct { RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"` } From f435fa68abfea7602ab279e9458ab40247ce879e Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:06:13 +0100 Subject: [PATCH 020/128] Make loadSessionFromString private --- pkg/sessions/redis/redis_store.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 05ff5ec..a428033 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -94,15 +94,15 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro if !ok { return nil, fmt.Errorf("Cookie Signature not valid") } - session, err := store.LoadSessionFromString(val) + session, err := store.loadSessionFromString(val) if err != nil { return nil, fmt.Errorf("error loading session: %s", err) } return session, nil } -// LoadSessionFromString loads the session based on the ticket value -func (store *SessionStore) LoadSessionFromString(value string) (*sessions.SessionState, error) { +// loadSessionFromString loads the session based on the ticket value +func (store *SessionStore) loadSessionFromString(value string) (*sessions.SessionState, error) { ticket, err := decodeTicket(store.CookieOptions.CookieName, value) if err != nil { return nil, err From 2f61e42c37687193a2f3fe52a7f56e3943b31693 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:07:43 +0100 Subject: [PATCH 021/128] More obvious comment on CFB --- pkg/sessions/redis/redis_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index a428033..154b839 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -197,7 +197,7 @@ func (store *SessionStore) storeValue(value string, expiresOn time.Time, request return "", fmt.Errorf("error initiating cipher block %s", err) } - // Use secret as the IV too, because each entry has it's own key + // Use secret as the Initialization Vector too, because each entry has it's own key stream := cipher.NewCFBEncrypter(block, ticket.Secret) stream.XORKeyStream(ciphertext, []byte(value)) From a6b8f7bde2987b77fe1127892a0098c793ff077c Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:08:10 +0100 Subject: [PATCH 022/128] Rename expire -> expiration --- pkg/sessions/redis/redis_store.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 154b839..fd71c4a 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -202,8 +202,8 @@ func (store *SessionStore) storeValue(value string, expiresOn time.Time, request stream.XORKeyStream(ciphertext, []byte(value)) handle := ticket.asHandle(store.CookieOptions.CookieName) - expires := expiresOn.Sub(time.Now()) - err = store.Client.Set(handle, ciphertext, expires).Err() + expiration := expiresOn.Sub(time.Now()) + err = store.Client.Set(handle, ciphertext, expiration).Err() if err != nil { return "", err } From 93df7d913236a46fbbbe6270940c1ae0267ec09f Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:08:59 +0100 Subject: [PATCH 023/128] Remove spurious comment --- pkg/sessions/redis/redis_store.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index fd71c4a..c55e9c7 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -250,7 +250,6 @@ func decodeTicket(cookieName string, ticketString string) (*TicketData, error) { _, err := hex.DecodeString(ticketID) if err != nil { return nil, fmt.Errorf("server ticket failed sanity checks") - // s is not a valid } secret, err := base64.RawURLEncoding.DecodeString(secretBase64) From a7693cc72ad7322b624d60c96ebfd868170d2294 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:13:14 +0100 Subject: [PATCH 024/128] Tranfser all cookies in tests --- pkg/sessions/session_store_test.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 47780f6..ff074e2 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -115,8 +115,9 @@ var _ = Describe("NewSessionStore", func() { err := ss.Save(saveResp, req, session) Expect(err).ToNot(HaveOccurred()) - resultCookie := saveResp.Result().Cookies()[0] - request.AddCookie(resultCookie) + for _, c := range saveResp.Result().Cookies() { + request.AddCookie(c) + } err = ss.Clear(response, request) Expect(err).ToNot(HaveOccurred()) }) @@ -177,14 +178,18 @@ var _ = Describe("NewSessionStore", func() { err := ss.Save(saveResp, req, session) Expect(err).ToNot(HaveOccurred()) - resultCookie := saveResp.Result().Cookies()[0] - request.AddCookie(resultCookie) + resultCookies := saveResp.Result().Cookies() + for _, c := range resultCookies { + request.AddCookie(c) + } err = ss.Clear(response, request) Expect(err).ToNot(HaveOccurred()) // The following should only be for server stores loadReq := httptest.NewRequest("GET", "http://example.com/", nil) - loadReq.AddCookie(resultCookie) + for _, c := range resultCookies { + loadReq.AddCookie(c) + } loadedAfterClear, err = ss.Load(loadReq) }) From 42f14a41d906b3cf9a7f1bb5fa92f8978794b79f Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:25:41 +0100 Subject: [PATCH 025/128] Clean up persistent SessionStore tests --- pkg/sessions/session_store_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index ff074e2..b4e36ca 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -122,7 +122,7 @@ var _ = Describe("NewSessionStore", func() { Expect(err).ToNot(HaveOccurred()) }) - It("sets a `set-cookie` header in the response and session to not be loadable after clear", func() { + It("sets a `set-cookie` header in the response", func() { Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) }) @@ -169,8 +169,9 @@ var _ = Describe("NewSessionStore", func() { }) } + // The following should only be for server stores PersistentSessionStoreTests := func() { - Context("when Clear is called the session should not be recoverable", func() { + Context("when Clear is called on a persistent store", func() { var loadedAfterClear *sessionsapi.SessionState BeforeEach(func() { req := httptest.NewRequest("GET", "http://example.com/", nil) @@ -185,16 +186,21 @@ var _ = Describe("NewSessionStore", func() { err = ss.Clear(response, request) Expect(err).ToNot(HaveOccurred()) - // The following should only be for server stores loadReq := httptest.NewRequest("GET", "http://example.com/", nil) for _, c := range resultCookies { loadReq.AddCookie(c) } + loadedAfterClear, err = ss.Load(loadReq) + // If we have cleared the session, Load should fail + Expect(err).To(HaveOccurred()) }) - It("sets a `set-cookie` header in the response and session to not be loadable after clear", func() { + It("sets a `set-cookie` header in the response", func() { Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) + }) + + It("attempting to Load returns an empty session", func() { Expect(loadedAfterClear).To(BeNil()) }) From bc3d75a2ed6c34883de232362757e91b2479bd95 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:29:53 +0100 Subject: [PATCH 026/128] Run persistent tests with multiple option groups --- pkg/sessions/session_store_test.go | 109 +++++++++++++---------------- 1 file changed, 50 insertions(+), 59 deletions(-) diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index b4e36ca..c21987e 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -90,7 +90,46 @@ var _ = Describe("NewSessionStore", func() { }) } - SessionStoreInterfaceTests := func() { + // The following should only be for server stores + PersistentSessionStoreTests := func() { + Context("when Clear is called on a persistent store", func() { + var loadedAfterClear *sessionsapi.SessionState + BeforeEach(func() { + req := httptest.NewRequest("GET", "http://example.com/", nil) + saveResp := httptest.NewRecorder() + err := ss.Save(saveResp, req, session) + Expect(err).ToNot(HaveOccurred()) + + resultCookies := saveResp.Result().Cookies() + for _, c := range resultCookies { + request.AddCookie(c) + } + err = ss.Clear(response, request) + Expect(err).ToNot(HaveOccurred()) + + loadReq := httptest.NewRequest("GET", "http://example.com/", nil) + for _, c := range resultCookies { + loadReq.AddCookie(c) + } + + loadedAfterClear, err = ss.Load(loadReq) + // If we have cleared the session, Load should fail + Expect(err).To(HaveOccurred()) + }) + + It("sets a `set-cookie` header in the response", func() { + Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) + }) + + It("attempting to Load returns an empty session", func() { + Expect(loadedAfterClear).To(BeNil()) + }) + + CheckCookieOptions() + }) + } + + SessionStoreInterfaceTests := func(persistent bool) { Context("when Save is called", func() { BeforeEach(func() { err := ss.Save(response, request, session) @@ -167,48 +206,13 @@ var _ = Describe("NewSessionStore", func() { } }) }) + + if persistent { + PersistentSessionStoreTests() + } } - // The following should only be for server stores - PersistentSessionStoreTests := func() { - Context("when Clear is called on a persistent store", func() { - var loadedAfterClear *sessionsapi.SessionState - BeforeEach(func() { - req := httptest.NewRequest("GET", "http://example.com/", nil) - saveResp := httptest.NewRecorder() - err := ss.Save(saveResp, req, session) - Expect(err).ToNot(HaveOccurred()) - - resultCookies := saveResp.Result().Cookies() - for _, c := range resultCookies { - request.AddCookie(c) - } - err = ss.Clear(response, request) - Expect(err).ToNot(HaveOccurred()) - - loadReq := httptest.NewRequest("GET", "http://example.com/", nil) - for _, c := range resultCookies { - loadReq.AddCookie(c) - } - - loadedAfterClear, err = ss.Load(loadReq) - // If we have cleared the session, Load should fail - Expect(err).To(HaveOccurred()) - }) - - It("sets a `set-cookie` header in the response", func() { - Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) - }) - - It("attempting to Load returns an empty session", func() { - Expect(loadedAfterClear).To(BeNil()) - }) - - CheckCookieOptions() - }) - } - - RunSessionTests := func() { + RunSessionTests := func(persistent bool) { Context("with default options", func() { BeforeEach(func() { var err error @@ -216,7 +220,7 @@ var _ = Describe("NewSessionStore", func() { Expect(err).ToNot(HaveOccurred()) }) - SessionStoreInterfaceTests() + SessionStoreInterfaceTests(persistent) }) Context("with non-default options", func() { @@ -236,7 +240,7 @@ var _ = Describe("NewSessionStore", func() { Expect(err).ToNot(HaveOccurred()) }) - SessionStoreInterfaceTests() + SessionStoreInterfaceTests(persistent) }) Context("with a cipher", func() { @@ -254,19 +258,7 @@ var _ = Describe("NewSessionStore", func() { Expect(err).ToNot(HaveOccurred()) }) - SessionStoreInterfaceTests() - }) - } - - RunPersistentSessionStoreTests := func() { - Context("with default options", func() { - BeforeEach(func() { - var err error - ss, err = sessions.NewSessionStore(opts, cookieOpts) - Expect(err).ToNot(HaveOccurred()) - }) - - PersistentSessionStoreTests() + SessionStoreInterfaceTests(persistent) }) } @@ -309,7 +301,7 @@ var _ = Describe("NewSessionStore", func() { }) Context("the cookie.SessionStore", func() { - RunSessionTests() + RunSessionTests(false) }) }) @@ -330,8 +322,7 @@ var _ = Describe("NewSessionStore", func() { }) Context("the redis.SessionStore", func() { - RunSessionTests() - RunPersistentSessionStoreTests() + RunSessionTests(true) }) }) From 7e7bfb5daffb3ad7beb103d1fc1af3236828d397 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:32:54 +0100 Subject: [PATCH 027/128] Stop miniredis after each test --- pkg/sessions/session_store_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index c21987e..8c5202b 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -306,15 +306,19 @@ var _ = Describe("NewSessionStore", func() { }) Context("with type 'redis'", func() { + var mr *miniredis.Miniredis BeforeEach(func() { - mr, err := miniredis.Run() - if err != nil { - panic(err) - } + var err error + mr, err = miniredis.Run() + Expect(err).ToNot(HaveOccurred()) opts.Type = options.RedisSessionStoreType opts.RedisConnectionURL = "redis://" + mr.Addr() }) + AfterEach(func() { + mr.Close() + }) + It("creates a redis.SessionStore", func() { ss, err := sessions.NewSessionStore(opts, cookieOpts) Expect(err).NotTo(HaveOccurred()) From 4f5dbace9f6dfd08f4a53cd6b2ecf69a258df2dd Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 16 May 2019 17:38:42 +0100 Subject: [PATCH 028/128] Refactor persistent tests with more Context --- pkg/sessions/session_store_test.go | 38 +++++++++++++++++------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 8c5202b..76722df 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -93,36 +93,42 @@ var _ = Describe("NewSessionStore", func() { // The following should only be for server stores PersistentSessionStoreTests := func() { Context("when Clear is called on a persistent store", func() { - var loadedAfterClear *sessionsapi.SessionState + var resultCookies []*http.Cookie + BeforeEach(func() { req := httptest.NewRequest("GET", "http://example.com/", nil) saveResp := httptest.NewRecorder() err := ss.Save(saveResp, req, session) Expect(err).ToNot(HaveOccurred()) - resultCookies := saveResp.Result().Cookies() + resultCookies = saveResp.Result().Cookies() for _, c := range resultCookies { request.AddCookie(c) } err = ss.Clear(response, request) Expect(err).ToNot(HaveOccurred()) - - loadReq := httptest.NewRequest("GET", "http://example.com/", nil) - for _, c := range resultCookies { - loadReq.AddCookie(c) - } - - loadedAfterClear, err = ss.Load(loadReq) - // If we have cleared the session, Load should fail - Expect(err).To(HaveOccurred()) }) - It("sets a `set-cookie` header in the response", func() { - Expect(response.Header().Get("Set-Cookie")).ToNot(BeEmpty()) - }) + Context("attempting to Load", func() { + var loadedAfterClear *sessionsapi.SessionState + var loadErr error - It("attempting to Load returns an empty session", func() { - Expect(loadedAfterClear).To(BeNil()) + BeforeEach(func() { + loadReq := httptest.NewRequest("GET", "http://example.com/", nil) + for _, c := range resultCookies { + loadReq.AddCookie(c) + } + + loadedAfterClear, loadErr = ss.Load(loadReq) + }) + + It("returns an empty session", func() { + Expect(loadedAfterClear).To(BeNil()) + }) + + It("returns an error", func() { + Expect(loadErr).To(HaveOccurred()) + }) }) CheckCookieOptions() From 5095c3647d9433ca1bf1e65a64b89b780c8e73aa Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 17 May 2019 13:16:43 +0100 Subject: [PATCH 029/128] Add redis-connection-url flag --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index a74c245..f647651 100644 --- a/main.go +++ b/main.go @@ -76,6 +76,7 @@ func main() { flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") 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("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") From fc06e2dbef3389e9ba71c72f9569566902fff0aa Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 20 May 2019 14:46:38 -0700 Subject: [PATCH 030/128] Update documentation and changelog for redis store --- CHANGELOG.md | 6 ++++++ docs/configuration/configuration.md | 1 + docs/configuration/sessions.md | 24 ++++++++++++++++++++++++ main.go | 2 +- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3c69b..8c85c9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ ## Changes since v3.2.0 +- [#155](https://github.com/pusher/outh2_proxy/pull/155) Add RedisSessionStore implementation (@brianv0, @JoelSpeed) + - Implement flags to configure the redis session store + - `-redis-connection-url` + - Introduces the concept of a session ticket. Tickets are composed of the cookie name, a session ID, and a secret. + - Sessions are stored encrypted with a per-session secret + - Added Some tests for a Server based session store - [#168](https://github.com/pusher/outh2_proxy/pull/168) Drop Go 1.11 support in Travis (@JoelSpeed) - [#169](https://github.com/pusher/outh2_proxy/pull/169) Update Alpine to 3.9 (@kskewes) - [#148](https://github.com/pusher/outh2_proxy/pull/148) Implement SessionStore interface within proxy (@JoelSpeed) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index fd33d37..82b45a3 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -75,6 +75,7 @@ Usage of oauth2_proxy: -pubjwk-url string: JWK pubkey access endpoint: required by login.gov -redeem-url string: Token redemption endpoint -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" + -redis-connection-url string: URL of redis server for redis session storage type (eg: redis://HOST[:PORT]) -request-logging: Log requests to stdout (default true) -request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below) -resource string: The resource that is protected (Azure AD only) diff --git a/docs/configuration/sessions.md b/docs/configuration/sessions.md index 6e9d9d7..103d424 100644 --- a/docs/configuration/sessions.md +++ b/docs/configuration/sessions.md @@ -16,6 +16,7 @@ data in one of the available session storage backends. At present the available backends are (as passed to `--session-store-type`): - [cookie](cookie-storage) (default) +- [redis](redis-storage) ### Cookie Storage @@ -32,3 +33,26 @@ The following should be known when using this 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 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. \ No newline at end of file diff --git a/main.go b/main.go index f647651..e649eba 100644 --- a/main.go +++ b/main.go @@ -76,7 +76,7 @@ func main() { flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") 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 type (eg: redis://HOST[:PORT])") 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") From 518c1d3e8ed0fbebac48074518314acc5e65e6bc Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 24 May 2019 17:32:55 +0100 Subject: [PATCH 031/128] Add Redis sentinel compatibility (cherry picked from commit ff36b61f8cee4ecf0b91a90b5e1b651b526bb6b6) --- main.go | 6 +++++- pkg/apis/options/sessions.go | 5 ++++- pkg/sessions/redis/redis_store.go | 24 ++++++++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index e649eba..7f230f4 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,7 @@ func main() { upstreams := StringArray{} skipAuthRegex := StringArray{} googleGroups := StringArray{} + redisSentinelConnectionURLs := StringArray{} config := flagSet.String("config", "", "path to config file") showVersion := flagSet.Bool("version", false, "print version string") @@ -76,7 +77,10 @@ func main() { flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") 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 type (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-conneciton-urls to use this feature") + 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 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.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation") diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 6787ec9..c72da3d 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -25,5 +25,8 @@ var RedisSessionStoreType = "redis" // RedisStoreOptions contains configuration options for the RedisSessionStore. type RedisStoreOptions struct { - RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"` + RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"` + UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"` + SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"` + SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"` } diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index c55e9c7..61aa1c7 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -36,13 +36,11 @@ type SessionStore struct { // NewRedisSessionStore initialises a new instance of the SessionStore from // the configuration given func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) { - opt, err := redis.ParseURL(opts.RedisStoreOptions.RedisConnectionURL) + client, err := newRedisClient(opts.RedisStoreOptions) if err != nil { - return nil, fmt.Errorf("unable to parse redis url: %s", err) + return nil, fmt.Errorf("error constructing redis client: %v", err) } - client := redis.NewClient(opt) - rs := &SessionStore{ Client: client, CookieCipher: opts.Cipher, @@ -52,6 +50,24 @@ func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cook } +func newRedisClient(opts options.RedisStoreOptions) (*redis.Client, error) { + if opts.UseSentinel { + client := redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: opts.SentinelMasterName, + SentinelAddrs: opts.SentinelConnectionURLs, + }) + return client, nil + } + + opt, err := redis.ParseURL(opts.RedisConnectionURL) + if err != nil { + return nil, fmt.Errorf("unable to parse redis url: %s", err) + } + + client := redis.NewClient(opt) + return client, nil +} + // Save takes a sessions.SessionState and stores the information from it // to redies, and adds a new ticket cookie on the HTTP response writer func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error { From ae0258a20385b59a401f64e99d3febd2ff43f179 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Tue, 28 May 2019 13:26:40 -0700 Subject: [PATCH 032/128] Documentation updates around Redis and Redis Sentinel use --- docs/configuration/configuration.md | 5 ++++- docs/configuration/sessions.md | 11 ++++++++++- main.go | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 82b45a3..d631eaf 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -75,7 +75,10 @@ Usage of oauth2_proxy: -pubjwk-url string: JWK pubkey access endpoint: required by login.gov -redeem-url string: Token redemption endpoint -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" - -redis-connection-url string: URL of redis server for redis session storage type (eg: redis://HOST[:PORT]) + -redis-connection-url string: URL of redis server for redis session storage (eg: redis://HOST[:PORT]) + -redis-sentinel-master-name string: Redis sentinel master name. Used in conjuction with --redis-use-sentinel + -redis-sentinel-connection-urls: List of Redis sentinel conneciton URLs (eg redis://HOST[:PORT]). Used in conjuction with --redis-use-sentinel + -redis-use-sentinel: Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature (default: false) -request-logging: Log requests to stdout (default true) -request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below) -resource string: The resource that is protected (Azure AD only) diff --git a/docs/configuration/sessions.md b/docs/configuration/sessions.md index 103d424..0ffe392 100644 --- a/docs/configuration/sessions.md +++ b/docs/configuration/sessions.md @@ -55,4 +55,13 @@ to which the session is stored. The encoded session is encrypted with the secret in redis via the `SETEX` command. Encrypting every session uniquely protects the refresh/access/id tokens stored in the session from -disclosure. \ No newline at end of file +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. diff --git a/main.go b/main.go index 7f230f4..a66c4fc 100644 --- a/main.go +++ b/main.go @@ -78,9 +78,9 @@ func main() { flagSet.String("session-store-type", "cookie", "the session storage provider to use") flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])") - flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-conneciton-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-connection-urls to use this feature") 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 conneciton URLs (eg redis://HOST[:PORT]). 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 conjuction with --redis-use-sentinel") 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") From 2e2327af6c941c46d61e2169b99389f3b16fce21 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 29 May 2019 11:59:58 +0100 Subject: [PATCH 033/128] Check SaveSession works when an existing session is present (cherry picked from commit 9dc1a96d817741632cb476456755a645b732db7d) --- pkg/sessions/redis/redis_store.go | 9 +++++- pkg/sessions/session_store_test.go | 44 ++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 61aa1c7..b32d5c2 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -195,7 +195,14 @@ func (store *SessionStore) storeValue(value string, expiresOn time.Time, request var ticket *TicketData if requestCookie != nil { var err error - ticket, err = decodeTicket(store.CookieOptions.CookieName, requestCookie.Value) + val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + if !ok { + ticket, err = newTicket() + if err != nil { + return "", fmt.Errorf("error creating new ticket: %s", err) + } + } + ticket, err = decodeTicket(store.CookieOptions.CookieName, val) if err != nil { return "", err } diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 76722df..85e63b3 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -137,17 +137,45 @@ var _ = Describe("NewSessionStore", func() { SessionStoreInterfaceTests := func(persistent bool) { Context("when Save is called", func() { - BeforeEach(func() { - err := ss.Save(response, request, session) - Expect(err).ToNot(HaveOccurred()) + Context("with no existing session", func() { + BeforeEach(func() { + err := ss.Save(response, request, session) + Expect(err).ToNot(HaveOccurred()) + }) + + It("sets a `set-cookie` header in the response", func() { + Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty()) + }) + + It("Ensures the session CreatedAt is not zero", func() { + Expect(session.CreatedAt.IsZero()).To(BeFalse()) + }) }) - It("sets a `set-cookie` header in the response", func() { - Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty()) - }) + Context("with an expired saved session", func() { + var err error + BeforeEach(func() { + By("saving a session") + req := httptest.NewRequest("GET", "http://example.com/", nil) + saveResp := httptest.NewRecorder() + err = ss.Save(saveResp, req, session) + Expect(err).ToNot(HaveOccurred()) - It("Ensures the session CreatedAt is not zero", func() { - Expect(session.CreatedAt.IsZero()).To(BeFalse()) + By("and clearing the session") + for _, c := range saveResp.Result().Cookies() { + request.AddCookie(c) + } + clearResp := httptest.NewRecorder() + err = ss.Clear(clearResp, request) + Expect(err).ToNot(HaveOccurred()) + + By("then saving a request with the cleared session") + err = ss.Save(response, request, session) + }) + + It("no error should occur", func() { + Expect(err).ToNot(HaveOccurred()) + }) }) CheckCookieOptions() From 3155ada287da8f3602ea1f68df385d0cfd78ee22 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 29 May 2019 15:25:56 +0100 Subject: [PATCH 034/128] Ensure sessions are refreshable in redis session store (cherry picked from commit 48edce3003d187a3eadc4ea96236845271dd9360) --- pkg/sessions/redis/redis_store.go | 5 +- pkg/sessions/session_store_test.go | 102 +++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index b32d5c2..6a5ffbb 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -82,7 +82,7 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se if err != nil { return err } - ticketString, err := store.storeValue(value, s.ExpiresOn, requestCookie) + ticketString, err := store.storeValue(value, store.CookieOptions.CookieExpire, requestCookie) if err != nil { return err } @@ -191,7 +191,7 @@ func (store *SessionStore) makeCookie(req *http.Request, value string, expires t ) } -func (store *SessionStore) storeValue(value string, expiresOn time.Time, requestCookie *http.Cookie) (string, error) { +func (store *SessionStore) storeValue(value string, expiration time.Duration, requestCookie *http.Cookie) (string, error) { var ticket *TicketData if requestCookie != nil { var err error @@ -225,7 +225,6 @@ func (store *SessionStore) storeValue(value string, expiresOn time.Time, request stream.XORKeyStream(ciphertext, []byte(value)) handle := ticket.asHandle(store.CookieOptions.CookieName) - expiration := expiresOn.Sub(time.Now()) err = store.Client.Set(handle, ciphertext, expiration).Err() if err != nil { return "", err diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 85e63b3..2ffc0bd 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -35,6 +35,7 @@ var _ = Describe("NewSessionStore", func() { var response *httptest.ResponseRecorder var session *sessionsapi.SessionState var ss sessionsapi.SessionStore + var mr *miniredis.Miniredis CheckCookieOptions := func() { Context("the cookies returned", func() { @@ -203,7 +204,38 @@ var _ = Describe("NewSessionStore", func() { }) Context("when Load is called", func() { - var loadedSession *sessionsapi.SessionState + LoadSessionTests := func() { + var loadedSession *sessionsapi.SessionState + BeforeEach(func() { + var err error + loadedSession, err = ss.Load(request) + Expect(err).ToNot(HaveOccurred()) + }) + + It("loads a session equal to the original session", func() { + if cookieOpts.CookieSecret == "" { + // Only Email and User stored in session when encrypted + Expect(loadedSession.Email).To(Equal(session.Email)) + Expect(loadedSession.User).To(Equal(session.User)) + } else { + // All fields stored in session if encrypted + + // Can't compare time.Time using Equal() so remove ExpiresOn from sessions + l := *loadedSession + l.CreatedAt = time.Time{} + l.ExpiresOn = time.Time{} + s := *session + s.CreatedAt = time.Time{} + s.ExpiresOn = time.Time{} + Expect(l).To(Equal(s)) + + // Compare time.Time separately + Expect(loadedSession.CreatedAt.Equal(session.CreatedAt)).To(BeTrue()) + Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue()) + } + }) + } + BeforeEach(func() { req := httptest.NewRequest("GET", "http://example.com/", nil) resp := httptest.NewRecorder() @@ -213,32 +245,49 @@ var _ = Describe("NewSessionStore", func() { for _, cookie := range resp.Result().Cookies() { request.AddCookie(cookie) } - loadedSession, err = ss.Load(request) - Expect(err).ToNot(HaveOccurred()) }) - It("loads a session equal to the original session", func() { - if cookieOpts.CookieSecret == "" { - // Only Email and User stored in session when encrypted - Expect(loadedSession.Email).To(Equal(session.Email)) - Expect(loadedSession.User).To(Equal(session.User)) - } else { - // All fields stored in session if encrypted - - // Can't compare time.Time using Equal() so remove ExpiresOn from sessions - l := *loadedSession - l.CreatedAt = time.Time{} - l.ExpiresOn = time.Time{} - s := *session - s.CreatedAt = time.Time{} - s.ExpiresOn = time.Time{} - Expect(l).To(Equal(s)) - - // Compare time.Time separately - Expect(loadedSession.CreatedAt.Equal(session.CreatedAt)).To(BeTrue()) - Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue()) - } + Context("before the refresh period", func() { + LoadSessionTests() }) + + // Test TTLs and cleanup of persistent session storage + // For non-persistent we rely on the browser cookie lifecycle + if persistent { + Context("after the refresh period, but before the cookie expire period", func() { + BeforeEach(func() { + switch ss.(type) { + case *redis.SessionStore: + mr.FastForward(cookieOpts.CookieRefresh + time.Minute) + } + }) + + LoadSessionTests() + }) + + Context("after the cookie expire period", func() { + var loadedSession *sessionsapi.SessionState + var err error + + BeforeEach(func() { + switch ss.(type) { + case *redis.SessionStore: + mr.FastForward(cookieOpts.CookieExpire + time.Minute) + } + + loadedSession, err = ss.Load(request) + Expect(err).To(HaveOccurred()) + }) + + It("returns an error loading the session", func() { + Expect(err).To(HaveOccurred()) + }) + + It("returns an empty session", func() { + Expect(loadedSession).To(BeNil()) + }) + }) + } }) if persistent { @@ -263,7 +312,7 @@ var _ = Describe("NewSessionStore", func() { CookieName: "_cookie_name", CookiePath: "/path", CookieExpire: time.Duration(72) * time.Hour, - CookieRefresh: time.Duration(3600), + CookieRefresh: time.Duration(2) * time.Hour, CookieSecure: false, CookieHTTPOnly: false, CookieDomain: "example.com", @@ -305,7 +354,7 @@ var _ = Describe("NewSessionStore", func() { CookieName: "_oauth2_proxy", CookiePath: "/", CookieExpire: time.Duration(168) * time.Hour, - CookieRefresh: time.Duration(0), + CookieRefresh: time.Duration(1) * time.Hour, CookieSecure: true, CookieHTTPOnly: true, } @@ -340,7 +389,6 @@ var _ = Describe("NewSessionStore", func() { }) Context("with type 'redis'", func() { - var mr *miniredis.Miniredis BeforeEach(func() { var err error mr, err = miniredis.Run() From 22199fa4173ca2f1b4b72f545231c7103ff86848 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 30 May 2019 10:10:28 +0100 Subject: [PATCH 035/128] Fix ticket retrieval with an invalid ticket (cherry picked from commit 66bbf146ec45d127bdd374120743aeef936894a7) --- pkg/sessions/redis/redis_store.go | 41 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 6a5ffbb..552b48d 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -192,26 +192,9 @@ func (store *SessionStore) makeCookie(req *http.Request, value string, expires t } func (store *SessionStore) storeValue(value string, expiration time.Duration, requestCookie *http.Cookie) (string, error) { - var ticket *TicketData - if requestCookie != nil { - var err error - val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) - if !ok { - ticket, err = newTicket() - if err != nil { - return "", fmt.Errorf("error creating new ticket: %s", err) - } - } - ticket, err = decodeTicket(store.CookieOptions.CookieName, val) - if err != nil { - return "", err - } - } else { - var err error - ticket, err = newTicket() - if err != nil { - return "", fmt.Errorf("error creating new ticket: %s", err) - } + ticket, err := store.getTicket(requestCookie) + if err != nil { + return "", fmt.Errorf("error getting ticket: %v", err) } ciphertext := make([]byte, len(value)) @@ -232,6 +215,24 @@ func (store *SessionStore) storeValue(value string, expiration time.Duration, re return ticket.encodeTicket(store.CookieOptions.CookieName), nil } +// getTicket retrieves an existing ticket from the cookie if present, +// or creates a new ticket +func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, error) { + if requestCookie == nil { + return newTicket() + } + + // An existing cookie exists, try to retrieve the ticket + val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + if !ok { + // Cookie is invalid, create a new ticket + return newTicket() + } + + // Valid cookie, decode the ticket + return decodeTicket(store.CookieOptions.CookieName, val) +} + func newTicket() (*TicketData, error) { rawID := make([]byte, 16) if _, err := io.ReadFull(rand.Reader, rawID); err != nil { From c1ae0ca807818b4bfdf7a49e59e064bafc05618c Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 30 May 2019 10:53:53 +0100 Subject: [PATCH 036/128] Make sure the cookie exists before we clear the session in redis (cherry picked from commit 6d7f0ab57d554706425f76aed4df60717dd63ece) --- pkg/sessions/redis/redis_store.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 552b48d..1d34d84 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -148,13 +148,6 @@ func (store *SessionStore) loadSessionFromString(value string) (*sessions.Sessio // Clear clears any saved session information for a given ticket cookie // from redis, and then clears the session func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error { - requestCookie, _ := req.Cookie(store.CookieOptions.CookieName) - - val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) - if !ok { - return fmt.Errorf("Cookie Signature not valid") - } - // We go ahead and clear the cookie first, always. clearCookie := store.makeCookie( req, @@ -164,6 +157,20 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro ) http.SetCookie(rw, clearCookie) + // If there was an existing cookie we should clear the session in redis + requestCookie, err := req.Cookie(store.CookieOptions.CookieName) + if err != nil && err == http.ErrNoCookie { + // No existing cookie so can't clear redis + return nil + } else if err != nil { + return fmt.Errorf("error retrieving cookie: %v", err) + } + + val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + if !ok { + return fmt.Errorf("Cookie Signature not valid") + } + // We only return an error if we had an issue with redis // If there's an issue decoding the ticket, ignore it ticket, _ := decodeTicket(store.CookieOptions.CookieName, val) From 4721da02f2516a68f2504126a0f60cdcd0d35ac6 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Thu, 30 May 2019 11:55:42 +0100 Subject: [PATCH 037/128] Ensure SessionStores can handle recieving cookies for the wrong implementation (cherry picked from commit 131206cf41697543583751ac2714287898c19ad0) --- pkg/sessions/redis/redis_store.go | 7 ++++++- pkg/sessions/session_store_test.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 1d34d84..82e941e 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -237,7 +237,12 @@ func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, e } // Valid cookie, decode the ticket - return decodeTicket(store.CookieOptions.CookieName, val) + ticket, err := decodeTicket(store.CookieOptions.CookieName, val) + if err != nil { + // If we can't decode the ticket we have to create a new one + return newTicket() + } + return ticket, nil } func newTicket() (*TicketData, error) { diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 2ffc0bd..47ad4b7 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -16,6 +16,7 @@ import ( "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/pkg/apis/options" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/cookies" "github.com/pusher/oauth2_proxy/pkg/sessions" sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" "github.com/pusher/oauth2_proxy/pkg/sessions/redis" @@ -153,6 +154,27 @@ var _ = Describe("NewSessionStore", func() { }) }) + Context("with a broken session", func() { + BeforeEach(func() { + By("Using a valid cookie with a different providers session encoding") + broken := "BrokenSessionFromADifferentSessionImplementation" + value := cookie.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now()) + cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now()) + request.AddCookie(cookie) + + err := ss.Save(response, request, session) + Expect(err).ToNot(HaveOccurred()) + }) + + It("sets a `set-cookie` header in the response", func() { + Expect(response.Header().Get("set-cookie")).ToNot(BeEmpty()) + }) + + It("Ensures the session CreatedAt is not zero", func() { + Expect(session.CreatedAt.IsZero()).To(BeFalse()) + }) + }) + Context("with an expired saved session", func() { var err error BeforeEach(func() { From 405f9b3bb006b10fe16b8db854915781382db726 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 5 Jun 2019 00:02:49 -0700 Subject: [PATCH 038/128] Update CHANGELOG with descriptions about redis support Add updates from master --- CHANGELOG.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c85c9b..362cf1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,14 @@ - [#155](https://github.com/pusher/outh2_proxy/pull/155) Add RedisSessionStore implementation (@brianv0, @JoelSpeed) - Implement flags to configure the redis session store - - `-redis-connection-url` - - Introduces the concept of a session ticket. Tickets are composed of the cookie name, a session ID, and a secret. - - Sessions are stored encrypted with a per-session secret - - Added Some tests for a Server based 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/outh2_proxy/pull/168) Drop Go 1.11 support in Travis (@JoelSpeed) - [#169](https://github.com/pusher/outh2_proxy/pull/169) Update Alpine to 3.9 (@kskewes) - [#148](https://github.com/pusher/outh2_proxy/pull/148) Implement SessionStore interface within proxy (@JoelSpeed) From 9e59b4f62e4abd2812699ba9ac2d00ae83d43dfd Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Fri, 7 Jun 2019 13:50:44 +1000 Subject: [PATCH 039/128] Restructure so that serving data from upstream is only done when explicity allowed, rather than as implicit dangling else --- oauthproxy.go | 78 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 389b2a9..dfc2086 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -47,6 +47,11 @@ var SignatureHeaders = []string{ "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 type OAuthProxy struct { CookieSeed string @@ -477,20 +482,19 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool { } // IsWhitelistedRequest is used to check if auth should be skipped for this request -func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) { +func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool { isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS" return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path) } // IsWhitelistedPath is used to check if the request path is allowed without auth -func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) { +func (p *OAuthProxy) IsWhitelistedPath(path string) bool { for _, u := range p.compiledRegex { - ok = u.MatchString(path) - if ok { - return + if u.MatchString(path) { + return true } } - return + return false } func getRemoteAddr(req *http.Request) (s string) { @@ -641,10 +645,11 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { // AuthenticateOnly checks whether the user is currently logged in func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { - status := p.Authenticate(rw, req) - if status == http.StatusAccepted { + _, err := p.getAuthenticatedSession(rw, req) + switch err { + case nil: rw.WriteHeader(http.StatusAccepted) - } else { + default: http.Error(rw, "unauthorized request", http.StatusUnauthorized) } } @@ -652,25 +657,40 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) // Proxy proxies the user request if the user is authenticated else it prompts // them to authenticate func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { - status := p.Authenticate(rw, req) - if status == http.StatusInternalServerError { - p.ErrorPage(rw, http.StatusInternalServerError, - "Internal Error", "Internal Error") - } else if status == http.StatusForbidden { + session, err := p.getAuthenticatedSession(rw, req) + switch err { + case nil: + // we are authenticated + p.addHeadersForProxying(rw, req, session) + p.serveMux.ServeHTTP(rw, req) + + case ErrNeedsLogin: + // we need to send the user to a login screen + if isAjax(req) { + // no point redirecting an AJAX request + p.ErrorJSON(rw, http.StatusUnauthorized) + return + } + if p.SkipProviderButton { p.OAuthStart(rw, req) } else { p.SignInPage(rw, req, http.StatusForbidden) } - } else if status == http.StatusUnauthorized { - p.ErrorJSON(rw, status) - } else { - p.serveMux.ServeHTTP(rw, req) + + default: + // unknown error + logger.Printf("Unexpected internal error: %s", err) + p.ErrorPage(rw, http.StatusInternalServerError, + "Internal Error", "Internal Error") } + } -// Authenticate checks whether a user is authenticated -func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int { +// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so +// Returns nil, ErrNeedsLogin if user needs to login. +// Set-Cookie headers may be set on the response as a side-effect of calling this method. +func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) { var saveSession, clearSession, revalidated bool remoteAddr := getRemoteAddr(req) @@ -720,7 +740,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int err = p.SaveSession(rw, req, session) if err != nil { logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err) - return http.StatusInternalServerError + return nil, err } } @@ -736,15 +756,14 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } if session == nil { - // 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 nil, ErrNeedsLogin } - // At this point, the user is authenticated. proxy normally + return session, nil +} + +// addHeadersForProxying adds the appropriate headers the request / response for proxying +func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) { if p.PassBasicAuth { req.SetBasicAuth(session.User, p.BasicAuthPassword) req.Header["X-Forwarded-User"] = []string{session.User} @@ -781,7 +800,6 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } else { rw.Header().Set("GAP-Auth", session.Email) } - return http.StatusAccepted } // CheckBasicAuth checks the requests Authorization header for basic auth @@ -815,7 +833,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionStat } // isAjax checks if a request is an ajax request -func (p *OAuthProxy) isAjax(req *http.Request) bool { +func isAjax(req *http.Request) bool { acceptValues, ok := req.Header["accept"] if !ok { acceptValues = req.Header["Accept"] From f35c82bb0f7bb3877f7e60e6c2c62c5d1c7f9c99 Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Fri, 7 Jun 2019 14:25:12 +1000 Subject: [PATCH 040/128] The AuthOnly path also needs the response headers set --- oauthproxy.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oauthproxy.go b/oauthproxy.go index dfc2086..687a9b2 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -645,9 +645,11 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { // AuthenticateOnly checks whether the user is currently logged in func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { - _, err := p.getAuthenticatedSession(rw, req) + session, err := p.getAuthenticatedSession(rw, req) switch err { case nil: + // we are authenticated + p.addHeadersForProxying(rw, req, session) rw.WriteHeader(http.StatusAccepted) default: http.Error(rw, "unauthorized request", http.StatusUnauthorized) From 8027cc454e0fd571de69d496f1de13c51d2ab6e7 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 24 May 2019 16:55:12 +0100 Subject: [PATCH 041/128] Move api to pkg/requests --- api/api.go => pkg/requests/requests.go | 2 +- api/api_test.go => pkg/requests/requests_test.go | 2 +- providers/azure.go | 4 ++-- providers/facebook.go | 4 ++-- providers/gitlab.go | 4 ++-- providers/internal_util.go | 4 ++-- providers/linkedin.go | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) rename api/api.go => pkg/requests/requests.go (98%) rename api/api_test.go => pkg/requests/requests_test.go (99%) diff --git a/api/api.go b/pkg/requests/requests.go similarity index 98% rename from api/api.go rename to pkg/requests/requests.go index c5d5623..aac22e4 100644 --- a/api/api.go +++ b/pkg/requests/requests.go @@ -1,4 +1,4 @@ -package api +package requests import ( "encoding/json" diff --git a/api/api_test.go b/pkg/requests/requests_test.go similarity index 99% rename from api/api_test.go rename to pkg/requests/requests_test.go index 7bdf1b7..99a4c3b 100644 --- a/api/api_test.go +++ b/pkg/requests/requests_test.go @@ -1,4 +1,4 @@ -package api +package requests import ( "io/ioutil" diff --git a/providers/azure.go b/providers/azure.go index a7961d2..3154432 100644 --- a/providers/azure.go +++ b/providers/azure.go @@ -7,9 +7,9 @@ import ( "net/url" "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/requests" ) // 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) - json, err := api.Request(req) + json, err := requests.Request(req) if err != nil { return "", err diff --git a/providers/facebook.go b/providers/facebook.go index 9897a1b..abd5382 100644 --- a/providers/facebook.go +++ b/providers/facebook.go @@ -6,8 +6,8 @@ import ( "net/http" "net/url" - "github.com/pusher/oauth2_proxy/api" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/requests" ) // FacebookProvider represents an Facebook based Identity Provider @@ -69,7 +69,7 @@ func (p *FacebookProvider) GetEmailAddress(s *sessions.SessionState) (string, er Email string } var r result - err = api.RequestJSON(req, &r) + err = requests.RequestJSON(req, &r) if err != nil { return "", err } diff --git a/providers/gitlab.go b/providers/gitlab.go index af956c4..c9a4a1f 100644 --- a/providers/gitlab.go +++ b/providers/gitlab.go @@ -4,9 +4,9 @@ import ( "net/http" "net/url" - "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/requests" ) // GitLabProvider represents an GitLab based Identity Provider @@ -53,7 +53,7 @@ func (p *GitLabProvider) GetEmailAddress(s *sessions.SessionState) (string, erro logger.Printf("failed building request %s", err) return "", err } - json, err := api.Request(req) + json, err := requests.Request(req) if err != nil { logger.Printf("failed making request %s", err) return "", err diff --git a/providers/internal_util.go b/providers/internal_util.go index 7144dee..bb5f4f5 100644 --- a/providers/internal_util.go +++ b/providers/internal_util.go @@ -5,8 +5,8 @@ import ( "net/http" "net/url" - "github.com/pusher/oauth2_proxy/api" "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/requests" ) // stripToken is a helper function to obfuscate "access_token" @@ -55,7 +55,7 @@ func validateToken(p Provider, accessToken string, header http.Header) bool { params := url.Values{"access_token": {accessToken}} endpoint = endpoint + "?" + params.Encode() } - resp, err := api.RequestUnparsedResponse(endpoint, header) + resp, err := requests.RequestUnparsedResponse(endpoint, header) if err != nil { logger.Printf("GET %s", stripToken(endpoint)) logger.Printf("token validation request failed: %s", err) diff --git a/providers/linkedin.go b/providers/linkedin.go index a31b4a1..bca2936 100644 --- a/providers/linkedin.go +++ b/providers/linkedin.go @@ -6,8 +6,8 @@ import ( "net/http" "net/url" - "github.com/pusher/oauth2_proxy/api" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/requests" ) // 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) - json, err := api.Request(req) + json, err := requests.Request(req) if err != nil { return "", err } From 7a8fb58ad1a2ed1984e5b6be305b68de3fe1c608 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Fri, 14 Jun 2019 11:33:05 -0400 Subject: [PATCH 042/128] Only validate tokens if ValidateURL resolves to a non-empty string Fix an unsupported protocol scheme error when validating tokens by ensuring that the ValidateURL generates a non-empty string. The Azure provider doesn't define any ValidateURL and therefore uses the default value of `url.Parse("")` which is not `nil`. The following log summary shows the issue: 2019/06/14 12:26:04 oauthproxy.go:799: 10.244.1.3:34112 ("10.244.1.1") refreshing 16h26m29s old session cookie for Session{email:jonas.fonseca@example.com user:jonas.fonseca token:true} (refresh after 1h0m0s) 2019/06/14 12:26:04 internal_util.go:60: GET ?access_token=eyJ0... 2019/06/14 12:26:04 internal_util.go:61: token validation request failed: Get ?access_token=eyJ0...: unsupported protocol scheme "" 2019/06/14 12:26:04 oauthproxy.go:822: 10.244.1.3:34112 ("10.244.1.1") removing session. error validating Session{email:jonas.fonseca@example.com user:jonas.fonseca token:true} --- CHANGELOG.md | 1 + providers/internal_util.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3fc3d..6cfd7dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ - [#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) # v3.2.0 diff --git a/providers/internal_util.go b/providers/internal_util.go index 7144dee..849e2c7 100644 --- a/providers/internal_util.go +++ b/providers/internal_util.go @@ -47,7 +47,7 @@ func stripParam(param, endpoint string) string { // validateToken returns true if token is valid func validateToken(p Provider, accessToken string, header http.Header) bool { - if accessToken == "" || p.Data().ValidateURL == nil { + if accessToken == "" || p.Data().ValidateURL == nil || p.Data().ValidateURL.String() == "" { return false } endpoint := p.Data().ValidateURL.String() From d69560d0200e778bfc1a5db372f4a27f6947f4fb Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Sat, 15 Jun 2019 18:48:27 +1000 Subject: [PATCH 043/128] No need for case when only 2 conditions --- CHANGELOG.md | 1 + oauthproxy.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3fc3d..92522cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ## Changes since v3.2.0 +- [#180](https://github.com/pusher/outh2_proxy/pull/180) Minor refactor of core proxying path (@aeijdenberg). - [#175](https://github.com/pusher/outh2_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/outh2_proxy/pull/155) Add RedisSessionStore implementation (@brianv0, @JoelSpeed) diff --git a/oauthproxy.go b/oauthproxy.go index 687a9b2..ef97415 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -646,14 +646,14 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { // AuthenticateOnly checks whether the user is currently logged in func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) { session, err := p.getAuthenticatedSession(rw, req) - switch err { - case nil: - // we are authenticated - p.addHeadersForProxying(rw, req, session) - rw.WriteHeader(http.StatusAccepted) - default: + if err != nil { http.Error(rw, "unauthorized request", http.StatusUnauthorized) + return } + + // we are authenticated + p.addHeadersForProxying(rw, req, session) + rw.WriteHeader(http.StatusAccepted) } // Proxy proxies the user request if the user is authenticated else it prompts From d1ef14becc300ee2ad65c07a032daf51dac2ca01 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 24 May 2019 17:06:48 +0100 Subject: [PATCH 044/128] Move cookie to pkg/encryption --- oauthproxy.go | 4 ++-- options.go | 6 +++--- pkg/apis/options/sessions.go | 6 ++---- pkg/apis/sessions/session_state.go | 8 ++++---- pkg/apis/sessions/session_state_test.go | 14 +++++++------- cookie/cookies.go => pkg/encryption/cipher.go | 2 +- .../encryption/cipher_test.go | 2 +- {cookie => pkg/encryption}/nonce.go | 2 +- pkg/sessions/cookie/session_store.go | 8 ++++---- pkg/sessions/redis/redis_store.go | 12 ++++++------ pkg/sessions/session_store_test.go | 6 +++--- pkg/sessions/utils/utils.go | 6 +++--- providers/provider_default.go | 6 +++--- providers/providers.go | 6 +++--- 14 files changed, 43 insertions(+), 45 deletions(-) rename cookie/cookies.go => pkg/encryption/cipher.go (99%) rename cookie/cookies_test.go => pkg/encryption/cipher_test.go (98%) rename {cookie => pkg/encryption}/nonce.go (93%) diff --git a/oauthproxy.go b/oauthproxy.go index 389b2a9..62d1a18 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -14,9 +14,9 @@ import ( "time" "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" + "github.com/pusher/oauth2_proxy/pkg/encryption" "github.com/pusher/oauth2_proxy/providers" "github.com/yhat/wsutil" ) @@ -555,7 +555,7 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) { // OAuthStart starts the OAuth2 authentication flow func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) { - nonce, err := cookie.Nonce() + nonce, err := encryption.Nonce() if err != nil { logger.Printf("Error obtaining nonce: %s", err.Error()) p.ErrorPage(rw, 500, "Internal Error", err.Error()) diff --git a/options.go b/options.go index 0460bce..2b506e3 100644 --- a/options.go +++ b/options.go @@ -17,10 +17,10 @@ import ( oidc "github.com/coreos/go-oidc" "github.com/dgrijalva/jwt-go" "github.com/mbland/hmacauth" - "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/logger" "github.com/pusher/oauth2_proxy/pkg/apis/options" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/encryption" "github.com/pusher/oauth2_proxy/pkg/sessions" "github.com/pusher/oauth2_proxy/providers" "gopkg.in/natefinch/lumberjack.v2" @@ -268,7 +268,7 @@ func (o *Options) Validate() error { } msgs = parseProviderInfo(o, msgs) - var cipher *cookie.Cipher + var cipher *encryption.Cipher if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.CookieRefresh != time.Duration(0)) { validCookieSecretSize := false for _, i := range []int{16, 24, 32} { @@ -293,7 +293,7 @@ func (o *Options) Validate() error { len(secretBytes(o.CookieSecret)), suffix)) } else { var err error - cipher, err = cookie.NewCipher(secretBytes(o.CookieSecret)) + cipher, err = encryption.NewCipher(secretBytes(o.CookieSecret)) if err != nil { msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err)) } diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index c72da3d..c96d490 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -1,13 +1,11 @@ package options -import ( - "github.com/pusher/oauth2_proxy/cookie" -) +import "github.com/pusher/oauth2_proxy/pkg/encryption" // SessionOptions contains configuration options for the SessionStore providers. type SessionOptions struct { Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"` - Cipher *cookie.Cipher + Cipher *encryption.Cipher CookieStoreOptions RedisStoreOptions } diff --git a/pkg/apis/sessions/session_state.go b/pkg/apis/sessions/session_state.go index 01789ff..84c0dc9 100644 --- a/pkg/apis/sessions/session_state.go +++ b/pkg/apis/sessions/session_state.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/pusher/oauth2_proxy/cookie" + "github.com/pusher/oauth2_proxy/pkg/encryption" ) // SessionState is used to store information about the currently authenticated user session @@ -66,7 +66,7 @@ func (s *SessionState) String() string { } // EncodeSessionState returns string representation of the current session -func (s *SessionState) EncodeSessionState(c *cookie.Cipher) (string, error) { +func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error) { var ss SessionState if c == nil { // 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 // generated by v3.1.0 or older -func legacyDecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) { +func legacyDecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { chunks := strings.Split(v, "|") if c == nil { @@ -176,7 +176,7 @@ func legacyDecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) } // DecodeSessionState decodes the session cookie string into a SessionState -func DecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) { +func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) { var ssj SessionStateJSON var ss *SessionState err := json.Unmarshal([]byte(v), &ssj) diff --git a/pkg/apis/sessions/session_state_test.go b/pkg/apis/sessions/session_state_test.go index a48344e..c8ccff1 100644 --- a/pkg/apis/sessions/session_state_test.go +++ b/pkg/apis/sessions/session_state_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/encryption" "github.com/stretchr/testify/assert" ) @@ -14,9 +14,9 @@ const secret = "0123456789abcdefghijklmnopqrstuv" const altSecret = "0000000000abcdefghijklmnopqrstuv" func TestSessionStateSerialization(t *testing.T) { - c, err := cookie.NewCipher([]byte(secret)) + c, err := encryption.NewCipher([]byte(secret)) assert.Equal(t, nil, err) - c2, err := cookie.NewCipher([]byte(altSecret)) + c2, err := encryption.NewCipher([]byte(altSecret)) assert.Equal(t, nil, err) s := &sessions.SessionState{ Email: "user@domain.com", @@ -54,9 +54,9 @@ func TestSessionStateSerialization(t *testing.T) { } func TestSessionStateSerializationWithUser(t *testing.T) { - c, err := cookie.NewCipher([]byte(secret)) + c, err := encryption.NewCipher([]byte(secret)) assert.Equal(t, nil, err) - c2, err := cookie.NewCipher([]byte(altSecret)) + c2, err := encryption.NewCipher([]byte(altSecret)) assert.Equal(t, nil, err) s := &sessions.SessionState{ User: "just-user", @@ -146,7 +146,7 @@ func TestExpired(t *testing.T) { type testCase struct { sessions.SessionState Encoded string - Cipher *cookie.Cipher + Cipher *encryption.Cipher Error bool } @@ -203,7 +203,7 @@ func TestDecodeSessionState(t *testing.T) { eString := string(eJSON) eUnix := e.Unix() - c, err := cookie.NewCipher([]byte(secret)) + c, err := encryption.NewCipher([]byte(secret)) assert.NoError(t, err) testCases := []testCase{ diff --git a/cookie/cookies.go b/pkg/encryption/cipher.go similarity index 99% rename from cookie/cookies.go rename to pkg/encryption/cipher.go index 0d354e1..c308330 100644 --- a/cookie/cookies.go +++ b/pkg/encryption/cipher.go @@ -1,4 +1,4 @@ -package cookie +package encryption import ( "crypto/aes" diff --git a/cookie/cookies_test.go b/pkg/encryption/cipher_test.go similarity index 98% rename from cookie/cookies_test.go rename to pkg/encryption/cipher_test.go index 500550e..fb6a4aa 100644 --- a/cookie/cookies_test.go +++ b/pkg/encryption/cipher_test.go @@ -1,4 +1,4 @@ -package cookie +package encryption import ( "encoding/base64" diff --git a/cookie/nonce.go b/pkg/encryption/nonce.go similarity index 93% rename from cookie/nonce.go rename to pkg/encryption/nonce.go index 6def148..69850c4 100644 --- a/cookie/nonce.go +++ b/pkg/encryption/nonce.go @@ -1,4 +1,4 @@ -package cookie +package encryption import ( "crypto/rand" diff --git a/pkg/sessions/cookie/session_store.go b/pkg/sessions/cookie/session_store.go index c40dd23..960be90 100644 --- a/pkg/sessions/cookie/session_store.go +++ b/pkg/sessions/cookie/session_store.go @@ -8,10 +8,10 @@ import ( "strings" "time" - "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/pkg/apis/options" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/cookies" + "github.com/pusher/oauth2_proxy/pkg/encryption" "github.com/pusher/oauth2_proxy/pkg/sessions/utils" ) @@ -28,7 +28,7 @@ var _ sessions.SessionStore = &SessionStore{} // interface that stores sessions in client side cookies type SessionStore struct { CookieOptions *options.CookieOptions - CookieCipher *cookie.Cipher + CookieCipher *encryption.Cipher } // 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 return nil, fmt.Errorf("Cookie %q not present", s.CookieOptions.CookieName) } - val, _, ok := cookie.Validate(c, s.CookieOptions.CookieSecret, s.CookieOptions.CookieExpire) + val, _, ok := encryption.Validate(c, s.CookieOptions.CookieSecret, s.CookieOptions.CookieExpire) if !ok { return nil, errors.New("Cookie Signature not valid") } @@ -96,7 +96,7 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques // authentication details func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie { if value != "" { - value = cookie.SignedValue(s.CookieOptions.CookieSecret, s.CookieOptions.CookieName, value, now) + value = encryption.SignedValue(s.CookieOptions.CookieSecret, s.CookieOptions.CookieName, value, now) } c := s.makeCookie(req, s.CookieOptions.CookieName, value, s.CookieOptions.CookieExpire, now) if len(c.Value) > 4096-len(s.CookieOptions.CookieName) { diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 82e941e..ed33d72 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -13,10 +13,10 @@ import ( "time" "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/sessions" "github.com/pusher/oauth2_proxy/pkg/cookies" + "github.com/pusher/oauth2_proxy/pkg/encryption" ) // TicketData is a structure representing the ticket used in server session storage @@ -28,7 +28,7 @@ type TicketData struct { // SessionStore is an implementation of the sessions.SessionStore // interface that stores sessions in redis type SessionStore struct { - CookieCipher *cookie.Cipher + CookieCipher *encryption.Cipher CookieOptions *options.CookieOptions 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) } - val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) if !ok { 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) } - val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) if !ok { 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 func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie { if value != "" { - value = cookie.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now) + value = encryption.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now) } return cookies.MakeCookieFromOptions( req, @@ -230,7 +230,7 @@ func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, e } // An existing cookie exists, try to retrieve the ticket - val, _, ok := cookie.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) + val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire) if !ok { // Cookie is invalid, create a new ticket return newTicket() diff --git a/pkg/sessions/session_store_test.go b/pkg/sessions/session_store_test.go index 47ad4b7..fd0b0e5 100644 --- a/pkg/sessions/session_store_test.go +++ b/pkg/sessions/session_store_test.go @@ -13,10 +13,10 @@ import ( "github.com/alicebob/miniredis" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/pkg/apis/options" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/cookies" + "github.com/pusher/oauth2_proxy/pkg/encryption" "github.com/pusher/oauth2_proxy/pkg/sessions" sessionscookie "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" "github.com/pusher/oauth2_proxy/pkg/sessions/redis" @@ -158,7 +158,7 @@ var _ = Describe("NewSessionStore", func() { BeforeEach(func() { By("Using a valid cookie with a different providers session encoding") broken := "BrokenSessionFromADifferentSessionImplementation" - value := cookie.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now()) + value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now()) cookie := cookies.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now()) request.AddCookie(cookie) @@ -354,7 +354,7 @@ var _ = Describe("NewSessionStore", func() { _, err := rand.Read(secret) Expect(err).ToNot(HaveOccurred()) cookieOpts.CookieSecret = base64.URLEncoding.EncodeToString(secret) - cipher, err := cookie.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret)) + cipher, err := encryption.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret)) Expect(err).ToNot(HaveOccurred()) Expect(cipher).ToNot(BeNil()) opts.Cipher = cipher diff --git a/pkg/sessions/utils/utils.go b/pkg/sessions/utils/utils.go index 051e9cc..1fb27f4 100644 --- a/pkg/sessions/utils/utils.go +++ b/pkg/sessions/utils/utils.go @@ -3,17 +3,17 @@ package utils import ( "encoding/base64" - "github.com/pusher/oauth2_proxy/cookie" "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 -func CookieForSession(s *sessions.SessionState, c *cookie.Cipher) (string, error) { +func CookieForSession(s *sessions.SessionState, c *encryption.Cipher) (string, error) { return s.EncodeSessionState(c) } // SessionFromCookie deserializes a session from a cookie value -func SessionFromCookie(v string, c *cookie.Cipher) (s *sessions.SessionState, err error) { +func SessionFromCookie(v string, c *encryption.Cipher) (s *sessions.SessionState, err error) { return sessions.DecodeSessionState(v, c) } diff --git a/providers/provider_default.go b/providers/provider_default.go index 4716014..d87b939 100644 --- a/providers/provider_default.go +++ b/providers/provider_default.go @@ -10,8 +10,8 @@ import ( "net/url" "time" - "github.com/pusher/oauth2_proxy/cookie" "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 @@ -96,12 +96,12 @@ func (p *ProviderData) GetLoginURL(redirectURI, state string) string { } // CookieForSession serializes a session state for storage in a cookie -func (p *ProviderData) CookieForSession(s *sessions.SessionState, c *cookie.Cipher) (string, error) { +func (p *ProviderData) CookieForSession(s *sessions.SessionState, c *encryption.Cipher) (string, error) { return s.EncodeSessionState(c) } // SessionFromCookie deserializes a session from a cookie value -func (p *ProviderData) SessionFromCookie(v string, c *cookie.Cipher) (s *sessions.SessionState, err error) { +func (p *ProviderData) SessionFromCookie(v string, c *encryption.Cipher) (s *sessions.SessionState, err error) { return sessions.DecodeSessionState(v, c) } diff --git a/providers/providers.go b/providers/providers.go index 57ace41..baf723d 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -1,8 +1,8 @@ package providers import ( - "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/encryption" ) // Provider represents an upstream identity provider implementation @@ -15,8 +15,8 @@ type Provider interface { ValidateSessionState(*sessions.SessionState) bool GetLoginURL(redirectURI, finalRedirect string) string RefreshSessionIfNeeded(*sessions.SessionState) (bool, error) - SessionFromCookie(string, *cookie.Cipher) (*sessions.SessionState, error) - CookieForSession(*sessions.SessionState, *cookie.Cipher) (string, error) + SessionFromCookie(string, *encryption.Cipher) (*sessions.SessionState, error) + CookieForSession(*sessions.SessionState, *encryption.Cipher) (string, error) } // New provides a new Provider based on the configured provider string From fb9616160e7406c804e60f08bd73223ce5d1e40f Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 24 May 2019 17:08:48 +0100 Subject: [PATCH 045/128] Move logger to pkg/logger --- htpasswd.go | 2 +- http.go | 2 +- logging_handler.go | 2 +- logging_handler_test.go | 2 +- main.go | 2 +- oauthproxy.go | 2 +- oauthproxy_test.go | 2 +- options.go | 2 +- pkg/cookies/cookies.go | 2 +- {logger => pkg/logger}/logger.go | 0 pkg/requests/requests.go | 2 +- providers/azure.go | 2 +- providers/github.go | 2 +- providers/gitlab.go | 2 +- providers/google.go | 2 +- providers/internal_util.go | 2 +- templates.go | 2 +- validator.go | 2 +- watcher.go | 2 +- watcher_unsupported.go | 2 +- 20 files changed, 19 insertions(+), 19 deletions(-) rename {logger => pkg/logger}/logger.go (100%) diff --git a/htpasswd.go b/htpasswd.go index 0166e08..b7c8d57 100644 --- a/htpasswd.go +++ b/htpasswd.go @@ -7,7 +7,7 @@ import ( "io" "os" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "golang.org/x/crypto/bcrypt" ) diff --git a/http.go b/http.go index 8ccc6f6..2cee227 100644 --- a/http.go +++ b/http.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" ) // Server represents an HTTP server diff --git a/logging_handler.go b/logging_handler.go index 77c2fca..b4f829d 100644 --- a/logging_handler.go +++ b/logging_handler.go @@ -10,7 +10,7 @@ import ( "net/http" "time" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" ) // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status diff --git a/logging_handler_test.go b/logging_handler_test.go index f92c7e0..fd77e0f 100644 --- a/logging_handler_test.go +++ b/logging_handler_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" ) func TestLoggingHandler_ServeHTTP(t *testing.T) { diff --git a/main.go b/main.go index a66c4fc..8af6461 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( "github.com/BurntSushi/toml" options "github.com/mreiferson/go-options" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" ) func main() { diff --git a/oauthproxy.go b/oauthproxy.go index 62d1a18..fc4bb43 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -14,7 +14,7 @@ import ( "time" "github.com/mbland/hmacauth" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/encryption" "github.com/pusher/oauth2_proxy/providers" diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 1d09bbb..2fa3e00 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -15,7 +15,7 @@ import ( "time" "github.com/mbland/hmacauth" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" "github.com/pusher/oauth2_proxy/providers" diff --git a/options.go b/options.go index 2b506e3..5053e94 100644 --- a/options.go +++ b/options.go @@ -17,7 +17,7 @@ import ( oidc "github.com/coreos/go-oidc" "github.com/dgrijalva/jwt-go" "github.com/mbland/hmacauth" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/options" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/encryption" diff --git a/pkg/cookies/cookies.go b/pkg/cookies/cookies.go index 08e6a9b..5a7343b 100644 --- a/pkg/cookies/cookies.go +++ b/pkg/cookies/cookies.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/options" ) diff --git a/logger/logger.go b/pkg/logger/logger.go similarity index 100% rename from logger/logger.go rename to pkg/logger/logger.go diff --git a/pkg/requests/requests.go b/pkg/requests/requests.go index aac22e4..82d1176 100644 --- a/pkg/requests/requests.go +++ b/pkg/requests/requests.go @@ -7,7 +7,7 @@ import ( "net/http" "github.com/bitly/go-simplejson" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" ) // Request parses the request body into a simplejson.Json object diff --git a/providers/azure.go b/providers/azure.go index 3154432..12e2320 100644 --- a/providers/azure.go +++ b/providers/azure.go @@ -7,7 +7,7 @@ import ( "net/url" "github.com/bitly/go-simplejson" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/requests" ) diff --git a/providers/github.go b/providers/github.go index b60ffe1..e1a7ed8 100644 --- a/providers/github.go +++ b/providers/github.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" ) diff --git a/providers/gitlab.go b/providers/gitlab.go index c9a4a1f..f8e6739 100644 --- a/providers/gitlab.go +++ b/providers/gitlab.go @@ -4,7 +4,7 @@ import ( "net/http" "net/url" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/requests" ) diff --git a/providers/google.go b/providers/google.go index 6f29c2c..e7821e2 100644 --- a/providers/google.go +++ b/providers/google.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "golang.org/x/oauth2" "golang.org/x/oauth2/google" diff --git a/providers/internal_util.go b/providers/internal_util.go index bb5f4f5..0cf2a12 100644 --- a/providers/internal_util.go +++ b/providers/internal_util.go @@ -5,7 +5,7 @@ import ( "net/http" "net/url" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/requests" ) diff --git a/templates.go b/templates.go index ec1ba87..99637ed 100644 --- a/templates.go +++ b/templates.go @@ -4,7 +4,7 @@ import ( "html/template" "path" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" ) func loadTemplates(dir string) *template.Template { diff --git a/validator.go b/validator.go index 1a5c465..a0dc585 100644 --- a/validator.go +++ b/validator.go @@ -8,7 +8,7 @@ import ( "sync/atomic" "unsafe" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" ) // UserMap holds information from the authenticated emails file diff --git a/watcher.go b/watcher.go index 34e98d7..ed2bc0e 100644 --- a/watcher.go +++ b/watcher.go @@ -7,7 +7,7 @@ import ( "path/filepath" "time" - "github.com/pusher/oauth2_proxy/logger" + "github.com/pusher/oauth2_proxy/pkg/logger" fsnotify "gopkg.in/fsnotify/fsnotify.v1" ) diff --git a/watcher_unsupported.go b/watcher_unsupported.go index 1f6e3fc..ff708b7 100644 --- a/watcher_unsupported.go +++ b/watcher_unsupported.go @@ -2,7 +2,7 @@ package main -import "github.com/pusher/oauth2_proxy/logger" +import "github.com/pusher/oauth2_proxy/pkg/logger" func WatchForUpdates(filename string, done <-chan bool, action func()) { logger.Printf("file watching not implemented on this platform") From 417fde190cf719999cd8cf9d3aa6cb06a7c1ba30 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Sat, 15 Jun 2019 11:22:41 +0200 Subject: [PATCH 046/128] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3fc3d..634cbd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ## Changes since v3.2.0 +- [#187](https://github.com/pusher/oauth2_proxy/pull/187) Move root packages to pkg folder (@JoelSpeed) - [#175](https://github.com/pusher/outh2_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/outh2_proxy/pull/155) Add RedisSessionStore implementation (@brianv0, @JoelSpeed) @@ -24,7 +25,7 @@ - `-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 + - Redis Sessions are stored encrypted with a per-session secret - Added tests for server based session stores - [#168](https://github.com/pusher/outh2_proxy/pull/168) Drop Go 1.11 support in Travis (@JoelSpeed) - [#169](https://github.com/pusher/outh2_proxy/pull/169) Update Alpine to 3.9 (@kskewes) From 636669092744c0362dbec14471b8b7f59b88509c Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Sat, 15 Jun 2019 11:33:29 +0200 Subject: [PATCH 047/128] Fix gofmt for changed files --- oauthproxy.go | 2 +- oauthproxy_test.go | 2 +- options.go | 2 +- pkg/cookies/cookies.go | 2 +- providers/azure.go | 2 +- providers/github.go | 2 +- providers/gitlab.go | 2 +- providers/google.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index fc4bb43..b431d67 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -14,9 +14,9 @@ import ( "time" "github.com/mbland/hmacauth" - "github.com/pusher/oauth2_proxy/pkg/logger" 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/yhat/wsutil" ) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 2fa3e00..b278fd4 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -15,8 +15,8 @@ import ( "time" "github.com/mbland/hmacauth" - "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/sessions/cookie" "github.com/pusher/oauth2_proxy/providers" "github.com/stretchr/testify/assert" diff --git a/options.go b/options.go index 5053e94..c7d5d52 100644 --- a/options.go +++ b/options.go @@ -17,10 +17,10 @@ import ( oidc "github.com/coreos/go-oidc" "github.com/dgrijalva/jwt-go" "github.com/mbland/hmacauth" - "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/options" sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/encryption" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/sessions" "github.com/pusher/oauth2_proxy/providers" "gopkg.in/natefinch/lumberjack.v2" diff --git a/pkg/cookies/cookies.go b/pkg/cookies/cookies.go index 5a7343b..75b93e4 100644 --- a/pkg/cookies/cookies.go +++ b/pkg/cookies/cookies.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/options" + "github.com/pusher/oauth2_proxy/pkg/logger" ) // MakeCookie constructs a cookie from the given parameters, diff --git a/providers/azure.go b/providers/azure.go index 12e2320..653090b 100644 --- a/providers/azure.go +++ b/providers/azure.go @@ -7,8 +7,8 @@ import ( "net/url" "github.com/bitly/go-simplejson" - "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/requests" ) diff --git a/providers/github.go b/providers/github.go index e1a7ed8..ba58bb1 100644 --- a/providers/github.go +++ b/providers/github.go @@ -10,8 +10,8 @@ import ( "strconv" "strings" - "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/logger" ) // GitHubProvider represents an GitHub based Identity Provider diff --git a/providers/gitlab.go b/providers/gitlab.go index f8e6739..663ebd4 100644 --- a/providers/gitlab.go +++ b/providers/gitlab.go @@ -4,8 +4,8 @@ import ( "net/http" "net/url" - "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/requests" ) diff --git a/providers/google.go b/providers/google.go index e7821e2..6f53887 100644 --- a/providers/google.go +++ b/providers/google.go @@ -13,8 +13,8 @@ import ( "strings" "time" - "github.com/pusher/oauth2_proxy/pkg/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/logger" "golang.org/x/oauth2" "golang.org/x/oauth2/google" admin "google.golang.org/api/admin/directory/v1" From 8083501da68e19c8633726bcab87ea680d9048a0 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 17 Jan 2019 12:49:14 -0800 Subject: [PATCH 048/128] Support JWT Bearer Token and Pass through --- docs/configuration/configuration.md | 2 + main.go | 3 + oauthproxy.go | 187 ++++++++++++++++++++++------ options.go | 63 ++++++++-- 4 files changed, 213 insertions(+), 42 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index d631eaf..1295269 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -41,6 +41,7 @@ Usage of oauth2_proxy: -custom-templates-dir string: path to custom html templates -display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true) -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email + -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) -flush-interval: period between flushing response buffers when streaming responses (default "1s") -footer string: custom footer string. Use "-" to disable default footer. -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) @@ -89,6 +90,7 @@ Usage of oauth2_proxy: -signature-key string: GAP-Signature request signature key (algorithm:secretkey) -skip-auth-preflight: will skip authentication for OPTIONS requests -skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times) + -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS diff --git a/main.go b/main.go index a66c4fc..8c86271 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ func main() { whitelistDomains := StringArray{} upstreams := StringArray{} skipAuthRegex := StringArray{} + jwtIssuers := StringArray{} googleGroups := StringArray{} redisSentinelConnectionURLs := StringArray{} @@ -48,6 +49,8 @@ func main() { flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS") flagSet.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") + 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(&whitelistDomains, "whitelist-domain", "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)") diff --git a/oauthproxy.go b/oauthproxy.go index ef97415..f7bbef3 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -1,6 +1,7 @@ package main import ( + "context" b64 "encoding/base64" "errors" "fmt" @@ -13,6 +14,7 @@ import ( "strings" "time" + "github.com/coreos/go-oidc" "github.com/mbland/hmacauth" "github.com/pusher/oauth2_proxy/cookie" "github.com/pusher/oauth2_proxy/logger" @@ -92,6 +94,8 @@ type OAuthProxy struct { PassAuthorization bool skipAuthRegex []string skipAuthPreflight bool + skipJwtBearerTokens bool + jwtBearerVerifiers []*oidc.IDTokenVerifier compiledRegex []*regexp.Regexp templates *template.Template Footer string @@ -206,6 +210,12 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { 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 if redirectURL.Path == "" { redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix) @@ -239,25 +249,27 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix), AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix), - ProxyPrefix: opts.ProxyPrefix, - provider: opts.provider, - sessionStore: opts.sessionStore, - serveMux: serveMux, - redirectURL: redirectURL, - whitelistDomains: opts.WhitelistDomains, - skipAuthRegex: opts.SkipAuthRegex, - skipAuthPreflight: opts.SkipAuthPreflight, - compiledRegex: opts.CompiledRegex, - SetXAuthRequest: opts.SetXAuthRequest, - PassBasicAuth: opts.PassBasicAuth, - PassUserHeaders: opts.PassUserHeaders, - BasicAuthPassword: opts.BasicAuthPassword, - PassAccessToken: opts.PassAccessToken, - SetAuthorization: opts.SetAuthorization, - PassAuthorization: opts.PassAuthorization, - SkipProviderButton: opts.SkipProviderButton, - templates: loadTemplates(opts.CustomTemplatesDir), - Footer: opts.Footer, + ProxyPrefix: opts.ProxyPrefix, + provider: opts.provider, + sessionStore: opts.sessionStore, + serveMux: serveMux, + redirectURL: redirectURL, + whitelistDomains: opts.WhitelistDomains, + skipAuthRegex: opts.SkipAuthRegex, + skipAuthPreflight: opts.SkipAuthPreflight, + skipJwtBearerTokens: opts.SkipJwtBearerTokens, + jwtBearerVerifiers: opts.jwtBearerVerifiers, + compiledRegex: opts.CompiledRegex, + SetXAuthRequest: opts.SetXAuthRequest, + PassBasicAuth: opts.PassBasicAuth, + PassUserHeaders: opts.PassUserHeaders, + BasicAuthPassword: opts.BasicAuthPassword, + PassAccessToken: opts.PassAccessToken, + SetAuthorization: opts.SetAuthorization, + PassAuthorization: opts.PassAuthorization, + SkipProviderButton: opts.SkipProviderButton, + templates: loadTemplates(opts.CustomTemplatesDir), + Footer: opts.Footer, } } @@ -693,26 +705,42 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) { // 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 + + if p.skipJwtBearerTokens && req.Header.Get("Authorization") != "" { + session, err = p.GetJwtSession(req) + if err != nil { + logger.Printf("Error validating JWT token from Authorization header: %s", err) + } + if session != nil { + saveSession = false + } + } + remoteAddr := getRemoteAddr(req) - - session, err := p.LoadCookiedSession(req) - if err != nil { - logger.Printf("Error loading cookied session: %s", err) - } - if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { - logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) - saveSession = true + if session == nil { + session, err = p.LoadCookiedSession(req) + if err != nil { + logger.Printf("Error loading cookied session: %s", err) + } + if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { + logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) + saveSession = true + } } - 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) - clearSession = true - session = nil - } else if ok { - saveSession = true - revalidated = true + if session != 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) + clearSession = true + session = nil + } else if ok { + saveSession = true + revalidated = true + } } if session != nil && session.IsExpired() { @@ -854,3 +882,92 @@ func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) { rw.Header().Set("Content-Type", applicationJSON) 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 { + 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 == "" { + return nil, fmt.Errorf("id_token did not contain an email") + } + + if claims.Verified != nil && !*claims.Verified { + return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) + } + user := strings.Split(claims.Email, "@")[0] + + session = &sessionsapi.SessionState{ + AccessToken: rawBearerToken, + IDToken: rawBearerToken, + RefreshToken: "", + ExpiresOn: bearerToken.Expiry, + Email: claims.Email, + User: user, + } + 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) + } + + var rawBearerToken string + if s[0] == "Bearer" { + 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 + jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`) + 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 + } + } else { + return "", fmt.Errorf("invalid authorization header %s", auth) + } + + return rawBearerToken, nil +} diff --git a/options.go b/options.go index 0460bce..331c8f6 100644 --- a/options.go +++ b/options.go @@ -61,6 +61,8 @@ type Options struct { 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"` + SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"` + ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"` 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"` PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"` @@ -110,13 +112,15 @@ type Options struct { GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"` // internal values that are set after config validation - redirectURL *url.URL - proxyURLs []*url.URL - CompiledRegex []*regexp.Regexp - provider providers.Provider - sessionStore sessionsapi.SessionStore - signatureData *SignatureData - oidcVerifier *oidc.IDTokenVerifier + redirectURL *url.URL + proxyURLs []*url.URL + CompiledRegex []*regexp.Regexp + provider providers.Provider + sessionStore sessionsapi.SessionStore + signatureData *SignatureData + oidcVerifier *oidc.IDTokenVerifier + jwtIssuers [][]string + jwtBearerVerifiers []*oidc.IDTokenVerifier } // SignatureData holds hmacauth signature hash and key @@ -244,6 +248,38 @@ 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 { + msgs = parseJwtIssuers(o, msgs) + for _, pair := range o.jwtIssuers { + issuer, audience := pair[0], pair[1] + config := &oidc.Config{ + ClientID: audience, + } + // Try as an OpenID Connect Provider first + var verifier *oidc.IDTokenVerifier + provider, err := oidc.NewProvider(context.Background(), issuer) + if err != nil { + // Try as JWKS URI + jwksURI := strings.TrimSuffix(issuer, "/") + "/.well-known/jwks.json" + _, err := http.NewRequest("GET", jwksURI, nil) + if err != nil { + return err + } + verifier = oidc.NewVerifier(issuer, oidc.NewRemoteKeySet(context.Background(), jwksURI), config) + } else { + verifier = provider.Verifier(config) + } + o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier) + } + } + } + o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs) for _, u := range o.Upstreams { @@ -430,6 +466,19 @@ func parseSignatureKey(o *Options, msgs []string) []string { return msgs } +func parseJwtIssuers(o *Options, msgs []string) []string { + for _, jwtVerifier := range o.ExtraJwtIssuers { + components := strings.Split(jwtVerifier, "=") + if len(components) < 2 { + return append(msgs, "invalid jwt verifier uri=audience spec: "+ + jwtVerifier) + } + uri, audience := components[0], strings.Join(components[1:], "=") + o.jwtIssuers = append(o.jwtIssuers, []string{uri, audience}) + } + return msgs +} + func validateCookieName(o *Options, msgs []string) []string { cookie := &http.Cookie{Name: o.CookieName} if cookie.String() == "" { From b895f49c52ef3d37fa4ab55e656b5e279d3237d9 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Tue, 12 Feb 2019 10:32:26 -0800 Subject: [PATCH 049/128] Use idToken expiry because that's the time checked for refresh RefreshSessionIfNeeded checks the token expiry, we want to use the ID token's expiry --- providers/oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/oidc.go b/providers/oidc.go index 08ea082..b0d2dda 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -128,7 +128,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok IDToken: rawIDToken, RefreshToken: token.RefreshToken, CreatedAt: time.Now(), - ExpiresOn: token.Expiry, + ExpiresOn: idToken.Expiry, Email: claims.Email, User: claims.Subject, }, nil From 8413c30c26491dc2f234f88a146fdb7f4cf7db8d Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 14 Feb 2019 15:00:49 -0800 Subject: [PATCH 050/128] Update changelog with info about -skip-jwt-bearer-tokens --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef731cb..136775f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ ## Changes since v3.2.0 +- [#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. + - 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/outh2_proxy/pull/180) Minor refactor of core proxying path (@aeijdenberg). - [#175](https://github.com/pusher/outh2_proxy/pull/175) Bump go-oidc to v2.0.0 (@aeijdenberg). - Includes fix for potential signature checking issue when OIDC discovery is skipped. From 187960e9d884b38644aa8e015b9cec9c548c9a2e Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 24 Apr 2019 08:25:29 -0700 Subject: [PATCH 051/128] Improve token pattern matching Unit tests for token discovery --- oauthproxy.go | 12 ++++----- oauthproxy_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index f7bbef3..714c7a3 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -712,7 +712,7 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R if p.skipJwtBearerTokens && req.Header.Get("Authorization") != "" { session, err = p.GetJwtSession(req) if err != nil { - logger.Printf("Error validating JWT token from Authorization header: %s", err) + logger.Printf("Error retrieving session from token in Authorization header: %s", err) } if session != nil { saveSession = false @@ -938,9 +938,9 @@ func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) { 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" { + 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 @@ -955,7 +955,6 @@ func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) { user, password := pair[0], pair[1] // check user, user+password, or just password for a token - jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`) if jwtRegex.MatchString(user) { // Support blank passwords or magic `x-oauth-basic` passwords - nothing else if password == "" || password == "x-oauth-basic" { @@ -965,8 +964,9 @@ func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) { // support passwords and ignore user rawBearerToken = password } - } else { - return "", fmt.Errorf("invalid authorization header %s", auth) + } + if rawBearerToken == "" { + return "", fmt.Errorf("no valid bearer token found in authorization header") } return rawBearerToken, nil diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 1d09bbb..493ce72 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -3,6 +3,7 @@ package main import ( "crypto" "encoding/base64" + "fmt" "io" "io/ioutil" "net" @@ -1132,3 +1133,65 @@ func TestClearSingleCookie(t *testing.T) { assert.Equal(t, 1, len(header["Set-Cookie"]), "should have 1 set-cookie header entries") } + +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) +} From 69cb34a04e6d00d9dca03940cddd78e99fcef4dd Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Fri, 26 Apr 2019 19:16:45 -0700 Subject: [PATCH 052/128] Add unit tests for JWT -> session translation --- oauthproxy_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 493ce72..042e6a5 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1,9 +1,11 @@ package main import ( + "context" "crypto" "encoding/base64" "fmt" + "github.com/coreos/go-oidc" "io" "io/ioutil" "net" @@ -1134,6 +1136,53 @@ func TestClearSingleCookie(t *testing.T) { 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}) + p := OAuthProxy{} + p.jwtBearerVerifiers = append(p.jwtBearerVerifiers, verifier) + getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}} + + // Bearer + getReq.Header = map[string][]string{ + "Authorization": {fmt.Sprintf("Bearer %s", goodJwt)}, + } + session, _ := p.GetJwtSession(getReq) + assert.Equal(t, session.User, "john") + assert.Equal(t, session.Email, "john@example.com") + assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0)) + assert.Equal(t, session.IDToken, goodJwt) +} + func TestFindJwtBearerToken(t *testing.T) { p := OAuthProxy{CookieName: "oauth2", CookieDomain: "abc"} getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}} From 1ff74d322a112331f0dd62524b39b1a908ce8e23 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Fri, 26 Apr 2019 19:47:53 -0700 Subject: [PATCH 053/128] Fix imports --- oauthproxy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 042e6a5..0704596 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -5,7 +5,6 @@ import ( "crypto" "encoding/base64" "fmt" - "github.com/coreos/go-oidc" "io" "io/ioutil" "net" @@ -17,6 +16,7 @@ import ( "testing" "time" + "github.com/coreos/go-oidc" "github.com/mbland/hmacauth" "github.com/pusher/oauth2_proxy/logger" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" From 10f65e03814afd55534fafc828860348d883dbe8 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Tue, 30 Apr 2019 14:06:11 -0700 Subject: [PATCH 054/128] Add a more realistic test for JWT passthrough --- oauthproxy_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 0704596..bff3682 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1170,17 +1170,67 @@ func TestGetJwtSession(t *testing.T) { &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) p := OAuthProxy{} p.jwtBearerVerifiers = append(p.jwtBearerVerifiers, verifier) - getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: "example.com"}} + + req, _ := http.NewRequest("GET", "/", strings.NewReader("")) + authHeader := fmt.Sprintf("Bearer %s", goodJwt) + req.Header = map[string][]string{ + "Authorization": {authHeader}, + } // Bearer - getReq.Header = map[string][]string{ - "Authorization": {fmt.Sprintf("Bearer %s", goodJwt)}, - } - session, _ := p.GetJwtSession(getReq) + session, _ := p.GetJwtSession(req) assert.Equal(t, session.User, "john") assert.Equal(t, session.Email, "john@example.com") assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0)) assert.Equal(t, session.IDToken, goodJwt) + + jwtProviderServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%#v", r) + var payload string + payload = r.Header.Get("Authorization") + if payload == "" { + payload = "No Authorization header found." + } + w.WriteHeader(200) + w.Write([]byte(payload)) + })) + + opts := NewOptions() + opts.Upstreams = append(opts.Upstreams, jwtProviderServer.URL) + opts.PassAuthorization = true + opts.SetAuthorization = true + opts.SetXAuthRequest = true + opts.CookieSecret = "0123456789abcdef0123" + opts.SkipJwtBearerTokens = true + opts.Validate() + + // We can't actually use opts.Validate() because it will attempt to find a jwks URI + opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier) + + providerURL, _ := url.Parse(jwtProviderServer.URL) + const emailAddress = "john@example.com" + + opts.provider = NewTestProvider(providerURL, emailAddress) + jwtTestProxy := NewOAuthProxy(opts, func(email string) bool { + return email == emailAddress + }) + + rw := httptest.NewRecorder() + jwtTestProxy.ServeHTTP(rw, req) + if rw.Code >= 400 { + t.Fatalf("expected 3xx got %d", rw.Code) + } + + // Check PassAuthorization, should overwrite Basic header + assert.Equal(t, req.Header.Get("Authorization"), authHeader) + assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john") + assert.Equal(t, req.Header.Get("X-Forwarded-Email"), "john@example.com") + + // SetAuthorization and SetXAuthRequest + assert.Equal(t, rw.Header().Get("Authorization"), authHeader) + assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john") + assert.Equal(t, rw.Header().Get("X-Auth-Request-Email"), "john@example.com") + } func TestFindJwtBearerToken(t *testing.T) { From 79acef90366f24d3e898c90d78cfe19070492629 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 09:18:54 -0700 Subject: [PATCH 055/128] Clarify skip-jwt-bearer-tokens default and add env tags --- main.go | 2 +- options.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 8c86271..054bb30 100644 --- a/main.go +++ b/main.go @@ -49,7 +49,7 @@ func main() { flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS") flagSet.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") + 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") diff --git a/options.go b/options.go index 331c8f6..a8b0330 100644 --- a/options.go +++ b/options.go @@ -61,8 +61,8 @@ type Options struct { 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"` - SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"` - ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"` + 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"` 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"` From 58b06ce761e0b706c9ba7e3081aa0441e75527de Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 09:22:25 -0700 Subject: [PATCH 056/128] Fall back to using sub if email is none (as in PR #57) --- oauthproxy.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oauthproxy.go b/oauthproxy.go index 714c7a3..e79793b 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -901,6 +901,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState } var claims struct { + Subject string `json:"sub"` Email string `json:"email"` Verified *bool `json:"email_verified"` } @@ -910,7 +911,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState } if claims.Email == "" { - return nil, fmt.Errorf("id_token did not contain an email") + claims.Email = claims.Subject } if claims.Verified != nil && !*claims.Verified { From 350c1cd127515bea8b617944b0d2621d7017ea95 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 10:00:54 -0700 Subject: [PATCH 057/128] Use JwtIssuer struct when parsing --- options.go | 68 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/options.go b/options.go index a8b0330..511666b 100644 --- a/options.go +++ b/options.go @@ -119,7 +119,6 @@ type Options struct { sessionStore sessionsapi.SessionStore signatureData *SignatureData oidcVerifier *oidc.IDTokenVerifier - jwtIssuers [][]string jwtBearerVerifiers []*oidc.IDTokenVerifier } @@ -172,6 +171,12 @@ func NewOptions() *Options { } } +// JwtIssuer hold parsed JWT issuer info that's used to construct a verifier. +type JwtIssuer struct { + issuerURI string + audience string +} + func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) { parsed, err := url.Parse(toParse) if err != nil { @@ -255,25 +260,12 @@ func (o *Options) Validate() error { } // Configure extra issuers if len(o.ExtraJwtIssuers) > 0 { - msgs = parseJwtIssuers(o, msgs) - for _, pair := range o.jwtIssuers { - issuer, audience := pair[0], pair[1] - config := &oidc.Config{ - ClientID: audience, - } - // Try as an OpenID Connect Provider first - var verifier *oidc.IDTokenVerifier - provider, err := oidc.NewProvider(context.Background(), issuer) + var jwtIssuers []JwtIssuer + jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs) + for _, jwtIssuer := range jwtIssuers { + verifier, err := newVerifierFromJwtIssuer(jwtIssuer) if err != nil { - // Try as JWKS URI - jwksURI := strings.TrimSuffix(issuer, "/") + "/.well-known/jwks.json" - _, err := http.NewRequest("GET", jwksURI, nil) - if err != nil { - return err - } - verifier = oidc.NewVerifier(issuer, oidc.NewRemoteKeySet(context.Background(), jwksURI), config) - } else { - verifier = provider.Verifier(config) + msgs = append(msgs, fmt.Sprintf("error building verifiers: %s", err)) } o.jwtBearerVerifiers = append(o.jwtBearerVerifiers, verifier) } @@ -466,17 +458,43 @@ func parseSignatureKey(o *Options, msgs []string) []string { return msgs } -func parseJwtIssuers(o *Options, msgs []string) []string { - for _, jwtVerifier := range o.ExtraJwtIssuers { +// 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 { - return append(msgs, "invalid jwt verifier uri=audience spec: "+ - jwtVerifier) + msgs = append(msgs, fmt.Sprintf("invalid jwt verifier uri=audience spec: %s", jwtVerifier)) + continue } uri, audience := components[0], strings.Join(components[1:], "=") - o.jwtIssuers = append(o.jwtIssuers, []string{uri, audience}) + parsedIssuers = append(parsedIssuers, JwtIssuer{issuerURI: uri, audience: audience}) } - return msgs + 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 { From 54d91c69cca9b90e04d4fd194760a6b0f5c687b9 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 1 May 2019 10:19:00 -0700 Subject: [PATCH 058/128] Use logger instead of log --- oauthproxy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index bff3682..decae03 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1185,7 +1185,7 @@ func TestGetJwtSession(t *testing.T) { assert.Equal(t, session.IDToken, goodJwt) jwtProviderServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Printf("%#v", r) + logger.Printf("%#v", r) var payload string payload = r.Header.Get("Authorization") if payload == "" { From 48dbb391bc1103461bfb4c84a93de2c97737fb86 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 20 May 2019 15:24:59 -0700 Subject: [PATCH 059/128] Move around CHANGELOG.md update --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 136775f..6858187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,6 @@ - [#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. From 2f6dcf3b5f93ec3439e8e87298bb97688862ac34 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 5 Jun 2019 16:08:34 -0700 Subject: [PATCH 060/128] Move refreshing code to block acquiring cookied session --- oauthproxy.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index e79793b..e0f5e48 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -725,21 +725,21 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R if err != nil { logger.Printf("Error loading cookied session: %s", err) } - if session != nil && session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { - logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) - saveSession = true - } - } - if session != 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) - clearSession = true - session = nil - } else if ok { - saveSession = true - revalidated = true + if session != nil { + if session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) { + logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh) + saveSession = true + } + + if ok, err := p.provider.RefreshSessionIfNeeded(session); err != nil { + logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session) + clearSession = true + session = nil + } else if ok { + saveSession = true + revalidated = true + } } } From 100f126405043fe42ab13f9679da1c94f972708e Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Wed, 5 Jun 2019 16:09:29 -0700 Subject: [PATCH 061/128] Make JwtIssuer struct private --- options.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/options.go b/options.go index 511666b..8c73eb9 100644 --- a/options.go +++ b/options.go @@ -171,8 +171,8 @@ func NewOptions() *Options { } } -// JwtIssuer hold parsed JWT issuer info that's used to construct a verifier. -type JwtIssuer struct { +// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier. +type jwtIssuer struct { issuerURI string audience string } @@ -260,7 +260,7 @@ func (o *Options) Validate() error { } // Configure extra issuers if len(o.ExtraJwtIssuers) > 0 { - var jwtIssuers []JwtIssuer + var jwtIssuers []jwtIssuer jwtIssuers, msgs = parseJwtIssuers(o.ExtraJwtIssuers, msgs) for _, jwtIssuer := range jwtIssuers { verifier, err := newVerifierFromJwtIssuer(jwtIssuer) @@ -459,9 +459,9 @@ func parseSignatureKey(o *Options, msgs []string) []string { } // 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 +// 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 { @@ -469,14 +469,14 @@ func parseJwtIssuers(issuers []string, msgs []string) ([]JwtIssuer, []string) { continue } uri, audience := components[0], strings.Join(components[1:], "=") - parsedIssuers = append(parsedIssuers, JwtIssuer{issuerURI: uri, audience: audience}) + parsedIssuers = append(parsedIssuers, jwtIssuer{issuerURI: uri, audience: audience}) } return parsedIssuers, msgs } -// newVerifierFromJwtIssuer takes in issuer information in JwtIssuer info and returns +// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns // a verifier for that issuer. -func newVerifierFromJwtIssuer(jwtIssuer JwtIssuer) (*oidc.IDTokenVerifier, error) { +func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error) { config := &oidc.Config{ ClientID: jwtIssuer.audience, } From 5a50f6223f33d2e68ea4b23e9ffae977d8423bbe Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 17 Jun 2019 12:58:40 -0700 Subject: [PATCH 062/128] Do not infer username from email --- oauthproxy.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index e0f5e48..9afa991 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -917,7 +917,6 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState if claims.Verified != nil && !*claims.Verified { return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) } - user := strings.Split(claims.Email, "@")[0] session = &sessionsapi.SessionState{ AccessToken: rawBearerToken, @@ -925,7 +924,7 @@ func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState RefreshToken: "", ExpiresOn: bearerToken.Expiry, Email: claims.Email, - User: user, + User: claims.Email, } return session, nil } From 058ffd1047e7ec35e9be6288c9f4ef9ac714abb3 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Mon, 17 Jun 2019 13:11:49 -0700 Subject: [PATCH 063/128] Update unit tests for username --- oauthproxy_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index decae03..34bf030 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -1179,7 +1179,7 @@ func TestGetJwtSession(t *testing.T) { // Bearer session, _ := p.GetJwtSession(req) - assert.Equal(t, session.User, "john") + 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) @@ -1223,12 +1223,12 @@ func TestGetJwtSession(t *testing.T) { // Check PassAuthorization, should overwrite Basic header assert.Equal(t, req.Header.Get("Authorization"), authHeader) - assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john") + assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john@example.com") assert.Equal(t, req.Header.Get("X-Forwarded-Email"), "john@example.com") // SetAuthorization and SetXAuthRequest assert.Equal(t, rw.Header().Get("Authorization"), authHeader) - assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john") + assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john@example.com") assert.Equal(t, rw.Header().Get("X-Auth-Request-Email"), "john@example.com") } From bd651df3c25c5e82eec093b5c7b5ef1067bbf489 Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 20 Jun 2019 13:40:04 -0700 Subject: [PATCH 064/128] Ensure groups in JWT Bearer tokens are also validated Fix a minor auth logging bug --- oauthproxy.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 9afa991..99dfb36 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -650,7 +650,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { } http.Redirect(rw, req, redirect, 302) } else { - logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Invalid authentication via OAuth2: unauthorized") + logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized") p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account") } } @@ -759,11 +759,13 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R } } - if session != nil && session.Email != "" && !p.Validator(session.Email) { - logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session) - session = nil - saveSession = false - clearSession = true + if session != nil && session.Email != "" { + if !p.Validator(session.Email) || !p.provider.ValidateGroup(session.Email) { + logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session) + session = nil + saveSession = false + clearSession = true + } } if saveSession && session != nil { From 3881955605c9edbec15f2a72cb2cbb9ee4a90e2a Mon Sep 17 00:00:00 2001 From: Brian Van Klaveren Date: Thu, 20 Jun 2019 16:57:20 -0700 Subject: [PATCH 065/128] Update unit tests for ValidateGroup --- oauthproxy_test.go | 137 +++++++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 48 deletions(-) diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 34bf030..35ed59a 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -229,8 +229,9 @@ func TestIsValidRedirect(t *testing.T) { type TestProvider struct { *providers.ProviderData - EmailAddress string - ValidToken bool + EmailAddress string + ValidToken bool + GroupValidator func(string) bool } func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider { @@ -255,6 +256,9 @@ func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider { Scope: "profile.email", }, EmailAddress: emailAddress, + GroupValidator: func(s string) bool { + return true + }, } } @@ -266,6 +270,13 @@ func (tp *TestProvider) ValidateSessionState(session *sessions.SessionState) boo 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) { providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { logger.Printf("%#v", r) @@ -791,6 +802,25 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) { 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) { var pcTest ProcessCookieTest @@ -1168,69 +1198,80 @@ func TestGetJwtSession(t *testing.T) { keyset := NoOpKeySet{} verifier := oidc.NewVerifier("https://issuer.example.com", keyset, &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true}) - p := OAuthProxy{} - p.jwtBearerVerifiers = append(p.jwtBearerVerifiers, verifier) - req, _ := http.NewRequest("GET", "/", strings.NewReader("")) + 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) - req.Header = map[string][]string{ + test.req.Header = map[string][]string{ "Authorization": {authHeader}, } // Bearer - session, _ := p.GetJwtSession(req) + 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) - jwtProviderServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logger.Printf("%#v", r) - var payload string - payload = r.Header.Get("Authorization") - if payload == "" { - payload = "No Authorization header found." - } - w.WriteHeader(200) - w.Write([]byte(payload)) - })) - - opts := NewOptions() - opts.Upstreams = append(opts.Upstreams, jwtProviderServer.URL) - opts.PassAuthorization = true - opts.SetAuthorization = true - opts.SetXAuthRequest = true - opts.CookieSecret = "0123456789abcdef0123" - opts.SkipJwtBearerTokens = true - opts.Validate() - - // We can't actually use opts.Validate() because it will attempt to find a jwks URI - opts.jwtBearerVerifiers = append(opts.jwtBearerVerifiers, verifier) - - providerURL, _ := url.Parse(jwtProviderServer.URL) - const emailAddress = "john@example.com" - - opts.provider = NewTestProvider(providerURL, emailAddress) - jwtTestProxy := NewOAuthProxy(opts, func(email string) bool { - return email == emailAddress - }) - - rw := httptest.NewRecorder() - jwtTestProxy.ServeHTTP(rw, req) - if rw.Code >= 400 { - t.Fatalf("expected 3xx got %d", rw.Code) + 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, req.Header.Get("Authorization"), authHeader) - assert.Equal(t, req.Header.Get("X-Forwarded-User"), "john@example.com") - assert.Equal(t, req.Header.Get("X-Forwarded-Email"), "john@example.com") + 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, rw.Header().Get("Authorization"), authHeader) - assert.Equal(t, rw.Header().Get("X-Auth-Request-User"), "john@example.com") - assert.Equal(t, rw.Header().Get("X-Auth-Request-Email"), "john@example.com") + 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) { From 411adf6f21ac6de81a2ade696b53b8d4d012f6a4 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 23 Jun 2019 20:40:59 +0100 Subject: [PATCH 066/128] Switch linter to golangci-lint --- .golangci.yml | 14 ++++++++++++++ .travis.yml | 3 +-- CHANGELOG.md | 1 + Dockerfile | 1 + Dockerfile.arm64 | 1 + Dockerfile.armv6 | 1 + Makefile | 12 +----------- configure | 4 ++-- 8 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..4dc2d94 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,14 @@ +run: + deadline: 120s +linters: + enable: + - govet + # TODO: Not supported by golang-ci - vetshadow + - golint + - ineffassign + - goconst + - deadcode + - gofmt + - goimports + enable-all: false + disable-all: true diff --git a/.travis.yml b/.travis.yml index d1151db..445eb11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,7 @@ install: - wget -O dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 - chmod +x dep - mv dep $GOPATH/bin/dep - - go get github.com/alecthomas/gometalinter - - gometalinter --install + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.17.1 script: - ./configure && make test sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 6858187..69d071f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - [#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. +- [#XX](https://github.com/pusher/outh2_proxy/pull/XX) Switch from gometalinter to golangci-lint # v3.2.0 diff --git a/Dockerfile b/Dockerfile index f6649a9..757a1f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM golang:1.12-stretch AS builder # Download tools 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 +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 7f011ba..f4bf495 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -3,6 +3,7 @@ FROM golang:1.12-stretch AS builder # Download tools 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 +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index e2c57b9..32bb124 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -3,6 +3,7 @@ FROM golang:1.12-stretch AS builder # Download tools 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 +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy diff --git a/Makefile b/Makefile index 720fd4d..3286bda 100644 --- a/Makefile +++ b/Makefile @@ -17,17 +17,7 @@ distclean: clean .PHONY: lint lint: - $(GOMETALINTER) --vendor --disable-all \ - --enable=vet \ - --enable=vetshadow \ - --enable=golint \ - --enable=ineffassign \ - --enable=goconst \ - --enable=deadcode \ - --enable=gofmt \ - --enable=goimports \ - --deadline=120s \ - --tests ./... + $(GOLANGCILINT) run .PHONY: dep dep: diff --git a/configure b/configure index 5d74683..f1b8ae6 100755 --- a/configure +++ b/configure @@ -126,7 +126,7 @@ check_for go check_go_version check_go_env check_for dep -check_for gometalinter +check_for golangci-lint echo @@ -135,7 +135,7 @@ cat <<- EOF > .env GO := "${tools[go]}" GO_VERSION := ${tools[go_version]} DEP := "${tools[dep]}" - GOMETALINTER := "${tools[gometalinter]}" + GOLANGCILINT := "${tools[golangci-lint]}" EOF echo "Environment configuration written to .env" From d24aacdb5c34630a7a8ca3b2f9fe0ef1c093a37d Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 23 Jun 2019 20:41:23 +0100 Subject: [PATCH 067/128] Fix lint errors --- http_test.go | 27 ++++++++++++++----------- oauthproxy.go | 17 +++++++++++++--- oauthproxy_test.go | 50 +++++++++++++++++++++++----------------------- options.go | 2 +- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/http_test.go b/http_test.go index b365d6f..f5ee142 100644 --- a/http_test.go +++ b/http_test.go @@ -8,6 +8,9 @@ import ( "github.com/stretchr/testify/assert" ) +const localhost = "127.0.0.1" +const host = "test-server" + func TestGCPHealthcheckLiveness(t *testing.T) { handler := func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("test")) @@ -16,8 +19,8 @@ func TestGCPHealthcheckLiveness(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/liveness_check", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host h.ServeHTTP(rw, r) assert.Equal(t, 200, rw.Code) @@ -32,8 +35,8 @@ func TestGCPHealthcheckReadiness(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/readiness_check", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host h.ServeHTTP(rw, r) assert.Equal(t, 200, rw.Code) @@ -48,8 +51,8 @@ func TestGCPHealthcheckNotHealthcheck(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/not_any_check", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host h.ServeHTTP(rw, r) assert.Equal(t, "test", rw.Body.String()) @@ -63,8 +66,8 @@ func TestGCPHealthcheckIngress(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) h.ServeHTTP(rw, r) @@ -80,8 +83,8 @@ func TestGCPHealthcheckNotIngress(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("GET", "/foo", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) h.ServeHTTP(rw, r) @@ -96,8 +99,8 @@ func TestGCPHealthcheckNotIngressPut(t *testing.T) { h := gcpHealthcheck(http.HandlerFunc(handler)) rw := httptest.NewRecorder() r, _ := http.NewRequest("PUT", "/", nil) - r.RemoteAddr = "127.0.0.1" - r.Host = "test-server" + r.RemoteAddr = localhost + r.Host = host r.Header.Set(userAgentHeader, googleHealthCheckUserAgent) h.ServeHTTP(rw, r) diff --git a/oauthproxy.go b/oauthproxy.go index 99dfb36..da56cfc 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -160,7 +160,7 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) { } // NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url -func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) (restProxy http.Handler) { +func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler { u.Path = "" proxy := NewReverseProxy(u, opts.FlushInterval) if !opts.PassHostHeader { @@ -176,7 +176,12 @@ func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.Hma wsURL := &url.URL{Scheme: wsScheme, Host: u.Host} wsProxy = wsutil.NewSingleHostReverseProxy(wsURL) } - return &UpstreamProxy{u.Host, proxy, wsProxy, auth} + return &UpstreamProxy{ + upstream: u.Host, + handler: proxy, + wsHandler: wsProxy, + auth: auth, + } } // NewOAuthProxy creates a new instance of OOuthProxy from the options provided @@ -201,7 +206,13 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { } logger.Printf("mapping path %q => file system %q", path, u.Path) proxy := NewFileServer(path, u.Path) - serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil}) + uProxy := UpstreamProxy{ + upstream: path, + handler: proxy, + wsHandler: nil, + auth: nil, + } + serveMux.Handle(path, &uProxy) default: panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme)) } diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 35ed59a..f114c7e 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -163,9 +163,9 @@ func TestEncodedSlashes(t *testing.T) { func TestRobotsTxt(t *testing.T) { opts := NewOptions() - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" - opts.CookieSecret = "xyzzyplugh" + opts.ClientID = "asdlkjx" + opts.ClientSecret = "alkgks" + opts.CookieSecret = "asdkugkj" opts.Validate() proxy := NewOAuthProxy(opts, func(string) bool { return true }) @@ -178,9 +178,9 @@ func TestRobotsTxt(t *testing.T) { func TestIsValidRedirect(t *testing.T) { opts := NewOptions() - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" - opts.CookieSecret = "xyzzyplugh" + opts.ClientID = "skdlfj" + opts.ClientSecret = "fgkdsgj" + opts.CookieSecret = "ljgiogbj" // Should match domains that are exactly foo.bar and any subdomain of bar.foo opts.WhitelistDomains = []string{"foo.bar", ".bar.foo"} opts.Validate() @@ -298,8 +298,8 @@ func TestBasicAuthPassword(t *testing.T) { // The CookieSecret must be 32 bytes in order to create the AES // cipher. opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp" - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" + opts.ClientID = "dlgkj" + opts.ClientSecret = "alkgret" opts.CookieSecure = false opts.PassBasicAuth = true opts.PassUserHeaders = true @@ -392,8 +392,8 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes // The CookieSecret must be 32 bytes in order to create the AES // cipher. t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp" - t.opts.ClientID = "bazquux" - t.opts.ClientSecret = "foobar" + t.opts.ClientID = "slgkj" + t.opts.ClientSecret = "gfjgojl" t.opts.CookieSecure = false t.opts.PassAccessToken = opts.PassAccessToken t.opts.Validate() @@ -518,9 +518,9 @@ func NewSignInPageTest(skipProvider bool) *SignInPageTest { var sipTest SignInPageTest sipTest.opts = NewOptions() - sipTest.opts.CookieSecret = "foobar" - sipTest.opts.ClientID = "bazquux" - sipTest.opts.ClientSecret = "xyzzyplugh" + sipTest.opts.CookieSecret = "adklsj2" + sipTest.opts.ClientID = "lkdgj" + sipTest.opts.ClientSecret = "sgiufgoi" sipTest.opts.SkipProviderButton = skipProvider sipTest.opts.Validate() @@ -624,8 +624,8 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi for _, modifier := range modifiers { modifier(pcTest.opts) } - pcTest.opts.ClientID = "bazquux" - pcTest.opts.ClientSecret = "xyzzyplugh" + pcTest.opts.ClientID = "asdfljk" + pcTest.opts.ClientSecret = "lkjfdsig" pcTest.opts.CookieSecret = "0123456789abcdefabcd" // First, set the CookieRefresh option so proxy.AesCipher is created, // needed to encrypt the access_token. @@ -860,9 +860,9 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) { opts := NewOptions() opts.Upstreams = append(opts.Upstreams, upstream.URL) - opts.ClientID = "bazquux" - opts.ClientSecret = "foobar" - opts.CookieSecret = "xyzzyplugh" + opts.ClientID = "aljsal" + opts.ClientSecret = "jglkfsdgj" + opts.CookieSecret = "dkfjgdls" opts.SkipAuthPreflight = true opts.Validate() @@ -999,8 +999,8 @@ func TestNoRequestSignature(t *testing.T) { func TestRequestSignatureGetRequest(t *testing.T) { st := NewSignatureTest() defer st.Close() - st.opts.SignatureKey = "sha1:foobar" - st.MakeRequestWithExpectedKey("GET", "", "foobar") + st.opts.SignatureKey = "sha1:7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d" + st.MakeRequestWithExpectedKey("GET", "", "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d") assert.Equal(t, 200, st.rw.Code) assert.Equal(t, st.rw.Body.String(), "signatures match") } @@ -1008,9 +1008,9 @@ func TestRequestSignatureGetRequest(t *testing.T) { func TestRequestSignaturePostRequest(t *testing.T) { st := NewSignatureTest() defer st.Close() - st.opts.SignatureKey = "sha1:foobar" + st.opts.SignatureKey = "sha1:d90df39e2d19282840252612dd7c81421a372f61" payload := `{ "hello": "world!" }` - st.MakeRequestWithExpectedKey("POST", payload, "foobar") + st.MakeRequestWithExpectedKey("POST", payload, "d90df39e2d19282840252612dd7c81421a372f61") assert.Equal(t, 200, st.rw.Code) assert.Equal(t, st.rw.Body.String(), "signatures match") } @@ -1056,9 +1056,9 @@ type ajaxRequestTest struct { func newAjaxRequestTest() *ajaxRequestTest { test := &ajaxRequestTest{} test.opts = NewOptions() - test.opts.CookieSecret = "foobar" - test.opts.ClientID = "bazquux" - test.opts.ClientSecret = "xyzzyplugh" + test.opts.CookieSecret = "sdflsw" + test.opts.ClientID = "gkljfdl" + test.opts.ClientSecret = "sdflkjs" test.opts.Validate() test.proxy = NewOAuthProxy(test.opts, func(email string) bool { return true diff --git a/options.go b/options.go index 8c73eb9..1f438b5 100644 --- a/options.go +++ b/options.go @@ -454,7 +454,7 @@ func parseSignatureKey(o *Options, msgs []string) []string { return append(msgs, "unsupported signature hash algorithm: "+ o.SignatureKey) } - o.signatureData = &SignatureData{hash, secretKey} + o.signatureData = &SignatureData{hash: hash, key: secretKey} return msgs } From 5bcb998e6be341857d2c845a837a3c3c79dc63af Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 23 Jun 2019 20:55:42 +0100 Subject: [PATCH 068/128] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d071f..816fa97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,7 @@ - [#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. -- [#XX](https://github.com/pusher/outh2_proxy/pull/XX) Switch from gometalinter to golangci-lint +- [#198](https://github.com/pusher/outh2_proxy/pull/198) Switch from gometalinter to golangci-lint (@steakunderscore) # v3.2.0 From 924eab6355ae4414407a83583ba45cc29d8054db Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Wed, 19 Jun 2019 15:24:25 +0100 Subject: [PATCH 069/128] Adds banner flag This is to override what's displayed on the main page. --- docs/configuration/configuration.md | 1 + main.go | 9 ++++++++- oauthproxy.go | 2 ++ options.go | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 1295269..5232d4e 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -43,6 +43,7 @@ Usage of oauth2_proxy: -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email -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) -flush-interval: period between flushing response buffers when streaming responses (default "1s") + -banner string: custom banner string. Use "-" to disable default banner. -footer string: custom footer string. Use "-" to disable default footer. -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) -github-org string: restrict logins to members of this organisation diff --git a/main.go b/main.go index 054bb30..6884462 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,7 @@ 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.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("banner", "", "custom banner string. Use \"-\" to disable default banner.") 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. //sign_in)") flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying") @@ -148,7 +149,13 @@ func main() { validator := NewValidator(opts.EmailDomains, opts.AuthenticatedEmailsFile) oauthproxy := NewOAuthProxy(opts, validator) - if len(opts.EmailDomains) != 0 && opts.AuthenticatedEmailsFile == "" { + if len(opts.Banner) >= 1 { + if opts.Banner == "-" { + oauthproxy.SignInMessage = "" + } else { + oauthproxy.SignInMessage = opts.Banner + } + } else if len(opts.EmailDomains) != 0 && opts.AuthenticatedEmailsFile == "" { if len(opts.EmailDomains) > 1 { oauthproxy.SignInMessage = fmt.Sprintf("Authenticate using one of the following domains: %v", strings.Join(opts.EmailDomains, ", ")) } else if opts.EmailDomains[0] != "*" { diff --git a/oauthproxy.go b/oauthproxy.go index 99dfb36..08dcfab 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -98,6 +98,7 @@ type OAuthProxy struct { jwtBearerVerifiers []*oidc.IDTokenVerifier compiledRegex []*regexp.Regexp templates *template.Template + Banner string Footer string } @@ -269,6 +270,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { PassAuthorization: opts.PassAuthorization, SkipProviderButton: opts.SkipProviderButton, templates: loadTemplates(opts.CustomTemplatesDir), + Banner: opts.Banner, Footer: opts.Footer, } } diff --git a/options.go b/options.go index 8c73eb9..a8de2d7 100644 --- a/options.go +++ b/options.go @@ -51,6 +51,7 @@ type Options struct { 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"` 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"` // Embed CookieOptions From b9cfa8f49f82aeb6bdae879957aea3aa236b400b Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Wed, 19 Jun 2019 15:35:32 +0100 Subject: [PATCH 070/128] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6858187..37092f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - [#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/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) # v3.2.0 From ce7e3840958fe3e2e62eab2386f25dd6e20f13b9 Mon Sep 17 00:00:00 2001 From: hjenkins Date: Mon, 1 Jul 2019 16:27:19 +0100 Subject: [PATCH 071/128] Remove TODO vetshadow as it's part of govet --- .golangci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 4dc2d94..a0658e1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,6 @@ run: linters: enable: - govet - # TODO: Not supported by golang-ci - vetshadow - golint - ineffassign - goconst From 387a7267e124d5bcee37f551fe1491b96c4fc98c Mon Sep 17 00:00:00 2001 From: "Seip, Nikolai" Date: Wed, 10 Jul 2019 10:26:31 +0200 Subject: [PATCH 072/128] update configuration.md auth_request section --- docs/configuration/configuration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 5232d4e..b3c38b2 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -313,3 +313,5 @@ nginx.ingress.kubernetes.io/configuration-snippet: | 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=". From 018a25be04dfcab9fae3ee12ea6710bc52d578ad Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Mon, 20 May 2019 12:32:10 +0100 Subject: [PATCH 073/128] Create option to skip verified email check in OIDC provider --- docs/configuration/configuration.md | 1 + main.go | 1 + options.go | 69 +++++++++++++++-------------- providers/oidc.go | 5 ++- 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index b3c38b2..d7cdcd9 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -63,6 +63,7 @@ Usage of oauth2_proxy: -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 -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 -login-url string: Authentication endpoint + -oidc-allow-unverified-email: don't fail if an email address in an id_token is not verified -oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com" -oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled -pass-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header diff --git a/main.go b/main.go index 6884462..4187257 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,7 @@ func main() { flagSet.String("provider", "google", "OAuth provider") flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") + flagSet.Bool("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.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)") flagSet.String("login-url", "", "Authentication endpoint") diff --git a/options.go b/options.go index 4a41ebe..3cd6e3a 100644 --- a/options.go +++ b/options.go @@ -79,17 +79,18 @@ type Options struct { // These options allow for other providers besides Google, with // potential overrides. - 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"` - SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"` - OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_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"` - ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` - ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` - ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` - Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` - ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` + 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"` + OIDCAllowUnverifiedEmail bool `flag:"oidc-allow-unverified-email" cfg:"oidc_allow_unverified_email" env:"OAUTH2_PROXY_OIDC_ALLOW_UNVERIFIED_EMAIL"` + SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"` + OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_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"` + ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` + ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` + ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` + Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` + ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Configuration values for logging LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"` @@ -147,28 +148,29 @@ func NewOptions() *Options { SessionOptions: options.SessionOptions{ Type: "cookie", }, - SetXAuthRequest: false, - SkipAuthPreflight: false, - PassBasicAuth: true, - PassUserHeaders: true, - PassAccessToken: false, - PassHostHeader: true, - SetAuthorization: false, - PassAuthorization: false, - ApprovalPrompt: "force", - SkipOIDCDiscovery: false, - LoggingFilename: "", - LoggingMaxSize: 100, - LoggingMaxAge: 7, - LoggingMaxBackups: 0, - LoggingLocalTime: true, - LoggingCompress: false, - StandardLogging: true, - StandardLoggingFormat: logger.DefaultStandardLoggingFormat, - RequestLogging: true, - RequestLoggingFormat: logger.DefaultRequestLoggingFormat, - AuthLogging: true, - AuthLoggingFormat: logger.DefaultAuthLoggingFormat, + SetXAuthRequest: false, + SkipAuthPreflight: false, + PassBasicAuth: true, + PassUserHeaders: true, + PassAccessToken: false, + PassHostHeader: true, + SetAuthorization: false, + PassAuthorization: false, + ApprovalPrompt: "force", + OIDCAllowUnverifiedEmail: false, + SkipOIDCDiscovery: false, + LoggingFilename: "", + LoggingMaxSize: 100, + LoggingMaxAge: 7, + LoggingMaxBackups: 0, + LoggingLocalTime: true, + LoggingCompress: false, + StandardLogging: true, + StandardLoggingFormat: logger.DefaultStandardLoggingFormat, + RequestLogging: true, + RequestLoggingFormat: logger.DefaultRequestLoggingFormat, + AuthLogging: true, + AuthLoggingFormat: logger.DefaultAuthLoggingFormat, } } @@ -397,6 +399,7 @@ func parseProviderInfo(o *Options, msgs []string) []string { } } case *providers.OIDCProvider: + p.AllowUnverifiedEmail = o.OIDCAllowUnverifiedEmail if o.oidcVerifier == nil { msgs = append(msgs, "oidc provider requires an oidc issuer URL") } else { diff --git a/providers/oidc.go b/providers/oidc.go index b0d2dda..86a58f6 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -14,7 +14,8 @@ import ( type OIDCProvider struct { *ProviderData - Verifier *oidc.IDTokenVerifier + Verifier *oidc.IDTokenVerifier + AllowUnverifiedEmail bool } // NewOIDCProvider initiates a new OIDCProvider @@ -119,7 +120,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok // TODO: Try getting email from /userinfo before falling back to Subject claims.Email = claims.Subject } - if claims.Verified != nil && !*claims.Verified { + if !p.AllowUnverifiedEmail && claims.Verified != nil && !*claims.Verified { return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email) } From 39b6a42d43b96cf41ee110406c4dd1b7ffc565b7 Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Thu, 11 Jul 2019 15:18:36 +0100 Subject: [PATCH 074/128] Mark option to skip verified email check as insecure --- options.go | 72 +++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/options.go b/options.go index 3cd6e3a..7436d85 100644 --- a/options.go +++ b/options.go @@ -79,18 +79,18 @@ type Options struct { // These options allow for other providers besides Google, with // potential overrides. - 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"` - OIDCAllowUnverifiedEmail bool `flag:"oidc-allow-unverified-email" cfg:"oidc_allow_unverified_email" env:"OAUTH2_PROXY_OIDC_ALLOW_UNVERIFIED_EMAIL"` - SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_SKIP_OIDC_DISCOVERY"` - OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_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"` - ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` - ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` - ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` - Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` - ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` + 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"` + 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"` + OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_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"` + ProfileURL string `flag:"profile-url" cfg:"profile_url" env:"OAUTH2_PROXY_PROFILE_URL"` + ProtectedResource string `flag:"resource" cfg:"resource" env:"OAUTH2_PROXY_RESOURCE"` + ValidateURL string `flag:"validate-url" cfg:"validate_url" env:"OAUTH2_PROXY_VALIDATE_URL"` + Scope string `flag:"scope" cfg:"scope" env:"OAUTH2_PROXY_SCOPE"` + ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Configuration values for logging LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"` @@ -148,29 +148,29 @@ func NewOptions() *Options { SessionOptions: options.SessionOptions{ Type: "cookie", }, - SetXAuthRequest: false, - SkipAuthPreflight: false, - PassBasicAuth: true, - PassUserHeaders: true, - PassAccessToken: false, - PassHostHeader: true, - SetAuthorization: false, - PassAuthorization: false, - ApprovalPrompt: "force", - OIDCAllowUnverifiedEmail: false, - SkipOIDCDiscovery: false, - LoggingFilename: "", - LoggingMaxSize: 100, - LoggingMaxAge: 7, - LoggingMaxBackups: 0, - LoggingLocalTime: true, - LoggingCompress: false, - StandardLogging: true, - StandardLoggingFormat: logger.DefaultStandardLoggingFormat, - RequestLogging: true, - RequestLoggingFormat: logger.DefaultRequestLoggingFormat, - AuthLogging: true, - AuthLoggingFormat: logger.DefaultAuthLoggingFormat, + SetXAuthRequest: false, + SkipAuthPreflight: false, + PassBasicAuth: true, + PassUserHeaders: true, + PassAccessToken: false, + PassHostHeader: true, + SetAuthorization: false, + PassAuthorization: false, + ApprovalPrompt: "force", + InsecureOIDCAllowUnverifiedEmail: false, + SkipOIDCDiscovery: false, + LoggingFilename: "", + LoggingMaxSize: 100, + LoggingMaxAge: 7, + LoggingMaxBackups: 0, + LoggingLocalTime: true, + LoggingCompress: false, + StandardLogging: true, + StandardLoggingFormat: logger.DefaultStandardLoggingFormat, + RequestLogging: true, + RequestLoggingFormat: logger.DefaultRequestLoggingFormat, + AuthLogging: true, + AuthLoggingFormat: logger.DefaultAuthLoggingFormat, } } @@ -399,7 +399,7 @@ func parseProviderInfo(o *Options, msgs []string) []string { } } case *providers.OIDCProvider: - p.AllowUnverifiedEmail = o.OIDCAllowUnverifiedEmail + p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail if o.oidcVerifier == nil { msgs = append(msgs, "oidc provider requires an oidc issuer URL") } else { From 776d063b987f950bb238680cc6e52174dbd4a7e4 Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Thu, 11 Jul 2019 15:23:24 +0100 Subject: [PATCH 075/128] Update changelog to include --insecure-oidc-allow-unverified-email --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e2212..ed9d4df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ - Google Group membership is additionally checked via email address, allowing users outside a GSuite domain to be authorized. - [#195](https://github.com/pusher/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) - [#198](https://github.com/pusher/outh2_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` # v3.2.0 From 9823971b7d9e7e5f24a988b488c8a0955dbafaa8 Mon Sep 17 00:00:00 2001 From: Daryl Finlay Date: Thu, 11 Jul 2019 15:58:31 +0100 Subject: [PATCH 076/128] Make insecure-oidc-allow-unverified-email configuration usage consistent --- docs/configuration/configuration.md | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index d7cdcd9..016fe3a 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -63,7 +63,7 @@ Usage of oauth2_proxy: -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 -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 -login-url string: Authentication endpoint - -oidc-allow-unverified-email: don't fail if an email address in an id_token is not verified + -insecure-oidc-allow-unverified-email: don't fail if an email address in an id_token is not verified -oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com" -oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled -pass-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header diff --git a/main.go b/main.go index 4187257..f82205d 100644 --- a/main.go +++ b/main.go @@ -104,7 +104,7 @@ func main() { flagSet.String("provider", "google", "OAuth provider") flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") - flagSet.Bool("oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified") + 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.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)") flagSet.String("login-url", "", "Authentication endpoint") From e245ef4854d35a602280cada4a70928ab69e1ae0 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sat, 13 Jul 2019 21:24:40 +0100 Subject: [PATCH 077/128] Switch from dep to go mod Update modules to avoid issues with golangci-lint --- Dockerfile | 4 +- Gopkg.lock | 426 ------------------------------------------ Gopkg.toml | 56 ------ Makefile | 24 +-- configure | 6 - go.mod | 87 +++++++++ go.sum | 531 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 629 insertions(+), 505 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 go.mod create mode 100644 go.sum diff --git a/Dockerfile b/Dockerfile index 757a1f5..ea7a7d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,6 @@ FROM golang:1.12-stretch AS builder # Download tools -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 RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources @@ -10,7 +8,7 @@ WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy COPY . . # Fetch dependencies -RUN dep ensure --vendor-only +RUN go mod download # Build binary and make sure there is at least an empty key file. # This is useful for GCP App Engine custom runtime builds, because diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 89ba394..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,426 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:b24249f5a5e6fbe1eddc94b25973172339ccabeadef4779274f3ed0167c18812" - name = "cloud.google.com/go" - packages = ["compute/metadata"] - pruneopts = "" - revision = "2d3a6656c17a60b0815b7e06ab0be04eacb6e613" - version = "v0.16.0" - -[[projects]] - digest = "1:289dd4d7abfb3ad2b5f728fbe9b1d5c1bf7d265a3eb9ef92869af1f7baba4c7a" - name = "github.com/BurntSushi/toml" - packages = ["."] - pruneopts = "" - revision = "b26d9c308763d68093482582cea63d69be07a0f0" - version = "v0.3.0" - -[[projects]] - 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]] - digest = "1:d8ee1b165eb7f4fd9ada718e1e7eeb0bc1fd462592d0bd823df694443f448681" - name = "github.com/coreos/go-oidc" - packages = ["."] - pruneopts = "" - revision = "1180514eaf4d9f38d0d19eef639a1d695e066e72" - version = "v2.0.0" - -[[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 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 1263b95..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,56 +0,0 @@ - -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# - -[[constraint]] - name = "github.com/BurntSushi/toml" - version = "~0.3.0" - -[[constraint]] - name = "github.com/bitly/go-simplejson" - version = "~0.5.0" - -[[constraint]] - version = "^2" - 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" diff --git a/Makefile b/Makefile index 3286bda..f41f320 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ VERSION := $(shell git describe --always --dirty --tags 2>/dev/null || echo "und .NOTPARALLEL: .PHONY: all -all: dep lint $(BINARY) +all: lint $(BINARY) .PHONY: clean clean: @@ -17,17 +17,13 @@ distclean: clean .PHONY: lint lint: - $(GOLANGCILINT) run - -.PHONY: dep -dep: - $(DEP) ensure --vendor-only + GO111MODULE=on $(GOLANGCILINT) run .PHONY: build build: clean $(BINARY) $(BINARY): - CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/pusher/oauth2_proxy + GO111MODULE=on CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/pusher/oauth2_proxy .PHONY: docker docker: @@ -58,8 +54,8 @@ docker-push-all: docker-push docker push quay.io/pusher/oauth2_proxy:${VERSION}-armv6 .PHONY: test -test: dep lint - $(GO) test -v -race ./... +test: lint + GO111MODULE=on $(GO) test -v -race ./... .PHONY: release release: lint test @@ -69,15 +65,15 @@ release: lint test mkdir release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION) mkdir release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION) mkdir release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION) - GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ + GO111MODULE=on GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ -o release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy - GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ + GO111MODULE=on GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ -o release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy - GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" \ + GO111MODULE=on GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" \ -o release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy - GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" \ + GO111MODULE=on GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" \ -o release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy - GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ + GO111MODULE=on GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \ -o release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy shasum -a 256 release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).darwin-amd64-sha256sum.txt shasum -a 256 release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-amd64-sha256sum.txt diff --git a/configure b/configure index f1b8ae6..959447b 100755 --- a/configure +++ b/configure @@ -13,13 +13,9 @@ for arg in "$@"; do "--with-go") desired[go]="${arg##*=}" ;; - "--with-dep") - desired[dep]="${arg##*=}" - ;; "--help") printf "${GREEN}$0${NC}\n" printf " available options:\n" - printf " --with-dep=${BLUE}${NC}\n" printf " --with-go=${BLUE}${NC}\n" exit 0 ;; @@ -125,7 +121,6 @@ check_for awk check_for go check_go_version check_go_env -check_for dep check_for golangci-lint echo @@ -134,7 +129,6 @@ cat <<- EOF > .env MAKE := "${tools[make]}" GO := "${tools[go]}" GO_VERSION := ${tools[go_version]} - DEP := "${tools[dep]}" GOLANGCILINT := "${tools[golangci-lint]}" EOF diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7880da9 --- /dev/null +++ b/go.mod @@ -0,0 +1,87 @@ +module github.com/pusher/oauth2_proxy + +go 1.12 + +require ( + cloud.google.com/go v0.41.0 // indirect + github.com/BurntSushi/toml v0.3.1 + github.com/OpenPeeDeeP/depguard v1.0.0 // indirect + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/alicebob/miniredis v0.0.0-20190417180845-3d7aa1333af5 + github.com/bitly/go-simplejson v0.5.0 + github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect + github.com/coreos/bbolt v1.3.3 // indirect + github.com/coreos/etcd v3.3.13+incompatible // indirect + github.com/coreos/go-oidc v2.0.0+incompatible + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/fatih/color v1.7.0 // indirect + github.com/go-critic/go-critic v0.3.4 // indirect + github.com/go-kit/kit v0.9.0 // indirect + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/go-redis/redis v6.15.2+incompatible + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/protobuf v1.3.2 // indirect + github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 // indirect + github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0 // indirect + github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d // indirect + github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 // indirect + github.com/golangci/golangci-lint v1.17.1 // indirect + github.com/golangci/gosec v0.0.0-20180901114220-8afd9cbb6cfb // indirect + github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 // indirect + github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 // indirect + github.com/gostaticanalysis/analysisutil v0.0.2 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.9.4 // indirect + github.com/kisielk/errcheck v1.2.0 // indirect + github.com/klauspost/compress v1.7.2 // indirect + github.com/klauspost/cpuid v1.2.1 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/kr/pty v1.1.8 // indirect + github.com/logrusorgru/aurora v0.0.0-20190428105938-cea283e61946 // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mattn/go-colorable v0.1.2 // indirect + github.com/mbland/hmacauth v0.0.0-20170912224942-107c17adcc5e + github.com/mreiferson/go-options v0.0.0-20190302064952-20ba7d382d05 + github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect + github.com/onsi/ginkgo v1.8.0 + github.com/onsi/gomega v1.5.0 + github.com/pelletier/go-toml v1.4.0 // indirect + github.com/pkg/errors v0.8.1 // indirect + github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect + github.com/prometheus/common v0.6.0 // indirect + github.com/prometheus/procfs v0.0.3 // indirect + github.com/rogpeppe/fastuuid v1.2.0 // indirect + github.com/rogpeppe/go-internal v1.3.0 // indirect + github.com/russross/blackfriday v2.0.0+incompatible // indirect + github.com/shirou/gopsutil v2.18.12+incompatible // indirect + github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 // indirect + github.com/sirupsen/logrus v1.4.2 // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cobra v0.0.5 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/viper v1.4.0 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/testify v1.3.0 + github.com/timakin/bodyclose v0.0.0-20190713050349-d96ec0dee822 // indirect + github.com/ugorji/go v1.1.7 // indirect + github.com/valyala/fasthttp v1.4.0 // indirect + github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 + go.etcd.io/bbolt v1.3.3 // indirect + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f // indirect + golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 // indirect + golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9 // indirect + golang.org/x/net v0.0.0-20190628185345-da137c7871d7 + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect + golang.org/x/tools v0.0.0-20190712213246-8b927904ee0d // indirect + google.golang.org/api v0.7.0 + google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 // indirect + google.golang.org/grpc v1.22.0 // indirect + gopkg.in/fsnotify/fsnotify.v1 v1.2.11 + gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 + gopkg.in/square/go-jose.v2 v2.1.3 + mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe // indirect + sourcegraph.com/sqs/pbtypes v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c8ce175 --- /dev/null +++ b/go.sum @@ -0,0 +1,531 @@ +cloud.google.com/go v0.16.0 h1:alV/SO2XpH+lrvqjDl94dYez7FfeT8ptayazgWwHPIU= +cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.41.0 h1:NFvqUTDnSNYPX5oReekmB+D+90jrJIcVImxQ3qrBVgM= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= +github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= +github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/OpenPeeDeeP/depguard v0.0.0-20180806142446-a69c782687b2/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= +github.com/OpenPeeDeeP/depguard v1.0.0 h1:k9QF73nrHT3nPLz3lu6G5s+3Hi8Je36ODr1F5gjAXXM= +github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U= +github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis v0.0.0-20190417180845-3d7aa1333af5 h1:+xnalaRl7JEs6xynGsLgGilz75ljDYZTFKuCadGquPY= +github.com/alicebob/miniredis v0.0.0-20190417180845-3d7aa1333af5/go.mod h1:8cBZ4R1fh1lx8l4UVit3jNxyybdDi+rjnukCwTYVQE0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5LbakNExNmZIg= +github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-critic/go-critic v0.0.0-20181204210945-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= +github.com/go-critic/go-critic v0.3.4 h1:FYaiaLjX0Nqei80KPhm4CyFQUBbmJwSrHxQ73taaGBc= +github.com/go-critic/go-critic v0.3.4/go.mod h1:AHR42Lk/E/aOznsrYdMYeIQS5RH10HZHSqP+rD6AJrc= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-lintpack/lintpack v0.5.1/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= +github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-toolsmith/astcast v0.0.0-20181028201508-b7a89ed70af1/go.mod h1:TEo3Ghaj7PsZawQHxT/oBvo4HK/sl1RcuUHDKTTju+o= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v0.0.0-20180903214859-79b422d080c4/go.mod h1:c9CPdq2AzM8oPomdlPniEfPAC6g1s7NqZzODt8y6ib8= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astinfo v1.0.0/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v0.0.0-20180903215201-830b6daa1241/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v0.0.0-20181030061450-d63dc7650676/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181003203344-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/go-tools v0.0.0-20180109140146-af6baa5dc196/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= +github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0 h1:MRhC9XbUjE6XDOInSJ8pwHuPagqsyO89QDU9IdVhe3o= +github.com/golangci/go-tools v0.0.0-20190124090046-35a9f45a5db0/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d h1:pXTK/gkVNs7Zyy7WKgLXmpQ5bHTrq5GDsp8R9Qs67g0= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20181105071733-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiDKfK9XHgmc2wbDlRMD2hD8anAJHU= +github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.17.1 h1:lc8Hf9GPCjIr0hg3S/xhvFT1+Hydass8F1xchr8jkME= +github.com/golangci/golangci-lint v1.17.1/go.mod h1:+5sJSl2h3aly+fpmL2meSP8CaSKua2E4Twi9LPy7b1g= +github.com/golangci/gosec v0.0.0-20180901114220-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU= +github.com/golangci/gosec v0.0.0-20180901114220-8afd9cbb6cfb h1:Bi7BYmZVg4C+mKGi8LeohcP2GGUl2XJD4xCkJoZSaYc= +github.com/golangci/gosec v0.0.0-20180901114220-8afd9cbb6cfb/go.mod h1:ON/c2UR0VAAv6ZEAFKhjCLplESSmRFfZcDLASbI1GWo= +github.com/golangci/ineffassign v0.0.0-20180808204949-42439a7714cc h1:XRFao922N8F3EcIXBSNX8Iywk+GI0dxD/8FicMX2D/c= +github.com/golangci/ineffassign v0.0.0-20180808204949-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20180610141402-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039 h1:XQKc8IYQOeRwVs36tDrEmTgDgP88d5iEURwpmtiAlOM= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.2 h1:OZ4/Q9Lt9bzdyyjAgAWzJfL5dSwPrbkN+6UOHwYeJDM= +github.com/gostaticanalysis/analysisutil v0.0.2/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.7.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/logrusorgru/aurora v0.0.0-20190428105938-cea283e61946/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mbland/hmacauth v0.0.0-20170912224942-107c17adcc5e h1:eMYU396eZUQ/ex49JNVJOEhShOhQe3Lf/opF61nFtlA= +github.com/mbland/hmacauth v0.0.0-20170912224942-107c17adcc5e/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mreiferson/go-options v0.0.0-20190302064952-20ba7d382d05 h1:9cELXrXqZu2sczHBZHRpZ+84SR27+yXSKb1MBiUaPhA= +github.com/mreiferson/go-options v0.0.0-20190302064952-20ba7d382d05/go.mod h1:zHtCks/HQvOt8ATyfwVe3JJq2PPuImzXINPRTC03+9w= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/tsdb v0.9.1/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.1/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/timakin/bodyclose v0.0.0-20190407043127-4a873e97b2bb/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timakin/bodyclose v0.0.0-20190713050349-d96ec0dee822 h1:uVnVN3IUKAVcB3xG26bThgwXkWaGFc9i5qFHYKy4TKc= +github.com/timakin/bodyclose v0.0.0-20190713050349-d96ec0dee822/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/fasthttp v1.4.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q= +github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997/go.mod h1:DIGbh/f5XMAessMV/uaIik81gkDVjUeQ9ApdaU7wRKE= +github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583 h1:SZPG5w7Qxq7bMcMVl6e3Ht2X7f+AAGQdzjkbyOnNNZ8= +github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3 h1:f4/ZD59VsBOaJmWeI2yqtHvJhmRRPzi73C88ZtfhAIk= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20171106152852-9ff8ebcc8e24 h1:nP0LlV1P7+z/qtbjHygz+Bba7QsbB4MqdhGJmAyicuI= +golang.org/x/oauth2 v0.0.0-20171106152852-9ff8ebcc8e24/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190506115046-ca7f33d4116e h1:bq5BY1tGuaK8HxuwN6pT6kWgTVLeJ5KwuyBpsl1CZL4= +golang.org/x/sys v0.0.0-20190506115046-ca7f33d4116e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190213192042-740235f6c0d8/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190712213246-8b927904ee0d h1:JZPFnINSinLUdC0BJDoKhrky8niIzXMPIY2oR07+I+E= +golang.org/x/tools v0.0.0-20190712213246-8b927904ee0d/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +google.golang.org/api v0.0.0-20171116170945-8791354e7ab1 h1:g6iAMpIfX2EaDmaU3Nm8KcWAuf9yDiM3uE5a7/9gZao= +google.golang.org/api v0.0.0-20171116170945-8791354e7ab1/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/appengine v1.0.0 h1:dN4LljjBKVChsv0XCSI+zbyzdqrkEwX5LQFUMRSGqOc= +google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 h1:5pOB7se0B2+IssELuQUs6uoBgYJenkU2AQlvopc2sRw= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/fsnotify/fsnotify.v1 v1.2.11 h1:m6l7lekIm1I7UEqyXifLaHywMLxwlWea1o3zUGDGQHs= +gopkg.in/fsnotify/fsnotify.v1 v1.2.11/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 h1:AFxeG48hTWHhDTQDk/m2gorfVHUEa9vo3tp3D7TzwjI= +gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.1.3 h1:/FoFBTvlJN6MTTVCe9plTOG+YydzkjvDGxiSPzIyoDM= +gopkg.in/square/go-jose.v2 v2.1.3/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190124213536-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= +mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe h1:Ekmnp+NcP2joadI9pbK4Bva87QKZSeY7le//oiMrc9g= +mvdan.cc/unparam v0.0.0-20190310220240-1b9ccfa71afe/go.mod h1:BnhuWBAqxH3+J5bDybdxgw5ZfS+DsVd4iylsKQePN8o= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o= +sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= From c98ff79aba40bd268887de6e09a9a54ec833b2e4 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sat, 13 Jul 2019 22:12:20 +0100 Subject: [PATCH 078/128] Update other docker files --- Dockerfile.arm64 | 4 +--- Dockerfile.armv6 | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index f4bf495..e2e29c8 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,8 +1,6 @@ FROM golang:1.12-stretch AS builder # Download tools -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 RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources @@ -10,7 +8,7 @@ WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy COPY . . # Fetch dependencies -RUN dep ensure --vendor-only +RUN go mod download # Build binary and make sure there is at least an empty key file. # This is useful for GCP App Engine custom runtime builds, because diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index 32bb124..21ca4cb 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -1,8 +1,6 @@ FROM golang:1.12-stretch AS builder # Download tools -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 RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 # Copy sources @@ -10,7 +8,7 @@ WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy COPY . . # Fetch dependencies -RUN dep ensure --vendor-only +RUN go mod download # Build binary and make sure there is at least an empty key file. # This is useful for GCP App Engine custom runtime builds, because From 27bdb194b1ce7ad7e79ae47343d5c7d2794cfb31 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sat, 13 Jul 2019 22:14:05 +0100 Subject: [PATCH 079/128] Update to Alpine 3.10 --- Dockerfile | 2 +- Dockerfile.arm64 | 2 +- Dockerfile.armv6 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 757a1f5..53fca88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN dep ensure --vendor-only RUN ./configure && make build && touch jwt_signing_key.pem # Copy binary to alpine -FROM alpine:3.9 +FROM alpine:3.10 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/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index f4bf495..1db9050 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -21,7 +21,7 @@ RUN dep ensure --vendor-only RUN ./configure && GOARCH=arm64 make build && touch jwt_signing_key.pem # Copy binary to alpine -FROM arm64v8/alpine:3.9 +FROM arm64v8/alpine:3.10 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/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index 32bb124..40cc5f5 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -21,7 +21,7 @@ RUN dep ensure --vendor-only RUN ./configure && GOARCH=arm GOARM=6 make build && touch jwt_signing_key.pem # Copy binary to alpine -FROM arm32v6/alpine:3.9 +FROM arm32v6/alpine:3.10 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/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem From e92e2f0cb45ad34fd1c81bc84adec76c0c61ac18 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 14 Jul 2019 13:32:37 +0100 Subject: [PATCH 080/128] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9d4df..839a720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ - [#195](https://github.com/pusher/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) - [#198](https://github.com/pusher/outh2_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` +- [#210](https://github.com/pusher/oauth2_proxy/pull/210) Update base image from Alpine 3.9 to 3.10 (@steakunderscore) # v3.2.0 From 179ee6c2dbefeced4eea353038266387f0e6ff2f Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 14 Jul 2019 13:51:46 +0100 Subject: [PATCH 081/128] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9d4df..6684bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ - [#195](https://github.com/pusher/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) - [#198](https://github.com/pusher/outh2_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` +- [#211](https://github.com/pusher/oauth2_proxy/pull/211) Switch from dep to go modules (@steakunderscore) # v3.2.0 From f0d006259ea302cf1d03d6fe1239a92181ba8511 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Sun, 9 Jun 2019 23:47:18 +0200 Subject: [PATCH 082/128] Ensure all options use a consistent format for flag vs cfg vs env --- options.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/options.go b/options.go index 0941683..f1874f3 100644 --- a/options.go +++ b/options.go @@ -29,7 +29,7 @@ import ( // Options holds Configuration Options that can be set by Command Line Flag, // or Config File 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"` 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"` HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"` @@ -82,8 +82,8 @@ type Options struct { 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"` 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"` - OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_OIDC_JWKS_URL"` + SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"` + OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url" env:"OAUTH2_PROXY_OIDC_JWKS_URL"` LoginURL string `flag:"login-url" cfg:"login_url" env:"OAUTH2_PROXY_LOGIN_URL"` 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"` @@ -93,18 +93,18 @@ type Options struct { ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt" env:"OAUTH2_PROXY_APPROVAL_PROMPT"` // Configuration values for logging - LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_LOGGING_FILENAME"` - 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_LOGGING_MAX_AGE"` - 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_LOGGING_LOCAL_TIME"` - LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_LOGGING_COMPRESS"` - StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_STANDARD_LOGGING"` - 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_REQUEST_LOGGING"` - RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_REQUEST_LOGGING_FORMAT"` - AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_LOGGING_AUTH_LOGGING"` - AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_AUTH_LOGGING_FORMAT"` + LoggingFilename string `flag:"logging-filename" cfg:"logging_filename" env:"OAUTH2_PROXY_LOGGING_FILENAME"` + LoggingMaxSize int `flag:"logging-max-size" cfg:"logging_max_size" env:"OAUTH2_PROXY_LOGGING_MAX_SIZE"` + LoggingMaxAge int `flag:"logging-max-age" cfg:"logging_max_age" env:"OAUTH2_PROXY_LOGGING_MAX_AGE"` + LoggingMaxBackups int `flag:"logging-max-backups" cfg:"logging_max_backups" env:"OAUTH2_PROXY_LOGGING_MAX_BACKUPS"` + LoggingLocalTime bool `flag:"logging-local-time" cfg:"logging_local_time" env:"OAUTH2_PROXY_LOGGING_LOCAL_TIME"` + LoggingCompress bool `flag:"logging-compress" cfg:"logging_compress" env:"OAUTH2_PROXY_LOGGING_COMPRESS"` + StandardLogging bool `flag:"standard-logging" cfg:"standard_logging" env:"OAUTH2_PROXY_STANDARD_LOGGING"` + StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"` + RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"` + RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_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"` AcrValues string `flag:"acr-values" cfg:"acr_values" env:"OAUTH2_PROXY_ACR_VALUES"` From bdcdfb74f97fe2341956315bc75951b03e387669 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Sat, 15 Jun 2019 11:12:21 +0200 Subject: [PATCH 083/128] Update docs and changelog --- CHANGELOG.md | 17 +++++++++++++++-- docs/configuration/configuration.md | 17 +++++++---------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c1bb1f..27c20a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## Breaking Changes +- [#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 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 (@gargath) - 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. @@ -14,10 +26,11 @@ ## Changes since v3.2.0 +- [#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. - - 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 + the `-skip-jwt-bearer-token` options. + - 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/outh2_proxy/pull/180) Minor refactor of core proxying path (@aeijdenberg). - [#175](https://github.com/pusher/outh2_proxy/pull/175) Bump go-oidc to v2.0.0 (@aeijdenberg). diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 016fe3a..dad9ea1 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -120,17 +120,14 @@ Multiple upstreams can either be configured by supplying a comma separated list ### Environment variables -The following environment variables can be used in place of the corresponding command-line arguments: +Every command line argument can be specified as an environment variable by +prefixing it with `OAUTH2_PROXY_`, capitalising it, and replacing hypens (`-`) +with underscores (`_`). This is particularly useful for storing secrets outside +of a configuration file or the command line. + +For example, the `--cookie-secret` flag becomes `OAUTH2_PROXY_COOKIE_SECRET` and +the `--set-authorization-header` flag becomes `OAUTH2_PROXY_SET_AUTHORIZATION_HEADER`. -- `OAUTH2_PROXY_CLIENT_ID` -- `OAUTH2_PROXY_CLIENT_SECRET` -- `OAUTH2_PROXY_COOKIE_NAME` -- `OAUTH2_PROXY_COOKIE_SECRET` -- `OAUTH2_PROXY_COOKIE_DOMAIN` -- `OAUTH2_PROXY_COOKIE_PATH` -- `OAUTH2_PROXY_COOKIE_EXPIRE` -- `OAUTH2_PROXY_COOKIE_REFRESH` -- `OAUTH2_PROXY_SIGNATURE_KEY` ## Logging Configuration From 874c147e04210662f80c533fe09e22cea47498ad Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 21 Jun 2019 15:44:06 +0100 Subject: [PATCH 084/128] Fix tls-key-file and tls-cert-file consistency --- CHANGELOG.md | 7 ++++++- options.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27c20a9..e3e458d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,14 @@ - [#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 + 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: diff --git a/options.go b/options.go index f1874f3..f1ca55d 100644 --- a/options.go +++ b/options.go @@ -36,8 +36,8 @@ type Options struct { 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"` ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"` - TLSCertFile string `flag:"tls-cert" cfg:"tls_cert_file" env:"OAUTH2_PROXY_TLS_CERT_FILE"` - TLSKeyFile string `flag:"tls-key" cfg:"tls_key_file" env:"OAUTH2_PROXY_TLS_KEY_FILE"` + TLSCertFile string `flag:"tls-cert-file" 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"` AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"` AzureTenant string `flag:"azure-tenant" cfg:"azure_tenant" env:"OAUTH2_PROXY_AZURE_TENANT"` From 816c2a6da90b41945ea396bfb8b51f041840be6e Mon Sep 17 00:00:00 2001 From: Daniel Kimsey Date: Thu, 11 Jul 2019 17:18:07 -0500 Subject: [PATCH 085/128] Move docker dep commands to earlier in the build This will let Docker cache the results of the vendor dependencies. Making re-builds during testing faster. Also clean-up spurious test & rm in ./configure --- CHANGELOG.md | 1 + Dockerfile | 5 ++++- Dockerfile.arm64 | 5 ++++- Dockerfile.armv6 | 5 ++++- configure | 4 +--- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e458d..8463401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ## Changes since v3.2.0 +- [#209](https://github.com/pusher/outh2_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 diff --git a/Dockerfile b/Dockerfile index 53fca88..cc4a26e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,11 +7,14 @@ RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.s # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy -COPY . . # Fetch dependencies +COPY Gopkg.toml Gopkg.lock ./ RUN dep ensure --vendor-only +# Now pull in our code +COPY . . + # Build binary and make sure there is at least an empty key file. # This is useful for GCP App Engine custom runtime builds, because # you cannot use multiline variables in their app.yaml, so you have to diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 1db9050..5360abb 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -7,11 +7,14 @@ RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.s # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy -COPY . . # Fetch dependencies +COPY Gopkg.toml Gopkg.lock ./ RUN dep ensure --vendor-only +# Now pull in our code +COPY . . + # Build binary and make sure there is at least an empty key file. # This is useful for GCP App Engine custom runtime builds, because # you cannot use multiline variables in their app.yaml, so you have to diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index 40cc5f5..71bbe50 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -7,11 +7,14 @@ RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.s # Copy sources WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy -COPY . . # Fetch dependencies +COPY Gopkg.toml Gopkg.lock ./ RUN dep ensure --vendor-only +# Now pull in our code +COPY . . + # Build binary and make sure there is at least an empty key file. # This is useful for GCP App Engine custom runtime builds, because # you cannot use multiline variables in their app.yaml, so you have to diff --git a/configure b/configure index f1b8ae6..71b9726 100755 --- a/configure +++ b/configure @@ -116,9 +116,7 @@ check_go_env() { cd ${0%/*} -if [ ! -f .env ]; then - rm .env -fi +rm -fv .env check_for make check_for awk From 03f218a63c36def6b7428f8cbc0d333c8f3cc9ac Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Mon, 15 Jul 2019 21:49:38 +0100 Subject: [PATCH 086/128] Ensure gomodules are used when downloading --- Dockerfile | 2 +- Dockerfile.arm64 | 2 +- Dockerfile.armv6 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index cc3a745..db18b41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy # Fetch dependencies COPY go.mod go.sum ./ -RUN go mod download +RUN GO111MODULE=on go mod download # Now pull in our code COPY . . diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 7138da8..5b3c1f5 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -8,7 +8,7 @@ WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy # Fetch dependencies COPY go.mod go.sum ./ -RUN go mod download +RUN GO111MODULE=on go mod download # Now pull in our code COPY . . diff --git a/Dockerfile.armv6 b/Dockerfile.armv6 index 15db884..5f5fe75 100644 --- a/Dockerfile.armv6 +++ b/Dockerfile.armv6 @@ -8,7 +8,7 @@ WORKDIR $GOPATH/src/github.com/pusher/oauth2_proxy # Fetch dependencies COPY go.mod go.sum ./ -RUN go mod download +RUN GO111MODULE=on go mod download # Now pull in our code COPY . . From ec97000169e15de6d5870681498c71084c14f954 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Fri, 31 May 2019 20:11:28 +1200 Subject: [PATCH 087/128] Add silence ping logging flag Add ability to silence logging of requests to /ping endpoint, reducing log clutter Pros: - Don't have to change all handlers to set/not set silent ping logging - Don't have to duplicate `loggingHandler` (this could be preferable yet) Cons: - Leaking oauth2proxy logic into `package logger` - Defining default pingPath in two locations Alternative: - Add generic exclude path to `logger.go` and pass in `/ping`. --- CHANGELOG.md | 1 + docs/configuration/configuration.md | 3 +++ logging_handler.go | 5 +++-- logging_handler_test.go | 16 +++++++++++---- main.go | 1 + options.go | 6 ++++++ pkg/logger/logger.go | 32 +++++++++++++++++++++++++++++ 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8463401..015a315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ## Changes since v3.2.0 +- [#178](https://github.com/pusher/outh2_proxy/pull/178) Add silence ping logging and exclude logging paths flags (@kskewes) - [#209](https://github.com/pusher/outh2_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) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index dad9ea1..5fffc43 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -90,6 +90,7 @@ Usage of oauth2_proxy: -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) -set-authorization-header: set Authorization Bearer response header (useful in Nginx auth_request mode) -signature-key string: GAP-Signature request signature key (algorithm:secretkey) + -silence-ping-logging bool: disable logging of requests to ping endpoint (default false) -skip-auth-preflight: will skip authentication for OPTIONS requests -skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times) -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens @@ -139,6 +140,8 @@ There are three different types of logging: standard, authentication, and HTTP r Each type of logging has their own configurable format and variables. By default these formats are similar to the Apache Combined Log. +Logging of requests to the `/ping` endpoint can be disabled with `-silence-ping-logging` reducing log volume. + ### 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: diff --git a/logging_handler.go b/logging_handler.go index b4f829d..9915e27 100644 --- a/logging_handler.go +++ b/logging_handler.go @@ -75,18 +75,19 @@ func (l *responseLogger) Status() int { return l.status } -// Size returns teh response size +// Size returns the response size func (l *responseLogger) Size() int { return l.size } +// Flush sends any buffered data to the client func (l *responseLogger) Flush() { if flusher, ok := l.w.(http.Flusher); ok { flusher.Flush() } } -// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends +// loggingHandler is the http.Handler implementation for LoggingHandler type loggingHandler struct { handler http.Handler } diff --git a/logging_handler_test.go b/logging_handler_test.go index fd77e0f..9d966b3 100644 --- a/logging_handler_test.go +++ b/logging_handler_test.go @@ -17,10 +17,17 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) { tests := []struct { Format, - ExpectedLogMessage string + ExpectedLogMessage, + Path string + SilentPing 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))}, - {"{{.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", 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)), "/foo/bar", true}, + {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/ping", false}, + {"{{.RequestMethod}}", "GET\n", "/foo/bar", false}, + {"{{.RequestMethod}}", "GET\n", "/foo/bar", true}, + {"{{.RequestMethod}}", "GET\n", "/ping", false}, + {"{{.RequestMethod}}", "", "/ping", true}, } for _, test := range tests { @@ -36,9 +43,10 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) { logger.SetOutput(buf) logger.SetReqTemplate(test.Format) + logger.SetSilentPing(test.SilentPing) h := LoggingHandler(http.HandlerFunc(handler)) - r, _ := http.NewRequest("GET", "/foo/bar", nil) + r, _ := http.NewRequest("GET", test.Path, nil) r.RemoteAddr = "127.0.0.1" r.Host = "test-server" diff --git a/main.go b/main.go index 4ccc25b..395a55a 100644 --- a/main.go +++ b/main.go @@ -98,6 +98,7 @@ func main() { flagSet.Bool("request-logging", true, "Log HTTP requests") flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines") + flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint") flagSet.Bool("auth-logging", true, "Log authentication attempts") flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines") diff --git a/options.go b/options.go index f1ca55d..69919c3 100644 --- a/options.go +++ b/options.go @@ -103,6 +103,8 @@ type Options struct { StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"` RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"` RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"` + PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"` + SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"` AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"` AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"` @@ -165,6 +167,8 @@ func NewOptions() *Options { LoggingMaxBackups: 0, LoggingLocalTime: true, LoggingCompress: false, + PingPath: "/ping", + SilencePingLogging: false, StandardLogging: true, StandardLoggingFormat: logger.DefaultStandardLoggingFormat, RequestLogging: true, @@ -567,6 +571,8 @@ func setupLogger(o *Options, msgs []string) []string { logger.SetStandardEnabled(o.StandardLogging) logger.SetAuthEnabled(o.AuthLogging) logger.SetReqEnabled(o.RequestLogging) + logger.SetSilentPing(o.SilencePingLogging) + logger.SetPingPath(o.PingPath) logger.SetStandardTemplate(o.StandardLoggingFormat) logger.SetAuthTemplate(o.AuthLoggingFormat) logger.SetReqTemplate(o.RequestLoggingFormat) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 8e27733..8954613 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -88,6 +88,8 @@ type Logger struct { stdEnabled bool authEnabled bool reqEnabled bool + silentPing bool + pingPath string stdLogTemplate *template.Template authTemplate *template.Template reqTemplate *template.Template @@ -101,6 +103,8 @@ func New(flag int) *Logger { stdEnabled: true, authEnabled: true, reqEnabled: true, + silentPing: false, + pingPath: "/ping", stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)), authTemplate: template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)), reqTemplate: template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)), @@ -177,6 +181,9 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url. return } + if url.Path == l.pingPath && l.silentPing { + return + } duration := float64(time.Now().Sub(ts)) / float64(time.Second) if username == "" { @@ -302,6 +309,20 @@ func (l *Logger) SetReqEnabled(e bool) { l.reqEnabled = e } +// SetPingPath sets the ping path. +func (l *Logger) SetPingPath(s string) { + l.mu.Lock() + defer l.mu.Unlock() + l.pingPath = s +} + +// SetSilentPing disables ping request logging. +func (l *Logger) SetSilentPing(e bool) { + l.mu.Lock() + defer l.mu.Unlock() + l.silentPing = e +} + // SetStandardTemplate sets the template for standard logging. func (l *Logger) SetStandardTemplate(t string) { l.mu.Lock() @@ -365,6 +386,17 @@ func SetReqEnabled(e bool) { std.SetReqEnabled(e) } +// SetPingPath sets the healthcheck endpoint path. +// FIXME: Seems wrong to define this +func SetPingPath(s string) { + std.SetPingPath(s) +} + +// SetSilentPing disables request logging for the ping endpoint. +func SetSilentPing(e bool) { + std.SetSilentPing(e) +} + // SetStandardTemplate sets the template for standard logging for // the standard logger. func SetStandardTemplate(t string) { From c4f20fff3d961f186a041c3f00aab7de40492608 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Sun, 2 Jun 2019 14:36:54 +1200 Subject: [PATCH 088/128] Add exclude logging path option Useful for excluding /ping endpoint to reduce log volume. This is somewhat more verbose than a simple bool to disable logging of the `/ping` endpoint. Perhaps better to add `-silence-ping-logging` bool flag to `options.go` and pass in the `/ping` endpoint as part of `logger` declaration in `options.go`. Could be extended into a slice of paths similar to go-gin's `SkipPaths`: https://github.com/gin-gonic/gin/blob/master/logger.go#L46 --- docs/configuration/configuration.md | 4 ++-- logging_handler_test.go | 18 ++++++++-------- main.go | 2 +- options.go | 18 ++++++++-------- pkg/logger/logger.go | 33 ++++++++--------------------- 5 files changed, 30 insertions(+), 45 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 5fffc43..4e66ec1 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -42,6 +42,7 @@ Usage of oauth2_proxy: -display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true) -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email -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) + -exclude-logging-path: don't log requests to this path, eg: /ping (default "" = no paths excluded) -flush-interval: period between flushing response buffers when streaming responses (default "1s") -banner string: custom banner string. Use "-" to disable default banner. -footer string: custom footer string. Use "-" to disable default footer. @@ -90,7 +91,6 @@ Usage of oauth2_proxy: -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) -set-authorization-header: set Authorization Bearer response header (useful in Nginx auth_request mode) -signature-key string: GAP-Signature request signature key (algorithm:secretkey) - -silence-ping-logging bool: disable logging of requests to ping endpoint (default false) -skip-auth-preflight: will skip authentication for OPTIONS requests -skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times) -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens @@ -140,7 +140,7 @@ 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. -Logging of requests to the `/ping` endpoint can be disabled with `-silence-ping-logging` reducing log volume. +A specific path can be excluded from request logs by setting `-exclude-logging-path`. This is useful for disabling logging of requests to the `/ping` endpoint to reduce log volume when health checking `oauth2_proxy`. ### 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: diff --git a/logging_handler_test.go b/logging_handler_test.go index 9d966b3..b036a1f 100644 --- a/logging_handler_test.go +++ b/logging_handler_test.go @@ -19,15 +19,15 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) { Format, ExpectedLogMessage, Path string - SilentPing bool + ExcludePath string }{ - {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", 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)), "/foo/bar", true}, - {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/ping", false}, - {"{{.RequestMethod}}", "GET\n", "/foo/bar", false}, - {"{{.RequestMethod}}", "GET\n", "/foo/bar", true}, - {"{{.RequestMethod}}", "GET\n", "/ping", false}, - {"{{.RequestMethod}}", "", "/ping", true}, + {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", ""}, + {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", "/ping"}, + {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/ping", ""}, + {"{{.RequestMethod}}", "GET\n", "/foo/bar", ""}, + {"{{.RequestMethod}}", "GET\n", "/foo/bar", "/ping"}, + {"{{.RequestMethod}}", "GET\n", "/ping", ""}, + {"{{.RequestMethod}}", "", "/ping", "/ping"}, } for _, test := range tests { @@ -43,7 +43,7 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) { logger.SetOutput(buf) logger.SetReqTemplate(test.Format) - logger.SetSilentPing(test.SilentPing) + logger.SetExcludePath(test.ExcludePath) h := LoggingHandler(http.HandlerFunc(handler)) r, _ := http.NewRequest("GET", test.Path, nil) diff --git a/main.go b/main.go index 395a55a..222aa69 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { flagSet.Bool("request-logging", true, "Log HTTP requests") flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines") - flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint") + flagSet.String("exclude-logging-path", "", "Exclude logging requests to path (eg: /ping)") flagSet.Bool("auth-logging", true, "Log authentication attempts") flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines") diff --git a/options.go b/options.go index 69919c3..88e76e9 100644 --- a/options.go +++ b/options.go @@ -105,15 +105,15 @@ type Options struct { RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"` PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"` SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"` + ExcludeLoggingPath string `flag:"exclude-logging-path" cfg:"exclude_logging_path" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATH"` 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"` - 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"` - JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file" env:"OAUTH2_PROXY_JWT_KEY_FILE"` - PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"` - GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"` + 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"` + JWTKey string `flag:"jwt-key" cfg:"jwt_key" env:"OAUTH2_PROXY_JWT_KEY"` + JWTKeyFile string `flag:"jwt-key-file" cfg:"jwt_key_file" env:"OAUTH2_PROXY_JWT_KEY_FILE"` + PubJWKURL string `flag:"pubjwk-url" cfg:"pubjwk_url" env:"OAUTH2_PROXY_PUBJWK_URL"` + GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks" env:"OAUTH2_PROXY_GCP_HEALTHCHECKS"` // internal values that are set after config validation redirectURL *url.URL @@ -167,6 +167,7 @@ func NewOptions() *Options { LoggingMaxBackups: 0, LoggingLocalTime: true, LoggingCompress: false, + ExcludeLoggingPath: "", PingPath: "/ping", SilencePingLogging: false, StandardLogging: true, @@ -571,8 +572,7 @@ func setupLogger(o *Options, msgs []string) []string { logger.SetStandardEnabled(o.StandardLogging) logger.SetAuthEnabled(o.AuthLogging) logger.SetReqEnabled(o.RequestLogging) - logger.SetSilentPing(o.SilencePingLogging) - logger.SetPingPath(o.PingPath) + logger.SetExcludePath(o.ExcludeLoggingPath) logger.SetStandardTemplate(o.StandardLoggingFormat) logger.SetAuthTemplate(o.AuthLoggingFormat) logger.SetReqTemplate(o.RequestLoggingFormat) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 8954613..68149c7 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -88,8 +88,7 @@ type Logger struct { stdEnabled bool authEnabled bool reqEnabled bool - silentPing bool - pingPath string + excludePath string stdLogTemplate *template.Template authTemplate *template.Template reqTemplate *template.Template @@ -103,8 +102,7 @@ func New(flag int) *Logger { stdEnabled: true, authEnabled: true, reqEnabled: true, - silentPing: false, - pingPath: "/ping", + excludePath: "", stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)), authTemplate: template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)), reqTemplate: template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)), @@ -181,7 +179,7 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url. return } - if url.Path == l.pingPath && l.silentPing { + if url.Path == l.excludePath { return } duration := float64(time.Now().Sub(ts)) / float64(time.Second) @@ -309,18 +307,11 @@ func (l *Logger) SetReqEnabled(e bool) { l.reqEnabled = e } -// SetPingPath sets the ping path. -func (l *Logger) SetPingPath(s string) { +// SetExcludePath sets the path to exclude from logging. +func (l *Logger) SetExcludePath(s string) { l.mu.Lock() defer l.mu.Unlock() - l.pingPath = s -} - -// SetSilentPing disables ping request logging. -func (l *Logger) SetSilentPing(e bool) { - l.mu.Lock() - defer l.mu.Unlock() - l.silentPing = e + l.excludePath = s } // SetStandardTemplate sets the template for standard logging. @@ -386,15 +377,9 @@ func SetReqEnabled(e bool) { std.SetReqEnabled(e) } -// SetPingPath sets the healthcheck endpoint path. -// FIXME: Seems wrong to define this -func SetPingPath(s string) { - std.SetPingPath(s) -} - -// SetSilentPing disables request logging for the ping endpoint. -func SetSilentPing(e bool) { - std.SetSilentPing(e) +// SetExcludePath sets the path to exclude from logging, eg: health checks +func SetExcludePath(s string) { + std.SetExcludePath(s) } // SetStandardTemplate sets the template for standard logging for From 08021429eafd7241a8990597f1c8bcad8a424aee Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Mon, 3 Jun 2019 10:04:15 +1200 Subject: [PATCH 089/128] formatting and extra test Can probably slim down the `ExcludePath` tests. --- logging_handler_test.go | 1 + pkg/logger/logger.go | 1 + 2 files changed, 2 insertions(+) diff --git a/logging_handler_test.go b/logging_handler_test.go index b036a1f..0adec08 100644 --- a/logging_handler_test.go +++ b/logging_handler_test.go @@ -24,6 +24,7 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) { {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", ""}, {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", "/ping"}, {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/ping", ""}, + {logger.DefaultRequestLoggingFormat, "", "/ping", "/ping"}, {"{{.RequestMethod}}", "GET\n", "/foo/bar", ""}, {"{{.RequestMethod}}", "GET\n", "/foo/bar", "/ping"}, {"{{.RequestMethod}}", "GET\n", "/ping", ""}, diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 68149c7..89b8450 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -182,6 +182,7 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url. if url.Path == l.excludePath { return } + duration := float64(time.Now().Sub(ts)) / float64(time.Second) if username == "" { From 4e10cc76e061558a170c42b1d7c0da81794ae2e6 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Mon, 3 Jun 2019 13:51:59 +1200 Subject: [PATCH 090/128] Add silence ping logging flag using ExcludePath - Add `ping-path` option to enable switching on and passing to `logger.go` Default remains unchanged at: `"/ping"` - Add note in configuration.md about silence flag taking precedence Potential tests: - `options.go` sets `logger.SetExcludePath` based on silence flag? - Changing `PingPath` reflected in router? --- docs/configuration/configuration.md | 4 +++- main.go | 2 ++ oauthproxy.go | 2 +- options.go | 13 +++++++++---- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 4e66ec1..178e009 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -74,6 +74,7 @@ Usage of oauth2_proxy: -pass-user-headers: pass X-Forwarded-User and X-Forwarded-Email information to upstream (default true) -profile-url string: Profile access endpoint -provider string: OAuth provider (default "google") + -ping-path string: the ping endpoint that can be used for basic health checks (default "/ping") -proxy-prefix string: the url root path that this proxy should be nested under (e.g. //sign_in) (default "/oauth2") -proxy-websockets: enables WebSocket proxying (default true) -pubjwk-url string: JWK pubkey access endpoint: required by login.gov @@ -91,6 +92,7 @@ Usage of oauth2_proxy: -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) -set-authorization-header: set Authorization Bearer response header (useful in Nginx auth_request mode) -signature-key string: GAP-Signature request signature key (algorithm:secretkey) + -silence-ping-logging bool: disable logging of requests to ping endpoint (default false) -skip-auth-preflight: will skip authentication for OPTIONS requests -skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times) -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens @@ -140,7 +142,7 @@ 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. -A specific path can be excluded from request logs by setting `-exclude-logging-path`. This is useful for disabling logging of requests to the `/ping` endpoint to reduce log volume when health checking `oauth2_proxy`. +Logging of requests to the `/ping` endpoint can be disabled with `-silence-ping-logging` reducing log volume. This flag sets the `-exclude-logging-path` value to the `-ping-path` and takes precedence over any other value `-exclude-logging-path` may have been set to directly. ### 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: diff --git a/main.go b/main.go index 222aa69..4ae9260 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,7 @@ func main() { flagSet.String("banner", "", "custom banner string. Use \"-\" to disable default banner.") 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. //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.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates") @@ -99,6 +100,7 @@ func main() { flagSet.Bool("request-logging", true, "Log HTTP requests") flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines") flagSet.String("exclude-logging-path", "", "Exclude logging requests to path (eg: /ping)") + flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint") flagSet.Bool("auth-logging", true, "Log authentication attempts") flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines") diff --git a/oauthproxy.go b/oauthproxy.go index d2ff74c..5ef7a39 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -254,7 +254,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { Validator: validator, RobotsPath: "/robots.txt", - PingPath: "/ping", + PingPath: opts.PingPath, SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix), SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix), OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix), diff --git a/options.go b/options.go index 88e76e9..b2afe93 100644 --- a/options.go +++ b/options.go @@ -30,6 +30,7 @@ import ( // or Config File type Options struct { 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"` 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"` @@ -103,9 +104,8 @@ type Options struct { StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"` RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"` RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"` - PingPath string `flag:"ping-path" cfg:"ping_path" env:"OAUTH2_PROXY_PING_PATH"` - SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"` ExcludeLoggingPath string `flag:"exclude-logging-path" cfg:"exclude_logging_path" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATH"` + SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"` AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"` AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"` SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"` @@ -136,6 +136,7 @@ type SignatureData struct { func NewOptions() *Options { return &Options{ ProxyPrefix: "/oauth2", + PingPath: "/ping", ProxyWebSockets: true, HTTPAddress: "127.0.0.1:4180", HTTPSAddress: ":443", @@ -168,7 +169,6 @@ func NewOptions() *Options { LoggingLocalTime: true, LoggingCompress: false, ExcludeLoggingPath: "", - PingPath: "/ping", SilencePingLogging: false, StandardLogging: true, StandardLoggingFormat: logger.DefaultStandardLoggingFormat, @@ -572,11 +572,16 @@ func setupLogger(o *Options, msgs []string) []string { logger.SetStandardEnabled(o.StandardLogging) logger.SetAuthEnabled(o.AuthLogging) logger.SetReqEnabled(o.RequestLogging) - logger.SetExcludePath(o.ExcludeLoggingPath) logger.SetStandardTemplate(o.StandardLoggingFormat) logger.SetAuthTemplate(o.AuthLoggingFormat) logger.SetReqTemplate(o.RequestLoggingFormat) + if o.SilencePingLogging { + logger.SetExcludePath(o.PingPath) + } else { + logger.SetExcludePath(o.ExcludeLoggingPath) + } + if !o.LoggingLocalTime { logger.SetFlags(logger.Flags() | logger.LUTC) } From 289dfce28ad8863bbf65023f15302ee273a20803 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Sat, 22 Jun 2019 09:39:46 +1200 Subject: [PATCH 091/128] logger.go ExcludedPaths changed to slice of paths. - `logger.go` convert slice of paths to map for quicker lookup - `options.go` combines csv paths and pingpath into slice --- docs/configuration/configuration.md | 5 +++-- logging_handler_test.go | 28 ++++++++++++++++++---------- main.go | 2 +- options.go | 12 +++++++----- pkg/logger/logger.go | 21 ++++++++++++--------- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 178e009..d84d40d 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -41,8 +41,9 @@ Usage of oauth2_proxy: -custom-templates-dir string: path to custom html templates -display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true) -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email +<<<<<<< HEAD -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) - -exclude-logging-path: don't log requests to this path, eg: /ping (default "" = no paths excluded) + -exclude-logging-paths: comma separated list of paths to exclude from logging, eg: "/ping,/path2" (default "" = no paths excluded) -flush-interval: period between flushing response buffers when streaming responses (default "1s") -banner string: custom banner string. Use "-" to disable default banner. -footer string: custom footer string. Use "-" to disable default footer. @@ -142,7 +143,7 @@ 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. -Logging of requests to the `/ping` endpoint can be disabled with `-silence-ping-logging` reducing log volume. This flag sets the `-exclude-logging-path` value to the `-ping-path` and takes precedence over any other value `-exclude-logging-path` may have been set to directly. +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 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: diff --git a/logging_handler_test.go b/logging_handler_test.go index 0adec08..ddc9778 100644 --- a/logging_handler_test.go +++ b/logging_handler_test.go @@ -19,16 +19,21 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) { Format, ExpectedLogMessage, Path string - ExcludePath 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", ""}, - {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", "/ping"}, - {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/ping", ""}, - {logger.DefaultRequestLoggingFormat, "", "/ping", "/ping"}, - {"{{.RequestMethod}}", "GET\n", "/foo/bar", ""}, - {"{{.RequestMethod}}", "GET\n", "/foo/bar", "/ping"}, - {"{{.RequestMethod}}", "GET\n", "/ping", ""}, - {"{{.RequestMethod}}", "", "/ping", "/ping"}, + {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)), "/foo/bar", []string{}, true}, + {logger.DefaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", logger.FormatTimestamp(ts)), "/foo/bar", []string{"/ping"}, false}, + {logger.DefaultRequestLoggingFormat, "", "/foo/bar", []string{"/foo/bar"}, false}, + {logger.DefaultRequestLoggingFormat, "", "/ping", []string{}, true}, + {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}, false}, + {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/ping"}, true}, + {logger.DefaultRequestLoggingFormat, "", "/ping", []string{"/foo/bar", "/ping"}, false}, + {"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{}, true}, + {"{{.RequestMethod}}", "GET\n", "/foo/bar", []string{"/ping"}, false}, + {"{{.RequestMethod}}", "GET\n", "/ping", []string{}, false}, + {"{{.RequestMethod}}", "", "/ping", []string{"/ping"}, true}, } for _, test := range tests { @@ -44,7 +49,10 @@ func TestLoggingHandler_ServeHTTP(t *testing.T) { logger.SetOutput(buf) logger.SetReqTemplate(test.Format) - logger.SetExcludePath(test.ExcludePath) + if test.SilencePingLogging { + test.ExcludePaths = append(test.ExcludePaths, "/ping") + } + logger.SetExcludePaths(test.ExcludePaths) h := LoggingHandler(http.HandlerFunc(handler)) r, _ := http.NewRequest("GET", test.Path, nil) diff --git a/main.go b/main.go index 4ae9260..6900845 100644 --- a/main.go +++ b/main.go @@ -99,7 +99,7 @@ func main() { flagSet.Bool("request-logging", true, "Log HTTP requests") flagSet.String("request-logging-format", logger.DefaultRequestLoggingFormat, "Template for HTTP request log lines") - flagSet.String("exclude-logging-path", "", "Exclude logging requests to path (eg: /ping)") + 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") diff --git a/options.go b/options.go index b2afe93..3eda23c 100644 --- a/options.go +++ b/options.go @@ -104,7 +104,7 @@ type Options struct { StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"` RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"` RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"` - ExcludeLoggingPath string `flag:"exclude-logging-path" cfg:"exclude_logging_path" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATH"` + ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_EXCLUDE_LOGGING_PATHS"` SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"` AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"` AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"` @@ -168,7 +168,7 @@ func NewOptions() *Options { LoggingMaxBackups: 0, LoggingLocalTime: true, LoggingCompress: false, - ExcludeLoggingPath: "", + ExcludeLoggingPaths: "", SilencePingLogging: false, StandardLogging: true, StandardLoggingFormat: logger.DefaultStandardLoggingFormat, @@ -576,12 +576,14 @@ func setupLogger(o *Options, msgs []string) []string { logger.SetAuthTemplate(o.AuthLoggingFormat) logger.SetReqTemplate(o.RequestLoggingFormat) + excludePaths := make([]string, 0) + excludePaths = append(excludePaths, strings.Split(o.ExcludeLoggingPaths, ",")...) if o.SilencePingLogging { - logger.SetExcludePath(o.PingPath) - } else { - logger.SetExcludePath(o.ExcludeLoggingPath) + excludePaths = append(excludePaths, o.PingPath) } + logger.SetExcludePaths(excludePaths) + if !o.LoggingLocalTime { logger.SetFlags(logger.Flags() | logger.LUTC) } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 89b8450..1ef9b7d 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -88,7 +88,7 @@ type Logger struct { stdEnabled bool authEnabled bool reqEnabled bool - excludePath string + excludePaths map[string]struct{} stdLogTemplate *template.Template authTemplate *template.Template reqTemplate *template.Template @@ -102,7 +102,7 @@ func New(flag int) *Logger { stdEnabled: true, authEnabled: true, reqEnabled: true, - excludePath: "", + excludePaths: nil, stdLogTemplate: template.Must(template.New("std-log").Parse(DefaultStandardLoggingFormat)), authTemplate: template.Must(template.New("auth-log").Parse(DefaultAuthLoggingFormat)), reqTemplate: template.Must(template.New("req-log").Parse(DefaultRequestLoggingFormat)), @@ -179,7 +179,7 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url. return } - if url.Path == l.excludePath { + if _, excludedPath := l.excludePaths[url.Path]; excludedPath { return } @@ -308,11 +308,14 @@ func (l *Logger) SetReqEnabled(e bool) { l.reqEnabled = e } -// SetExcludePath sets the path to exclude from logging. -func (l *Logger) SetExcludePath(s string) { +// SetExcludePaths sets the paths to exclude from logging. +func (l *Logger) SetExcludePaths(s []string) { l.mu.Lock() defer l.mu.Unlock() - l.excludePath = s + l.excludePaths = make(map[string]struct{}) + for _, p := range s { + l.excludePaths[p] = struct{}{} + } } // SetStandardTemplate sets the template for standard logging. @@ -378,9 +381,9 @@ func SetReqEnabled(e bool) { std.SetReqEnabled(e) } -// SetExcludePath sets the path to exclude from logging, eg: health checks -func SetExcludePath(s string) { - std.SetExcludePath(s) +// 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 From 7236039b9dc203b78562731a299533e61e8823c1 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Mon, 24 Jun 2019 19:53:22 +1200 Subject: [PATCH 092/128] remove remnant from rebase --- docs/configuration/configuration.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index d84d40d..9e7a7e3 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -41,7 +41,6 @@ Usage of oauth2_proxy: -custom-templates-dir string: path to custom html templates -display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true) -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email -<<<<<<< HEAD -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) -exclude-logging-paths: comma separated list of paths to exclude from logging, eg: "/ping,/path2" (default "" = no paths excluded) -flush-interval: period between flushing response buffers when streaming responses (default "1s") From 9ed5623f2a61e8dc3bb9de34a99edda19d187e7c Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Mon, 24 Jun 2019 19:53:43 +1200 Subject: [PATCH 093/128] Change env vars to suit incoming PR186 --- options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options.go b/options.go index 3eda23c..7f474c8 100644 --- a/options.go +++ b/options.go @@ -104,7 +104,7 @@ type Options struct { StandardLoggingFormat string `flag:"standard-logging-format" cfg:"standard_logging_format" env:"OAUTH2_PROXY_STANDARD_LOGGING_FORMAT"` RequestLogging bool `flag:"request-logging" cfg:"request_logging" env:"OAUTH2_PROXY_REQUEST_LOGGING"` RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format" env:"OAUTH2_PROXY_REQUEST_LOGGING_FORMAT"` - ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_EXCLUDE_LOGGING_PATHS"` + ExcludeLoggingPaths string `flag:"exclude-logging-paths" cfg:"exclude_logging_paths" env:"OAUTH2_PROXY_EXCLUDE_LOGGING_PATHS"` SilencePingLogging bool `flag:"silence-ping-logging" cfg:"silence_ping_logging" env:"OAUTH2_PROXY_SILENCE_PING_LOGGING"` AuthLogging bool `flag:"auth-logging" cfg:"auth_logging" env:"OAUTH2_PROXY_LOGGING_AUTH_LOGGING"` AuthLoggingFormat string `flag:"auth-logging-format" cfg:"auth_logging_format" env:"OAUTH2_PROXY_AUTH_LOGGING_FORMAT"` From 84da3c3d8cf1037aaba2a048710ad6bbc96bbf35 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Mon, 24 Jun 2019 20:05:05 +1200 Subject: [PATCH 094/128] update changelog with both flags --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 015a315..3b55539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ ## Changes since v3.2.0 -- [#178](https://github.com/pusher/outh2_proxy/pull/178) Add silence ping logging and exclude logging paths flags (@kskewes) +- [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) - [#209](https://github.com/pusher/outh2_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) From b57d7f77e1d712231e48ca151b9244cf05f30bc4 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Mon, 1 Jul 2019 13:13:51 +1200 Subject: [PATCH 095/128] Use ok naming convention for map presence check --- pkg/logger/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 1ef9b7d..1b6017b 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -179,7 +179,7 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url. return } - if _, excludedPath := l.excludePaths[url.Path]; excludedPath { + if _, ok := l.excludePaths[url.Path]; ok { return } From f00a474d91c9fba94b866d31e4b0ae2fbbebadc3 Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Tue, 16 Jul 2019 11:39:06 +1200 Subject: [PATCH 096/128] Correct tls cert flag name per 186 --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 6900845..97e2357 100644 --- a/main.go +++ b/main.go @@ -32,8 +32,8 @@ func main() { flagSet.String("http-address", "127.0.0.1:4180", "[http://]: or unix:// to listen on for HTTP clients") flagSet.String("https-address", ":443", ": to listen on for HTTPS clients") - flagSet.String("tls-cert", "", "path to certificate file") - flagSet.String("tls-key", "", "path to private key file") + flagSet.String("tls-cert-file", "", "path to certificate file") + flagSet.String("tls-key-file", "", "path to private key file") flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") flagSet.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") From 6bf3f2a51bc50aa3cd89bfefb0beaabb74334eae Mon Sep 17 00:00:00 2001 From: Karl Skewes Date: Tue, 16 Jul 2019 11:39:06 +1200 Subject: [PATCH 097/128] Correct tls cert flag name per 186 --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 4ccc25b..1441ee2 100644 --- a/main.go +++ b/main.go @@ -32,8 +32,8 @@ func main() { flagSet.String("http-address", "127.0.0.1:4180", "[http://]: or unix:// to listen on for HTTP clients") flagSet.String("https-address", ":443", ": to listen on for HTTPS clients") - flagSet.String("tls-cert", "", "path to certificate file") - flagSet.String("tls-key", "", "path to private key file") + flagSet.String("tls-cert-file", "", "path to certificate file") + flagSet.String("tls-key-file", "", "path to private key file") flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") flagSet.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") From 7b1132df13dfac02ff9105ae9a285aa3afe1ccb2 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 17 Jul 2019 09:58:11 +0100 Subject: [PATCH 098/128] Fix tls-*-file docs --- docs/4_tls.md | 6 +++--- docs/configuration/configuration.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/4_tls.md b/docs/4_tls.md index 706b665..ad96086 100644 --- a/docs/4_tls.md +++ b/docs/4_tls.md @@ -9,7 +9,7 @@ nav_order: 4 There are two recommended configurations. -1. Configure SSL Termination with OAuth2 Proxy by providing a `--tls-cert=/path/to/cert.pem` and `--tls-key=/path/to/cert.key`. +1. Configure SSL Termination with OAuth2 Proxy by providing a `--tls-cert-file=/path/to/cert.pem` and `--tls-key-file=/path/to/cert.key`. The command line to run `oauth2_proxy` in this configuration would look like this: @@ -17,8 +17,8 @@ The command line to run `oauth2_proxy` in this configuration would look like thi ./oauth2_proxy \ --email-domain="yourcompany.com" \ --upstream=http://127.0.0.1:8080/ \ - --tls-cert=/path/to/cert.pem \ - --tls-key=/path/to/cert.key \ + --tls-cert-file=/path/to/cert.pem \ + --tls-key-file=/path/to/cert.key \ --cookie-secret=... \ --cookie-secure=true \ --provider=... \ diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index dad9ea1..e19fd93 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -98,8 +98,8 @@ Usage of oauth2_proxy: -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS -standard-logging: Log standard runtime information (default true) -standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below) - -tls-cert string: path to certificate file - -tls-key string: path to private key file + -tls-cert-file string: path to certificate file + -tls-key-file string: path to private key file -upstream value: 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: print version string From f29e353586c1cb51b2e0bc562b1cc39ef8a4facf Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 19 Jul 2019 22:11:53 +1200 Subject: [PATCH 099/128] Update options.go Co-Authored-By: Joel Speed --- options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options.go b/options.go index 7f474c8..7dcb12b 100644 --- a/options.go +++ b/options.go @@ -30,7 +30,7 @@ import ( // or Config File type Options struct { 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"` + 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"` 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"` From 2eecf756e43a2d77e489d41251af4ab2f419aa06 Mon Sep 17 00:00:00 2001 From: Ryan Luckie Date: Fri, 3 May 2019 17:33:56 -0500 Subject: [PATCH 100/128] Add OIDC support for UserInfo Endpoint Email Verification * Current OIDC implementation asserts that user email check must come from JWT token claims. OIDC specification also allows for source of user email to be fetched from userinfo profile endpoint. http://openid.net/specs/openid-connect-core-1_0.html#UserInfo * First, attempt to retrieve email from JWT token claims. Then fall back to requesting email from userinfo endpoint. * Don't fallback to subject for email https://github.com/bitly/oauth2_proxy/pull/481 --- providers/oidc.go | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/providers/oidc.go b/providers/oidc.go index 86a58f6..396310f 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -3,11 +3,15 @@ package providers import ( "context" "fmt" + "net/http" "time" oidc "github.com/coreos/go-oidc" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/requests" + "golang.org/x/oauth2" + ) // OIDCProvider represents an OIDC based Identity Provider @@ -117,8 +121,31 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok } if claims.Email == "" { - // TODO: Try getting email from /userinfo before falling back to Subject - claims.Email = claims.Subject + if p.ProfileURL.String() == "" { + return nil, fmt.Errorf("id_token did not contain an email") + } + + // 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) + + json, err := requests.Request(req) + if err != nil { + return nil, err + } + + email, err := json.Get("email").String() + if err != nil { + return nil, fmt.Errorf("id_token nor userinfo endpoint did not contain 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) @@ -145,3 +172,10 @@ func (p *OIDCProvider) ValidateSessionState(s *sessions.SessionState) bool { return true } + +func getOIDCHeader(access_token string) http.Header { + header := make(http.Header) + header.Set("Accept", "application/json") + header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token)) + return header +} From 0d94f5e51597628a5dc920a723290a73e6d59695 Mon Sep 17 00:00:00 2001 From: Ryan Luckie Date: Fri, 3 May 2019 23:07:48 +0000 Subject: [PATCH 101/128] fix lint error --- providers/oidc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/oidc.go b/providers/oidc.go index 396310f..937a16b 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -173,9 +173,9 @@ func (p *OIDCProvider) ValidateSessionState(s *sessions.SessionState) bool { return true } -func getOIDCHeader(access_token string) http.Header { +func getOIDCHeader(accessToken string) http.Header { header := make(http.Header) header.Set("Accept", "application/json") - header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token)) + header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) return header } From 122ec45dd8f3da63d74a91fcd31e6ed5e11bdf49 Mon Sep 17 00:00:00 2001 From: Ryan Luckie Date: Tue, 7 May 2019 16:17:38 -0500 Subject: [PATCH 102/128] Requested changes --- providers/oidc.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/providers/oidc.go b/providers/oidc.go index 937a16b..7b3df62 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -11,7 +11,6 @@ import ( "github.com/pusher/oauth2_proxy/pkg/requests" "golang.org/x/oauth2" - ) // OIDCProvider represents an OIDC based Identity Provider @@ -135,12 +134,12 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok } req.Header = getOIDCHeader(token.AccessToken) - json, err := requests.Request(req) + respJson, err := requests.Request(req) if err != nil { return nil, err } - email, err := json.Get("email").String() + email, err := respJson.Get("email").String() if err != nil { return nil, fmt.Errorf("id_token nor userinfo endpoint did not contain an email") } From f537720b5290e8e9b8d222fe56c77567b98f4746 Mon Sep 17 00:00:00 2001 From: Ryan Luckie Date: Tue, 7 May 2019 16:29:21 -0500 Subject: [PATCH 103/128] fix lint errors --- providers/oidc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/oidc.go b/providers/oidc.go index 7b3df62..3eaac15 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -134,12 +134,12 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok } req.Header = getOIDCHeader(token.AccessToken) - respJson, err := requests.Request(req) + respJSON, err := requests.Request(req) if err != nil { return nil, err } - email, err := respJson.Get("email").String() + email, err := respJSON.Get("email").String() if err != nil { return nil, fmt.Errorf("id_token nor userinfo endpoint did not contain an email") } From 93cb575d7c94ea8ec5056c100fd0a4779ffcd8c5 Mon Sep 17 00:00:00 2001 From: Ryan Luckie Date: Fri, 19 Jul 2019 08:59:29 -0500 Subject: [PATCH 104/128] Fix error message for clarity --- providers/oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/oidc.go b/providers/oidc.go index 3eaac15..2abf2ca 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -141,7 +141,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok email, err := respJSON.Get("email").String() if err != nil { - return nil, fmt.Errorf("id_token nor userinfo endpoint did not contain an email") + return nil, fmt.Errorf("Neither id_token nor userinfo endpoint contained an email") } claims.Email = email From 4a6b703c543b72e60421ca597749cd08780701e8 Mon Sep 17 00:00:00 2001 From: Ryan Luckie Date: Fri, 19 Jul 2019 09:03:01 -0500 Subject: [PATCH 105/128] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f09c2..4166882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ - [#159](https://github.com/pusher/oauth2_proxy/pull/159) Add option to skip the OIDC provider verified email check: `--insecure-oidc-allow-unverified-email` - [#210](https://github.com/pusher/oauth2_proxy/pull/210) Update base image from Alpine 3.9 to 3.10 (@steakunderscore) - [#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 From e48d28d1b9dd36c53194386c60fb13a2766528e2 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Tue, 23 Jul 2019 16:20:45 +0100 Subject: [PATCH 106/128] Add MAINTAINERS and update CODEOWNERS --- .github/CODEOWNERS | 6 ++++-- MAINTAINERS | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 MAINTAINERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 47d0e56..da3f739 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,8 @@ -# Default owner should be a Pusher cloud-team member unless overridden by later -# rules in this file +# Default owner should be a Pusher cloud-team member or another maintainer +# unless overridden by later rules in this file * @pusher/cloud-team +* @syscll +* @steakunderscore # login.gov provider # Note: If @timothy-spencer terms out of his appointment, your best bet diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..25fb475 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,3 @@ +Joel Speed (@JoelSpeed) +Dan Bond (@syscll) +Henry Jenkins (@steakunderscore) From 23309adc7cc6a3e7bc587605d935e974315305f5 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Wed, 24 Jul 2019 09:21:08 +0100 Subject: [PATCH 107/128] Fix CODEOWNERS file --- .github/CODEOWNERS | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index da3f739..a6f7701 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1,6 @@ # Default owner should be a Pusher cloud-team member or another maintainer # unless overridden by later rules in this file -* @pusher/cloud-team -* @syscll -* @steakunderscore +* @pusher/cloud-team @syscll @steakunderscore # login.gov provider # Note: If @timothy-spencer terms out of his appointment, your best bet From 1ab63304a1231705fa82669b020f599ef746f6b4 Mon Sep 17 00:00:00 2001 From: Reilly Brogan Date: Sat, 3 Aug 2019 13:22:42 -0500 Subject: [PATCH 108/128] Fix a bunch of places where the repo link was incorrect --- CHANGELOG.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f09c2..9610a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,18 +31,18 @@ ## Changes since v3.2.0 -- [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) -- [#209](https://github.com/pusher/outh2_proxy/pull/209) Improve docker build caching of layers (@dekimsey) +- [#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. - 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/outh2_proxy/pull/180) Minor refactor of core proxying path (@aeijdenberg). -- [#175](https://github.com/pusher/outh2_proxy/pull/175) Bump go-oidc to v2.0.0 (@aeijdenberg). +- [#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/outh2_proxy/pull/155) Add RedisSessionStore implementation (@brianv0, @JoelSpeed) +- [#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 @@ -52,10 +52,10 @@ - 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/outh2_proxy/pull/168) Drop Go 1.11 support in Travis (@JoelSpeed) -- [#169](https://github.com/pusher/outh2_proxy/pull/169) Update Alpine to 3.9 (@kskewes) -- [#148](https://github.com/pusher/outh2_proxy/pull/148) Implement SessionStore interface within proxy (@JoelSpeed) -- [#147](https://github.com/pusher/outh2_proxy/pull/147) Add SessionStore interfaces and initial implementation (@JoelSpeed) +- [#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 - Adds tests suite for interface to ensure consistency across implementations - Refactor some configuration options (around cookies) into packages @@ -83,8 +83,8 @@ - [#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/outh2_proxy/pull/195) Add `-banner` flag for overriding the banner line that is displayed (@steakunderscore) -- [#198](https://github.com/pusher/outh2_proxy/pull/198) Switch from gometalinter to golangci-lint (@steakunderscore) +- [#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` - [#210](https://github.com/pusher/oauth2_proxy/pull/210) Update base image from Alpine 3.9 to 3.10 (@steakunderscore) - [#211](https://github.com/pusher/oauth2_proxy/pull/211) Switch from dep to go modules (@steakunderscore) From d3462192934b1e19be561bccc793ebc4a3baedb2 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 4 Aug 2019 21:24:21 +0100 Subject: [PATCH 109/128] Remove dep from Travis CI Was missed from previous switch to go modules --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 445eb11..aedf434 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,6 @@ go: - 1.12.x install: # Fetch dependencies - - wget -O dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 - - chmod +x dep - - mv dep $GOPATH/bin/dep - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.17.1 script: - ./configure && make test From 8a24dd797fb1addaf31d87b9ab9648b5590acf3f Mon Sep 17 00:00:00 2001 From: hjenkins Date: Mon, 5 Aug 2019 09:25:14 +0100 Subject: [PATCH 110/128] Download modules in travis install step --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index aedf434..0537736 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: install: # Fetch dependencies - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v1.17.1 + - GO111MODULE=on go mod download script: - ./configure && make test sudo: false From 7d910c0ae8eef5620fd6524a38521e953d1adb8e Mon Sep 17 00:00:00 2001 From: Justin Palpant Date: Tue, 6 Aug 2019 02:38:24 -0700 Subject: [PATCH 111/128] Check Google group membership with hasMember and get. (#224) * Check Google group membership with hasMember and get. This PR is an enhancement built on https://github.com/pusher/oauth2_proxy/pull/160. That PR reduces the number of calls to the Google Admin API and simplifies the code by using the hasMember method. It also supports checking membership in nested groups. However, the above message doesn't handle members who are not a part of the domain. The hasMember API returns a 400 for that case. As a fallback, when the API returns a 400, this change will try using the `get` API which works as expected for members who aren't a part of the domain. Supporting members who belong to the Google group but aren't part of the domain is a requested feature from https://github.com/pusher/oauth2_proxy/issues/95. https://developers.google.com/admin-sdk/directory/v1/reference/members/get Note that nested members who are not a part of the domain will not be correctly detected with this change. * Update CHANGELOG. * Fix incorrect JSON and stop escaping strings. * Add comments for each scenario. --- CHANGELOG.md | 5 ++- providers/google.go | 83 +++++++++++++--------------------------- providers/google_test.go | 51 ++++++++++++++++-------- 3 files changed, 66 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9610a94..f979719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,8 +31,9 @@ ## Changes since v3.2.0 -- [#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) +- [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant) +- [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) +- [#209](https://github.com/pusher/outh2_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 diff --git a/providers/google.go b/providers/google.go index 28c488c..4748631 100644 --- a/providers/google.go +++ b/providers/google.go @@ -189,73 +189,44 @@ func getAdminService(adminEmail string, credentialsReader io.Reader) *admin.Serv } func userInGroup(service *admin.Service, groups []string, email string) bool { - user, err := fetchUser(service, email) - if err != nil { - logger.Printf("Warning: unable to fetch user: %v", err) - user = nil - } - for _, group := range groups { - members, err := fetchGroupMembers(service, group) + // Use the HasMember API to checking for the user's presence in each group or nested subgroups + req := service.Members.HasMember(group, email) + r, err := req.Do() if err != nil { - if err, ok := err.(*googleapi.Error); ok && err.Code == 404 { - logger.Printf("error fetching members for group %s: group does not exist", group) - } else { - logger.Printf("error fetching group members: %v", err) - return false - } - } + err, ok := err.(*googleapi.Error) + if ok && err.Code == 404 { + logger.Printf("error checking membership in group %s: group does not exist", group) + } else if ok && err.Code == 400 { + // It is possible for Members.HasMember to return false even if the email is a group member. + // One case that can cause this is if the user email is from a different domain than the group, + // e.g. "member@otherdomain.com" in the group "group@mydomain.com" will result in a 400 error + // from the HasMember API. In that case, attempt to query the member object directly from the group. + req := service.Members.Get(group, email) + r, err := req.Do() - for _, member := range members { - if member.Email == email { - return true - } - if user == nil { - continue - } - switch member.Type { - case "CUSTOMER": - if member.Id == user.CustomerId { - return true - } - case "USER": - if member.Id == user.Id { + 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 { + logger.Printf("error checking group membership: %v", err) } + continue + } + if r.IsMember { + return true } } return false } -func fetchUser(service *admin.Service, email string) (*admin.User, error) { - user, err := service.Users.Get(email).Do() - return user, err -} - -func fetchGroupMembers(service *admin.Service, group string) ([]*admin.Member, error) { - members := []*admin.Member{} - pageToken := "" - for { - req := service.Members.List(group) - if pageToken != "" { - req.PageToken(pageToken) - } - r, err := req.Do() - 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 // group(s). func (p *GoogleProvider) ValidateGroup(email string) bool { diff --git a/providers/google_test.go b/providers/google_test.go index 0c1725b..37b8326 100644 --- a/providers/google_test.go +++ b/providers/google_test.go @@ -1,6 +1,7 @@ package providers import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -12,6 +13,7 @@ import ( "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) { @@ -185,34 +187,53 @@ 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 == "/users/member-by-email@example.com" { - fmt.Fprintln(w, "{}") - } else if r.URL.Path == "/users/non-member-by-email@example.com" { - fmt.Fprintln(w, "{}") - } else if r.URL.Path == "/users/member-by-id@example.com" { - fmt.Fprintln(w, "{\"id\": \"member-id\"}") - } else if r.URL.Path == "/users/non-member-by-id@example.com" { - fmt.Fprintln(w, "{\"id\": \"non-member-id\"}") - } else if r.URL.Path == "/groups/group@example.com/members" { - fmt.Fprintln(w, "{\"members\": [{\"email\": \"member-by-email@example.com\"}, {\"id\": \"member-id\", \"type\": \"USER\"}]}") + 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() - service, err := admin.New(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-by-email@example.com") + result := userInGroup(service, []string{"group@example.com"}, "member-in-domain@example.com") assert.True(t, result) - result = userInGroup(service, []string{"group@example.com"}, "member-by-id@example.com") + 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-by-id@example.com") + 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-by-email@example.com") + result = userInGroup(service, []string{"group@example.com"}, "non-member-out-of-domain@otherexample.com") assert.False(t, result) } From 5f9a65f6b1fc771fc2252877e5cb2f6a736ab2b4 Mon Sep 17 00:00:00 2001 From: hjenkins Date: Tue, 6 Aug 2019 12:16:03 +0100 Subject: [PATCH 112/128] Adds reference to slack channel in readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b3a2806..9657e8f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,10 @@ Read the docs on our [Docs site](https://pusher.github.io/oauth2_proxy). ![OAuth2 Proxy Architecture](https://cloud.githubusercontent.com/assets/45028/8027702/bd040b7a-0d6a-11e5-85b9-f8d953d04f39.png) +## 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 Please see our [Contributing](CONTRIBUTING.md) guidelines. From 4de49983fb682e15460d9cd1670e37a15f7782ab Mon Sep 17 00:00:00 2001 From: Alexander Overvoorde Date: Tue, 6 Aug 2019 13:20:54 +0200 Subject: [PATCH 113/128] Rework GitLab provider (#231) * Initial version of OIDC based GitLab provider * Add support for email domain check to GitLab provider * Add gitlab.com as default issuer for GitLab provider * Update documentation for GitLab provider * Update unit tests for new GitLab provider implementation * Update CHANGELOG for GitLab provider * Rename GitLab test access token as response to linter --- CHANGELOG.md | 6 + docs/2_auth.md | 10 +- docs/configuration/configuration.md | 1 + main.go | 1 + options.go | 24 +++ providers/gitlab.go | 266 ++++++++++++++++++++++++---- providers/gitlab_test.go | 172 +++++++++++------- 7 files changed, 374 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f979719..8606b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Breaking Changes +- [#231](https://github.com/pusher/oauth2_proxy/pull/231) Rework GitLab provider (@Overv) + - 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 (`-`). @@ -32,6 +37,7 @@ ## Changes since v3.2.0 - [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant) +- [#231](https://github.com/pusher/oauth2_proxy/pull/231) Add optional group membership and email domain checks to the GitLab provider (@Overv) - [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) - [#209](https://github.com/pusher/outh2_proxy/pull/209) Improve docker build caching of layers (@dekimsey) - [#186](https://github.com/pusher/oauth2_proxy/pull/186) Make config consistent (@JoelSpeed) diff --git a/docs/2_auth.md b/docs/2_auth.md index 7a9bebd..e1a5ecd 100644 --- a/docs/2_auth.md +++ b/docs/2_auth.md @@ -103,13 +103,15 @@ If you are using GitHub enterprise, make sure you set the following to the appro ### GitLab Auth Provider -Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](http://doc.gitlab.com/ce/integration/oauth_provider.html) +Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](http://doc.gitlab.com/ce/integration/oauth_provider.html). Make sure to enable at least the `openid`, `profile` and `email` scopes. + +Restricting by group membership is possible with the following option: + + -gitlab-group="": restrict logins to members of any of these groups (slug), separated by a comma If you are using self-hosted GitLab, make sure you set the following to the appropriate URL: - -login-url="/oauth/authorize" - -redeem-url="/oauth/token" - -validate-url="/api/v4/user" + -oidc-issuer-url="" ### LinkedIn Auth Provider diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index f02d2ec..c9a70b1 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -49,6 +49,7 @@ Usage of oauth2_proxy: -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) -github-org string: restrict logins to members of this organisation -github-team string: restrict logins to members of any of these teams (slug), separated by a comma + -gitlab-group string: restrict logins to members of any of these groups (slug), separated by a comma -google-admin-email string: the google admin to impersonate for api calls -google-group value: restrict logins to members of this google group (may be given multiple times). -google-service-account-json string: the path to the service account json credentials diff --git a/main.go b/main.go index 97e2357..6db6025 100644 --- a/main.go +++ b/main.go @@ -57,6 +57,7 @@ func main() { flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.") flagSet.String("github-org", "", "restrict logins to members of this organisation") 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.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") diff --git a/options.go b/options.go index 7dcb12b..6969faa 100644 --- a/options.go +++ b/options.go @@ -46,6 +46,7 @@ type Options struct { 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"` 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"` 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"` @@ -410,6 +411,29 @@ func parseProviderInfo(o *Options, msgs []string) []string { } else { 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: p.AcrValues = o.AcrValues p.PubJWKURL, msgs = parseURL(o.PubJWKURL, "pubjwk", msgs) diff --git a/providers/gitlab.go b/providers/gitlab.go index 663ebd4..c32ebe8 100644 --- a/providers/gitlab.go +++ b/providers/gitlab.go @@ -1,62 +1,258 @@ package providers import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" "net/http" - "net/url" + "strings" + "time" + oidc "github.com/coreos/go-oidc" "github.com/pusher/oauth2_proxy/pkg/apis/sessions" - "github.com/pusher/oauth2_proxy/pkg/logger" - "github.com/pusher/oauth2_proxy/pkg/requests" + "golang.org/x/oauth2" ) -// GitLabProvider represents an GitLab based Identity Provider +// GitLabProvider represents a GitLab based Identity Provider type GitLabProvider struct { *ProviderData + + Group string + EmailDomains []string + + Verifier *oidc.IDTokenVerifier + AllowUnverifiedEmail bool } // NewGitLabProvider initiates a new GitLabProvider func NewGitLabProvider(p *ProviderData) *GitLabProvider { 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 == "" { - p.Scope = "read_user" + p.Scope = "openid email" } + 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 func (p *GitLabProvider) GetEmailAddress(s *sessions.SessionState) (string, error) { + // Retrieve user info + userInfo, err := p.getUserInfo(s) + if err != nil { + return "", fmt.Errorf("failed to retrieve user info: %v", err) + } - req, err := http.NewRequest("GET", - p.ValidateURL.String()+"?access_token="+s.AccessToken, nil) - if err != nil { - logger.Printf("failed building request %s", err) - return "", err + // Check if email is verified + if !p.AllowUnverifiedEmail && !userInfo.EmailVerified { + return "", fmt.Errorf("user email is not verified") } - json, err := requests.Request(req) + + // Check if email has valid domain + err = p.verifyEmailDomain(userInfo) if err != nil { - logger.Printf("failed making request %s", err) - return "", err + return "", fmt.Errorf("email domain check failed: %v", 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 } diff --git a/providers/gitlab_test.go b/providers/gitlab_test.go index 112eb89..f75c4bf 100644 --- a/providers/gitlab_test.go +++ b/providers/gitlab_test.go @@ -25,104 +25,142 @@ func testGitLabProvider(hostname string) *GitLabProvider { updateURL(p.Data().ProfileURL, hostname) updateURL(p.Data().ValidateURL, hostname) } + return p } -func testGitLabBackend(payload string) *httptest.Server { - path := "/api/v4/user" - query := "access_token=imaginary_access_token" +func testGitLabBackend() *httptest.Server { + userInfo := ` + { + "nickname": "FooBar", + "email": "foo@bar.com", + "email_verified": false, + "groups": ["foo", "bar"] + } + ` + authHeader := "Bearer gitlab_access_token" return httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != path || r.URL.RawQuery != query { - w.WriteHeader(404) + if r.URL.Path == "/oauth/userinfo" { + if r.Header["Authorization"][0] == authHeader { + w.WriteHeader(200) + w.Write([]byte(userInfo)) + } else { + w.WriteHeader(401) + } } else { - w.WriteHeader(200) - w.Write([]byte(payload)) + w.WriteHeader(404) } })) } -func TestGitLabProviderDefaults(t *testing.T) { - p := testGitLabProvider("") - assert.NotEqual(t, nil, p) - assert.Equal(t, "GitLab", p.Data().ProviderName) - assert.Equal(t, "https://gitlab.com/oauth/authorize", - p.Data().LoginURL.String()) - assert.Equal(t, "https://gitlab.com/oauth/token", - p.Data().RedeemURL.String()) - assert.Equal(t, "https://gitlab.com/api/v4/user", - p.Data().ValidateURL.String()) - assert.Equal(t, "read_user", p.Data().Scope) -} - -func TestGitLabProviderOverrides(t *testing.T) { - p := NewGitLabProvider( - &ProviderData{ - LoginURL: &url.URL{ - Scheme: "https", - Host: "example.com", - Path: "/oauth/auth"}, - RedeemURL: &url.URL{ - Scheme: "https", - Host: "example.com", - Path: "/oauth/token"}, - ValidateURL: &url.URL{ - Scheme: "https", - Host: "example.com", - Path: "/api/v4/user"}, - Scope: "profile"}) - assert.NotEqual(t, nil, p) - assert.Equal(t, "GitLab", p.Data().ProviderName) - assert.Equal(t, "https://example.com/oauth/auth", - p.Data().LoginURL.String()) - assert.Equal(t, "https://example.com/oauth/token", - p.Data().RedeemURL.String()) - assert.Equal(t, "https://example.com/api/v4/user", - p.Data().ValidateURL.String()) - assert.Equal(t, "profile", p.Data().Scope) -} - -func TestGitLabProviderGetEmailAddress(t *testing.T) { - b := testGitLabBackend("{\"email\": \"michael.bland@gsa.gov\"}") +func TestGitLabProviderBadToken(t *testing.T) { + b := testGitLabBackend() defer b.Close() bURL, _ := url.Parse(b.URL) p := testGitLabProvider(bURL.Host) - session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + session := &sessions.SessionState{AccessToken: "unexpected_gitlab_access_token"} + _, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) +} + +func TestGitLabProviderUnverifiedEmailDenied(t *testing.T) { + b := testGitLabBackend() + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testGitLabProvider(bURL.Host) + + session := &sessions.SessionState{AccessToken: "gitlab_access_token"} + _, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) +} + +func TestGitLabProviderUnverifiedEmailAllowed(t *testing.T) { + b := testGitLabBackend() + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testGitLabProvider(bURL.Host) + p.AllowUnverifiedEmail = true + + session := &sessions.SessionState{AccessToken: "gitlab_access_token"} email, err := p.GetEmailAddress(session) assert.Equal(t, nil, err) - assert.Equal(t, "michael.bland@gsa.gov", email) + assert.Equal(t, "foo@bar.com", email) } -// Note that trying to trigger the "failed building request" case is not -// practical, since the only way it can fail is if the URL fails to parse. -func TestGitLabProviderGetEmailAddressFailedRequest(t *testing.T) { - b := testGitLabBackend("unused payload") +func TestGitLabProviderUsername(t *testing.T) { + b := testGitLabBackend() defer b.Close() bURL, _ := url.Parse(b.URL) p := testGitLabProvider(bURL.Host) + p.AllowUnverifiedEmail = true - // 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) + session := &sessions.SessionState{AccessToken: "gitlab_access_token"} + username, err := p.GetUserName(session) + assert.Equal(t, nil, err) + assert.Equal(t, "FooBar", username) } -func TestGitLabProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) { - b := testGitLabBackend("{\"foo\": \"bar\"}") +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: "imaginary_access_token"} + session := &sessions.SessionState{AccessToken: "gitlab_access_token"} email, err := p.GetEmailAddress(session) - assert.NotEqual(t, nil, err) - assert.Equal(t, "", email) + 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) +} + +func TestGitLabProviderEmailDomainValid(t *testing.T) { + b := testGitLabBackend() + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testGitLabProvider(bURL.Host) + p.AllowUnverifiedEmail = true + p.EmailDomains = []string{"bar.com"} + + session := &sessions.SessionState{AccessToken: "gitlab_access_token"} + email, err := p.GetEmailAddress(session) + assert.Equal(t, nil, err) + assert.Equal(t, "foo@bar.com", email) +} + +func TestGitLabProviderEmailDomainInvalid(t *testing.T) { + b := testGitLabBackend() + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testGitLabProvider(bURL.Host) + p.AllowUnverifiedEmail = true + p.EmailDomains = []string{"baz.com"} + + session := &sessions.SessionState{AccessToken: "gitlab_access_token"} + _, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) } From d85660248cb10abeb0c2840fcaf45dbf699b4e91 Mon Sep 17 00:00:00 2001 From: mikesiegel Date: Wed, 7 Aug 2019 06:57:18 -0400 Subject: [PATCH 114/128] Adding docs for how to configure Okta for the OIDC provider (#235) * Adding documentation for Okta OIDC provider. * additional clean up. * Clearer heading * Forgot a word. * updated documentation based on ReillyProcentive review. * Per steakunderscore review: removed defaults. Removed extra hardening steps (expiration, https only etc) not directly related to setting up Okta w/ OIDC --- docs/2_auth.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/2_auth.md b/docs/2_auth.md index e1a5ecd..eba5f0c 100644 --- a/docs/2_auth.md +++ b/docs/2_auth.md @@ -146,6 +146,56 @@ OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many ma -cookie-secure=false -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 is an OIDC provider for the US Government. From 7134d22bcc9d7504e6082a6cbef97edd7390c71a Mon Sep 17 00:00:00 2001 From: jansinger Date: Wed, 7 Aug 2019 18:48:53 +0200 Subject: [PATCH 115/128] New flag "-ssl-upstream-insecure-skip-validation" (#234) * New flag "-ssl-upstream-insecure-skip-validation" to skip SSL validation for upstreams with self generated / invalid SSL certificates. * Fix tests for modified NewReverseProxy method. * Added change to the changelog. * Remove duplicate entries from changelog. --- CHANGELOG.md | 1 + docs/configuration/configuration.md | 3 ++- main.go | 3 ++- oauthproxy.go | 12 ++++++++--- oauthproxy_test.go | 4 ++-- options.go | 33 +++++++++++++++-------------- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8606b6b..81ee496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ ## 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) - [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant) - [#231](https://github.com/pusher/oauth2_proxy/pull/231) Add optional group membership and email domain checks to the GitLab provider (@Overv) - [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index c9a70b1..5bef512 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -99,7 +99,8 @@ Usage of oauth2_proxy: -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start - -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS + -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS providers + -ssl-upstream-insecure-skip-verify: skip validation of certificates presented when using HTTPS upstreams -standard-logging: Log standard runtime information (default true) -standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below) -tls-cert-file string: path to certificate file diff --git a/main.go b/main.go index 6db6025..823a16d 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,8 @@ func main() { 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-auth-preflight", false, "will skip authentication for OPTIONS requests") - flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS") + flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers") + flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams") flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses") flagSet.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)") diff --git a/oauthproxy.go b/oauthproxy.go index 5ef7a39..365a14e 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/tls" b64 "encoding/base64" "errors" "fmt" @@ -128,9 +129,14 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { // NewReverseProxy creates a new reverse proxy for proxying requests to upstream // servers -func NewReverseProxy(target *url.URL, flushInterval time.Duration) (proxy *httputil.ReverseProxy) { +func NewReverseProxy(target *url.URL, opts *Options) (proxy *httputil.ReverseProxy) { proxy = httputil.NewSingleHostReverseProxy(target) - proxy.FlushInterval = flushInterval + proxy.FlushInterval = opts.FlushInterval + if opts.SSLUpstreamInsecureSkipVerify { + proxy.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } return proxy } @@ -163,7 +169,7 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) { // NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler { u.Path = "" - proxy := NewReverseProxy(u, opts.FlushInterval) + proxy := NewReverseProxy(u, opts) if !opts.PassHostHeader { setProxyUpstreamHostHeader(proxy, u) } else { diff --git a/oauthproxy_test.go b/oauthproxy_test.go index c3d422c..8dd3adf 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -122,7 +122,7 @@ func TestNewReverseProxy(t *testing.T) { backendHost := net.JoinHostPort(backendHostname, backendPort) proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/") - proxyHandler := NewReverseProxy(proxyURL, time.Second) + proxyHandler := NewReverseProxy(proxyURL, &Options{FlushInterval: time.Second}) setProxyUpstreamHostHeader(proxyHandler, proxyURL) frontend := httptest.NewServer(proxyHandler) defer frontend.Close() @@ -144,7 +144,7 @@ func TestEncodedSlashes(t *testing.T) { defer backend.Close() b, _ := url.Parse(backend.URL) - proxyHandler := NewReverseProxy(b, time.Second) + proxyHandler := NewReverseProxy(b, &Options{FlushInterval: time.Second}) setProxyDirector(proxyHandler) frontend := httptest.NewServer(proxyHandler) defer frontend.Close() diff --git a/options.go b/options.go index 6969faa..03f5ff3 100644 --- a/options.go +++ b/options.go @@ -62,22 +62,23 @@ type Options struct { // Embed SessionOptions options.SessionOptions - 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"` - 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"` - 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"` - PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"` - 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"` - SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"` - 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"` - PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"` - SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"` - FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"` + 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"` + 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"` + 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"` + PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"` + 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"` + 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"` + 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"` + SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"` + FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"` // These options allow for other providers besides Google, with // potential overrides. From 02dfa87f118c99423d7ec5571272342b2c7d7d9f Mon Sep 17 00:00:00 2001 From: hjenkins Date: Wed, 7 Aug 2019 17:46:34 +0100 Subject: [PATCH 116/128] Fix typos in changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ee496..3b4a00b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,8 +39,8 @@ - [#234](https://github.com/pusher/oauth2_proxy/pull/234) Added option `-ssl-upstream-insecure-skip-validation` to skip validation of upstream SSL certificates (@jansinger) - [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant) - [#231](https://github.com/pusher/oauth2_proxy/pull/231) Add optional group membership and email domain checks to the GitLab provider (@Overv) -- [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) -- [#209](https://github.com/pusher/outh2_proxy/pull/209) Improve docker build caching of layers (@dekimsey) +- [#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 From 14c25c1d8afef983209fa8e0838295ade81361c7 Mon Sep 17 00:00:00 2001 From: Brady Mitchell Date: Sat, 10 Aug 2019 21:45:18 -0700 Subject: [PATCH 117/128] use a table for command line options --- docs/configuration/configuration.md | 185 ++++++++++++++-------------- 1 file changed, 92 insertions(+), 93 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 5bef512..c18cbcc 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -18,98 +18,97 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i ### Command Line Options -``` -Usage of oauth2_proxy: - -acr-values string: optional, used by login.gov (default "http://idmanagement.gov/ns/assurance/loa/1") - -approval-prompt string: OAuth approval_prompt (default "force") - -auth-logging: Log authentication attempts (default true) - -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) - -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 - -client-id string: the OAuth Client ID: ie: "123456.apps.googleusercontent.com" - -client-secret string: the OAuth Client Secret - -config string: path to config file - -cookie-domain string: an optional cookie domain to force cookies to (ie: .yourcompany.com) - -cookie-expire duration: expire timeframe for cookie (default 168h0m0s) - -cookie-httponly: set HttpOnly cookie flag (default true) - -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/)* (default "/") - -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-secure: set secure (HTTPS) cookie flag (default true) - -custom-templates-dir string: path to custom html templates - -display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true) - -email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email - -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) - -exclude-logging-paths: comma separated list of paths to exclude from logging, eg: "/ping,/path2" (default "" = no paths excluded) - -flush-interval: period between flushing response buffers when streaming responses (default "1s") - -banner string: custom banner string. Use "-" to disable default banner. - -footer string: custom footer string. Use "-" to disable default footer. - -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) - -github-org string: restrict logins to members of this organisation - -github-team string: restrict logins to members of any of these teams (slug), separated by a comma - -gitlab-group string: restrict logins to members of any of these groups (slug), separated by a comma - -google-admin-email string: the google admin to impersonate for api calls - -google-group value: restrict logins to members of this google group (may be given multiple times). - -google-service-account-json string: the path to the service account json credentials - -htpasswd-file string: additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption - -http-address string: [http://]: or unix:// to listen on for HTTP clients (default "127.0.0.1:4180") - -https-address string: : to listen on for HTTPS clients (default ":443") - -logging-compress: Should rotated log files be compressed using gzip (default false) - -logging-filename string: File to log requests to, empty for stdout (default to stdout) - -logging-local-time: If the time in log files and backup filenames are local or UTC time (default true) - -logging-max-age int: Maximum number of days to retain old log files (default 7) - -logging-max-backups int: Maximum number of old log files to retain; 0 to disable (default 0) - -logging-max-size int: Maximum size in megabytes of the log file before rotation (default 100) - -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 - -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 - -login-url string: Authentication endpoint - -insecure-oidc-allow-unverified-email: don't fail if an email address in an id_token is not verified - -oidc-issuer-url: the OpenID Connect issuer URL. ie: "https://accounts.google.com" - -oidc-jwks-url string: OIDC JWKS URI for token verification; required if OIDC discovery is disabled - -pass-access-token: pass OAuth access_token to upstream via X-Forwarded-Access-Token header - -pass-authorization-header: pass OIDC IDToken to upstream via Authorization Bearer header - -pass-basic-auth: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream (default true) - -pass-host-header: pass the request Host Header to upstream (default true) - -pass-user-headers: pass X-Forwarded-User and X-Forwarded-Email information to upstream (default true) - -profile-url string: Profile access endpoint - -provider string: OAuth provider (default "google") - -ping-path string: the ping endpoint that can be used for basic health checks (default "/ping") - -proxy-prefix string: the url root path that this proxy should be nested under (e.g. //sign_in) (default "/oauth2") - -proxy-websockets: enables WebSocket proxying (default true) - -pubjwk-url string: JWK pubkey access endpoint: required by login.gov - -redeem-url string: Token redemption endpoint - -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" - -redis-connection-url string: URL of redis server for redis session storage (eg: redis://HOST[:PORT]) - -redis-sentinel-master-name string: Redis sentinel master name. Used in conjuction with --redis-use-sentinel - -redis-sentinel-connection-urls: List of Redis sentinel conneciton URLs (eg redis://HOST[:PORT]). Used in conjuction with --redis-use-sentinel - -redis-use-sentinel: Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature (default: false) - -request-logging: Log requests to stdout (default true) - -request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below) - -resource string: The resource that is protected (Azure AD only) - -scope string: OAuth scope specification - -session-store-type: Session data storage backend (default: cookie) - -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) - -set-authorization-header: set Authorization Bearer response header (useful in Nginx auth_request mode) - -signature-key string: GAP-Signature request signature key (algorithm:secretkey) - -silence-ping-logging bool: disable logging of requests to ping endpoint (default false) - -skip-auth-preflight: will skip authentication for OPTIONS requests - -skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times) - -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens - -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case - -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start - -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS providers - -ssl-upstream-insecure-skip-verify: skip validation of certificates presented when using HTTPS upstreams - -standard-logging: Log standard runtime information (default true) - -standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below) - -tls-cert-file string: path to certificate file - -tls-key-file string: path to private key file - -upstream value: 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: print version string - -whitelist-domain: allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com) -``` +| Option | Type | Description | Default | +| ------ | ---- | ----------- | +| `-acr-values` | string | optional, used by login.gov | `"http://idmanagement.gov/ns/assurance/loa/1"` | +| `-approval-prompt` | string | OAuth approval_prompt | `"force"` | +| `-auth-logging` | bool | Log authentication attempts | true | +| `-auth-logging-format` | string | Template for authentication log lines | see [Logging Configuration](#logging-configuration) | +| `-authenticated-emails-file` | string | authenticate against emails via file (one per line) | | +| `-azure-tenant string` | string | go to a tenant-specific or common (tenant-independent) endpoint. | `"common"` | +| `-basic-auth-password` | string | the password to set when passing the HTTP Basic Auth header | | +| `-client-id` | string | the OAuth Client ID: ie: `"123456.apps.googleusercontent.com"` | | +| `-client-secret` | string | the OAuth Client Secret | | +| `-config` | string | path to config file | | +| `-cookie-domain` | string | an optional cookie domain to force cookies to (ie: `.yourcompany.com`) | | +| `-cookie-expire` | duration | expire timeframe for cookie | 168h0m0s | +| `-cookie-httponly` | bool | set HttpOnly cookie flag | true | +| `-cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` | +| `-cookie-path` | string | an optional cookie path to force cookies to (ie: `/poc/`) | `"/"` | +| `-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-secure` | bool | set secure (HTTPS) cookie flag | true | +| `-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 | +| `-email-domain` | string | 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`) | | +| `-exclude-logging-paths` | string | comma separated list of paths to exclude from logging, eg: `"/ping,/path2"` |`""` (no paths excluded) | +| `-flush-interval` | duration | period between flushing response buffers when streaming responses | `"1s"` | +| `-banner` | string | custom banner string. Use `"-"` to disable default banner. | | +| `-footer` | string | custom footer string. Use `"-"` to disable default footer. | | +| `-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 | +| `-github-org` | string | restrict logins to members of this organisation | | +| `-github-team` | string | restrict logins to members of any of these teams (slug), separated by a comma | | +| `-gitlab-group` | string | restrict logins to members of any of these groups (slug), separated by a comma | | +| `-google-admin-email` | string | the google admin to impersonate for api calls | | +| `-google-group` | string | restrict logins to members of this google group (may be given multiple times). | | +| `-google-service-account-json` | string | the path to the service account json credentials | | +| `-htpasswd-file` | string | additionally authenticate against a htpasswd file. Entries must be created with `htpasswd -s` for SHA encryption | | +| `-http-address` | string | `[http://]:` or `unix://` to listen on for HTTP clients | `"127.0.0.1:4180"` | +| `-https-address` | string | `:` to listen on for HTTPS clients | `":443"` | +| `-logging-compress` | bool | Should rotated log files be compressed using gzip | false | +| `-logging-filename` | string | File to log requests to, empty for `stdout` | `""` (stdout) | +| `-logging-local-time` | bool | Use local time in log files and backup filenames instead of UTC | true (local time) | +| `-logging-max-age` | int | Maximum number of days to retain old log files | 7 | +| `-logging-max-backups` | int | Maximum number of old log files to retain; 0 to disable | 0 | +| `-logging-max-size` | int | Maximum size in megabytes of the log file before rotation | 100 | +| `-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 | | +| `-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 | | +| `-login-url` | string | Authentication endpoint | | +| `-insecure-oidc-allow-unverified-email` | bool | don't fail if an email address in an id_token is not verified | false | +| `-oidc-issuer-url` | string | the OpenID Connect issuer URL. ie: `"https://accounts.google.com"` | | +| `-oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | | +| `-pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header | false | +| `-pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false | +| `-pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream | true | +| `-pass-host-header` | bool | pass the request Host Header to upstream | true | +| `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true | +| `-profile-url` | string | Profile access endpoint | | +| `-provider` | string | OAuth provider | google | +| `-ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` | +| `-proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`/sign_in`) | `"/oauth2"` | +| `-proxy-websockets` | bool | enables WebSocket proxying | true | +| `-pubjwk-url` | string | JWK pubkey access endpoint: required by login.gov | | +| `-redeem-url` | string | Token redemption endpoint | | +| `-redirect-url` | string | the OAuth Redirect URL. ie: `"https://internalapp.yourcompany.com/oauth2/callback"` | | +| `-redis-connection-url` | string | URL of redis server for redis session storage (eg: `redis://HOST[:PORT]`) | | +| `-redis-sentinel-master-name` | string | Redis sentinel master name. Used in conjunction with `--redis-use-sentinel` | | +| `-redis-sentinel-connection-urls` | string \| list | List of Redis sentinel connection URLs (eg `redis://HOST[:PORT]`). Used in conjunction with `--redis-use-sentinel` | | +| `-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 | +| `-request-logging` | bool | Log requests | true | +| `-request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) | +| `-resource` | string | The resource that is protected (Azure AD only) | | +| `-scope` | string | OAuth scope specification | | +| `-session-store-type` | string | Session data storage backend | cookie | +| `-set-xauthrequest` | bool | set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) | false | +| `-set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false | +| `-signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | | +| `-silence-ping-logging` | bool | disable logging of requests to ping endpoint | false | +| `-skip-auth-preflight` | bool | will skip authentication for OPTIONS requests | false | +| `-skip-auth-regex` | string | bypass authentication for requests paths that match (may be given multiple times) | | +| `-skip-jwt-bearer-tokens` | bool | will skip requests that have verified JWT bearer tokens | false | +| `-skip-oidc-discovery` | bool | bypass OIDC endpoint discovery. `-login-url`, `-redeem-url` and `-oidc-jwks-url` must be configured in this case | false | +| `-skip-provider-button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false | +| `-ssl-insecure-skip-verify` | bool | skip validation of certificates presented when using HTTPS providers | false | +| `-ssl-upstream-insecure-skip-verify` | bool | skip validation of certificates presented when using HTTPS upstreams | false | +| `-standard-logging` | bool | Log standard runtime information | true | +| `-standard-logging-format` | string | Template for standard log lines | see [Logging Configuration](#logging-configuration) | +| `-tls-cert-file` | string | path to certificate file | | +| `-tls-key-file` | string | path to private key file | | +| `-upstream` | string \| list | the http url(s) of the upstream endpoint or `file://` paths for static files. Routing is based on the path | | +| `-validate-url` | string | Access token validation endpoint | | +| `-version` | n/a | print version string | | +| `-whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (eg `.example.com`) | | Note, when using the `whitelist-domain` option, any domain prefixed with a `.` will allow any subdomain of the specified domain as a valid redirect URL. @@ -134,7 +133,7 @@ For example, the `--cookie-secret` flag becomes `OAUTH2_PROXY_COOKIE_SECRET` and the `--set-authorization-header` flag becomes `OAUTH2_PROXY_SET_AUTHORIZATION_HEADER`. -## Logging Configuration +## Logging Configuration By default, OAuth2 Proxy logs all output to stdout. Logging can be configured to output to a rotating log file using the `-logging-filename` command. From 18156713e348ad3dbca108c515b0c122ba84bcdb Mon Sep 17 00:00:00 2001 From: Brady Mitchell Date: Sat, 10 Aug 2019 21:46:13 -0700 Subject: [PATCH 118/128] indent content in ordered list, fixes 165 --- docs/4_tls.md | 98 +++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/4_tls.md b/docs/4_tls.md index ad96086..5e54dd9 100644 --- a/docs/4_tls.md +++ b/docs/4_tls.md @@ -11,63 +11,63 @@ 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`. -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: -```bash -./oauth2_proxy \ - --email-domain="yourcompany.com" \ - --upstream=http://127.0.0.1:8080/ \ - --tls-cert-file=/path/to/cert.pem \ - --tls-key-file=/path/to/cert.key \ - --cookie-secret=... \ - --cookie-secure=true \ - --provider=... \ - --client-id=... \ - --client-secret=... -``` + ```bash + ./oauth2_proxy \ + --email-domain="yourcompany.com" \ + --upstream=http://127.0.0.1:8080/ \ + --tls-cert-file=/path/to/cert.pem \ + --tls-key-file=/path/to/cert.key \ + --cookie-secret=... \ + --cookie-secure=true \ + --provider=... \ + --client-id=... \ + --client-secret=... + ``` 2. Configure SSL Termination with [Nginx](http://nginx.org/) (example config below), Amazon ELB, Google Cloud Platform Load Balancing, or .... -Because `oauth2_proxy` listens on `127.0.0.1:4180` by default, to listen on all interfaces (needed when using an -external load balancer like Amazon ELB or Google Platform Load Balancing) use `--http-address="0.0.0.0:4180"` or -`--http-address="http://:4180"`. + Because `oauth2_proxy` listens on `127.0.0.1:4180` by default, to listen on all interfaces (needed when using an + external load balancer like Amazon ELB or Google Platform Load Balancing) use `--http-address="0.0.0.0:4180"` or + `--http-address="http://:4180"`. -Nginx will listen on port `443` and handle SSL connections while proxying to `oauth2_proxy` on port `4180`. -`oauth2_proxy` will then authenticate requests for an upstream application. The external endpoint for this example -would be `https://internal.yourcompany.com/`. + Nginx will listen on port `443` and handle SSL connections while proxying to `oauth2_proxy` on port `4180`. + `oauth2_proxy` will then authenticate requests for an upstream application. The external endpoint for this example + would be `https://internal.yourcompany.com/`. -An example Nginx config follows. Note the use of `Strict-Transport-Security` header to pin requests to SSL -via [HSTS](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security): + An example Nginx config follows. Note the use of `Strict-Transport-Security` header to pin requests to SSL + via [HSTS](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security): -``` -server { - listen 443 default ssl; - server_name internal.yourcompany.com; - ssl_certificate /path/to/cert.pem; - ssl_certificate_key /path/to/cert.key; - add_header Strict-Transport-Security max-age=2592000; + ``` + server { + listen 443 default ssl; + server_name internal.yourcompany.com; + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/cert.key; + add_header Strict-Transport-Security max-age=2592000; - location / { - proxy_pass http://127.0.0.1:4180; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Scheme $scheme; - proxy_connect_timeout 1; - proxy_send_timeout 30; - proxy_read_timeout 30; + location / { + proxy_pass http://127.0.0.1:4180; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_connect_timeout 1; + proxy_send_timeout 30; + proxy_read_timeout 30; + } } -} -``` + ``` -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: -```bash -./oauth2_proxy \ - --email-domain="yourcompany.com" \ - --upstream=http://127.0.0.1:8080/ \ - --cookie-secret=... \ - --cookie-secure=true \ - --provider=... \ - --client-id=... \ - --client-secret=... -``` + ```bash + ./oauth2_proxy \ + --email-domain="yourcompany.com" \ + --upstream=http://127.0.0.1:8080/ \ + --cookie-secret=... \ + --cookie-secure=true \ + --provider=... \ + --client-id=... \ + --client-secret=... + ``` From 9e37de53e39b47854839c73565fd44d15a86f084 Mon Sep 17 00:00:00 2001 From: Vitalii Tverdokhlib Date: Sun, 11 Aug 2019 14:55:19 +0300 Subject: [PATCH 119/128] docs: fix path to oauth2_proxy.cfg --- docs/configuration/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 5bef512..ea677d5 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -14,7 +14,7 @@ To generate a strong cookie secret use `python -c 'import os,base64; print base6 ### Config File -An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is in the contrib directory. It can be used by specifying `-config=/etc/oauth2_proxy.cfg` +An example [oauth2_proxy.cfg](https://github.com/pusher/oauth2_proxy/blob/master/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 From 8b61559b8d293d67df68ace0d892ea5f8ced0682 Mon Sep 17 00:00:00 2001 From: Henry Jenkins Date: Sun, 11 Aug 2019 16:07:03 +0100 Subject: [PATCH 120/128] Fix links in docs - Fixed a bunch of references to the repo, which were 404ing - Fixed a couple of things that 301/302ed - Fixed some in page references --- docs/0_index.md | 4 ++-- docs/2_auth.md | 8 ++++---- docs/6_request_signatures.md | 2 +- docs/_config.yml | 1 + docs/configuration/configuration.md | 2 +- docs/configuration/sessions.md | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/0_index.md b/docs/0_index.md index 30c3555..376bd75 100644 --- a/docs/0_index.md +++ b/docs/0_index.md @@ -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. Versions v3.0.0 and up are from this fork and will have diverged from any changes in the original fork. -A list of changes can be seen in the [CHANGELOG](CHANGELOG.md). +A list of changes can be seen in the [CHANGELOG]({{ site.gitweb }}/CHANGELOG.md). [![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy) @@ -20,4 +20,4 @@ A list of changes can be seen in the [CHANGELOG](CHANGELOG.md). ## Architecture -![OAuth2 Proxy Architecture](https://cloud.githubusercontent.com/assets/45028/8027702/bd040b7a-0d6a-11e5-85b9-f8d953d04f39.png) \ No newline at end of file +![OAuth2 Proxy Architecture](https://cloud.githubusercontent.com/assets/45028/8027702/bd040b7a-0d6a-11e5-85b9-f8d953d04f39.png) diff --git a/docs/2_auth.md b/docs/2_auth.md index eba5f0c..e6c5cc6 100644 --- a/docs/2_auth.md +++ b/docs/2_auth.md @@ -103,7 +103,7 @@ If you are using GitHub enterprise, make sure you set the following to the appro ### GitLab Auth Provider -Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](http://doc.gitlab.com/ce/integration/oauth_provider.html). 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](https://docs.gitlab.com/ce/integration/oauth_provider.html). Make sure to enable at least the `openid`, `profile` and `email` scopes. Restricting by group membership is possible with the following option: @@ -126,7 +126,7 @@ For LinkedIn, the registration steps are: ### Microsoft Azure AD Provider -For adding an application to the Microsoft Azure AD follow [these steps to add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/). +For adding an application to the Microsoft Azure AD follow [these steps to add an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app). Take note of your `TenantId` if applicable for your situation. The `TenantId` can be used to override the default `common` authorization server with a tenant specific server. @@ -277,7 +277,7 @@ To authorize by email domain use `--email-domain=yourcompany.com`. To authorize ## Adding a new Provider -Follow the examples in the [`providers` package](providers/) to define a new +Follow the examples in the [`providers` package]({{ site.gitweb }}/providers/) to define a new `Provider` instance. Add a new `case` to -[`providers.New()`](providers/providers.go) to allow `oauth2_proxy` to use the +[`providers.New()`]({{ site.gitweb }}/providers/providers.go) to allow `oauth2_proxy` to use the new `Provider`. diff --git a/docs/6_request_signatures.md b/docs/6_request_signatures.md index 9feb961..0aa60aa 100644 --- a/docs/6_request_signatures.md +++ b/docs/6_request_signatures.md @@ -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 (HMAC)](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) of selected request information and the request body [see `SIGNATURE_HEADERS` -in `oauthproxy.go`](./oauthproxy.go). +in `oauthproxy.go`]({{ site.gitweb }}/oauthproxy.go). `signature_key` must be of the form `algorithm:secretkey`, (ie: `signature_key = "sha1:secret0"`) diff --git a/docs/_config.yml b/docs/_config.yml index dcc24a1..87f026c 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -18,6 +18,7 @@ description: >- # this means to ignore newlines until "baseurl:" OAuth2_Proxy documentation site 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 +gitweb: "https://github.com/pusher/oauth2_proxy/blob/master" # Build settings markdown: kramdown diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index ea677d5..ef2680f 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -14,7 +14,7 @@ To generate a strong cookie secret use `python -c 'import os,base64; print base6 ### Config File -An example [oauth2_proxy.cfg](https://github.com/pusher/oauth2_proxy/blob/master/contrib/oauth2_proxy.cfg.example) config file is in the contrib directory. It can be used by specifying `-config=/etc/oauth2_proxy.cfg` +An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example) config file is in the contrib directory. It can be used by specifying `-config=/etc/oauth2_proxy.cfg` ### Command Line Options diff --git a/docs/configuration/sessions.md b/docs/configuration/sessions.md index 0ffe392..4cfb09d 100644 --- a/docs/configuration/sessions.md +++ b/docs/configuration/sessions.md @@ -15,8 +15,8 @@ The OAuth2 Proxy uses a Cookie to track user sessions and will store the session data in one of the available session storage backends. At present the available backends are (as passed to `--session-store-type`): -- [cookie](cookie-storage) (default) -- [redis](redis-storage) +- [cookie](#cookie-storage) (default) +- [redis](#redis-storage) ### Cookie Storage From 4b985992d8d649761b4d0059be94087e78ab57e5 Mon Sep 17 00:00:00 2001 From: Brady Mitchell Date: Sun, 11 Aug 2019 17:21:32 -0700 Subject: [PATCH 121/128] add missing header border --- docs/configuration/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index c18cbcc..4478c4d 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -19,7 +19,7 @@ An example [oauth2_proxy.cfg](contrib/oauth2_proxy.cfg.example) config file is i ### Command Line Options | Option | Type | Description | Default | -| ------ | ---- | ----------- | +| ------ | ---- | ----------- | ------- | | `-acr-values` | string | optional, used by login.gov | `"http://idmanagement.gov/ns/assurance/loa/1"` | | `-approval-prompt` | string | OAuth approval_prompt | `"force"` | | `-auth-logging` | bool | Log authentication attempts | true | From fb52bdb90cbf638b65c900c05582eace43b23223 Mon Sep 17 00:00:00 2001 From: ferhat elmas Date: Tue, 13 Aug 2019 12:42:23 +0200 Subject: [PATCH 122/128] Fix some typos --- docs/configuration/configuration.md | 4 ++-- main.go | 4 ++-- oauthproxy.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index ea677d5..5e06883 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -82,8 +82,8 @@ Usage of oauth2_proxy: -redeem-url string: Token redemption endpoint -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" -redis-connection-url string: URL of redis server for redis session storage (eg: redis://HOST[:PORT]) - -redis-sentinel-master-name string: Redis sentinel master name. Used in conjuction with --redis-use-sentinel - -redis-sentinel-connection-urls: List of Redis sentinel conneciton URLs (eg redis://HOST[:PORT]). Used in conjuction with --redis-use-sentinel + -redis-sentinel-master-name string: Redis sentinel master name. Used in conjunction with --redis-use-sentinel + -redis-sentinel-connection-urls: List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel -redis-use-sentinel: Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature (default: false) -request-logging: Log requests to stdout (default true) -request-logging-format: Template for request log lines (see "Logging Configuration" paragraph below) diff --git a/main.go b/main.go index 823a16d..872990a 100644 --- a/main.go +++ b/main.go @@ -86,8 +86,8 @@ func main() { flagSet.String("session-store-type", "cookie", "the session storage provider to use") flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])") flagSet.Bool("redis-use-sentinel", false, "Connect to redis via sentinels. Must set --redis-sentinel-master-name and --redis-sentinel-connection-urls to use this feature") - flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjuction with --redis-use-sentinel") - flagSet.Var(&redisSentinelConnectionURLs, "redis-sentinel-connection-urls", "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjuction with --redis-use-sentinel") + flagSet.String("redis-sentinel-master-name", "", "Redis sentinel master name. Used in conjunction with --redis-use-sentinel") + flagSet.Var(&redisSentinelConnectionURLs, "redis-sentinel-connection-urls", "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel") flagSet.String("logging-filename", "", "File to log requests to, empty for stdout") flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation") diff --git a/oauthproxy.go b/oauthproxy.go index 365a14e..f3d9fd9 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -898,7 +898,7 @@ func isAjax(req *http.Request) bool { return false } -// ErrorJSON returns the error code witht an application/json mime type +// ErrorJSON returns the error code with an application/json mime type func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) { rw.Header().Set("Content-Type", applicationJSON) rw.WriteHeader(code) From bc5fc5a5131aa8e9befb0cafb93c50e7c67be5f6 Mon Sep 17 00:00:00 2001 From: Brady Mitchell Date: Tue, 13 Aug 2019 09:01:07 -0700 Subject: [PATCH 123/128] remove unnecessary tags --- docs/configuration/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 5488d58..a5ef23f 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -133,7 +133,7 @@ For example, the `--cookie-secret` flag becomes `OAUTH2_PROXY_COOKIE_SECRET` and the `--set-authorization-header` flag becomes `OAUTH2_PROXY_SET_AUTHORIZATION_HEADER`. -## Logging Configuration +## Logging Configuration By default, OAuth2 Proxy logs all output to stdout. Logging can be configured to output to a rotating log file using the `-logging-filename` command. @@ -231,7 +231,7 @@ Available variables for standard logging: | File | main.go:40 | The file and line number of the logging statement. | | Message | HTTP: listening on 127.0.0.1:4180 | The details of the log statement. | -## Configuring for use with the Nginx `auth_request` directive +## Configuring for use with the Nginx `auth_request` directive The [Nginx `auth_request` directive](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html) allows Nginx to authenticate requests via the oauth2_proxy's `/auth` endpoint, which only returns a 202 Accepted response or a 401 Unauthorized response without proxying the request through. For example: From 272fb96024f985bde5403f18bf9936bfb6b0687b Mon Sep 17 00:00:00 2001 From: Brady Mitchell Date: Tue, 13 Aug 2019 09:12:48 -0700 Subject: [PATCH 124/128] add back nginx-auth-request Configuring for use with the Nginx `auth_request` directive The [Nginx `auth_request` directive](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html) allows Nginx to authenticate requests via the oauth2_proxy's `/auth` endpoint, which only returns a 202 Accepted response or a 401 Unauthorized response without proxying the request through. For example: From d5d4878a2934355f944eaf131d9fd8d12aba69b9 Mon Sep 17 00:00:00 2001 From: Adam Eijdenberg Date: Thu, 20 Jun 2019 14:17:15 +1000 Subject: [PATCH 125/128] Made setting of proxied headers deterministic based on configuration alone Previously some headers that are normally set by the proxy (and may be replied upstream for authorization decisiions) were not being set depending on values in the users sesssion. This change ensure that if a given header is sometimes set, it will always be either set or removed. It might be worth considerating always deleting these headers if we didn't add them. --- CHANGELOG.md | 1 + oauthproxy.go | 44 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 824e37c..fec05c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - [#234](https://github.com/pusher/oauth2_proxy/pull/234) Added option `-ssl-upstream-insecure-skip-validation` to skip validation of upstream SSL certificates (@jansinger) - [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant) - [#231](https://github.com/pusher/oauth2_proxy/pull/231) Add optional group membership and email domain checks to the GitLab provider (@Overv) +- [#226](https://github.com/pusher/oauth2_proxy/pull/226) Made setting of proxied headers deterministic based on configuration alone (@aeijdenberg) - [#178](https://github.com/pusher/oauth2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes) - [#209](https://github.com/pusher/oauth2_proxy/pull/209) Improve docker build caching of layers (@dekimsey) - [#186](https://github.com/pusher/oauth2_proxy/pull/186) Make config consistent (@JoelSpeed) diff --git a/oauthproxy.go b/oauthproxy.go index f3d9fd9..2418e73 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -820,32 +820,60 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req req.Header["X-Forwarded-User"] = []string{session.User} if session.Email != "" { req.Header["X-Forwarded-Email"] = []string{session.Email} + } else { + req.Header.Del("X-Forwarded-Email") } } + if p.PassUserHeaders { req.Header["X-Forwarded-User"] = []string{session.User} if session.Email != "" { req.Header["X-Forwarded-Email"] = []string{session.Email} + } else { + req.Header.Del("X-Forwarded-Email") } } + if p.SetXAuthRequest { rw.Header().Set("X-Auth-Request-User", session.User) if session.Email != "" { rw.Header().Set("X-Auth-Request-Email", session.Email) + } else { + rw.Header().Del("X-Auth-Request-Email") } - if p.PassAccessToken && session.AccessToken != "" { - rw.Header().Set("X-Auth-Request-Access-Token", session.AccessToken) + + if p.PassAccessToken { + if 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 != "" { - req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken} + + if p.PassAccessToken { + if session.AccessToken != "" { + req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken} + } else { + req.Header.Del("X-Forwarded-Access-Token") + } } - if p.PassAuthorization && session.IDToken != "" { - req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)} + + if p.PassAuthorization { + if session.IDToken != "" { + req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)} + } else { + req.Header.Del("Authorization") + } } - if p.SetAuthorization && session.IDToken != "" { - rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken)) + if p.SetAuthorization { + if session.IDToken != "" { + rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken)) + } else { + rw.Header().Del("Authorization") + } } + if session.Email == "" { rw.Header().Set("GAP-Auth", session.User) } else { From fa6c4792a1e3f3b44950d6498bac7974b45380f1 Mon Sep 17 00:00:00 2001 From: aledeganopix4d <40891147+aledeganopix4d@users.noreply.github.com> Date: Fri, 16 Aug 2019 15:53:22 +0200 Subject: [PATCH 126/128] Add Bitbucket provider. (#201) Add a new provider for Bitbucket, can be configured from the options specifying team and/or repository that the user must be part/have access to in order to grant login. --- .github/CODEOWNERS | 4 + CHANGELOG.md | 4 + main.go | 2 + options.go | 5 ++ providers/bitbucket.go | 163 ++++++++++++++++++++++++++++++++++ providers/bitbucket_test.go | 170 ++++++++++++++++++++++++++++++++++++ providers/providers.go | 2 + 7 files changed, 350 insertions(+) create mode 100644 providers/bitbucket.go create mode 100644 providers/bitbucket_test.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a6f7701..c7f2960 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,3 +10,7 @@ # or the public devops channel at https://chat.18f.gov/). providers/logingov.go @timothy-spencer providers/logingov_test.go @timothy-spencer + +# Bitbucket provider +providers/bitbucket.go @aledeganopix4d +providers/bitbucket_test.go @aledeganopix4d diff --git a/CHANGELOG.md b/CHANGELOG.md index fec05c6..526ecdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,10 @@ - [#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` - [#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) diff --git a/main.go b/main.go index 872990a..a9f1e4a 100644 --- a/main.go +++ b/main.go @@ -56,6 +56,8 @@ func main() { 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.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-team", "", "restrict logins to members of this team") flagSet.String("gitlab-group", "", "restrict logins to members of this group") diff --git a/options.go b/options.go index 03f5ff3..706f6d5 100644 --- a/options.go +++ b/options.go @@ -42,6 +42,8 @@ type Options struct { AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file" env:"OAUTH2_PROXY_AUTHENTICATED_EMAILS_FILE"` 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"` 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"` @@ -405,6 +407,9 @@ func parseProviderInfo(o *Options, msgs []string) []string { p.SetGroupRestriction(o.GoogleGroups, o.GoogleAdminEmail, file) } } + case *providers.BitbucketProvider: + p.SetTeam(o.BitbucketTeam) + p.SetRepository(o.BitbucketRepository) case *providers.OIDCProvider: p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail if o.oidcVerifier == nil { diff --git a/providers/bitbucket.go b/providers/bitbucket.go new file mode 100644 index 0000000..63c1d0f --- /dev/null +++ b/providers/bitbucket.go @@ -0,0 +1,163 @@ +package providers + +import ( + "net/http" + "net/url" + "strings" + + "github.com/pusher/oauth2_proxy/pkg/apis/sessions" + "github.com/pusher/oauth2_proxy/pkg/logger" + "github.com/pusher/oauth2_proxy/pkg/requests" +) + +// BitbucketProvider represents an Bitbucket based Identity Provider +type BitbucketProvider struct { + *ProviderData + Team string + Repository string +} + +// NewBitbucketProvider initiates a new BitbucketProvider +func NewBitbucketProvider(p *ProviderData) *BitbucketProvider { + p.ProviderName = "Bitbucket" + if p.LoginURL == nil || p.LoginURL.String() == "" { + p.LoginURL = &url.URL{ + Scheme: "https", + Host: "bitbucket.org", + Path: "/site/oauth2/authorize", + } + } + if p.RedeemURL == nil || p.RedeemURL.String() == "" { + p.RedeemURL = &url.URL{ + Scheme: "https", + Host: "bitbucket.org", + Path: "/site/oauth2/access_token", + } + } + if p.ValidateURL == nil || p.ValidateURL.String() == "" { + p.ValidateURL = &url.URL{ + Scheme: "https", + Host: "api.bitbucket.org", + Path: "/2.0/user/emails", + } + } + if p.Scope == "" { + p.Scope = "email" + } + return &BitbucketProvider{ProviderData: p} +} + +// SetTeam defines the Bitbucket team the user must be part of +func (p *BitbucketProvider) SetTeam(team string) { + p.Team = team + if !strings.Contains(p.Scope, "team") { + p.Scope += " team" + } +} + +// SetRepository defines the repository the user must have access to +func (p *BitbucketProvider) SetRepository(repository string) { + p.Repository = repository + if !strings.Contains(p.Scope, "repository") { + p.Scope += " repository" + } +} + +// GetEmailAddress returns the email of the authenticated user +func (p *BitbucketProvider) GetEmailAddress(s *sessions.SessionState) (string, error) { + + var emails struct { + Values []struct { + Email string `json:"email"` + Primary bool `json:"is_primary"` + } + } + var teams struct { + Values []struct { + Name string `json:"username"` + } + } + var repositories struct { + Values []struct { + FullName string `json:"full_name"` + } + } + req, err := http.NewRequest("GET", + p.ValidateURL.String()+"?access_token="+s.AccessToken, nil) + if err != nil { + logger.Printf("failed building request %s", err) + return "", err + } + err = requests.RequestJSON(req, &emails) + if err != nil { + logger.Printf("failed making request %s", err) + return "", err + } + + if p.Team != "" { + teamURL := &url.URL{} + *teamURL = *p.ValidateURL + teamURL.Path = "/2.0/teams" + req, err = http.NewRequest("GET", + teamURL.String()+"?role=member&access_token="+s.AccessToken, nil) + if err != nil { + logger.Printf("failed building request %s", err) + return "", err + } + err = requests.RequestJSON(req, &teams) + if err != nil { + logger.Printf("failed requesting teams membership %s", err) + return "", err + } + var found = false + for _, team := range teams.Values { + if p.Team == team.Name { + found = true + break + } + } + if found != true { + logger.Print("team membership test failed, access denied") + return "", nil + } + } + + if p.Repository != "" { + repositoriesURL := &url.URL{} + *repositoriesURL = *p.ValidateURL + repositoriesURL.Path = "/2.0/repositories/" + strings.Split(p.Repository, "/")[0] + req, err = http.NewRequest("GET", + repositoriesURL.String()+"?role=contributor"+ + "&q=full_name="+url.QueryEscape("\""+p.Repository+"\"")+ + "&access_token="+s.AccessToken, + nil) + if err != nil { + logger.Printf("failed building request %s", err) + return "", err + } + err = requests.RequestJSON(req, &repositories) + if err != nil { + logger.Printf("failed checking repository access %s", err) + return "", err + } + var found = false + for _, repository := range repositories.Values { + if p.Repository == repository.FullName { + found = true + break + } + } + if found != true { + logger.Print("repository access test failed, access denied") + return "", nil + } + } + + for _, email := range emails.Values { + if email.Primary { + return email.Email, nil + } + } + + return "", nil +} diff --git a/providers/bitbucket_test.go b/providers/bitbucket_test.go new file mode 100644 index 0000000..585603d --- /dev/null +++ b/providers/bitbucket_test.go @@ -0,0 +1,170 @@ +package providers + +import ( + "log" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/pusher/oauth2_proxy/pkg/apis/sessions" +) + +func testBitbucketProvider(hostname, team string, repository string) *BitbucketProvider { + p := NewBitbucketProvider( + &ProviderData{ + ProviderName: "", + LoginURL: &url.URL{}, + RedeemURL: &url.URL{}, + ProfileURL: &url.URL{}, + ValidateURL: &url.URL{}, + Scope: ""}) + + if team != "" { + p.SetTeam(team) + } + + if repository != "" { + p.SetRepository(repository) + } + + if hostname != "" { + updateURL(p.Data().LoginURL, hostname) + updateURL(p.Data().RedeemURL, hostname) + updateURL(p.Data().ProfileURL, hostname) + updateURL(p.Data().ValidateURL, hostname) + } + return p +} + +func testBitbucketBackend(payload string) *httptest.Server { + paths := map[string]bool{ + "/2.0/user/emails": true, + "/2.0/teams": true, + } + + return httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + url := r.URL + if !paths[url.Path] { + log.Printf("%s not in %+v\n", url.Path, paths) + w.WriteHeader(404) + } else if r.URL.Query().Get("access_token") != "imaginary_access_token" { + w.WriteHeader(403) + } else { + w.WriteHeader(200) + w.Write([]byte(payload)) + } + })) +} + +func TestBitbucketProviderDefaults(t *testing.T) { + p := testBitbucketProvider("", "", "") + assert.NotEqual(t, nil, p) + assert.Equal(t, "Bitbucket", p.Data().ProviderName) + assert.Equal(t, "https://bitbucket.org/site/oauth2/authorize", + p.Data().LoginURL.String()) + assert.Equal(t, "https://bitbucket.org/site/oauth2/access_token", + p.Data().RedeemURL.String()) + assert.Equal(t, "https://api.bitbucket.org/2.0/user/emails", + p.Data().ValidateURL.String()) + assert.Equal(t, "email", p.Data().Scope) +} + +func TestBitbucketProviderScopeAdjustForTeam(t *testing.T) { + p := testBitbucketProvider("", "test-team", "") + assert.NotEqual(t, nil, p) + assert.Equal(t, "email team", p.Data().Scope) +} + +func TestBitbucketProviderScopeAdjustForRepository(t *testing.T) { + p := testBitbucketProvider("", "", "rest-repo") + assert.NotEqual(t, nil, p) + assert.Equal(t, "email repository", p.Data().Scope) +} + +func TestBitbucketProviderOverrides(t *testing.T) { + p := NewBitbucketProvider( + &ProviderData{ + LoginURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/oauth/auth"}, + RedeemURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/oauth/token"}, + ValidateURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/api/v3/user"}, + Scope: "profile"}) + assert.NotEqual(t, nil, p) + assert.Equal(t, "Bitbucket", p.Data().ProviderName) + assert.Equal(t, "https://example.com/oauth/auth", + p.Data().LoginURL.String()) + assert.Equal(t, "https://example.com/oauth/token", + p.Data().RedeemURL.String()) + assert.Equal(t, "https://example.com/api/v3/user", + p.Data().ValidateURL.String()) + assert.Equal(t, "profile", p.Data().Scope) +} + +func TestBitbucketProviderGetEmailAddress(t *testing.T) { + b := testBitbucketBackend("{\"values\": [ { \"email\": \"michael.bland@gsa.gov\", \"is_primary\": true } ] }") + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "", "") + + session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +func TestBitbucketProviderGetEmailAddressAndGroup(t *testing.T) { + b := testBitbucketBackend("{\"values\": [ { \"email\": \"michael.bland@gsa.gov\", \"is_primary\": true, \"username\": \"bioinformatics\" } ] }") + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "bioinformatics", "") + + session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +// Note that trying to trigger the "failed building request" case is not +// practical, since the only way it can fail is if the URL fails to parse. +func TestBitbucketProviderGetEmailAddressFailedRequest(t *testing.T) { + b := testBitbucketBackend("unused payload") + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "", "") + + // We'll trigger a request failure by using an unexpected access + // token. Alternatively, we could allow the parsing of the payload as + // JSON to fail. + session := &sessions.SessionState{AccessToken: "unexpected_access_token"} + email, err := p.GetEmailAddress(session) + assert.NotEqual(t, nil, err) + assert.Equal(t, "", email) +} + +func TestBitbucketProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) { + b := testBitbucketBackend("{\"foo\": \"bar\"}") + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "", "") + + session := &sessions.SessionState{AccessToken: "imaginary_access_token"} + email, err := p.GetEmailAddress(session) + assert.Equal(t, "", email) + assert.Equal(t, nil, err) +} diff --git a/providers/providers.go b/providers/providers.go index baf723d..276fab6 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -36,6 +36,8 @@ func New(provider string, p *ProviderData) Provider { return NewOIDCProvider(p) case "login.gov": return NewLoginGovProvider(p) + case "bitbucket": + return NewBitbucketProvider(p) default: return NewGoogleProvider(p) } From 44ea6920a7cb768b7da422d555438a6e9452e8cb Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 16 Aug 2019 15:06:53 +0100 Subject: [PATCH 127/128] Update changelog for v4.0.0 release --- CHANGELOG.md | 26 +++++++++++++++++++------- README.md | 4 ++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 526ecdf..44cd0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,26 @@ # Vx.x.x (Pre-release) +## Changes since v4.0.0 + +# 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 -- [#231](https://github.com/pusher/oauth2_proxy/pull/231) Rework GitLab provider (@Overv) +- [#231](https://github.com/pusher/oauth2_proxy/pull/231) Rework GitLab provider - This PR changes the configuration options for the GitLab provider to use a self-hosted instance. You now need to specify a `-oidc-issuer-url` rather than explicit `-login-url`, `-redeem-url` and `-validate-url` parameters. - - [#186](https://github.com/pusher/oauth2_proxy/pull/186) Make config consistent - This PR changes configuration options so that all flags have a config counterpart of the same name but with underscores (`_`) in place of hyphens (`-`). @@ -23,8 +37,7 @@ 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 (@gargath) +- [#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 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 @@ -45,7 +58,7 @@ - [#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. + 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). @@ -86,7 +99,6 @@ - Implement two new flags to customize the logging format - `-standard-logging-format` Sets the format for standard 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) - [#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) @@ -94,7 +106,7 @@ - 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` +- [#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: diff --git a/README.md b/README.md index 9657e8f..ad88331 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A list of changes can be seen in the [CHANGELOG](CHANGELOG.md). 1. Choose how to deploy: - a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v3.2.0`) + a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v4.0.0`) b. Build with `$ go get github.com/pusher/oauth2_proxy` which will put the binary in `$GOROOT/bin` @@ -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 -oauth2_proxy-3.2.0.linux-amd64: OK +oauth2_proxy-4.0.0.linux-amd64: OK ``` 2. [Select a Provider and Register an OAuth Application with a Provider](https://pusher.github.io/oauth2_proxy/auth-configuration) From b83b7565f33a60421cbf485eb6566698166615fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2019 10:05:52 +0000 Subject: [PATCH 128/128] Bump nokogiri from 1.10.1 to 1.10.4 in /docs Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.1 to 1.10.4. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/master/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.1...v1.10.4) Signed-off-by: dependabot[bot] --- docs/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 8d96e5d..934baab 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -208,7 +208,7 @@ GEM jekyll-seo-tag (~> 2.1) minitest (5.11.3) multipart-post (2.0.0) - nokogiri (1.10.1) + nokogiri (1.10.4) mini_portile2 (~> 2.4.0) octokit (4.13.0) sawyer (~> 0.8.0, >= 0.5.3)