Skip to content
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

Support host based routing #5

Open
wants to merge 5 commits into
base: arista-2
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
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Builder Image #
#################

FROM golang:1.15.3-alpine3.12 as builder
FROM golang:1.16.0-alpine3.13 as builder

ENV GO111MODULE=on
WORKDIR /go/src/oidc-authservice
Expand All @@ -19,7 +19,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o /go
# Release Image #
#################

FROM alpine:3.12
FROM alpine:3.13.2
RUN apk add --no-cache ca-certificates

ENV USER=authservice
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ Session store-related settings:
| `OIDC_STATE_STORE_PATH` | "/var/lib/authservice/oidc_state.db" | Path to the session store used to save the sessions for the OIDC state parameter. |
| `SESSION_MAX_AGE` | "86400" | Time in seconds after which sessions expire. Defaults to a day (24h). |
| `SESSION_SAME_SITE` | "Lax" | SameSite attribute of the session cookie. Check details of SameSite attribute [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). Its value can be "None", "Lax" or "Strict". |
| `SESSION_DOMAIN` | "" | Domain attribute of the session cookie. Check details of Domain attribute [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie). If len(SESSION_DOMAIN) > 0 the incoming request's host and scheme are also saved in the state rather than just the path. This enables AuthService to service all subdomains of SESSION_DOMAIN. |
| `SCHEME_DEFAULT` | `https` | Default scheme used when the `X-Forwarded-Proto` header is not present in an incoming request. Only used if len(SESSION_DOMAIN) > 0. |

By default, the AuthService keeps sessions to check if a user is authenticated. However, there may be times where
we want to check a user's logged in status at the Provider, effectively making the Provider the one keeping the
Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ require (
github.com/boltdb/bolt v1.3.1
github.com/cenkalti/backoff/v4 v4.0.2
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/emirpasic/gods v1.12.0
github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.7.3
github.com/gorilla/sessions v1.2.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5
github.com/yosssi/boltstore v1.0.1-0.20150916121936-36632d491655
golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 // indirect
golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gonum.org/v1/gonum v0.8.1
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
k8s.io/api v0.18.2
Expand Down
46 changes: 18 additions & 28 deletions go.sum

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ func main() {
// TODO: Add support for Redis
store: store,
oidcStateStore: oidcStateStore,
stateFunc: NewStateFunc(c),
afterLoginRedirectURL: c.AfterLoginURL.String(),
homepageURL: c.HomepageURL.String(),
afterLogoutRedirectURL: c.AfterLogoutURL.String(),
Expand All @@ -184,6 +185,7 @@ func main() {
},
sessionMaxAgeSeconds: c.SessionMaxAge,
strictSessionValidation: c.StrictSessionValidation,
sessionDomain: c.SessionDomain,
authHeader: c.AuthHeader,
caBundle: caBundle,
authenticators: []authenticator.Request{
Expand Down
5 changes: 4 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ type server struct {
afterLoginRedirectURL string
homepageURL string
afterLogoutRedirectURL string
sessionDomain string
sessionMaxAgeSeconds int
strictSessionValidation bool
authHeader string
idTokenOpts jwtClaimOpts
upstreamHTTPHeaderOpts httpHeaderOpts
caBundle []byte
sessionSameSite http.SameSite
stateFunc StateFunc
}

// jwtClaimOpts specifies the location of the user's identity inside a JWT's
Expand Down Expand Up @@ -141,7 +143,7 @@ func (s *server) authCodeFlowAuthenticationRequest(w http.ResponseWriter, r *htt
logger := loggerForRequest(r)

// Initiate OIDC Flow with Authorization Request.
state, err := createState(r, w, s.oidcStateStore)
state, err := createState(r, w, s.oidcStateStore, s.stateFunc)
if err != nil {
logger.Errorf("Failed to save state in store: %v", err)
returnMessage(w, http.StatusInternalServerError, "Failed to save state in store.")
Expand Down Expand Up @@ -230,6 +232,7 @@ func (s *server) callback(w http.ResponseWriter, r *http.Request) {
session.Options.Path = "/"
// Extra layer of CSRF protection
session.Options.SameSite = s.sessionSameSite
session.Options.Domain = s.sessionDomain

userID, ok := claims[s.idTokenOpts.userIDClaim].(string)
if !ok {
Expand Down
2 changes: 2 additions & 0 deletions settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type config struct {
SessionStorePath string `split_words:"true" default:"/var/lib/authservice/data.db"`
SessionMaxAge int `split_words:"true" default:"86400"`
SessionSameSite string `split_words:"true" default:"Lax"`
SessionDomain string `split_words:"true"`
SchemeDefault string `split_words:"true"`

// Site
ClientName string `split_words:"true" default:"AuthService"`
Expand Down
41 changes: 39 additions & 2 deletions state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ package main
import (
"encoding/gob"
"net/http"
"strings"
"time"

"github.com/gorilla/sessions"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)

const (
Expand All @@ -20,6 +22,8 @@ func init() {
gob.Register(State{})
}

// TODO maybe move state code to its own package

type State struct {
// FirstVisitedURL is the URL that the user visited when we redirected them
// to login.
Expand All @@ -32,14 +36,47 @@ func newState(firstVisitedURL string) *State {
}
}

type StateFunc func(*http.Request) *State

func NewStateFunc(config *config) StateFunc {
if len(config.SessionDomain) > 0 {
return newSchemeAndHost(config)
}
return relativeURL
}

func relativeURL(r *http.Request) *State {
return &State{
FirstVisitedURL: r.URL.String(),
}
}

func newSchemeAndHost(config *config) StateFunc {
return func(r *http.Request) *State {
// try to get request protocol from header
p := r.Header.Get("X-Forwarded-Proto")
if p == "" {
// XXX maybe could hardcode "https"
p = config.SchemeDefault
}
// XXX maybe return an error here. would require changing the StateFunc type
if !strings.HasSuffix(r.Host, config.SessionDomain) {
log.Warnf("Request host %q is not a subdomain of %q", r.Host, config.SessionDomain)
}
return &State{
FirstVisitedURL: p + "://" + r.Host + r.URL.String(),
}
}
}

// createState creates the state parameter from the incoming request, stores
// it in the session store and sets a cookie with the session key.
// It returns the session key, which can be used as the state value to start
// an OIDC authentication request.
func createState(r *http.Request, w http.ResponseWriter,
store sessions.Store) (string, error) {
store sessions.Store, sf StateFunc) (string, error) {

s := newState(r.URL.Path)
s := sf(r)
session := sessions.NewSession(store, oidcStateCookie)
session.Options.MaxAge = int(20 * time.Minute)
session.Options.Path = "/"
Expand Down
6 changes: 4 additions & 2 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ func realpath(path string) (string, error) {

func loggerForRequest(r *http.Request) *log.Entry {
return log.WithContext(r.Context()).WithFields(log.Fields{
"ip": getUserIP(r),
"request": r.URL.String(),
"ip": getUserIP(r),
"host": r.Host,
"path": r.URL.String(),
"method": r.Method,
})
}

Expand Down