Skip to content

Oauth2 port #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ COPY . .
RUN VERSION=${VERSION} make build && touch jwt_signing_key.pem

# Copy binary to alpine
FROM alpine:3.13
FROM photon:3.0
RUN tdnf update -y

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
COPY --from=builder /go/src/github.com/oauth2-proxy/oauth2-proxy/jwt_signing_key.pem /etc/ssl/private/jwt_signing_key.pem

USER 2000:2000
USER 65534:65533

ENTRYPOINT ["/bin/oauth2-proxy"]
27 changes: 25 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
GO ?= go
GOLANGCILINT ?= golangci-lint

# vmware additions
IMAGE ?= csp-rc-docker-local.artifactory.eng.vmware.com/dev-platform/oauth2-proxy
PUBLIC_IMAGE ?= vmware-docker-vdp.bintray.io/dev-platform/oauth2-proxy
TAG ?= test
# end vmware additions

BINARY := oauth2-proxy
VERSION ?= $(shell git describe --always --dirty --tags 2>/dev/null || echo "undefined")
# Allow to override image registry.
Expand Down Expand Up @@ -38,8 +44,24 @@ lint: validate-go-version
.PHONY: build
build: validate-go-version clean $(BINARY)

# vmware additions
build-image:
docker build . -t $(IMAGE):$(TAG)

push-image: build-image
docker push $(IMAGE):$(TAG)

shell:
docker run -ti --rm -v `pwd`:/workspace --net=host --entrypoint=/bin/bash \
$(IMAGE):$(TAG)

guess-tag:
@echo "v`git rev-parse --short HEAD`"

# end vmware additions

$(BINARY):
GO111MODULE=on CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" -o $@ github.com/oauth2-proxy/oauth2-proxy/v7
GO111MODULE=on CGO_ENABLED=0 $(GO) build -a -installsuffix cgo -ldflags="-X main.VERSION=${VERSION}" .

.PHONY: docker
docker:
Expand All @@ -57,7 +79,8 @@ docker-all: docker

.PHONY: docker-push
docker-push:
docker push $(REGISTRY)/oauth2-proxy:latest
docker tag $(IMAGE):$(TAG) $(PUBLIC_IMAGE):$(TAG)
docker push $(PUBLIC_IMAGE):$(TAG

.PHONY: docker-push-all
docker-push-all: docker-push
Expand Down
9 changes: 9 additions & 0 deletions VDP_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Build and push Docker image

Please fork the repo and clone into your `$GOPATH`.

```bash
cd $GOPATH/src/github.com # Create this directory if it doesn't exist
git clone [email protected]/csp/oauth2-proxy pusher/oauth2_proxy
make push-image TAG=$(make guess-tag)
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
github.com/alicebob/miniredis/v2 v2.13.0
github.com/bitly/go-simplejson v0.5.0
github.com/bitly/oauth2_proxy v2.0.1+incompatible
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bitly/oauth2_proxy v1.1.1 h1:fkMh/7FW/YS69jNO2PdNqS1+tEUuoLqRXclJXJ1kxV0=
github.com/bitly/oauth2_proxy v2.0.1+incompatible h1:gOO7Bagj26tnB9eAgSy4ZEctV9dKfUhnqynubsMtOY4=
github.com/bitly/oauth2_proxy v2.0.1+incompatible/go.mod h1:RC/16V/cJOgDM4At4kHPPC/Uko+GBWcthhdcMCW4o88=
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/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/options/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ type HeaderValue struct {

// Allow users to load the value from a session claim
*ClaimSource `json:",omitempty"`

// Allow headers to be provided via config
*StringSource `json:",omitempty"`
}

// StringSource is a header value provided by config
type StringSource struct {
Value string `json:"value,omitempty"`
}

// ClaimSource allows loading a header value from a claim within the session
Expand Down
31 changes: 31 additions & 0 deletions pkg/apis/options/legacy_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ type LegacyHeaders struct {
PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`

// vmware additions
ExtraHeaders []string `flag:"extra-headers" cfg:"extra_headers"`
GazIdpID string `flag:"gaz-idp-id" cfg:"gaz_idp_id"`
GazContextID string `flag:"gaz-context-id" cfg:"gaz_context_id"`
}

func legacyHeadersFlagSet() *pflag.FlagSet {
Expand All @@ -171,6 +176,11 @@ func legacyHeadersFlagSet() *pflag.FlagSet {
flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
flagSet.Bool("skip-auth-strip-headers", true, "strips X-Forwarded-* style authentication headers & Authorization header if they would be set by oauth2-proxy")

// vmware additions
flagSet.String("gaz-idp-id", "", "Value for the idp_id parameter")
flagSet.String("gaz-context-id", "", "Value for the context_id parameter")
flagSet.StringSlice("extra-headers", []string{}, "extra headers to pass to the upstream 'Header: Value' format")

return flagSet
}

Expand Down Expand Up @@ -201,6 +211,27 @@ func (l *LegacyHeaders) getRequestHeaders() []Header {
requestHeaders = append(requestHeaders, getAuthorizationHeader())
}

// vmware addition
for _, hv := range l.ExtraHeaders {
parts := strings.Split(hv, ":")
if len(parts) != 2 {
continue
}
name := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])

