Merge pull request #2 from oauth2-proxy/master
Merge Changes from Base Repo
This commit is contained in:
commit
451316829d
|
|
@ -0,0 +1,59 @@
|
|||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
# - $default-branch
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
# - $default-branch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
COVER: true
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Go 1.15
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.15.x
|
||||
id: go
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
|
||||
go mod download
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
chmod +x ./cc-test-reporter
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
make lint
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
make build
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
||||
run: |
|
||||
./test.sh
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Docker Build
|
||||
run: |
|
||||
make docker
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
env:
|
||||
- COVER=true
|
||||
install:
|
||||
|
|
|
|||
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
## Important Notes
|
||||
|
||||
- [#789](https://github.com/oauth2-proxy/oauth2-proxy/pull/789) `--skip-auth-route` is (almost) backwards compatible with `--skip-auth-regex`
|
||||
- We are marking `--skip-auth-regex` as DEPRECATED and will remove it in the next major version.
|
||||
- If your regex contains an `=` and you want it for all methods, you will need to add a leading `=` (this is the area where `--skip-auth-regex` doesn't port perfectly)
|
||||
- [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Sessions from v5.1.1 or earlier will no longer validate since they were not signed with SHA1.
|
||||
- Sessions from v6.0.0 or later had a graceful conversion to SHA256 that resulted in no reauthentication
|
||||
- Upgrading from v5.1.1 or earlier will result in a reauthentication
|
||||
|
|
@ -22,13 +25,21 @@
|
|||
|
||||
## Changes since v6.1.1
|
||||
|
||||
- [#705](https://github.com/oauth2-proxy/oauth2-proxy/pull/705) Add generic Header injectors for upstream request and response headers (@JoelSpeed)
|
||||
- [#753](https://github.com/oauth2-proxy/oauth2-proxy/pull/753) Pass resource parameter in login url (@codablock)
|
||||
- [#789](https://github.com/oauth2-proxy/oauth2-proxy/pull/789) Add `--skip-auth-route` configuration option for `METHOD=pathRegex` based allowlists (@NickMeves)
|
||||
- [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Stop accepting legacy SHA1 signed cookies (@NickMeves)
|
||||
- [#722](https://github.com/oauth2-proxy/oauth2-proxy/pull/722) Validate Redis configuration options at startup (@NickMeves)
|
||||
- [#791](https://github.com/oauth2-proxy/oauth2-proxy/pull/791) Remove GetPreferredUsername method from provider interface (@NickMeves)
|
||||
- [#764](https://github.com/oauth2-proxy/oauth2-proxy/pull/764) Document bcrypt encryption for htpasswd (and hide SHA) (@lentzi90)
|
||||
- [#616](https://github.com/oauth2-proxy/oauth2-proxy/pull/616) Add support to ensure user belongs in required groups when using the OIDC provider (@stefansedich)
|
||||
- [#800](https://github.com/oauth2-proxy/oauth2-proxy/pull/800) Fix import path for v7 (@johejo)
|
||||
- [#783](https://github.com/oauth2-proxy/oauth2-proxy/pull/783) Update Go to 1.15 (@johejo)
|
||||
- [#813](https://github.com/oauth2-proxy/oauth2-proxy/pull/813) Fix build (@thiagocaiubi)
|
||||
- [#801](https://github.com/oauth2-proxy/oauth2-proxy/pull/801) Update go-redis/redis to v8 (@johejo)
|
||||
- [#750](https://github.com/oauth2-proxy/oauth2-proxy/pull/750) ci: Migrate to Github Actions (@shinebayar-g)
|
||||
- [#829](https://github.com/oauth2-proxy/oauth2-proxy/pull/820) Rename test directory to testdata (@johejo)
|
||||
- [#819](https://github.com/oauth2-proxy/oauth2-proxy/pull/819) Improve CI (@johejo)
|
||||
|
||||
# v6.1.1
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
FROM golang:1.14-buster AS builder
|
||||
FROM golang:1.15-buster AS builder
|
||||
ARG VERSION
|
||||
|
||||
# Download tools
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
|
||||
|
||||
# Copy sources
|
||||
WORKDIR $GOPATH/src/github.com/oauth2-proxy/oauth2-proxy
|
||||
|
||||
|
|
@ -23,7 +20,7 @@ COPY . .
|
|||
RUN VERSION=${VERSION} make build && touch jwt_signing_key.pem
|
||||
|
||||
# Copy binary to alpine
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.12
|
||||
COPY nsswitch.conf /etc/nsswitch.conf
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /go/src/github.com/oauth2-proxy/oauth2-proxy/oauth2-proxy /bin/oauth2-proxy
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
FROM golang:1.14-buster AS builder
|
||||
FROM golang:1.15-buster AS builder
|
||||
ARG VERSION
|
||||
|
||||
# Download tools
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
|
||||
|
||||
# Copy sources
|
||||
WORKDIR $GOPATH/src/github.com/oauth2-proxy/oauth2-proxy
|
||||
|
||||
|
|
@ -23,7 +20,7 @@ COPY . .
|
|||
RUN VERSION=${VERSION} GOARCH=arm64 make build && touch jwt_signing_key.pem
|
||||
|
||||
# Copy binary to alpine
|
||||
FROM arm64v8/alpine:3.11
|
||||
FROM arm64v8/alpine:3.12
|
||||
COPY nsswitch.conf /etc/nsswitch.conf
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /go/src/github.com/oauth2-proxy/oauth2-proxy/oauth2-proxy /bin/oauth2-proxy
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
FROM golang:1.14-buster AS builder
|
||||
FROM golang:1.15-buster AS builder
|
||||
ARG VERSION
|
||||
|
||||
# Download tools
|
||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
|
||||
|
||||
# Copy sources
|
||||
WORKDIR $GOPATH/src/github.com/oauth2-proxy/oauth2-proxy
|
||||
|
||||
|
|
@ -23,7 +20,7 @@ COPY . .
|
|||
RUN VERSION=${VERSION} GOARCH=arm GOARM=6 make build && touch jwt_signing_key.pem
|
||||
|
||||
# Copy binary to alpine
|
||||
FROM arm32v6/alpine:3.11
|
||||
FROM arm32v6/alpine:3.12
|
||||
COPY nsswitch.conf /etc/nsswitch.conf
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /go/src/github.com/oauth2-proxy/oauth2-proxy/oauth2-proxy /bin/oauth2-proxy
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -10,7 +10,7 @@ REGISTRY ?= quay.io/oauth2-proxy
|
|||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MINIMUM_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MINIMUM_SUPPORTED_GO_MINOR_VERSION = 14
|
||||
MINIMUM_SUPPORTED_GO_MINOR_VERSION = 15
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MINIMUM_SUPPORTED_GO_MAJOR_VERSION).$(MINIMUM_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
DOCKER_BUILD := docker build --build-arg VERSION=${VERSION}
|
||||
|
|
@ -39,7 +39,7 @@ lint: validate-go-version
|
|||
build: validate-go-version clean $(BINARY)
|
||||
|
||||
$(BINARY):
|
||||
GO111MODULE=on CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/oauth2-proxy/oauth2-proxy
|
||||
GO111MODULE=on CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/oauth2-proxy/oauth2-proxy/v7
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
|
|
|
|||
|
|
@ -21,3 +21,12 @@ A list of changes can be seen in the [CHANGELOG]({{ site.gitweb }}/CHANGELOG.md)
|
|||
## Architecture
|
||||
|
||||

|
||||
|
||||
## Behavior
|
||||
|
||||
1. Any request passing through the proxy (and not matched by `--skip-auth-regex`) is checked for the proxy's session cookie (`--cookie-name`) (or, if allowed, a JWT token - see `--skip-jwt-bearer-tokens`).
|
||||
2. If authentication is required but missing then the user is asked to log in and redirected to the authentication provider (unless it is an Ajax request, i.e. one with `Accept: application/json`, in which case 401 Unauthorized is returned)
|
||||
3. After returning from the authentication provider, the oauth tokens are stored in the configured session store (cookie, redis, ...) and a cookie is set
|
||||
4. The request is forwarded to the upstream server with added user info and authentication headers (depending on the configuration)
|
||||
|
||||
Notice that the proxy also provides a number of useful [endpoints](/oauth2-proxy/endpoints).
|
||||
|
|
|
|||
|
|
@ -119,8 +119,9 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
|
|||
| `--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-auth-strip-headers` | bool | strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for request paths in `--skip-auth-regex` | false |
|
||||
| `--skip-auth-regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | |
|
||||
| `--skip-auth-route` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR path_regex alone for all methods | |
|
||||
| `--skip-auth-strip-headers` | bool | strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for allowlisted requests (`--skip-auth-route`, `--skip-auth-regex`, `--skip-auth-preflight`, `--trusted-ip`) | false |
|
||||
| `--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 |
|
||||
|
|
|
|||
10
go.mod
10
go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module github.com/oauth2-proxy/oauth2-proxy/v7
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
|
||||
|
|
@ -11,17 +11,17 @@ require (
|
|||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/frankban/quicktest v1.10.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-redis/redis/v7 v7.2.0
|
||||
github.com/go-redis/redis/v8 v8.2.3
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
|
||||
github.com/mitchellh/mapstructure v1.1.2
|
||||
github.com/onsi/ginkgo v1.14.0
|
||||
github.com/onsi/gomega v1.10.1
|
||||
github.com/onsi/ginkgo v1.14.1
|
||||
github.com/onsi/gomega v1.10.2
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.11
|
||||
github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
|
|
|
|||
54
go.sum
54
go.sum
|
|
@ -11,7 +11,6 @@ github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2F
|
|||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/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/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
|
|
@ -26,7 +25,10 @@ github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngE
|
|||
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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=
|
||||
|
|
@ -38,18 +40,18 @@ github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHo
|
|||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/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-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE=
|
||||
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
|
|
@ -57,8 +59,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
|||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
|
||||
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
|
||||
github.com/go-redis/redis/v8 v8.2.3 h1:eNesND+DWt/sjQOtPFxAbQkTIXaXX00qNLxjVWkZ70k=
|
||||
github.com/go-redis/redis/v8 v8.2.3/go.mod h1:ysgGY09J/QeDYbu3HikWEIPCwaeOkuNoTgKayTEaEOw=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
|
|
@ -69,9 +71,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
|||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
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/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
|
|
@ -80,18 +80,17 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
|||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8=
|
||||
github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
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 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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=
|
||||
|
|
@ -111,7 +110,6 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
|
|||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
|
@ -122,7 +120,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
|||
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
|
||||
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
|
|
@ -149,14 +146,13 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
|||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4=
|
||||
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
|
||||
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
|
||||
|
|
@ -198,8 +194,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
|
|
@ -218,6 +215,8 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBU
|
|||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opentelemetry.io/otel v0.11.0 h1:IN2tzQa9Gc4ZVKnTaMbPVcHjvzOdg5n9QfnmlqiET7E=
|
||||
go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0=
|
||||
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=
|
||||
|
|
@ -238,19 +237,14 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
|
|||
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-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
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-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
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/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -266,12 +260,9 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
|
|
@ -289,7 +280,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
|||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
@ -298,7 +288,6 @@ google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
|
|||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
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 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
|
|
@ -320,12 +309,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
|||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
|
@ -338,12 +324,12 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
|
|||
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=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ var (
|
|||
invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`)
|
||||
)
|
||||
|
||||
// allowedRoute manages method + path based allowlists
|
||||
type allowedRoute struct {
|
||||
method string
|
||||
pathRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
// OAuthProxy is the main authentication proxy
|
||||
type OAuthProxy struct {
|
||||
CookieSeed string
|
||||
|
|
@ -70,6 +76,7 @@ type OAuthProxy struct {
|
|||
AuthOnlyPath string
|
||||
UserInfoPath string
|
||||
|
||||
allowedRoutes []allowedRoute
|
||||
redirectURL *url.URL // the url to receive requests at
|
||||
whitelistDomains []string
|
||||
provider providers.Provider
|
||||
|
|
@ -90,13 +97,11 @@ type OAuthProxy struct {
|
|||
SetAuthorization bool
|
||||
PassAuthorization bool
|
||||
PreferEmailToUser bool
|
||||
skipAuthRegex []string
|
||||
skipAuthPreflight bool
|
||||
skipAuthStripHeaders bool
|
||||
skipJwtBearerTokens bool
|
||||
mainJwtBearerVerifier *oidc.IDTokenVerifier
|
||||
extraJwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||
compiledRegex []*regexp.Regexp
|
||||
templates *template.Template
|
||||
realClientIPParser ipapi.RealClientIPParser
|
||||
trustedIPs *ip.NetSet
|
||||
|
|
@ -121,10 +126,6 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
return nil, fmt.Errorf("error initialising upstream proxy: %v", err)
|
||||
}
|
||||
|
||||
for _, u := range opts.GetCompiledRegex() {
|
||||
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 {
|
||||
|
|
@ -163,6 +164,11 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
}
|
||||
}
|
||||
|
||||
allowedRoutes, err := buildRoutesAllowlist(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator)
|
||||
|
||||
return &OAuthProxy{
|
||||
|
|
@ -192,14 +198,13 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
|
|||
sessionStore: sessionStore,
|
||||
serveMux: upstreamProxy,
|
||||
redirectURL: redirectURL,
|
||||
allowedRoutes: allowedRoutes,
|
||||
whitelistDomains: opts.WhitelistDomains,
|
||||
skipAuthRegex: opts.SkipAuthRegex,
|
||||
skipAuthPreflight: opts.SkipAuthPreflight,
|
||||
skipAuthStripHeaders: opts.SkipAuthStripHeaders,
|
||||
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
||||
mainJwtBearerVerifier: opts.GetOIDCVerifier(),
|
||||
extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
|
||||
compiledRegex: opts.GetCompiledRegex(),
|
||||
realClientIPParser: opts.GetRealClientIPParser(),
|
||||
SetXAuthRequest: opts.SetXAuthRequest,
|
||||
PassBasicAuth: opts.PassBasicAuth,
|
||||
|
|
@ -277,6 +282,53 @@ func buildSignInMessage(opts *options.Options) string {
|
|||
return msg
|
||||
}
|
||||
|
||||
// buildRoutesAllowlist builds an []allowedRoute list from either the legacy
|
||||
// SkipAuthRegex option (paths only support) or newer SkipAuthRoutes option
|
||||
// (method=path support)
|
||||
func buildRoutesAllowlist(opts *options.Options) ([]allowedRoute, error) {
|
||||
routes := make([]allowedRoute, 0, len(opts.SkipAuthRegex)+len(opts.SkipAuthRoutes))
|
||||
|
||||
for _, path := range opts.SkipAuthRegex {
|
||||
compiledRegex, err := regexp.Compile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Printf("Skipping auth - Method: ALL | Path: %s", path)
|
||||
routes = append(routes, allowedRoute{
|
||||
method: "",
|
||||
pathRegex: compiledRegex,
|
||||
})
|
||||
}
|
||||
|
||||
for _, methodPath := range opts.SkipAuthRoutes {
|
||||
var (
|
||||
method string
|
||||
path string
|
||||
)
|
||||
|
||||
parts := strings.SplitN(methodPath, "=", 2)
|
||||
if len(parts) == 1 {
|
||||
method = ""
|
||||
path = parts[0]
|
||||
} else {
|
||||
method = strings.ToUpper(parts[0])
|
||||
path = parts[1]
|
||||
}
|
||||
|
||||
compiledRegex, err := regexp.Compile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Printf("Skipping auth - Method: %s | Path: %s", method, path)
|
||||
routes = append(routes, allowedRoute{
|
||||
method: method,
|
||||
pathRegex: compiledRegex,
|
||||
})
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
|
||||
// redirect clients to once authenticated
|
||||
func (p *OAuthProxy) GetRedirectURI(host string) string {
|
||||
|
|
@ -584,16 +636,16 @@ 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) bool {
|
||||
// IsAllowedRequest is used to check if auth should be skipped for this request
|
||||
func (p *OAuthProxy) IsAllowedRequest(req *http.Request) bool {
|
||||
isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
|
||||
return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path) || p.IsTrustedIP(req)
|
||||
return isPreflightRequestAllowed || p.isAllowedRoute(req) || p.IsTrustedIP(req)
|
||||
}
|
||||
|
||||
// IsWhitelistedPath is used to check if the request path is allowed without auth
|
||||
func (p *OAuthProxy) IsWhitelistedPath(path string) bool {
|
||||
for _, u := range p.compiledRegex {
|
||||
if u.MatchString(path) {
|
||||
// IsAllowedRoute is used to check if the request method & path is allowed without auth
|
||||
func (p *OAuthProxy) isAllowedRoute(req *http.Request) bool {
|
||||
for _, route := range p.allowedRoutes {
|
||||
if (route.method == "" || req.Method == route.method) && route.pathRegex.MatchString(req.URL.Path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -643,7 +695,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
switch path := req.URL.Path; {
|
||||
case path == p.RobotsPath:
|
||||
p.RobotsTxt(rw)
|
||||
case p.IsWhitelistedRequest(req):
|
||||
case p.IsAllowedRequest(req):
|
||||
p.SkipAuthProxy(rw, req)
|
||||
case path == p.SignInPath:
|
||||
p.SignIn(rw, req)
|
||||
|
|
@ -831,7 +883,7 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
|
|||
rw.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// SkipAuthProxy proxies whitelisted requests and skips authentication
|
||||
// SkipAuthProxy proxies allowlisted requests and skips authentication
|
||||
func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) {
|
||||
if p.skipAuthStripHeaders {
|
||||
p.stripAuthHeaders(req)
|
||||
|
|
@ -1026,7 +1078,7 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
|
|||
}
|
||||
}
|
||||
|
||||
// stripAuthHeaders removes Auth headers for whitelisted routes from skipAuthRegex
|
||||
// stripAuthHeaders removes Auth headers for allowlisted routes from skipAuthRegex
|
||||
func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
|
||||
if p.PassBasicAuth {
|
||||
req.Header.Del("X-Forwarded-User")
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ func TestOpenRedirects(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
file, err := os.Open("./test/openredirects.txt")
|
||||
file, err := os.Open("./testdata/openredirects.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -1482,28 +1482,28 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAuthSkippedForPreflightRequests(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
_, err := w.Write([]byte("response"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(upstream.Close)
|
||||
t.Cleanup(upstreamServer.Close)
|
||||
|
||||
opts := baseTestOptions()
|
||||
opts.UpstreamServers = options.Upstreams{
|
||||
{
|
||||
ID: upstream.URL,
|
||||
ID: upstreamServer.URL,
|
||||
Path: "/",
|
||||
URI: upstream.URL,
|
||||
URI: upstreamServer.URL,
|
||||
},
|
||||
}
|
||||
opts.SkipAuthPreflight = true
|
||||
err := validation.Validate(opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
upstreamURL, _ := url.Parse(upstream.URL)
|
||||
upstreamURL, _ := url.Parse(upstreamServer.URL)
|
||||
opts.SetProvider(NewTestProvider(upstreamURL, ""))
|
||||
|
||||
proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
|
||||
|
|
@ -1561,17 +1561,17 @@ func NewSignatureTest() (*SignatureTest, error) {
|
|||
opts.EmailDomains = []string{"acm.org"}
|
||||
|
||||
authenticator := &SignatureAuthenticator{}
|
||||
upstream := httptest.NewServer(
|
||||
upstreamServer := httptest.NewServer(
|
||||
http.HandlerFunc(authenticator.Authenticate))
|
||||
upstreamURL, err := url.Parse(upstream.URL)
|
||||
upstreamURL, err := url.Parse(upstreamServer.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.UpstreamServers = options.Upstreams{
|
||||
{
|
||||
ID: upstream.URL,
|
||||
ID: upstreamServer.URL,
|
||||
Path: "/",
|
||||
URI: upstream.URL,
|
||||
URI: upstreamServer.URL,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -1590,7 +1590,7 @@ func NewSignatureTest() (*SignatureTest, error) {
|
|||
|
||||
return &SignatureTest{
|
||||
opts,
|
||||
upstream,
|
||||
upstreamServer,
|
||||
upstreamURL.Host,
|
||||
provider,
|
||||
make(http.Header),
|
||||
|
|
@ -1974,20 +1974,20 @@ func Test_prepareNoCache(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_noCacheHeaders(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("upstream"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(upstream.Close)
|
||||
t.Cleanup(upstreamServer.Close)
|
||||
|
||||
opts := baseTestOptions()
|
||||
opts.UpstreamServers = options.Upstreams{
|
||||
{
|
||||
ID: upstream.URL,
|
||||
ID: upstreamServer.URL,
|
||||
Path: "/",
|
||||
URI: upstream.URL,
|
||||
URI: upstreamServer.URL,
|
||||
},
|
||||
}
|
||||
opts.SkipAuthRegex = []string{".*"}
|
||||
|
|
@ -2224,7 +2224,8 @@ func TestTrustedIPs(t *testing.T) {
|
|||
opts.TrustedIPs = tt.trustedIPs
|
||||
opts.ReverseProxy = tt.reverseProxy
|
||||
opts.RealClientIPHeader = tt.realClientIPHeader
|
||||
validation.Validate(opts)
|
||||
err := validation.Validate(opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -2240,6 +2241,255 @@ func TestTrustedIPs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_buildRoutesAllowlist(t *testing.T) {
|
||||
type expectedAllowedRoute struct {
|
||||
method string
|
||||
regexString string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
skipAuthRegex []string
|
||||
skipAuthRoutes []string
|
||||
expectedRoutes []expectedAllowedRoute
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "No skip auth configured",
|
||||
skipAuthRegex: []string{},
|
||||
skipAuthRoutes: []string{},
|
||||
expectedRoutes: []expectedAllowedRoute{},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Only skipAuthRegex configured",
|
||||
skipAuthRegex: []string{
|
||||
"^/foo/bar",
|
||||
"^/baz/[0-9]+/thing",
|
||||
},
|
||||
skipAuthRoutes: []string{},
|
||||
expectedRoutes: []expectedAllowedRoute{
|
||||
{
|
||||
method: "",
|
||||
regexString: "^/foo/bar",
|
||||
},
|
||||
{
|
||||
method: "",
|
||||
regexString: "^/baz/[0-9]+/thing",
|
||||
},
|
||||
},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Only skipAuthRoutes configured",
|
||||
skipAuthRegex: []string{},
|
||||
skipAuthRoutes: []string{
|
||||
"GET=^/foo/bar",
|
||||
"POST=^/baz/[0-9]+/thing",
|
||||
"^/all/methods$",
|
||||
"WEIRD=^/methods/are/allowed",
|
||||
"PATCH=/second/equals?are=handled&just=fine",
|
||||
},
|
||||
expectedRoutes: []expectedAllowedRoute{
|
||||
{
|
||||
method: "GET",
|
||||
regexString: "^/foo/bar",
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
regexString: "^/baz/[0-9]+/thing",
|
||||
},
|
||||
{
|
||||
method: "",
|
||||
regexString: "^/all/methods$",
|
||||
},
|
||||
{
|
||||
method: "WEIRD",
|
||||
regexString: "^/methods/are/allowed",
|
||||
},
|
||||
{
|
||||
method: "PATCH",
|
||||
regexString: "/second/equals?are=handled&just=fine",
|
||||
},
|
||||
},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Both skipAuthRegexes and skipAuthRoutes configured",
|
||||
skipAuthRegex: []string{
|
||||
"^/foo/bar/regex",
|
||||
"^/baz/[0-9]+/thing/regex",
|
||||
},
|
||||
skipAuthRoutes: []string{
|
||||
"GET=^/foo/bar",
|
||||
"POST=^/baz/[0-9]+/thing",
|
||||
"^/all/methods$",
|
||||
},
|
||||
expectedRoutes: []expectedAllowedRoute{
|
||||
{
|
||||
method: "",
|
||||
regexString: "^/foo/bar/regex",
|
||||
},
|
||||
{
|
||||
method: "",
|
||||
regexString: "^/baz/[0-9]+/thing/regex",
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
regexString: "^/foo/bar",
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
regexString: "^/baz/[0-9]+/thing",
|
||||
},
|
||||
{
|
||||
method: "",
|
||||
regexString: "^/all/methods$",
|
||||
},
|
||||
},
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid skipAuthRegex entry",
|
||||
skipAuthRegex: []string{
|
||||
"^/foo/bar",
|
||||
"^/baz/[0-9]+/thing",
|
||||
"(bad[regex",
|
||||
},
|
||||
skipAuthRoutes: []string{},
|
||||
expectedRoutes: []expectedAllowedRoute{},
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid skipAuthRoutes entry",
|
||||
skipAuthRegex: []string{},
|
||||
skipAuthRoutes: []string{
|
||||
"GET=^/foo/bar",
|
||||
"POST=^/baz/[0-9]+/thing",
|
||||
"^/all/methods$",
|
||||
"PUT=(bad[regex",
|
||||
},
|
||||
expectedRoutes: []expectedAllowedRoute{},
|
||||
shouldError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
opts := &options.Options{
|
||||
SkipAuthRegex: tc.skipAuthRegex,
|
||||
SkipAuthRoutes: tc.skipAuthRoutes,
|
||||
}
|
||||
routes, err := buildRoutesAllowlist(opts)
|
||||
if tc.shouldError {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i, route := range routes {
|
||||
assert.Greater(t, len(tc.expectedRoutes), i)
|
||||
assert.Equal(t, route.method, tc.expectedRoutes[i].method)
|
||||
assert.Equal(t, route.pathRegex.String(), tc.expectedRoutes[i].regexString)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedRequest(t *testing.T) {
|
||||
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
_, err := w.Write([]byte("Allowed Request"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(upstreamServer.Close)
|
||||
|
||||
opts := baseTestOptions()
|
||||
opts.UpstreamServers = options.Upstreams{
|
||||
{
|
||||
ID: upstreamServer.URL,
|
||||
Path: "/",
|
||||
URI: upstreamServer.URL,
|
||||
},
|
||||
}
|
||||
opts.SkipAuthRegex = []string{
|
||||
"^/skip/auth/regex$",
|
||||
}
|
||||
opts.SkipAuthRoutes = []string{
|
||||
"GET=^/skip/auth/routes/get",
|
||||
}
|
||||
err := validation.Validate(opts)
|
||||
assert.NoError(t, err)
|
||||
proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
method string
|
||||
url string
|
||||
allowed bool
|
||||
}{
|
||||
{
|
||||
name: "Regex GET allowed",
|
||||
method: "GET",
|
||||
url: "/skip/auth/regex",
|
||||
allowed: true,
|
||||
},
|
||||
{
|
||||
name: "Regex POST allowed ",
|
||||
method: "POST",
|
||||
url: "/skip/auth/regex",
|
||||
allowed: true,
|
||||
},
|
||||
{
|
||||
name: "Regex denied",
|
||||
method: "GET",
|
||||
url: "/wrong/denied",
|
||||
allowed: false,
|
||||
},
|
||||
{
|
||||
name: "Route allowed",
|
||||
method: "GET",
|
||||
url: "/skip/auth/routes/get",
|
||||
allowed: true,
|
||||
},
|
||||
{
|
||||
name: "Route denied with wrong method",
|
||||
method: "PATCH",
|
||||
url: "/skip/auth/routes/get",
|
||||
allowed: false,
|
||||
},
|
||||
{
|
||||
name: "Route denied with wrong path",
|
||||
method: "GET",
|
||||
url: "/skip/auth/routes/wrong/path",
|
||||
allowed: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req, err := http.NewRequest(tc.method, tc.url, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.allowed, proxy.isAllowedRoute(req))
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
proxy.ServeHTTP(rw, req)
|
||||
|
||||
if tc.allowed {
|
||||
assert.Equal(t, 200, rw.Code)
|
||||
assert.Equal(t, "Allowed Request", rw.Body.String())
|
||||
} else {
|
||||
assert.Equal(t, 403, rw.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyAllowedGroups(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -2265,18 +2515,18 @@ func TestProxyAllowedGroups(t *testing.T) {
|
|||
CreatedAt: &created,
|
||||
}
|
||||
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
t.Cleanup(upstream.Close)
|
||||
t.Cleanup(upstreamServer.Close)
|
||||
|
||||
test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
|
||||
opts.AllowedGroups = tt.allowedGroups
|
||||
opts.UpstreamServers = options.Upstreams{
|
||||
{
|
||||
ID: upstream.URL,
|
||||
ID: upstreamServer.URL,
|
||||
Path: "/",
|
||||
URI: upstream.URL,
|
||||
URI: upstreamServer.URL,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
@ -2287,7 +2537,8 @@ func TestProxyAllowedGroups(t *testing.T) {
|
|||
test.req, _ = http.NewRequest("GET", "/", nil)
|
||||
|
||||
test.req.Header.Add("accept", applicationJSON)
|
||||
test.SaveSession(session)
|
||||
err = test.SaveSession(session)
|
||||
assert.NoError(t, err)
|
||||
test.proxy.ServeHTTP(test.rw, test.req)
|
||||
|
||||
if tt.expectUnauthorized {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package options
|
||||
|
||||
// SecretSource references an individual secret value.
|
||||
// Only one source within the struct should be defined at any time.
|
||||
type SecretSource struct {
|
||||
// Value expects a base64 encoded string value.
|
||||
Value []byte
|
||||
|
||||
// FromEnv expects the name of an environment variable.
|
||||
FromEnv string
|
||||
|
||||
// FromFile expects a path to a file containing the secret value.
|
||||
FromFile string
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package options
|
||||
|
||||
// Header represents an individual header that will be added to a request or
|
||||
// response header.
|
||||
type Header struct {
|
||||
// Name is the header name to be used for this set of values.
|
||||
// Names should be unique within a list of Headers.
|
||||
Name string `json:"name"`
|
||||
|
||||
// PreserveRequestValue determines whether any values for this header
|
||||
// should be preserved for the request to the upstream server.
|
||||
// This option only takes effet on injected request headers.
|
||||
// Defaults to false (headers that match this header will be stripped).
|
||||
PreserveRequestValue bool `json:"preserveRequestValue"`
|
||||
|
||||
// Values contains the desired values for this header
|
||||
Values []HeaderValue `json:"values"`
|
||||
}
|
||||
|
||||
// HeaderValue represents a single header value and the sources that can
|
||||
// make up the header value
|
||||
type HeaderValue struct {
|
||||
// Allow users to load the value from a secret source
|
||||
*SecretSource
|
||||
|
||||
// Allow users to load the value from a session claim
|
||||
*ClaimSource
|
||||
}
|
||||
|
||||
// ClaimSource allows loading a header value from a claim within the session
|
||||
type ClaimSource struct {
|
||||
// Claim is the name of the claim in the session that the value should be
|
||||
// loaded from.
|
||||
Claim string `json:"claim,omitempty"`
|
||||
|
||||
// Prefix is an optional prefix that will be prepended to the value of the
|
||||
// claim if it is non-empty.
|
||||
Prefix string `json:"prefix,omitempty"`
|
||||
|
||||
// BasicAuthPassword converts this claim into a basic auth header.
|
||||
// Note the value of claim will become the basic auth username and the
|
||||
// basicAuthPassword will be used as the password value.
|
||||
BasicAuthPassword *SecretSource
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package options
|
|||
import (
|
||||
"crypto"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
|
||||
|
|
@ -67,6 +66,7 @@ type Options struct {
|
|||
UpstreamServers Upstreams `cfg:",internal"`
|
||||
|
||||
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
|
||||
SkipAuthRoutes []string `flag:"skip-auth-route" cfg:"skip_auth_routes"`
|
||||
SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
|
||||
SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
|
||||
ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
|
||||
|
|
@ -114,7 +114,6 @@ type Options struct {
|
|||
|
||||
// internal values that are set after config validation
|
||||
redirectURL *url.URL
|
||||
compiledRegex []*regexp.Regexp
|
||||
provider providers.Provider
|
||||
signatureData *SignatureData
|
||||
oidcVerifier *oidc.IDTokenVerifier
|
||||
|
|
@ -124,7 +123,6 @@ type Options struct {
|
|||
|
||||
// Options for Getting internal values
|
||||
func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
|
||||
func (o *Options) GetCompiledRegex() []*regexp.Regexp { return o.compiledRegex }
|
||||
func (o *Options) GetProvider() providers.Provider { return o.provider }
|
||||
func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
|
||||
func (o *Options) GetOIDCVerifier() *oidc.IDTokenVerifier { return o.oidcVerifier }
|
||||
|
|
@ -133,7 +131,6 @@ func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.re
|
|||
|
||||
// Options for Setting internal values
|
||||
func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
|
||||
func (o *Options) SetCompiledRegex(s []*regexp.Regexp) { o.compiledRegex = s }
|
||||
func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
|
||||
func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
|
||||
func (o *Options) SetOIDCVerifier(s *oidc.IDTokenVerifier) { o.oidcVerifier = s }
|
||||
|
|
@ -195,8 +192,9 @@ func NewFlagSet() *pflag.FlagSet {
|
|||
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
|
||||
flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
|
||||
flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
|
||||
flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)")
|
||||
flagSet.Bool("skip-auth-strip-headers", false, "strips X-Forwarded-* style authentication headers & Authorization header if they would be set by oauth2-proxy for request paths in --skip-auth-regex")
|
||||
flagSet.StringSlice("skip-auth-regex", []string{}, "(DEPRECATED for --skip-auth-route) bypass authentication for requests path's that match (may be given multiple times)")
|
||||
flagSet.StringSlice("skip-auth-route", []string{}, "bypass authentication for requests that match the method & path. Format: method=path_regex OR path_regex alone for all methods")
|
||||
flagSet.Bool("skip-auth-strip-headers", false, "strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy for allowlisted requests (`--skip-auth-route`, `--skip-auth-regex`, `--skip-auth-preflight`, `--trusted-ip`)")
|
||||
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 providers")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
)
|
||||
|
||||
// GetSecretValue returns the value of the Secret from its source
|
||||
func GetSecretValue(source *options.SecretSource) ([]byte, error) {
|
||||
switch {
|
||||
case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "":
|
||||
value := make([]byte, base64.StdEncoding.DecodedLen(len(source.Value)))
|
||||
decoded, err := base64.StdEncoding.Decode(value, source.Value)
|
||||
return value[:decoded], err
|
||||
case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "":
|
||||
return []byte(os.Getenv(source.FromEnv)), nil
|
||||
case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "":
|
||||
return ioutil.ReadFile(source.FromFile)
|
||||
default:
|
||||
return nil, errors.New("secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestUtilSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Options Util Suite")
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("GetSecretValue", func() {
|
||||
var fileDir string
|
||||
const secretEnvKey = "SECRET_ENV_KEY"
|
||||
const secretEnvValue = "secret-env-value"
|
||||
var secretFileValue = []byte("secret-file-value")
|
||||
|
||||
BeforeEach(func() {
|
||||
os.Setenv(secretEnvKey, secretEnvValue)
|
||||
|
||||
var err error
|
||||
fileDir, err = ioutil.TempDir("", "oauth2-proxy-util-get-secret-value")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ioutil.WriteFile(path.Join(fileDir, "secret-file"), secretFileValue, 0600)).To(Succeed())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
os.Unsetenv(secretEnvKey)
|
||||
os.RemoveAll(fileDir)
|
||||
})
|
||||
|
||||
It("returns the correct value from base64", func() {
|
||||
originalValue := []byte("secret-value-1")
|
||||
b64Value := base64.StdEncoding.EncodeToString((originalValue))
|
||||
|
||||
// Once encoded, the originalValue could have a decoded length longer than
|
||||
// its actual length, ensure we trim this.
|
||||
// This assertion ensures we are testing the triming
|
||||
Expect(len(originalValue)).To(BeNumerically("<", base64.StdEncoding.DecodedLen(len(b64Value))))
|
||||
|
||||
value, err := GetSecretValue(&options.SecretSource{
|
||||
Value: []byte(b64Value),
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(value).To(Equal(originalValue))
|
||||
})
|
||||
|
||||
It("returns the correct value from the environment", func() {
|
||||
value, err := GetSecretValue(&options.SecretSource{
|
||||
FromEnv: secretEnvKey,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(value).To(BeEquivalentTo(secretEnvValue))
|
||||
})
|
||||
|
||||
It("returns the correct value from a file", func() {
|
||||
value, err := GetSecretValue(&options.SecretSource{
|
||||
FromFile: path.Join(fileDir, "secret-file"),
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(value).To(Equal(secretFileValue))
|
||||
})
|
||||
|
||||
It("when the file does not exist", func() {
|
||||
value, err := GetSecretValue(&options.SecretSource{
|
||||
FromFile: path.Join(fileDir, "not-exist"),
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(value).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("with no source set", func() {
|
||||
value, err := GetSecretValue(&options.SecretSource{})
|
||||
Expect(err).To(MatchError("secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"))
|
||||
Expect(value).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("with multiple sources set", func() {
|
||||
value, err := GetSecretValue(&options.SecretSource{
|
||||
FromEnv: secretEnvKey,
|
||||
FromFile: path.Join(fileDir, "secret-file"),
|
||||
})
|
||||
Expect(err).To(MatchError("secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"))
|
||||
Expect(value).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
|
@ -69,6 +69,36 @@ func (s *SessionState) String() string {
|
|||
return o + "}"
|
||||
}
|
||||
|
||||
func (s *SessionState) GetClaim(claim string) []string {
|
||||
if s == nil {
|
||||
return []string{}
|
||||
}
|
||||
switch claim {
|
||||
case "access_token":
|
||||
return []string{s.AccessToken}
|
||||
case "id_token":
|
||||
return []string{s.IDToken}
|
||||
case "created_at":
|
||||
return []string{s.CreatedAt.String()}
|
||||
case "expires_on":
|
||||
return []string{s.ExpiresOn.String()}
|
||||
case "refresh_token":
|
||||
return []string{s.RefreshToken}
|
||||
case "email":
|
||||
return []string{s.Email}
|
||||
case "user":
|
||||
return []string{s.User}
|
||||
case "groups":
|
||||
groups := make([]string, len(s.Groups))
|
||||
copy(groups, s.Groups)
|
||||
return groups
|
||||
case "preferred_username":
|
||||
return []string{s.PreferredUsername}
|
||||
default:
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeSessionState returns an encrypted, lz4 compressed, MessagePack encoded session
|
||||
func (s *SessionState) EncodeSessionState(c encryption.Cipher, compress bool) ([]byte, error) {
|
||||
packed, err := msgpack.Marshal(s)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package header
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var (
|
||||
filesDir string
|
||||
)
|
||||
|
||||
func TestHeaderSuite(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Header")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
os.Setenv("SECRET_ENV", "super-secret-env")
|
||||
|
||||
dir, err := ioutil.TempDir("", "oauth2-proxy-header-suite")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ioutil.WriteFile(path.Join(dir, "secret-file"), []byte("super-secret-file"), 0644)).To(Succeed())
|
||||
filesDir = dir
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
os.Unsetenv("SECRET_ENV")
|
||||
Expect(os.RemoveAll(filesDir)).To(Succeed())
|
||||
})
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package header
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/util"
|
||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
)
|
||||
|
||||
type Injector interface {
|
||||
Inject(http.Header, *sessionsapi.SessionState)
|
||||
}
|
||||
|
||||
type injector struct {
|
||||
valueInjectors []valueInjector
|
||||
}
|
||||
|
||||
func (i injector) Inject(header http.Header, session *sessionsapi.SessionState) {
|
||||
for _, injector := range i.valueInjectors {
|
||||
injector.inject(header, session)
|
||||
}
|
||||
}
|
||||
|
||||
func NewInjector(headers []options.Header) (Injector, error) {
|
||||
injectors := []valueInjector{}
|
||||
for _, header := range headers {
|
||||
for _, value := range header.Values {
|
||||
injector, err := newValueinjector(header.Name, value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building injector for header %q: %v", header.Name, err)
|
||||
}
|
||||
injectors = append(injectors, injector)
|
||||
}
|
||||
}
|
||||
|
||||
return &injector{valueInjectors: injectors}, nil
|
||||
}
|
||||
|
||||
type valueInjector interface {
|
||||
inject(http.Header, *sessionsapi.SessionState)
|
||||
}
|
||||
|
||||
func newValueinjector(name string, value options.HeaderValue) (valueInjector, error) {
|
||||
switch {
|
||||
case value.SecretSource != nil && value.ClaimSource == nil:
|
||||
return newSecretInjector(name, value.SecretSource)
|
||||
case value.SecretSource == nil && value.ClaimSource != nil:
|
||||
return newClaimInjector(name, value.ClaimSource)
|
||||
default:
|
||||
return nil, fmt.Errorf("header %q value has multiple entries: only one entry per value is allowed", name)
|
||||
}
|
||||
}
|
||||
|
||||
type injectorFunc struct {
|
||||
injectFunc func(http.Header, *sessionsapi.SessionState)
|
||||
}
|
||||
|
||||
func (i *injectorFunc) inject(header http.Header, session *sessionsapi.SessionState) {
|
||||
i.injectFunc(header, session)
|
||||
}
|
||||
|
||||
func newInjectorFunc(injectFunc func(header http.Header, session *sessionsapi.SessionState)) valueInjector {
|
||||
return &injectorFunc{injectFunc: injectFunc}
|
||||
}
|
||||
|
||||
func newSecretInjector(name string, source *options.SecretSource) (valueInjector, error) {
|
||||
value, err := util.GetSecretValue(source)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting secret value: %v", err)
|
||||
}
|
||||
|
||||
return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) {
|
||||
header.Add(name, string(value))
|
||||
}), nil
|
||||
}
|
||||
|
||||
func newClaimInjector(name string, source *options.ClaimSource) (valueInjector, error) {
|
||||
switch {
|
||||
case source.BasicAuthPassword != nil:
|
||||
password, err := util.GetSecretValue(source.BasicAuthPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading basicAuthPassword: %v", err)
|
||||
}
|
||||
return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) {
|
||||
claimValues := session.GetClaim(source.Claim)
|
||||
for _, claim := range claimValues {
|
||||
if claim == "" {
|
||||
continue
|
||||
}
|
||||
auth := claim + ":" + string(password)
|
||||
header.Add(name, "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
|
||||
}
|
||||
}), nil
|
||||
case source.Prefix != "":
|
||||
return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) {
|
||||
claimValues := session.GetClaim(source.Claim)
|
||||
for _, claim := range claimValues {
|
||||
if claim == "" {
|
||||
continue
|
||||
}
|
||||
header.Add(name, source.Prefix+claim)
|
||||
}
|
||||
}), nil
|
||||
default:
|
||||
return newInjectorFunc(func(header http.Header, session *sessionsapi.SessionState) {
|
||||
claimValues := session.GetClaim(source.Claim)
|
||||
for _, claim := range claimValues {
|
||||
if claim == "" {
|
||||
continue
|
||||
}
|
||||
header.Add(name, claim)
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,417 @@
|
|||
package header
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Injector Suite", func() {
|
||||
Context("NewInjector", func() {
|
||||
type newInjectorTableInput struct {
|
||||
headers []options.Header
|
||||
initialHeaders http.Header
|
||||
session *sessionsapi.SessionState
|
||||
expectedHeaders http.Header
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
DescribeTable("creates an injector",
|
||||
func(in newInjectorTableInput) {
|
||||
injector, err := NewInjector(in.headers)
|
||||
if in.expectedErr != nil {
|
||||
Expect(err).To(MatchError(in.expectedErr))
|
||||
Expect(injector).To(BeNil())
|
||||
return
|
||||
}
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(injector).ToNot(BeNil())
|
||||
|
||||
headers := in.initialHeaders.Clone()
|
||||
injector.Inject(headers, in.session)
|
||||
Expect(headers).To(Equal(in.expectedHeaders))
|
||||
},
|
||||
Entry("with no configured headers", newInjectorTableInput{
|
||||
headers: []options.Header{},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a static valued header from base64", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Secret",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
SecretSource: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("super-secret"))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
"Secret": []string{"super-secret"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a static valued header from env", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Secret",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
SecretSource: &options.SecretSource{
|
||||
FromEnv: "SECRET_ENV",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
"Secret": []string{"super-secret-env"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a claim valued header", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
"Claim": []string{"IDToken-1234"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a claim valued header and a nil session", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: nil,
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a prefixed claim valued header", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
Prefix: "Bearer ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
"Claim": []string{"Bearer IDToken-1234"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a prefixed claim valued header missing the claim", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "idToken",
|
||||
Prefix: "Bearer ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a basicAuthPassword and claim valued header", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "X-Auth-Request-Authorization",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
BasicAuthPassword: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
User: "user-123",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
"X-Auth-Request-Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte("user-123:basic-password"))},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a basicAuthPassword and claim valued header missing the claim", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "X-Auth-Request-Authorization",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
BasicAuthPassword: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a header that already exists", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "X-Auth-Request-User",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"X-Auth-Request-User": []string{"user"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
User: "user-123",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"X-Auth-Request-User": []string{"user", "user-123"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
Entry("with a claim and secret valued header value", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
SecretSource: &options.SecretSource{
|
||||
FromEnv: "SECRET_ENV",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: nil,
|
||||
expectedErr: errors.New("error building injector for header \"Claim\": header \"Claim\" value has multiple entries: only one entry per value is allowed"),
|
||||
}),
|
||||
Entry("with an invalid static valued header", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Secret",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
SecretSource: &options.SecretSource{
|
||||
FromEnv: "SECRET_ENV",
|
||||
FromFile: "secret-file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: nil,
|
||||
expectedErr: errors.New("error building injector for header \"Secret\": error getting secret value: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"),
|
||||
}),
|
||||
Entry("with an invalid basicAuthPassword claim valued header", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "X-Auth-Request-Authorization",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
BasicAuthPassword: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
||||
FromEnv: "SECRET_ENV",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
User: "user-123",
|
||||
},
|
||||
expectedHeaders: nil,
|
||||
expectedErr: errors.New("error building injector for header \"X-Auth-Request-Authorization\": error loading basicAuthPassword: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile"),
|
||||
}),
|
||||
Entry("with a mix of configured headers", newInjectorTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "X-Auth-Request-Authorization",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
BasicAuthPassword: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "X-Auth-Request-User",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "X-Auth-Request-Email",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "email",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "X-Auth-Request-Version-Info",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
SecretSource: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("major=1"))),
|
||||
},
|
||||
},
|
||||
{
|
||||
SecretSource: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("minor=2"))),
|
||||
},
|
||||
},
|
||||
{
|
||||
SecretSource: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("patch=3"))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
User: "user-123",
|
||||
Email: "user@example.com",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
"X-Auth-Request-Authorization": []string{"Basic " + base64.StdEncoding.EncodeToString([]byte("user-123:basic-password"))},
|
||||
"X-Auth-Request-User": []string{"user-123"},
|
||||
"X-Auth-Request-Email": []string{"user@example.com"},
|
||||
"X-Auth-Request-Version-Info": []string{"major=1", "minor=2", "patch=3"},
|
||||
},
|
||||
expectedErr: nil,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/justinas/alice"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/header"
|
||||
)
|
||||
|
||||
func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, error) {
|
||||
headerInjector, err := newRequestHeaderInjector(headers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building request header injector: %v", err)
|
||||
}
|
||||
|
||||
strip := newStripHeaders(headers)
|
||||
if strip != nil {
|
||||
return alice.New(strip, headerInjector).Then, nil
|
||||
}
|
||||
return headerInjector, nil
|
||||
}
|
||||
|
||||
func newStripHeaders(headers []options.Header) alice.Constructor {
|
||||
headersToStrip := []string{}
|
||||
for _, header := range headers {
|
||||
if !header.PreserveRequestValue {
|
||||
headersToStrip = append(headersToStrip, header.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(headersToStrip) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return stripHeaders(headersToStrip, next)
|
||||
}
|
||||
}
|
||||
|
||||
func stripHeaders(headers []string, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
for _, header := range headers {
|
||||
req.Header.Del(header)
|
||||
}
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func newRequestHeaderInjector(headers []options.Header) (alice.Constructor, error) {
|
||||
injector, err := header.NewInjector(headers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building request injector: %v", err)
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return injectRequestHeaders(injector, next)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func injectRequestHeaders(injector header.Injector, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
scope := GetRequestScope(req)
|
||||
|
||||
// If scope is nil, this will panic.
|
||||
// A scope should always be injected before this handler is called.
|
||||
injector.Inject(req.Header, scope.Session)
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func NewResponseHeaderInjector(headers []options.Header) (alice.Constructor, error) {
|
||||
headerInjector, err := newResponseHeaderInjector(headers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building response header injector: %v", err)
|
||||
}
|
||||
|
||||
return headerInjector, nil
|
||||
}
|
||||
|
||||
func newResponseHeaderInjector(headers []options.Header) (alice.Constructor, error) {
|
||||
injector, err := header.NewInjector(headers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building response injector: %v", err)
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return injectResponseHeaders(injector, next)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func injectResponseHeaders(injector header.Injector, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
scope := GetRequestScope(req)
|
||||
|
||||
// If scope is nil, this will panic.
|
||||
// A scope should always be injected before this handler is called.
|
||||
injector.Inject(rw.Header(), scope.Session)
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Headers Suite", func() {
|
||||
type headersTableInput struct {
|
||||
headers []options.Header
|
||||
initialHeaders http.Header
|
||||
session *sessionsapi.SessionState
|
||||
expectedHeaders http.Header
|
||||
expectedErr string
|
||||
}
|
||||
|
||||
DescribeTable("the request header injector",
|
||||
func(in headersTableInput) {
|
||||
scope := &middlewareapi.RequestScope{
|
||||
Session: in.session,
|
||||
}
|
||||
|
||||
// Set up the request with a request scope
|
||||
req := httptest.NewRequest("", "/", nil)
|
||||
contextWithScope := context.WithValue(req.Context(), requestScopeKey, scope)
|
||||
req = req.WithContext(contextWithScope)
|
||||
req.Header = in.initialHeaders.Clone()
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
// Create the handler with a next handler that will capture the headers
|
||||
// from the request
|
||||
var gotHeaders http.Header
|
||||
injector, err := NewRequestHeaderInjector(in.headers)
|
||||
if in.expectedErr != "" {
|
||||
Expect(err).To(MatchError(in.expectedErr))
|
||||
return
|
||||
}
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
handler := injector(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotHeaders = r.Header.Clone()
|
||||
}))
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
Expect(gotHeaders).To(Equal(in.expectedHeaders))
|
||||
},
|
||||
Entry("with no configured headers", headersTableInput{
|
||||
headers: []options.Header{},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
"Claim": []string{"IDToken-1234"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header (without preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"Claim": []string{"IDToken-1234"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header (with preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
PreserveRequestValue: true,
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz", "IDToken-1234"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header that's not present (without preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: nil,
|
||||
expectedHeaders: http.Header{},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header that's not present (with preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
PreserveRequestValue: true,
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: nil,
|
||||
expectedHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with an invalid basicAuthPassword claim valued header", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "X-Auth-Request-Authorization",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
BasicAuthPassword: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
||||
FromEnv: "SECRET_ENV",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
User: "user-123",
|
||||
},
|
||||
expectedHeaders: nil,
|
||||
expectedErr: "error building request header injector: error building request injector: error building injector for header \"X-Auth-Request-Authorization\": error loading basicAuthPassword: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile",
|
||||
}),
|
||||
)
|
||||
|
||||
DescribeTable("the response header injector",
|
||||
func(in headersTableInput) {
|
||||
scope := &middlewareapi.RequestScope{
|
||||
Session: in.session,
|
||||
}
|
||||
|
||||
// Set up the request with a request scope
|
||||
req := httptest.NewRequest("", "/", nil)
|
||||
contextWithScope := context.WithValue(req.Context(), requestScopeKey, scope)
|
||||
req = req.WithContext(contextWithScope)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
for key, values := range in.initialHeaders {
|
||||
for _, value := range values {
|
||||
rw.Header().Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the handler with a next handler that will capture the headers
|
||||
// from the request
|
||||
var gotHeaders http.Header
|
||||
injector, err := NewResponseHeaderInjector(in.headers)
|
||||
if in.expectedErr != "" {
|
||||
Expect(err).To(MatchError(in.expectedErr))
|
||||
return
|
||||
}
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
handler := injector(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotHeaders = w.Header().Clone()
|
||||
}))
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
Expect(gotHeaders).To(Equal(in.expectedHeaders))
|
||||
},
|
||||
Entry("with no configured headers", headersTableInput{
|
||||
headers: []options.Header{},
|
||||
initialHeaders: http.Header{
|
||||
"Foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{},
|
||||
expectedHeaders: http.Header{
|
||||
"Foo": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"Foo": []string{"bar", "baz"},
|
||||
"Claim": []string{"IDToken-1234"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header (without preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz", "IDToken-1234"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header (with preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
PreserveRequestValue: true,
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
IDToken: "IDToken-1234",
|
||||
},
|
||||
expectedHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz", "IDToken-1234"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header that's not present (without preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: nil,
|
||||
expectedHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with a claim valued header that's not present (with preservation)", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "Claim",
|
||||
PreserveRequestValue: true,
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "id_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
session: nil,
|
||||
expectedHeaders: http.Header{
|
||||
"Claim": []string{"bar", "baz"},
|
||||
},
|
||||
expectedErr: "",
|
||||
}),
|
||||
Entry("with an invalid basicAuthPassword claim valued header", headersTableInput{
|
||||
headers: []options.Header{
|
||||
{
|
||||
Name: "X-Auth-Request-Authorization",
|
||||
Values: []options.HeaderValue{
|
||||
{
|
||||
ClaimSource: &options.ClaimSource{
|
||||
Claim: "user",
|
||||
BasicAuthPassword: &options.SecretSource{
|
||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
||||
FromEnv: "SECRET_ENV",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initialHeaders: http.Header{
|
||||
"foo": []string{"bar", "baz"},
|
||||
},
|
||||
session: &sessionsapi.SessionState{
|
||||
User: "user-123",
|
||||
},
|
||||
expectedHeaders: nil,
|
||||
expectedErr: "error building response header injector: error building response injector: error building injector for header \"X-Auth-Request-Authorization\": error loading basicAuthPassword: secret source is invalid: exactly one entry required, specify either value, fromEnv or fromFile",
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v7"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
// Client is wrapper interface for redis.Client and redis.ClusterClient.
|
||||
|
|
@ -25,15 +25,15 @@ func newClient(c *redis.Client) Client {
|
|||
}
|
||||
|
||||
func (c *client) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
return c.WithContext(ctx).Get(key).Bytes()
|
||||
return c.Client.Get(ctx, key).Bytes()
|
||||
}
|
||||
|
||||
func (c *client) Set(ctx context.Context, key string, value []byte, expiration time.Duration) error {
|
||||
return c.WithContext(ctx).Set(key, value, expiration).Err()
|
||||
return c.Client.Set(ctx, key, value, expiration).Err()
|
||||
}
|
||||
|
||||
func (c *client) Del(ctx context.Context, key string) error {
|
||||
return c.WithContext(ctx).Del(key).Err()
|
||||
return c.Client.Del(ctx, key).Err()
|
||||
}
|
||||
|
||||
var _ Client = (*clusterClient)(nil)
|
||||
|
|
@ -47,13 +47,13 @@ func newClusterClient(c *redis.ClusterClient) Client {
|
|||
}
|
||||
|
||||
func (c *clusterClient) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
return c.WithContext(ctx).Get(key).Bytes()
|
||||
return c.ClusterClient.Get(ctx, key).Bytes()
|
||||
}
|
||||
|
||||
func (c *clusterClient) Set(ctx context.Context, key string, value []byte, expiration time.Duration) error {
|
||||
return c.WithContext(ctx).Set(key, value, expiration).Err()
|
||||
return c.ClusterClient.Set(ctx, key, value, expiration).Err()
|
||||
}
|
||||
|
||||
func (c *clusterClient) Del(ctx context.Context, key string) error {
|
||||
return c.WithContext(ctx).Del(key).Err()
|
||||
return c.ClusterClient.Del(ctx, key).Err()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v7"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
|
@ -8,7 +9,7 @@ import (
|
|||
|
||||
"github.com/Bose/minisentinel"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/go-redis/redis/v7"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
|
|
@ -20,10 +21,20 @@ import (
|
|||
|
||||
const redisPassword = "0123456789abcdefghijklmnopqrstuv"
|
||||
|
||||
// wrappedRedisLogger wraps a logger so that we can coerce the logger to
|
||||
// fit the expected signature for go-redis logging
|
||||
type wrappedRedisLogger struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
func (l *wrappedRedisLogger) Printf(_ context.Context, format string, v ...interface{}) {
|
||||
l.Logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
func TestSessionStore(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
|
||||
redisLogger := log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)
|
||||
redisLogger := &wrappedRedisLogger{Logger: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)}
|
||||
redisLogger.SetOutput(GinkgoWriter)
|
||||
redis.SetLogger(redisLogger)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
)
|
||||
|
||||
func validateAllowlists(o *options.Options) []string {
|
||||
msgs := []string{}
|
||||
|
||||
msgs = append(msgs, validateRoutes(o)...)
|
||||
msgs = append(msgs, validateRegexes(o)...)
|
||||
msgs = append(msgs, validateTrustedIPs(o)...)
|
||||
|
||||
if len(o.TrustedIPs) > 0 && o.ReverseProxy {
|
||||
_, err := fmt.Fprintln(os.Stderr, "WARNING: mixing --trusted-ip with --reverse-proxy is a potential security vulnerability. An attacker can inject a trusted IP into an X-Real-IP or X-Forwarded-For header if they aren't properly protected outside of oauth2-proxy")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
// validateRoutes validates method=path routes passed with options.SkipAuthRoutes
|
||||
func validateRoutes(o *options.Options) []string {
|
||||
msgs := []string{}
|
||||
for _, route := range o.SkipAuthRoutes {
|
||||
var regex string
|
||||
parts := strings.SplitN(route, "=", 2)
|
||||
if len(parts) == 1 {
|
||||
regex = parts[0]
|
||||
} else {
|
||||
regex = parts[1]
|
||||
}
|
||||
_, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error compiling regex /%s/: %v", regex, err))
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
// validateRegex validates regex paths passed with options.SkipAuthRegex
|
||||
func validateRegexes(o *options.Options) []string {
|
||||
msgs := []string{}
|
||||
for _, regex := range o.SkipAuthRegex {
|
||||
_, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error compiling regex /%s/: %v", regex, err))
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
// validateTrustedIPs validates IP/CIDRs for IP based allowlists
|
||||
func validateTrustedIPs(o *options.Options) []string {
|
||||
msgs := []string{}
|
||||
for i, ipStr := range o.TrustedIPs {
|
||||
if nil == ip.ParseIPNet(ipStr) {
|
||||
msgs = append(msgs, fmt.Sprintf("trusted_ips[%d] (%s) could not be recognized", i, ipStr))
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
)
|
||||
|
||||
var _ = Describe("Allowlist", func() {
|
||||
type validateRoutesTableInput struct {
|
||||
routes []string
|
||||
errStrings []string
|
||||
}
|
||||
|
||||
type validateRegexesTableInput struct {
|
||||
regexes []string
|
||||
errStrings []string
|
||||
}
|
||||
|
||||
type validateTrustedIPsTableInput struct {
|
||||
trustedIPs []string
|
||||
errStrings []string
|
||||
}
|
||||
|
||||
DescribeTable("validateRoutes",
|
||||
func(r *validateRoutesTableInput) {
|
||||
opts := &options.Options{
|
||||
SkipAuthRoutes: r.routes,
|
||||
}
|
||||
Expect(validateRoutes(opts)).To(ConsistOf(r.errStrings))
|
||||
},
|
||||
Entry("Valid regex routes", &validateRoutesTableInput{
|
||||
routes: []string{
|
||||
"/foo",
|
||||
"POST=/foo/bar",
|
||||
"PUT=^/foo/bar$",
|
||||
"DELETE=/crazy/(?:regex)?/[^/]+/stuff$",
|
||||
},
|
||||
errStrings: []string{},
|
||||
}),
|
||||
Entry("Bad regexes do not compile", &validateRoutesTableInput{
|
||||
routes: []string{
|
||||
"POST=/(foo",
|
||||
"OPTIONS=/foo/bar)",
|
||||
"GET=^]/foo/bar[$",
|
||||
"GET=^]/foo/bar[$",
|
||||
},
|
||||
errStrings: []string{
|
||||
"error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`",
|
||||
"error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`",
|
||||
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
|
||||
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
DescribeTable("validateRegexes",
|
||||
func(r *validateRegexesTableInput) {
|
||||
opts := &options.Options{
|
||||
SkipAuthRegex: r.regexes,
|
||||
}
|
||||
Expect(validateRegexes(opts)).To(ConsistOf(r.errStrings))
|
||||
},
|
||||
Entry("Valid regex routes", &validateRegexesTableInput{
|
||||
regexes: []string{
|
||||
"/foo",
|
||||
"/foo/bar",
|
||||
"^/foo/bar$",
|
||||
"/crazy/(?:regex)?/[^/]+/stuff$",
|
||||
},
|
||||
errStrings: []string{},
|
||||
}),
|
||||
Entry("Bad regexes do not compile", &validateRegexesTableInput{
|
||||
regexes: []string{
|
||||
"/(foo",
|
||||
"/foo/bar)",
|
||||
"^]/foo/bar[$",
|
||||
"^]/foo/bar[$",
|
||||
},
|
||||
errStrings: []string{
|
||||
"error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`",
|
||||
"error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`",
|
||||
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
|
||||
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
DescribeTable("validateTrustedIPs",
|
||||
func(t *validateTrustedIPsTableInput) {
|
||||
opts := &options.Options{
|
||||
TrustedIPs: t.trustedIPs,
|
||||
}
|
||||
Expect(validateTrustedIPs(opts)).To(ConsistOf(t.errStrings))
|
||||
},
|
||||
Entry("Non-overlapping valid IPs", &validateTrustedIPsTableInput{
|
||||
trustedIPs: []string{
|
||||
"127.0.0.1",
|
||||
"10.32.0.1/32",
|
||||
"43.36.201.0/24",
|
||||
"::1",
|
||||
"2a12:105:ee7:9234:0:0:0:0/64",
|
||||
},
|
||||
errStrings: []string{},
|
||||
}),
|
||||
Entry("Overlapping valid IPs", &validateTrustedIPsTableInput{
|
||||
trustedIPs: []string{
|
||||
"135.180.78.199",
|
||||
"135.180.78.199/32",
|
||||
"d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4",
|
||||
"d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4/128",
|
||||
},
|
||||
errStrings: []string{},
|
||||
}),
|
||||
Entry("Invalid IPs", &validateTrustedIPsTableInput{
|
||||
trustedIPs: []string{"[::1]", "alkwlkbn/32"},
|
||||
errStrings: []string{
|
||||
"trusted_ips[0] ([::1]) could not be recognized",
|
||||
"trusted_ips[1] (alkwlkbn/32) could not be recognized",
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
|
|
@ -184,15 +183,6 @@ func Validate(o *options.Options) error {
|
|||
o.SetRedirectURL(redirectURL)
|
||||
|
||||
msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
|
||||
|
||||
for _, u := range o.SkipAuthRegex {
|
||||
compiledRegex, err := regexp.Compile(u)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
|
||||
continue
|
||||
}
|
||||
o.SetCompiledRegex(append(o.GetCompiledRegex(), compiledRegex))
|
||||
}
|
||||
msgs = parseProviderInfo(o, msgs)
|
||||
|
||||
if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
|
||||
|
|
@ -223,18 +213,8 @@ func Validate(o *options.Options) error {
|
|||
})
|
||||
}
|
||||
|
||||
if len(o.TrustedIPs) > 0 && o.ReverseProxy {
|
||||
_, err := fmt.Fprintln(os.Stderr, "WARNING: trusting of IPs with --reverse-proxy poses risks if a header spoofing attack is possible.")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
for i, ipStr := range o.TrustedIPs {
|
||||
if nil == ip.ParseIPNet(ipStr) {
|
||||
msgs = append(msgs, fmt.Sprintf("trusted_ips[%d] (%s) could not be recognized", i, ipStr))
|
||||
}
|
||||
}
|
||||
// Do this after ReverseProxy validation for TrustedIP coordinated checks
|
||||
msgs = append(msgs, validateAllowlists(o)...)
|
||||
|
||||
if len(msgs) != 0 {
|
||||
return fmt.Errorf("invalid configuration:\n %s",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package validation
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
|
|
@ -78,12 +77,19 @@ func TestClientSecretFileOption(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
f.WriteString("testcase")
|
||||
_, err = f.WriteString("testcase")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write to temp file: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("failed to close temp file: %v", err)
|
||||
}
|
||||
clientSecretFileName := f.Name()
|
||||
defer os.Remove(clientSecretFileName)
|
||||
defer func(t *testing.T) {
|
||||
if err := os.Remove(clientSecretFileName); err != nil {
|
||||
t.Fatalf("failed to delete temp file: %v", err)
|
||||
}
|
||||
}(t)
|
||||
|
||||
o := options.NewOptions()
|
||||
o.Cookie.Secret = cookieSecret
|
||||
|
|
@ -144,41 +150,6 @@ func TestRedirectURL(t *testing.T) {
|
|||
assert.Equal(t, expected, o.GetRedirectURL())
|
||||
}
|
||||
|
||||
func TestCompiledRegex(t *testing.T) {
|
||||
o := testOptions()
|
||||
regexps := []string{"/foo/.*", "/ba[rz]/quux"}
|
||||
o.SkipAuthRegex = regexps
|
||||
assert.Equal(t, nil, Validate(o))
|
||||
actual := make([]string, 0)
|
||||
for _, regex := range o.GetCompiledRegex() {
|
||||
actual = append(actual, regex.String())
|
||||
}
|
||||
assert.Equal(t, regexps, actual)
|
||||
}
|
||||
|
||||
func TestCompiledRegexError(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.SkipAuthRegex = []string{"(foobaz", "barquux)"}
|
||||
err := Validate(o)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
expected := errorMsg([]string{
|
||||
"error compiling regex=\"(foobaz\" error parsing regexp: " +
|
||||
"missing closing ): `(foobaz`",
|
||||
"error compiling regex=\"barquux)\" error parsing regexp: " +
|
||||
"unexpected ): `barquux)`"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
|
||||
o.SkipAuthRegex = []string{"foobaz", "barquux)"}
|
||||
err = Validate(o)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
expected = errorMsg([]string{
|
||||
"error compiling regex=\"barquux)\" error parsing regexp: " +
|
||||
"unexpected ): `barquux)`"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
|
||||
func TestDefaultProviderApiSettings(t *testing.T) {
|
||||
o := testOptions()
|
||||
assert.Equal(t, nil, Validate(o))
|
||||
|
|
@ -337,45 +308,6 @@ func TestRealClientIPHeader(t *testing.T) {
|
|||
assert.Nil(t, o.GetRealClientIPParser())
|
||||
}
|
||||
|
||||
func TestIPCIDRSetOption(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
trustedIPs []string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"TestSomeIPs",
|
||||
[]string{"127.0.0.1", "10.32.0.1/32", "43.36.201.0/24", "::1", "2a12:105:ee7:9234:0:0:0:0/64"},
|
||||
nil,
|
||||
}, {
|
||||
"TestOverlappingIPs",
|
||||
[]string{"135.180.78.199", "135.180.78.199/32", "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4", "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4/128"},
|
||||
nil,
|
||||
}, {
|
||||
"TestInvalidIPs",
|
||||
[]string{"[::1]", "alkwlkbn/32"},
|
||||
errors.New(
|
||||
"invalid configuration:\n" +
|
||||
" trusted_ips[0] ([::1]) could not be recognized\n" +
|
||||
" trusted_ips[1] (alkwlkbn/32) could not be recognized",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.TrustedIPs = tt.trustedIPs
|
||||
err := Validate(o)
|
||||
if tt.err == nil {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
assert.Equal(t, tt.err.Error(), err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderCAFilesError(t *testing.T) {
|
||||
file, err := ioutil.TempFile("", "absent.*.crt")
|
||||
assert.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -121,8 +121,8 @@ var _ = Describe("Sessions", func() {
|
|||
|
||||
const (
|
||||
clusterAndSentinelMsg = "unable to initialize a redis client: options redis-use-sentinel and redis-use-cluster are mutually exclusive"
|
||||
parseWrongSchemeMsg = "unable to initialize a redis client: unable to parse redis url: invalid redis URL scheme: https"
|
||||
parseWrongFormatMsg = "unable to initialize a redis client: unable to parse redis url: invalid redis database number: \"wrong\""
|
||||
parseWrongSchemeMsg = "unable to initialize a redis client: unable to parse redis url: redis: invalid URL scheme: https"
|
||||
parseWrongFormatMsg = "unable to initialize a redis client: unable to parse redis url: redis: invalid database number: \"wrong\""
|
||||
invalidPasswordSetMsg = "unable to set a redis initialization key: WRONGPASS invalid username-password pair"
|
||||
invalidPasswordDelMsg = "unable to delete the redis initialization key: WRONGPASS invalid username-password pair"
|
||||
unreachableRedisSetMsg = "unable to set a redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
# manually exiting from script, because after-build needs to run always
|
||||
set +e
|
||||
|
||||
if [ -z $CC_TEST_REPORT_ID ]; then
|
||||
echo "1. CC_TEST_REPORT_ID is unset, skipping"
|
||||
else
|
||||
echo "1. Running before-build"
|
||||
./cc-test-reporter before-build
|
||||
fi
|
||||
|
||||
echo "2. Running test"
|
||||
make test
|
||||
TEST_STATUS=$?
|
||||
echo "TEST_STATUS: ${TEST_STATUS}"
|
||||
|
||||
if [ -z $CC_TEST_REPORT_ID ]; then
|
||||
echo "3. CC_TEST_REPORT_ID is unset, skipping"
|
||||
else
|
||||
echo "3. Running after-build"
|
||||
./cc-test-reporter after-build --exit-code $TEST_STATUS -t gocov
|
||||
fi
|
||||
|
||||
if [ "$TEST_STATUS" -ne 0 ]; then
|
||||
echo "Test failed, status code: $TEST_STATUS"
|
||||
exit $TEST_STATUS
|
||||
fi
|
||||
Loading…
Reference in New Issue