diff --git a/.buildkite/build-binary.sh b/.buildkite/build-binary.sh new file mode 100644 index 000000000..1a9da67f5 --- /dev/null +++ b/.buildkite/build-binary.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -eo pipefail + +# Determine the image and cache tags +IMAGE_TAG=${BUILDKITE_BRANCH} +CACHE_TAG=${IMAGE_TAG} + +# Set IMAGE_NAME to something intermediate +IMAGE_NAME="oauth2_proxy-intermediate" + +# Determine the Dockerfile location +if [ -z "$DOCKERFILE" ]; then + DOCKERFILE="Dockerfile-buildbinary" +fi + +if [ ! -d build/public ]; then + mkdir -p build/public +fi + +git log -1 > build/public/REVISION.txt + +# Build the new image +docker build \ + --network host \ + --cache-from $IMAGE_NAME:$CACHE_TAG \ + --tag $IMAGE_NAME:$IMAGE_TAG \ + $EXTRA_TAGS \ + -f $DOCKERFILE \ + . + +# Execute the image so that we can get the binary out of it +docker run $IMAGE_NAME:$IMAGE_TAG + +# Copy the binary from the container +docker container cp $(docker ps -ql):/go/src/github.com/webflow/oauth2_proxy/oauth2_proxy . + +# Upload the binary as a Buildkite build artifact +buildkite-agent artifact upload oauth2_proxy diff --git a/.buildkite/build-image.sh b/.buildkite/build-image.sh new file mode 100644 index 000000000..dbb30fd2f --- /dev/null +++ b/.buildkite/build-image.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -eo pipefail + +# Determine the image and cache tags +IMAGE_TAG=${BUILDKITE_BRANCH} +CACHE_TAG=${IMAGE_TAG} + +# Determine the Dockerfile location +if [ -z "$DOCKERFILE" ]; then + DOCKERFILE="Dockerfile-buildimage" +fi + +eval $(aws ecr get-login --no-include-email --region us-east-1) + +# Pull the latest branch tag for caching, if it exists +IMAGE_EXISTS=1 +docker pull $IMAGE_NAME:$IMAGE_TAG || IMAGE_EXISTS=0 + +# If the branch image didn't already exist, pull the latest +if [ $IMAGE_EXISTS -eq 0 ]; then + docker pull $IMAGE_NAME:latest || true + CACHE_TAG=latest +fi + +EXTRA_TAGS="--tag $IMAGE_NAME:$BUILDKITE_COMMIT" + +# If the branch is master, also tag with latest +if [[ "$IMAGE_TAG" == "master" ]]; then + EXTRA_TAGS="$EXTRA_TAGS --tag $IMAGE_NAME:latest" +fi + +if [ ! -d build/public ]; then + mkdir -p build/public +fi + +git log -1 > build/public/REVISION.txt + +# Retrieve our artifact +buildkite-agent artifact download oauth2_proxy . + +# Build the new image +docker build \ + --network host \ + --cache-from $IMAGE_NAME:$CACHE_TAG \ + --tag $IMAGE_NAME:$IMAGE_TAG \ + $EXTRA_TAGS \ + -f $DOCKERFILE \ + . + +# Push to the repository +docker push $IMAGE_NAME:$IMAGE_TAG +echo "Pushing docker image to ECR: $IMAGE_NAME:$IMAGE_TAG" + +docker push $IMAGE_NAME:$BUILDKITE_COMMIT +echo "Pushing docker image to ECR: $IMAGE_NAME:$BUILDKITE_COMMIT" + +# If the branch is master, also push the latest tag +if [[ "$IMAGE_TAG" == "master" ]]; then + docker push $IMAGE_NAME:latest + echo "Pushing docker image to ECR: $IMAGE_NAME:latest" +fi diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 000000000..94a23deac --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,15 @@ +steps: + - label : ":oauth2_proxy: Build Binary" + command: "./.buildkite/build-binary.sh" + agents: + queue: 'autoscaling-build-cluster' + + - wait + + - label: ":oauth2_proxy: Build Image" + command: "./.buildkite/build-image.sh" + env: + IMAGE_NAME: 024376647576.dkr.ecr.us-east-1.amazonaws.com/oauth2_proxy + AWS_ECR_LOGIN: true + agents: + queue: 'autoscaling-build-cluster' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..d679cd774 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM golang:latest as builder + +# Install dep +RUN go get -u github.com/golang/dep/cmd/dep + +# Install upx, a Linux binary compression util +RUN apt-get update && apt-get install -y upx + +WORKDIR /go/src +COPY . github.com/webflow/oauth2_proxy +WORKDIR /go/src/github.com/webflow/oauth2_proxy + +# Load pinned dependencies into vendor/ +RUN dep ensure -v + +# Build and strip our binary +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w -X main.Version=`git log --pretty=format:'%h' -n 1`" -a -installsuffix cgo -o oauth2_proxy . + +# Compress the binary with upx +RUN upx oauth2_proxy + +FROM ubuntu + +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get -y dist-upgrade + +# Copy the binary over from the builder image +COPY --from=builder /go/src/github.com/webflow/oauth2_proxy/oauth2_proxy / + +# Run our entrypoint script when the container is executed +CMD ["/oauth2_proxy"] diff --git a/Dockerfile-buildbinary b/Dockerfile-buildbinary new file mode 100644 index 000000000..aa8cc144b --- /dev/null +++ b/Dockerfile-buildbinary @@ -0,0 +1,14 @@ +FROM golang:latest + +# Install dep +RUN go get -u github.com/golang/dep/cmd/dep + +WORKDIR /go/src +COPY . github.com/webflow/oauth2_proxy +WORKDIR /go/src/github.com/webflow/oauth2_proxy + +# Load pinned dependencies into vendor/ +RUN dep ensure -v + +# Build and strip our binary +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w -X main.Version=`git log --pretty=format:'%h' -n 1`" -a -installsuffix cgo -o oauth2_proxy . \ No newline at end of file diff --git a/Dockerfile-buildimage b/Dockerfile-buildimage new file mode 100644 index 000000000..774a38349 --- /dev/null +++ b/Dockerfile-buildimage @@ -0,0 +1,28 @@ +FROM ubuntu + +ENV GOSU_VERSION 1.10 + +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get -y dist-upgrade && \ + apt-get install -y curl gnupg && \ + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" && \ + curl -L -o /gosu "https://github.com/tianon/gosu/releases/download/1.10/gosu-${dpkgArch}" && \ + curl -L -o /gosu.asc "https://github.com/tianon/gosu/releases/download/1.10/gosu-${dpkgArch}.asc" && \ + export GNUPGHOME="$(mktemp -d)" && \ + gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 && \ + gpg --batch --verify /gosu.asc /gosu && \ + rm -r /gosu.asc && \ + chmod +x /gosu && \ + /gosu nobody true + +# Copy the binary over from the builder image +COPY oauth2_proxy / +RUN chmod +x /oauth2_proxy + +COPY entrypoint.sh / +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] + +CMD ["/oauth2_proxy"] diff --git a/Gopkg.lock b/Gopkg.lock index f4474968d..882ecd352 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,116 +2,278 @@ [[projects]] + digest = "1:8b95956b70e181b19025c7ba3578fdfd8efbec4ce916490700488afb9218972c" name = "cloud.google.com/go" packages = ["compute/metadata"] - revision = "2d3a6656c17a60b0815b7e06ab0be04eacb6e613" - version = "v0.16.0" + pruneopts = "" + revision = "64a2037ec6be8a4b0c1d1f706ed35b428b989239" + version = "v0.26.0" [[projects]] + digest = "1:289dd4d7abfb3ad2b5f728fbe9b1d5c1bf7d265a3eb9ef92869af1f7baba4c7a" name = "github.com/BurntSushi/toml" packages = ["."] + pruneopts = "" revision = "b26d9c308763d68093482582cea63d69be07a0f0" version = "v0.3.0" [[projects]] + digest = "1:9563d099d677a0c1f321508b20c52544d8fbb241904d205783227301ccfc207c" + name = "github.com/aws/aws-sdk-go" + packages = [ + "aws", + "aws/awserr", + "aws/awsutil", + "aws/client", + "aws/client/metadata", + "aws/corehandlers", + "aws/credentials", + "aws/credentials/ec2rolecreds", + "aws/credentials/endpointcreds", + "aws/credentials/stscreds", + "aws/csm", + "aws/defaults", + "aws/ec2metadata", + "aws/endpoints", + "aws/request", + "aws/session", + "aws/signer/v4", + "internal/sdkio", + "internal/sdkrand", + "internal/sdkuri", + "internal/shareddefaults", + "private/protocol", + "private/protocol/query", + "private/protocol/query/queryutil", + "private/protocol/rest", + "private/protocol/xml/xmlutil", + "service/sts", + ] + pruneopts = "" + revision = "04abd557eeaab3cfdded45467eea00fc03db9cb9" + version = "v1.15.26" + +[[projects]] + digest = "1:512883404c2a99156e410e9880e3bb35ecccc0c07c1159eb204b5f3ef3c431b3" name = "github.com/bitly/go-simplejson" packages = ["."] + pruneopts = "" revision = "aabad6e819789e569bd6aabf444c935aa9ba1e44" version = "v0.5.0" +[[projects]] + branch = "master" + digest = "1:a91cfbf2fb884ac6afd3307295fbc703f65f066221d8a1de54518d3f0b54fdc3" + name = "github.com/bitly/oauth2_proxy" + packages = [ + "api", + "cookie", + "providers", + ] + pruneopts = "" + revision = "a94b0a8b25e553f7333f7b84aeb89d9d18ec259b" + [[projects]] branch = "v2" + digest = "1:f3561ca1e798044ac9a31592d75fc44b61a679ecb2175a9268434d636c129c19" name = "github.com/coreos/go-oidc" packages = ["."] - revision = "77e7f2010a464ade7338597afe650dfcffbe2ca8" + pruneopts = "" + revision = "8ae1da518bd4d9d5a5909090a184af30f336436d" [[projects]] + digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" name = "github.com/davecgh/go-spew" packages = ["spew"] - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + pruneopts = "" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] - branch = "master" + digest = "1:cd5bab9c9e23ffa6858eaa79dc827fd84bc24bc00b0cfb0b14036e393da2b1fa" + name = "github.com/go-ini/ini" + packages = ["."] + pruneopts = "" + revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e" + version = "v1.38.2" + +[[projects]] + digest = "1:3dd078fda7500c341bc26cfbc6c6a34614f295a2457149fc1045cab767cbcf18" name = "github.com/golang/protobuf" packages = ["proto"] - revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845" + pruneopts = "" + revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" + version = "v1.2.0" [[projects]] + digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e" + name = "github.com/jmespath/go-jmespath" + packages = ["."] + pruneopts = "" + revision = "0b12d6b5" + +[[projects]] + digest = "1:af67386ca553c04c6222f7b5b2f17bc97a5dfb3b81b706882c7fd8c72c30cf8f" name = "github.com/mbland/hmacauth" packages = ["."] + pruneopts = "" revision = "107c17adcc5eccc9935cd67d9bc2feaf5255d2cb" version = "1.0.2" [[projects]] branch = "master" + digest = "1:9408fb9c637c103010e5147469c232ce6b68edc840879cc730a2a15918e6cae8" name = "github.com/mreiferson/go-options" packages = ["."] + pruneopts = "" revision = "77551d20752b54535462404ad9d877ebdb26e53d" [[projects]] + digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" name = "github.com/pmezard/go-difflib" packages = ["difflib"] + pruneopts = "" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:de5481dda0c081b66450e391bbb1a5c4435b13e3c0bbf0133ba1a5baeda7b7af" name = "github.com/pquerna/cachecontrol" - packages = [".","cacheobject"] - revision = "0dec1b30a0215bb68605dfc568e8855066c9202d" + packages = [ + ".", + "cacheobject", + ] + pruneopts = "" + revision = "1555304b9b35fdd2b425bccf1a5613677705e7d0" [[projects]] + digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159" name = "github.com/stretchr/testify" packages = ["assert"] + pruneopts = "" revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" version = "v1.1.4" [[projects]] branch = "master" + digest = "1:3ff237df1dca22c6296d2446c76ddcff8d359180ca53b8dcd301a44aa5c2f479" name = "golang.org/x/crypto" - packages = ["ed25519","ed25519/internal/edwards25519"] - revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94" + packages = [ + "ed25519", + "ed25519/internal/edwards25519", + "pbkdf2", + ] + pruneopts = "" + revision = "182538f80094b6a8efaade63a8fd8e0d9d5843dd" [[projects]] branch = "master" + digest = "1:7dd0f1b8c8bd70dbae4d3ed3fbfaec224e2b27bcc0fc65882d6f1dba5b1f6e22" name = "golang.org/x/net" - packages = ["context","context/ctxhttp"] - revision = "9dfe39835686865bff950a07b394c12a98ddc811" + packages = [ + "context", + "context/ctxhttp", + ] + pruneopts = "" + revision = "8a410e7b638dca158bf9e766925842f6651ff828" [[projects]] branch = "master" + digest = "1:b697592485cb412be4188c08ca0beed9aab87f36b86418e21acc4a3998f63734" name = "golang.org/x/oauth2" - packages = [".","google","internal","jws","jwt"] - revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402" + packages = [ + ".", + "google", + "internal", + "jws", + "jwt", + ] + pruneopts = "" + revision = "d2e6202438beef2727060aa7cabdd924d92ebfd9" [[projects]] branch = "master" + digest = "1:85fab7aaca4239017b02462635379f0b2203ab47961216c8e64b5fb48601ab4f" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "" + revision = "2b024373dcd9800f0cae693839fac6ede8d64a8c" + +[[projects]] + branch = "master" + digest = "1:b733c0bc39e94f61e47a81faffa8eac9668b161ab97d082d823a79dcc847f88c" name = "google.golang.org/api" - packages = ["admin/directory/v1","gensupport","googleapi","googleapi/internal/uritemplates"] - revision = "8791354e7ab150705ede13637a18c1fcc16b62e8" + packages = [ + "admin/directory/v1", + "gensupport", + "googleapi", + "googleapi/internal/uritemplates", + ] + pruneopts = "" + revision = "0ad5a633fea1d4b64bf5e6a01e30d1fc466038e5" [[projects]] + digest = "1:c1771ca6060335f9768dff6558108bc5ef6c58506821ad43377ee23ff059e472" name = "google.golang.org/appengine" - packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"] - revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" - version = "v1.0.0" + packages = [ + ".", + "internal", + "internal/app_identity", + "internal/base", + "internal/datastore", + "internal/log", + "internal/modules", + "internal/remote_api", + "internal/urlfetch", + "urlfetch", + ] + pruneopts = "" + revision = "b1f26356af11148e710935ed1ac8a7f5702c7612" + version = "v1.1.0" [[projects]] + digest = "1:eb53021a8aa3f599d29c7102e65026242bdedce998a54837dc67f14b6a97c5fd" name = "gopkg.in/fsnotify.v1" packages = ["."] - revision = "836bfd95fecc0f1511dd66bdbf2b5b61ab8b00b6" - version = "v1.2.11" + pruneopts = "" + revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" + source = "https://github.com/fsnotify/fsnotify/archive/v1.4.7.tar.gz" + version = "v1.4.7" [[projects]] + digest = "1:78c193a044c9c6f540da8158d8d8af12f3615d0261e7fa172e0cadcd05abd64f" name = "gopkg.in/square/go-jose.v2" - packages = [".","cipher","json"] - revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1" - version = "v2.1.3" + packages = [ + ".", + "cipher", + "json", + ] + pruneopts = "" + revision = "8254d6c783765f38c8675fae4427a1fe73fbd09d" + version = "v2.1.8" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "efab48a0e196c2a849bfbe9aa02d2ae28d281ce1bfe9f23720d648858eefc8e6" + input-imports = [ + "github.com/BurntSushi/toml", + "github.com/aws/aws-sdk-go/aws", + "github.com/aws/aws-sdk-go/aws/session", + "github.com/aws/aws-sdk-go/aws/signer/v4", + "github.com/bitly/go-simplejson", + "github.com/bitly/oauth2_proxy/api", + "github.com/bitly/oauth2_proxy/cookie", + "github.com/bitly/oauth2_proxy/providers", + "github.com/coreos/go-oidc", + "github.com/mbland/hmacauth", + "github.com/mreiferson/go-options", + "github.com/stretchr/testify/assert", + "golang.org/x/oauth2", + "golang.org/x/oauth2/google", + "google.golang.org/api/admin/directory/v1", + "google.golang.org/api/googleapi", + "gopkg.in/fsnotify.v1", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 97f83d011..8e3c8591f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -3,7 +3,7 @@ # for detailed Gopkg.toml documentation. # -[[constraint]] +[[override]] name = "github.com/18F/hmacauth" version = "~1.0.1" @@ -33,8 +33,12 @@ [[constraint]] branch = "master" - name = "google.golang.org/api" + name = "github.com/bitly/oauth2_proxy" [[constraint]] - name = "gopkg.in/fsnotify.v1" - version = "~1.2.0" + branch = "master" + name = "google.golang.org/api" + +[[override]] + source = "https://github.com/fsnotify/fsnotify/archive/v1.4.7.tar.gz" + name = "gopkg.in/fsnotify.v1" \ No newline at end of file diff --git a/README.md b/README.md index 178a8e8c8..c4ee0163f 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,8 @@ Usage of oauth2_proxy: -resource string: The resource that is protected (Azure AD only) -scope string: OAuth scope specification -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) + -sign-aws-request-region: sign requests with Authorization header for AWS authentication in this region (e.g. "us-east-1"). You must also provide the -sign-aws-request-service flag. + -sign-aws-request-service: sign requests with Authorization header for AWS authentication for this service (e.g. "es"). You must also provide the -sign-aws-request-region flag. -signature-key string: GAP-Signature request signature key (algorithm:secretkey) -skip-auth-preflight: will skip authentication for OPTIONS requests -skip-auth-regex value: bypass authentication for requests path's that match (may be given multiple times) @@ -346,6 +348,12 @@ following: * [rc3.org: Using HMAC to authenticate Web service requests](http://rc3.org/2011/12/02/using-hmac-to-authenticate-web-service-requests/) +## Signing AWS Requests + +If `sign-aws-request-region` is defined, proxied requests will be signed with an `Authorization` header, +which is used by AWS to authenticate requests to protected services (e.g. Kibana). You must provide both +the `sign-aws-request-region` and `sign-aws-request-service` flags for this feature to work. + ## Logging Format By default, OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log. diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 000000000..5c9df0207 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,91 @@ +#!/bin/bash +set -e +set -f + +export PATH=/bin:/usr/bin:/usr/local/bin:/usr/sbin:/ + +if [ ! -z ${OAUTH2_PROXY_CLIENT_ID+x} ] && [ ! -z $OAUTH2_PROXY_CLIENT_ID ]; then + PROXY_ARGS="${PROXY_ARGS} --client-id=${OAUTH2_PROXY_CLIENT_ID}" +fi + +if [ ! -z ${OAUTH2_PROXY_CLIENT_SECRET+x} ] && [ ! -z $OAUTH2_PROXY_CLIENT_SECRET ]; then + PROXY_ARGS="${PROXY_ARGS} --client-secret=${OAUTH2_PROXY_CLIENT_SECRET}" +fi + +if [ ! -z ${OAUTH2_PROXY_COOKIE_EXPIRE+x} ] && [ ! -z $OAUTH2_PROXY_COOKIE_EXPIRE ]; then + PROXY_ARGS="${PROXY_ARGS} --cookie-expire=${OAUTH2_PROXY_COOKIE_EXPIRE}" +fi + +if [ ! -z ${OAUTH2_PROXY_COOKIE_SECURE+x} ] && [ ! -z $OAUTH2_PROXY_COOKIE_SECURE ]; then + PROXY_ARGS="${PROXY_ARGS} --cookie-secure=${OAUTH2_PROXY_COOKIE_SECURE}" +fi + +if [ ! -z ${OAUTH2_PROXY_COOKIE_SECRET+x} ] && [ ! -z $OAUTH2_PROXY_COOKIE_SECRET ]; then + PROXY_ARGS="${PROXY_ARGS} --cookie-secret=${OAUTH2_PROXY_COOKIE_SECRET}" +fi + +if [ ! -z ${OAUTH2_PROXY_GITHUB_TEAM+x} ] && [ ! -z $OAUTH2_PROXY_GITHUB_TEAM ]; then + PROXY_ARGS="${PROXY_ARGS} --github-team=${OAUTH2_PROXY_GITHUB_TEAM}" +fi + +if [ ! -z ${OAUTH2_PROXY_GITHUB_ORG+x} ] && [ ! -z $OAUTH2_PROXY_GITHUB_ORG ]; then + PROXY_ARGS="${PROXY_ARGS} --github-org=${OAUTH2_PROXY_GITHUB_ORG}" +fi + +if [ ! -z ${OAUTH2_PROXY_HTTP_ADDRESS+x} ] && [ ! -z $OAUTH2_PROXY_HTTP_ADDRESS ]; then + PROXY_ARGS="${PROXY_ARGS} --http-address=${OAUTH2_PROXY_HTTP_ADDRESS}" +fi + +if [ ! -z ${OAUTH2_PROXY_HTTPS_ADDRESS+x} ] && [ ! -z $OAUTH2_PROXY_HTTPS_ADDRESS ]; then + PROXY_ARGS="${PROXY_ARGS} --https-address=${OAUTH2_PROXY_HTTPS_ADDRESS}" +fi + +if [ ! -z ${OAUTH2_PROXY_REDIRECT_URL+x} ] && [ ! -z $OAUTH2_PROXY_REDIRECT_URL ]; then + PROXY_ARGS="${PROXY_ARGS} --redirect-url=${OAUTH2_PROXY_REDIRECT_URL}" +fi + +if [ ! -z ${OAUTH2_PROXY_TLS_CERT+x} ] && [ ! -z $OAUTH2_PROXY_TLS_CERT ]; then + PROXY_ARGS="${PROXY_ARGS} --tls-cert=${OAUTH2_PROXY_TLS_CERT}" +fi + +if [ ! -z ${OAUTH2_PROXY_TLS_KEY+x} ] && [ ! -z $OAUTH2_PROXY_TLS_KEY ]; then + PROXY_ARGS="${PROXY_ARGS} --tls-key=${OAUTH2_PROXY_TLS_KEY}" +fi + +if [ ! -z ${OAUTH2_PROXY_PROVIDER+x} ] && [ ! -z $OAUTH2_PROXY_PROVIDER ]; then + PROXY_ARGS="${PROXY_ARGS} --provider=${OAUTH2_PROXY_PROVIDER}" +fi + +if [ ! -z ${OAUTH2_PROXY_UPSTREAM+x} ] && [ ! -z $OAUTH2_PROXY_UPSTREAM ]; then + PROXY_ARGS="${PROXY_ARGS} --upstream=${OAUTH2_PROXY_UPSTREAM}" +fi + +if [ ! -z ${OAUTH2_PROXY_SIGN_AWS_REQUEST_REGION+x} ] && [ ! -z $OAUTH2_PROXY_SIGN_AWS_REQUEST_REGION ]; then + PROXY_ARGS="${PROXY_ARGS} --sign-aws-request-region=${OAUTH2_PROXY_SIGN_AWS_REQUEST_REGION}" +fi + +if [ ! -z ${OAUTH2_PROXY_SIGN_AWS_REQUEST_SERVICE+x} ] && [ ! -z $OAUTH2_PROXY_SIGN_AWS_REQUEST_SERVICE ]; then + PROXY_ARGS="${PROXY_ARGS} --sign-aws-request-service=${OAUTH2_PROXY_SIGN_AWS_REQUEST_SERVICE}" +fi + +if [ ! -z ${OAUTH2_PROXY_AWS_ACCESS_KEY+x} ] && [ ! -z $OAUTH2_PROXY_AWS_ACCESS_KEY ]; then + export AWS_ACCESS_KEY=$OAUTH2_PROXY_AWS_ACCESS_KEY +fi + +if [ ! -z ${OAUTH2_PROXY_AWS_SECRET_ACCESS_KEY+x} ] && [ ! -z $OAUTH2_PROXY_AWS_SECRET_ACCESS_KEY ]; then + export AWS_SECRET_ACCESS_KEY=$OAUTH2_PROXY_AWS_SECRET_ACCESS_KEY +fi + +if [ ! -z ${OAUTH2_PROXY_EMAIL_DOMAIN+x} ] && [ ! -z ${OAUTH2_PROXY_EMAIL_DOMAIN} ]; then + PROXY_ARGS="${PROXY_ARGS} --email-domain=${OAUTH2_PROXY_EMAIL_DOMAIN}" +fi + +echo "Environment:" +set +echo "Launching oauth2_proxy..." +set -x +#exec /gosu nobody /oauth2_proxy ${PROXY_ARGS} +/oauth2_proxy ${PROXY_ARGS} +if [ $? -ne 0 ]; then + echo "Launch failed: /oauth2_proxy ${PROXY_ARGS}" +fi diff --git a/legal/LICENSE b/legal/LICENSE new file mode 100644 index 000000000..08e08f403 --- /dev/null +++ b/legal/LICENSE @@ -0,0 +1,221 @@ +This license covers the entrypoint.sh script. + +Copyright 2013-2018 Crate.IO GmbH ("Crate") + + +Licensed to Crate.IO GmbH (referred to in this notice as "Crate") +under one or more contributor license agreements. See the NOTICE file +distributed with this work for additional information regarding copyright +ownership. + +Crate licenses this software to you under the Apache License, Version 2.0. +However, if you have executed another commercial license agreement with +Crate these terms will supersede the license and you may use the software +solely pursuant to the terms of the relevant commercial agreement. + + +========================================================================= + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/main.go b/main.go index 4e4c7edb3..ac8f7778e 100644 --- a/main.go +++ b/main.go @@ -40,6 +40,9 @@ func main() { flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)") flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start") flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") + flagSet.String("sign-aws-request-region", "", "Sign upstream requests with AWS credentials for this region") + flagSet.String("sign-aws-request-service", "", "Sign upstream requests with AWS credentials for this service") + flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS") flagSet.Var(&emailDomains, "email-domain", "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email") diff --git a/oauthproxy.go b/oauthproxy.go index 21e5dfc74..dacc144ea 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -1,10 +1,12 @@ package main import ( + "bytes" b64 "encoding/base64" "errors" "fmt" "html/template" + "io/ioutil" "log" "net" "net/http" @@ -17,6 +19,10 @@ import ( "github.com/bitly/oauth2_proxy/cookie" "github.com/bitly/oauth2_proxy/providers" "github.com/mbland/hmacauth" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/aws/signer/v4" ) const SignatureHeader = "GAP-Signature" @@ -72,20 +78,34 @@ type OAuthProxy struct { compiledRegex []*regexp.Regexp templates *template.Template Footer string + AWSRegion string + AWSService string } type UpstreamProxy struct { - upstream string - handler http.Handler - auth hmacauth.HmacAuth + upstream string + handler http.Handler + auth hmacauth.HmacAuth + signer *v4.Signer + awsregion string + awsservice string } func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Header().Set("GAP-Upstream-Address", u.upstream) - if u.auth != nil { - r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth")) - u.auth.SignRequest(r) - } + // Sign this request if an AWS signer has been configured + if u.signer != nil { + // h := genAuthorizationHeader(r, u.signer, u.upstream, u.awsregion, u.awsservice) + // if h != "" { + // w.Header().Set("Authorization", h) + // } + r = signAWSRequest(r, u.signer, u.upstream, u.awsregion, u.awsservice) + + } + // w.Header().Set("GAP-Upstream-Address", u.upstream) + // if u.auth != nil { + // r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth")) + // u.auth.SignRequest(r) + // } u.handler.ServeHTTP(w, r) } @@ -102,7 +122,7 @@ func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) { req.URL.RawQuery = "" } } -func setProxyDirector(proxy *httputil.ReverseProxy) { +func setProxyDirector(opts *Options, proxy *httputil.ReverseProxy) { director := proxy.Director proxy.Director = func(req *http.Request) { director(req) @@ -118,6 +138,8 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) { func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { serveMux := http.NewServeMux() var auth hmacauth.HmacAuth + var signer *v4.Signer + if sigData := opts.signatureData; sigData != nil { auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key), SignatureHeader, SignatureHeaders) @@ -132,17 +154,27 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { if !opts.PassHostHeader { setProxyUpstreamHostHeader(proxy, u) } else { - setProxyDirector(proxy) + setProxyDirector(opts, proxy) + } + + if opts.SignAWSRequestRegion != "" { + sess := session.Must(session.NewSession()) + sess.Config.Region = aws.String(opts.SignAWSRequestRegion) + + signer = v4.NewSigner(sess.Config.Credentials) + + serveMux.Handle(path, + &UpstreamProxy{u.Host, proxy, auth, signer, opts.SignAWSRequestRegion, opts.SignAWSRequestService}) + } else { + serveMux.Handle(path, &UpstreamProxy{u.Host, proxy, nil, nil, "", ""}) } - serveMux.Handle(path, - &UpstreamProxy{u.Host, proxy, auth}) case "file": if u.Fragment != "" { path = u.Fragment } log.Printf("mapping path %q => file system %q", path, u.Path) proxy := NewFileServer(path, u.Path) - serveMux.Handle(path, &UpstreamProxy{path, proxy, nil}) + serveMux.Handle(path, &UpstreamProxy{path, proxy, nil, nil, "", ""}) default: panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme)) } @@ -206,6 +238,8 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { CookieCipher: cipher, templates: loadTemplates(opts.CustomTemplatesDir), Footer: opts.Footer, + AWSRegion: opts.SignAWSRequestRegion, + AWSService: opts.SignAWSRequestService, } } @@ -457,6 +491,7 @@ func getRemoteAddr(req *http.Request) (s string) { } func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + switch path := req.URL.Path; { case path == p.RobotsPath: p.RobotsTxt(rw) @@ -703,6 +738,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int } else { rw.Header().Set("GAP-Auth", session.Email) } + return http.StatusAccepted } @@ -732,3 +768,89 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, } return nil, fmt.Errorf("%s not in HtpasswdFile", pair[0]) } + +func genAuthorizationHeader(req *http.Request, signer *v4.Signer, upstream string, awsregion string, awsservice string) string { + var err error + + // Set the Host for this request to be our upstream host. If we don't, + // the signed request will not be accepted by AWS. + req.Host = upstream + + switch req.Body { + case nil: + log.Println("Signing a request with no body...") + _, err = signer.Sign(req, nil, awsservice, awsregion, time.Now()) + if err != nil { + log.Println("Could not sign request:", err) + return "" + } + + default: + b, err := ioutil.ReadAll(req.Body) + if err != nil { + log.Println("Error reading request body:", err) + } + req.Body = ioutil.NopCloser(bytes.NewReader(b)) + + // Set the Connection header to "close", otherwise, the signature will fail. + req.Header.Set("Connection", "close") + + // Sign the request + _, err = signer.Sign(req, bytes.NewReader(b), awsservice, awsregion, time.Now()) + if err != nil { + log.Println("Could not sign request:", err) + return "" + } + + } + + return req.Header.Get("Authorization") +} + +func signAWSRequest(req *http.Request, signer *v4.Signer, upstream string, awsregion string, awsservice string) *http.Request { + var err error + + // Set the Host for this request to be our upstream host. If we don't, + // the signed request will not be accepted by AWS. + req.Host = upstream + + for name, _ := range req.Header { + if name == "Referer" { + req.Header.Del(name) + } + if strings.Contains(name, "X-Forwarded-") { + req.Header.Del(name) + } + } + + switch req.Body { + case nil: + log.Println("Signing a request with no body...") + _, err = signer.Sign(req, nil, awsservice, awsregion, time.Now()) + if err != nil { + log.Println("Could not sign request:", err) + return nil + } + + default: + b, err := ioutil.ReadAll(req.Body) + if err != nil { + log.Println("Error reading request body:", err) + } + req.Body = ioutil.NopCloser(bytes.NewReader(b)) + + // Set the Connection header to "close", otherwise, the signature will fail. + req.Header.Set("Connection", "close") + + // Sign the request + _, err = signer.Sign(req, bytes.NewReader(b), awsservice, awsregion, time.Now()) + if err != nil { + log.Println("Could not sign request:", err) + return nil + } + + } + + return req + +} diff --git a/options.go b/options.go index 949fbba80..d92d2572b 100644 --- a/options.go +++ b/options.go @@ -61,6 +61,8 @@ type Options struct { SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"` SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"` SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"` + SignAWSRequestRegion string `flag:"sign-aws-request-region" cfg:"sign_aws_request_region"` + SignAWSRequestService string `flag:"sign-aws-request-service" cfg:"sign_aws_request_service"` // These options allow for other providers besides Google, with // potential overrides.