requestHeaders = append(requestHeaders, Header{
Name: name,
Values: []HeaderValue{
{
StringSource: &StringSource{
Value: value,
},
},
},
})
}

for i := range requestHeaders {
requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders
}
Expand Down
179 changes: 179 additions & 0 deletions providers/csp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package providers

import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"

"github.com/bitly/oauth2_proxy/api"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
)

type CspProvider struct {
*ProviderData
}

var _ Provider = (*CspProvider)(nil)

func NewCSPProvider(p *ProviderData) *CspProvider {
p.ProviderName = "csp"

return &CspProvider{ProviderData: p}
}

func (p *CspProvider) Redeem(ctx context.Context, redirectURL, code string) (s *sessions.SessionState, err error) {
if code == "" {
err = errors.New("missing code")
return
}

params := url.Values{}
params.Add("redirect_uri", redirectURL)
//params.Add("client_id", p.ClientID)
//params.Add("client_secret", p.ClientSecret)
params.Add("code", code)
params.Add("grant_type", "authorization_code")
if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
params.Add("resource", p.ProtectedResource.String())
}

var req *http.Request
req, err = http.NewRequest("POST", p.RedeemURL.String(), bytes.NewBufferString(params.Encode()))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
auth := fmt.Sprintf("%s:%s", p.ClientID, p.ClientSecret)
auth = base64.StdEncoding.EncodeToString([]byte(auth))
req.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth))

var resp *http.Response
resp, err = http.DefaultClient.Do(req)

if err != nil {
return nil, err
}
var body []byte
body, err = ioutil.ReadAll(resp.Body)
resp.Body.Close()

if err != nil {
return
}

if resp.StatusCode != 200 {
err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemURL.String(), body)
return
}

// blindly try json and x-www-form-urlencoded
var jsonResponse struct {
AccessToken string `json:"access_token"`
}
err = json.Unmarshal(body, &jsonResponse)
if err == nil {
s = &sessions.SessionState{
AccessToken: jsonResponse.AccessToken,
}
return
}

var v url.Values
v, err = url.ParseQuery(string(body))
if err != nil {
return
}
if a := v.Get("access_token"); a != "" {
s = &sessions.SessionState{AccessToken: a}
} else {
err = fmt.Errorf("no access token found %s", body)
}
return
}

// GetLoginURL with typical oauth parameters
func (p *CspProvider) GetLoginURL(redirectURI, state string) string {
var a url.URL
a = *p.LoginURL
params, _ := url.ParseQuery(a.RawQuery)
params.Set("redirect_uri", redirectURI)
params.Set("approval_prompt", p.ApprovalPrompt)
// params.Add("scope", p.Scope)
params.Set("client_id", p.ClientID)
params.Set("response_type", "code")
params.Add("state", state)
a.RawQuery = params.Encode()
return a.String()
}

// CookieForSession serializes a session state for storage in a cookie
func (p *CspProvider) CookieForSession(s *sessions.SessionState, c *encryption.Cipher) (string, error) {
bytes, err := s.EncodeSessionState(*c, true)
return string(bytes), err
}

// SessionFromCookie deserializes a session from a cookie value
func (p *CspProvider) SessionFromCookie(v string, c *encryption.Cipher) (s *sessions.SessionState, err error) {
return sessions.DecodeSessionState([]byte(v), *c, true)
}

func (p *CspProvider) GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error) {
req, err := http.NewRequest("GET", p.ValidateURL.String(), nil)
req.Header.Set("csp-auth-token", s.AccessToken)

if err != nil {
log.Printf("failed building request %s", err)
return "", err
}
json, err := api.Request(req)
if err != nil {
log.Printf("failed making request %s", err)
return "", err
}

username, err := json.Get("username").String()
if err != nil {
return "", err
}

if strings.Contains(username, "@") {
return username, nil
}

domain, err := json.Get("domain").String()
if err != nil {
return "", err
}

return fmt.Sprintf("%s@%s", username, domain), nil
}

// GetUserName returns the Account username
func (p *CspProvider) GetUserName(s *sessions.SessionState) (string, error) {
return p.GetEmailAddress(context.Background(), s)
}

// ValidateGroup validates that the provided email exists in the configured provider
// email group(s).
func (p *CspProvider) ValidateGroup(email string) bool {
return true
}

func (p *CspProvider) ValidateSessionState(s *sessions.SessionState) bool {
_, err := p.GetEmailAddress(context.Background(), s)
return err != nil
}

// RefreshSessionIfNeeded
func (p *CspProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
return false, nil
}
5 changes: 5 additions & 0 deletions providers/provider_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ type ProviderData struct {
// Universal Group authorization data structure
// any provider can set to consume
AllowedGroups map[string]struct{}

// vmware additions
GazIdpId string
GazContextId string
ExtraHeaders []string
}

// Data returns the ProviderData
Expand Down
3 changes: 3 additions & 0 deletions providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func New(provider string, p *ProviderData) Provider {
return NewDigitalOceanProvider(p)
case "google":
return NewGoogleProvider(p)
// vmware addition
case "csp":
return NewCSPProvider(p)
default:
return nil
}
Expand Down