Skip to content

Commit

Permalink
Fix/forward auth redirect (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethklee committed Apr 19, 2023
1 parent 3a363f6 commit 63e0f8e
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 9 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Provides a SAML SP authentication proxy for backend web services
Comma separated list of attribute=header pairs mapping SAML IdP response attributes to forwarded request header (env SAML_PROXY_ATTRIBUTE_HEADER_MAPPINGS)
-attribute-header-wildcard string
(env SAML_PROXY_ATTRIBUTE_HEADER_WILDCARD)
-auth-verify bool
Enables verify path endpoint for forward auth and trusts X-Forwarded headers (env SAML_PROXY_AUTH_VERIFY)
-auth-verify-path string
Path under BaseUrl that will respond with a 200 when authenticated (env SAML_PROXY_AUTH_VERIFY_PATH) (default "/_verify")
-authorize-attribute attribute
Expand Down Expand Up @@ -70,6 +72,10 @@ The authorization is configured with the combination of `--authorize-attribute`

The values are a comma separated list of authorized values and since the assertion attributes can contain more than one value also, the authorization performs an "intersection" matching any one of the expected values with any one of the assertion attribute values. That allows for matching user IDs where the assertion has a single value but you want to allow one or more users to be authorized. It also allows for matching group names where each user may be belong to more than one group and you may want to also authorize any number of groups.

The proxy also has [support for Traefik forward auth](https://doc.traefik.io/traefik/middlewares/http/forwardauth) and [the caddy variant](https://caddyserver.com/docs/caddyfile/directives/forward_auth). The `--auth-verify` and `--auth-verify-path` parameters can be used to enable a verify endpoint that will respond with a 204 when the user is authenticated.

**WARNING** the `--auth-verify` option trusts the `X-Forwarded-*` headers and should only be used when the proxy is behind a gateway; one that clears and sets those headers.

## Note for AJAX/Fetch Operations

If the web application being protected behind this proxy makes AJAX/Fetch calls, then be sure
Expand Down
3 changes: 2 additions & 1 deletion server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ type Config struct {
SpCertPath string `default:"saml-auth-proxy.cert" usage:"The [path] to the X509 public certificate PEM file for this SP"`
NameIdMapping string `usage:"Name of the request [header] to convey the SAML nameID/subject"`
AttributeHeaderMappings map[string]string `usage:"Comma separated list of [attribute=header] pairs mapping SAML IdP response attributes to forwarded request header"`
AttributeHeaderWildcard string `usage:"Maps all SAML attributes with this option as a prefix`
AttributeHeaderWildcard string `usage:"Maps all SAML attributes with this option as a prefix"`
NewAuthWebhookUrl string `usage:"[URL] of webhook that will get POST'ed when a new authentication is processed"`
AuthorizeAttribute string `usage:"Enables authorization and specifies the [attribute] to check for authorized values"`
AuthorizeValues []string `usage:"If enabled, comma separated list of [values] that must be present in the authorize attribute"`
CookieName string `usage:"Name of the cookie that tracks session token" default:"token"`
CookieMaxAge time.Duration `usage:"Specifies the amount of time the authentication token will remain valid" default:"2h"`
CookieDomain string `usage:"Overrides the domain set on the session cookie. By default the BaseUrl host is used."`
AllowIdpInitiated bool `usage:"If set, allows for IdP initiated authentication flow"`
AuthVerify bool `usage:"Enables verify path endpoint for forward auth and trusts X-Forwarded headers"`
AuthVerifyPath string `default:"/_verify" usage:"Path under BaseUrl that will respond with a 200 when authenticated"`
Debug bool `usage:"Enable debug logs"`
StaticRelayState string `usage:"A fixed RelayState value, such as a short URL. Will be trimmed to 80 characters to conform with SAML. The default generates random bytes that are Base64 encoded."`
Expand Down
7 changes: 5 additions & 2 deletions server/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import (
"bytes"
"encoding/json"
"fmt"
"go.uber.org/zap"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"

"go.uber.org/zap"

"github.com/crewjam/saml/samlsp"
"github.com/patrickmn/go-cache"
)
Expand All @@ -26,6 +27,8 @@ const (
HeaderForwardedProto = "X-Forwarded-Proto"
HeaderForwardedFor = "X-Forwarded-For"
HeaderForwardedHost = "X-Forwarded-Host"
HeaderForwardedURI = "X-Forwarded-Uri"
HeaderForwardedMethod = "X-Forwarded-Method"
)

type proxy struct {
Expand Down Expand Up @@ -88,7 +91,7 @@ func (p *proxy) handler(respOutWriter http.ResponseWriter, reqIn *http.Request)
return
}

if reqIn.URL.Path == p.config.AuthVerifyPath {
if p.config.AuthVerify && reqIn.URL.Path == p.config.AuthVerifyPath {
p.logger.
With(zap.String("remoteAddr", reqIn.RemoteAddr)).
Debug("Responding with 204 to auth verify request")
Expand Down
20 changes: 16 additions & 4 deletions server/request_tracker_cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package server

import (
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"

"github.com/crewjam/saml"
"github.com/crewjam/saml/samlsp"
Expand All @@ -13,8 +15,9 @@ import (
type CookieRequestTracker struct {
samlsp.CookieRequestTracker

CookieDomain string
StaticRelayState string
CookieDomain string
StaticRelayState string
TrustForwardedHeaders bool
}

func minOfInts(x, y int) int {
Expand All @@ -29,12 +32,21 @@ func minOfInts(x, y int) int {
// Changes:
// - Adds host in request URI
// - Adds CookieDomain config in http.SetCookie
// - Handles X-Forwarded headers
func (t CookieRequestTracker) TrackRequest(w http.ResponseWriter, r *http.Request, samlRequestID string) (string, error) {
r.URL.Host = r.Host
var redirectURI *url.URL
if t.TrustForwardedHeaders && r.Header.Get(HeaderForwardedProto) != "" && r.Header.Get(HeaderForwardedHost) != "" && r.Header.Get(HeaderForwardedURI) != "" {
// When X-Forwarded headers exist, use it
redirectURI, _ = url.Parse(fmt.Sprintf("%s://%s%s", r.Header.Get(HeaderForwardedProto), r.Header.Get(HeaderForwardedHost), r.Header.Get(HeaderForwardedURI)))
} else {
redirectURI, _ = url.Parse(r.URL.String()) // Clone
redirectURI.Host = r.Host
}

trackedRequest := samlsp.TrackedRequest{
Index: base64.RawURLEncoding.EncodeToString(randomBytes(42)),
SAMLRequestID: samlRequestID,
URI: r.URL.String(),
URI: redirectURI.String(),
}

if t.StaticRelayState != "" {
Expand Down
5 changes: 3 additions & 2 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ func Start(ctx context.Context, logger *zap.Logger, cfg *Config) error {
URL: *rootUrl,
Key: keyPair.PrivateKey.(*rsa.PrivateKey),
}, &middleware.ServiceProvider),
CookieDomain: cookieDomain,
StaticRelayState: cfg.StaticRelayState,
CookieDomain: cookieDomain,
StaticRelayState: cfg.StaticRelayState,
TrustForwardedHeaders: cfg.AuthVerify,
}
cookieSessionProvider := samlsp.DefaultSessionProvider(samlOpts)
cookieSessionProvider.Name = cfg.CookieName
Expand Down

0 comments on commit 63e0f8e

Please sign in to comment.