Merge pull request #1286 from instadeepai/allowed_email_domains-on-auth_request-endpoint

Add allowed_email_domains on auth_request endpoint
This commit is contained in:
Joel Speed
2022-02-17 17:10:43 +00:00
committed by GitHub
11 changed files with 305 additions and 90 deletions
+1 -1
View File
@@ -124,7 +124,7 @@ func NewFlagSet() *pflag.FlagSet {
flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
flagSet.StringSlice("email-domain", []string{}, "authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email")
flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . to allow subdomains (eg .example.com)")
flagSet.StringSlice("whitelist-domain", []string{}, "allowed domains for redirection after authentication. Prefix domain with a . or a *. to allow subdomains (eg .example.com, *.example.com)")
flagSet.String("authenticated-emails-file", "", "authenticate against emails via file (one per line)")
flagSet.String("htpasswd-file", "", "additionally authenticate against a htpasswd file. Entries must be created with \"htpasswd -B\" for bcrypt encryption")
flagSet.StringSlice("htpasswd-user-group", []string{}, "the groups to be set on sessions for htpasswd users (may be given multiple times)")
+4 -58
View File
@@ -6,6 +6,8 @@ import (
"strings"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
util "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
)
var (
@@ -50,28 +52,9 @@ func (v *validator) IsValidRedirect(redirect string) bool {
logger.Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect)
return false
}
redirectHostname := redirectURL.Hostname()
for _, allowedDomain := range v.allowedDomains {
allowedHost, allowedPort := splitHostPort(allowedDomain)
if allowedHost == "" {
continue
}
if redirectHostname == strings.TrimPrefix(allowedHost, ".") ||
(strings.HasPrefix(allowedHost, ".") &&
strings.HasSuffix(redirectHostname, allowedHost)) {
// the domain names match, now validate the ports
// if the whitelisted domain's port is '*', allow all ports
// if the whitelisted domain contains a specific port, only allow that port
// if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https
redirectPort := redirectURL.Port()
if allowedPort == "*" ||
allowedPort == redirectPort ||
(allowedPort == "" && redirectPort == "") {
return true
}
}
if util.IsEndpointAllowed(redirectURL, v.allowedDomains) {
return true
}
logger.Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect)
@@ -81,40 +64,3 @@ func (v *validator) IsValidRedirect(redirect string) bool {
return false
}
}
// splitHostPort separates host and port. If the port is not valid, it returns
// the entire input as host, and it doesn't check the validity of the host.
// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric.
// *** taken from net/url, modified validOptionalPort() to accept ":*"
func splitHostPort(hostport string) (host, port string) {
host = hostport
colon := strings.LastIndexByte(host, ':')
if colon != -1 && validOptionalPort(host[colon:]) {
host, port = host[:colon], host[colon+1:]
}
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return
}
// validOptionalPort reports whether port is either an empty string
// or matches /^:\d*$/
// *** taken from net/url, modified to accept ":*"
func validOptionalPort(port string) bool {
if port == "" || port == ":*" {
return true
}
if port[0] != ':' {
return false
}
for _, b := range port[1:] {
if b < '0' || b > '9' {
return false
}
}
return true
}
+20 -2
View File
@@ -5,6 +5,7 @@ import (
"net/url"
"os"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
@@ -22,6 +23,10 @@ var _ = Describe("Validator suite", func() {
"anyport.bar:*",
".sub.anyport.bar:*",
"www.whitelisteddomain.tld",
"*.wildcard.sub.port.bar:8080",
"*.wildcard.sub.anyport.bar:*",
"*.wildcard.bar",
"*.wildcard.proxy.foo.bar",
}
})
@@ -96,7 +101,20 @@ var _ = Describe("Validator suite", func() {
Entry("Quad Tab 2", "/\t\t\\\t\t/evil.com", false),
Entry("Relative Path", "/./\\evil.com", false),
Entry("Relative Subpath", "/./../../\\evil.com", false),
Entry("Partial Subdomain", "evilbar.foo", false),
Entry("Valid HTTP Wildcard Subdomain", "http://foo.wildcard.bar/redirect", true),
Entry("Valid HTTPS Wildcard Subdomain", "https://foo.wildcard.bar/redirect", true),
Entry("Valid HTTP Wildcard Subdomain Root", "http://wildcard.bar/redirect", true),
Entry("Valid HTTPS Wildcard Subdomain Root", "https://wildcard.bar/redirect", true),
Entry("Valid HTTP Wildcard Subdomain anyport", "http://foo.wildcard.sub.anyport.bar:4242/redirect", true),
Entry("Valid HTTPS Wildcard Subdomain anyport", "https://foo.wildcard.sub.anyport.bar:4242/redirect", true),
Entry("Valid HTTP Wildcard Subdomain Anyport Root", "http://wildcard.sub.anyport.bar:4242/redirect", true),
Entry("Valid HTTPS Wildcard Subdomain Anyport Root", "https://wildcard.sub.anyport.bar:4242/redirect", true),
Entry("Valid HTTP Wildcard Subdomain Defined Port", "http://foo.wildcard.sub.port.bar:8080/redirect", true),
Entry("Valid HTTPS Wildcard Subdomain Defined Port", "https://foo.wildcard.sub.port.bar:8080/redirect", true),
Entry("Valid HTTP Wildcard Subdomain Defined Port Root", "http://wildcard.sub.port.bar:8080/redirect", true),
Entry("Valid HTTPS Wildcard Subdomain Defined Port Root", "https://wildcard.sub.port.bar:8080/redirect", true),
Entry("Missing Protocol Root Domain", "foo.bar/redirect", false),
Entry("Missing Protocol Wildcard Subdomain", "proxy.wildcard.bar/redirect", false),
)
})
@@ -109,7 +127,7 @@ var _ = Describe("Validator suite", func() {
DescribeTable("Should split the host and port",
func(in splitHostPortTableInput) {
host, port := splitHostPort(in.hostport)
host, port := util.SplitHostPort(in.hostport)
Expect(host).To(Equal(in.expectedHost))
Expect(port).To(Equal(in.expectedPort))
},
+83
View File
@@ -9,6 +9,8 @@ import (
"io/ioutil"
"math/big"
"net"
"net/url"
"strings"
"time"
)
@@ -66,3 +68,84 @@ func GenerateCert() ([]byte, []byte, error) {
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
return certBytes, keyBytes, err
}
// SplitHostPort separates host and port. If the port is not valid, it returns
// the entire input as host, and it doesn't check the validity of the host.
// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric.
// *** taken from net/url, modified validOptionalPort() to accept ":*"
func SplitHostPort(hostport string) (host, port string) {
host = hostport
colon := strings.LastIndexByte(host, ':')
if colon != -1 && validOptionalPort(host[colon:]) {
host, port = host[:colon], host[colon+1:]
}
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return
}
// validOptionalPort reports whether port is either an empty string
// or matches /^:\d*$/
// *** taken from net/url, modified to accept ":*"
func validOptionalPort(port string) bool {
if port == "" || port == ":*" {
return true
}
if port[0] != ':' {
return false
}
for _, b := range port[1:] {
if b < '0' || b > '9' {
return false
}
}
return true
}
// IsEndpointAllowed checks whether the endpoint URL is allowed based
// on an allowed domains list.
func IsEndpointAllowed(endpoint *url.URL, allowedDomains []string) bool {
hostname := endpoint.Hostname()
for _, allowedDomain := range allowedDomains {
allowedHost, allowedPort := SplitHostPort(allowedDomain)
if allowedHost == "" {
continue
}
if isHostnameAllowed(hostname, allowedHost) {
// the domain names match, now validate the ports
// if the allowed domain's port is '*', allow all ports
// if the allowed domain contains a specific port, only allow that port
// if the allowed domain doesn't contain a port at all, only allow empty redirect ports ie http and https
redirectPort := endpoint.Port()
if allowedPort == "*" ||
allowedPort == redirectPort ||
(allowedPort == "" && redirectPort == "") {
return true
}
}
}
return false
}
func isHostnameAllowed(hostname, allowedHost string) bool {
// check if we have a perfect match between hostname and allowedHost
if hostname == strings.TrimPrefix(allowedHost, ".") ||
hostname == strings.TrimPrefix(allowedHost, "*.") {
return true
}
// check if hostname is a sub domain of the allowedHost
if (strings.HasPrefix(allowedHost, ".") && strings.HasSuffix(hostname, allowedHost)) ||
(strings.HasPrefix(allowedHost, "*.") && strings.HasSuffix(hostname, allowedHost[1:])) {
return true
}
return false
}