This commit is contained in:
Joel Speed
2022-06-03 12:41:30 +01:00
parent 374a676c9d
commit 0dbda5dfac
9 changed files with 520 additions and 493 deletions
-70
View File
@@ -1,70 +0,0 @@
package validation
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
)
func validateAllowlists(o *options.Options) []string {
msgs := []string{}
msgs = append(msgs, validateRoutes(o)...)
msgs = append(msgs, validateRegexes(o)...)
msgs = append(msgs, validateTrustedIPs(o)...)
if len(o.TrustedIPs) > 0 && o.ReverseProxy {
_, err := fmt.Fprintln(os.Stderr, "WARNING: mixing --trusted-ip with --reverse-proxy is a potential security vulnerability. An attacker can inject a trusted IP into an X-Real-IP or X-Forwarded-For header if they aren't properly protected outside of oauth2-proxy")
if err != nil {
panic(err)
}
}
return msgs
}
// validateRoutes validates method=path routes passed with options.SkipAuthRoutes
func validateRoutes(o *options.Options) []string {
msgs := []string{}
for _, route := range o.SkipAuthRoutes {
var regex string
parts := strings.SplitN(route, "=", 2)
if len(parts) == 1 {
regex = parts[0]
} else {
regex = parts[1]
}
_, err := regexp.Compile(regex)
if err != nil {
msgs = append(msgs, fmt.Sprintf("error compiling regex /%s/: %v", regex, err))
}
}
return msgs
}
// validateRegex validates regex paths passed with options.SkipAuthRegex
func validateRegexes(o *options.Options) []string {
msgs := []string{}
for _, regex := range o.SkipAuthRegex {
_, err := regexp.Compile(regex)
if err != nil {
msgs = append(msgs, fmt.Sprintf("error compiling regex /%s/: %v", regex, err))
}
}
return msgs
}
// validateTrustedIPs validates IP/CIDRs for IP based allowlists
func validateTrustedIPs(o *options.Options) []string {
msgs := []string{}
for i, ipStr := range o.TrustedIPs {
if nil == ip.ParseIPNet(ipStr) {
msgs = append(msgs, fmt.Sprintf("trusted_ips[%d] (%s) could not be recognized", i, ipStr))
}
}
return msgs
}
+123 -123
View File
@@ -1,125 +1,125 @@
package validation
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
)
var _ = Describe("Allowlist", func() {
type validateRoutesTableInput struct {
routes []string
errStrings []string
}
type validateRegexesTableInput struct {
regexes []string
errStrings []string
}
type validateTrustedIPsTableInput struct {
trustedIPs []string
errStrings []string
}
DescribeTable("validateRoutes",
func(r *validateRoutesTableInput) {
opts := &options.Options{
SkipAuthRoutes: r.routes,
}
Expect(validateRoutes(opts)).To(ConsistOf(r.errStrings))
},
Entry("Valid regex routes", &validateRoutesTableInput{
routes: []string{
"/foo",
"POST=/foo/bar",
"PUT=^/foo/bar$",
"DELETE=/crazy/(?:regex)?/[^/]+/stuff$",
},
errStrings: []string{},
}),
Entry("Bad regexes do not compile", &validateRoutesTableInput{
routes: []string{
"POST=/(foo",
"OPTIONS=/foo/bar)",
"GET=^]/foo/bar[$",
"GET=^]/foo/bar[$",
},
errStrings: []string{
"error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`",
"error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`",
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
},
}),
)
DescribeTable("validateRegexes",
func(r *validateRegexesTableInput) {
opts := &options.Options{
SkipAuthRegex: r.regexes,
}
Expect(validateRegexes(opts)).To(ConsistOf(r.errStrings))
},
Entry("Valid regex routes", &validateRegexesTableInput{
regexes: []string{
"/foo",
"/foo/bar",
"^/foo/bar$",
"/crazy/(?:regex)?/[^/]+/stuff$",
},
errStrings: []string{},
}),
Entry("Bad regexes do not compile", &validateRegexesTableInput{
regexes: []string{
"/(foo",
"/foo/bar)",
"^]/foo/bar[$",
"^]/foo/bar[$",
},
errStrings: []string{
"error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`",
"error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`",
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
"error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
},
}),
)
DescribeTable("validateTrustedIPs",
func(t *validateTrustedIPsTableInput) {
opts := &options.Options{
TrustedIPs: t.trustedIPs,
}
Expect(validateTrustedIPs(opts)).To(ConsistOf(t.errStrings))
},
Entry("Non-overlapping valid IPs", &validateTrustedIPsTableInput{
trustedIPs: []string{
"127.0.0.1",
"10.32.0.1/32",
"43.36.201.0/24",
"::1",
"2a12:105:ee7:9234:0:0:0:0/64",
},
errStrings: []string{},
}),
Entry("Overlapping valid IPs", &validateTrustedIPsTableInput{
trustedIPs: []string{
"135.180.78.199",
"135.180.78.199/32",
"d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4",
"d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4/128",
},
errStrings: []string{},
}),
Entry("Invalid IPs", &validateTrustedIPsTableInput{
trustedIPs: []string{"[::1]", "alkwlkbn/32"},
errStrings: []string{
"trusted_ips[0] ([::1]) could not be recognized",
"trusted_ips[1] (alkwlkbn/32) could not be recognized",
},
}),
)
})
// import (
// . "github.com/onsi/ginkgo"
// . "github.com/onsi/ginkgo/extensions/table"
// . "github.com/onsi/gomega"
//
// "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
// )
//
// var _ = Describe("Allowlist", func() {
// type validateRoutesTableInput struct {
// routes []string
// errStrings []string
// }
//
// type validateRegexesTableInput struct {
// regexes []string
// errStrings []string
// }
//
// type validateTrustedIPsTableInput struct {
// trustedIPs []string
// errStrings []string
// }
//
// DescribeTable("validateRoutes",
// func(r *validateRoutesTableInput) {
// opts := &options.Options{
// SkipAuthRoutes: r.routes,
// }
// Expect(validateRoutes(opts)).To(ConsistOf(r.errStrings))
// },
// Entry("Valid regex routes", &validateRoutesTableInput{
// routes: []string{
// "/foo",
// "POST=/foo/bar",
// "PUT=^/foo/bar$",
// "DELETE=/crazy/(?:regex)?/[^/]+/stuff$",
// },
// errStrings: []string{},
// }),
// Entry("Bad regexes do not compile", &validateRoutesTableInput{
// routes: []string{
// "POST=/(foo",
// "OPTIONS=/foo/bar)",
// "GET=^]/foo/bar[$",
// "GET=^]/foo/bar[$",
// },
// errStrings: []string{
// "error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`",
// "error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`",
// "error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
// "error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
// },
// }),
// )
//
// DescribeTable("validateRegexes",
// func(r *validateRegexesTableInput) {
// opts := &options.Options{
// SkipAuthRegex: r.regexes,
// }
// Expect(validateRegexes(opts)).To(ConsistOf(r.errStrings))
// },
// Entry("Valid regex routes", &validateRegexesTableInput{
// regexes: []string{
// "/foo",
// "/foo/bar",
// "^/foo/bar$",
// "/crazy/(?:regex)?/[^/]+/stuff$",
// },
// errStrings: []string{},
// }),
// Entry("Bad regexes do not compile", &validateRegexesTableInput{
// regexes: []string{
// "/(foo",
// "/foo/bar)",
// "^]/foo/bar[$",
// "^]/foo/bar[$",
// },
// errStrings: []string{
// "error compiling regex //(foo/: error parsing regexp: missing closing ): `/(foo`",
// "error compiling regex //foo/bar)/: error parsing regexp: unexpected ): `/foo/bar)`",
// "error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
// "error compiling regex /^]/foo/bar[$/: error parsing regexp: missing closing ]: `[$`",
// },
// }),
// )
//
// DescribeTable("validateTrustedIPs",
// func(t *validateTrustedIPsTableInput) {
// opts := &options.Options{
// TrustedIPs: t.trustedIPs,
// }
// Expect(validateTrustedIPs(opts)).To(ConsistOf(t.errStrings))
// },
// Entry("Non-overlapping valid IPs", &validateTrustedIPsTableInput{
// trustedIPs: []string{
// "127.0.0.1",
// "10.32.0.1/32",
// "43.36.201.0/24",
// "::1",
// "2a12:105:ee7:9234:0:0:0:0/64",
// },
// errStrings: []string{},
// }),
// Entry("Overlapping valid IPs", &validateTrustedIPsTableInput{
// trustedIPs: []string{
// "135.180.78.199",
// "135.180.78.199/32",
// "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4",
// "d910:a5a1:16f8:ddf5:e5b9:5cef:a65e:41f4/128",
// },
// errStrings: []string{},
// }),
// Entry("Invalid IPs", &validateTrustedIPsTableInput{
// trustedIPs: []string{"[::1]", "alkwlkbn/32"},
// errStrings: []string{
// "trusted_ips[0] ([::1]) could not be recognized",
// "trusted_ips[1] (alkwlkbn/32) could not be recognized",
// },
// }),
// )
// })
+94
View File
@@ -0,0 +1,94 @@
package validation
import (
"fmt"
"os"
"regexp"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
)
func validateAuthorization(authorization options.Authorization, reverseProxy bool) []string {
msgs := []string{}
msgs = append(msgs, validateRequestRules(authorization.RequestRules, reverseProxy)...)
return msgs
}
func validateRequestRules(rules []options.AuthorizationRule, reverseProxy bool) []string {
msgs := []string{}
ids := make(map[string]struct{})
for _, rule := range rules {
msgs = append(msgs, validateRequestRule(ids, rule, reverseProxy)...)
}
return msgs
}
func validateRequestRule(ids map[string]struct{}, rule options.AuthorizationRule, reverseProxy bool) []string {
msgs := []string{}
if rule.ID == "" {
msgs = append(msgs, "request rule has empty ID: IDs are required for all request rules")
}
if _, ok := ids[rule.ID]; ok {
msgs = append(msgs, fmt.Sprintf("multiple request rules found with ID %q: request rule IDs must be unique", rule.ID))
}
ids[rule.ID] = struct{}{}
msgs = append(msgs, validateRequestRulePolicy(rule.ID, rule.Policy)...)
msgs = append(msgs, validateRequestRulePath(rule.ID, rule.Path)...)
msgs = append(msgs, validateRequestRuleIPs(rule.ID, rule.IPs, reverseProxy)...)
return msgs
}
func validateRequestRulePolicy(ruleID string, policy options.AuthorizationPolicy) []string {
msgs := []string{}
switch policy {
case options.AllowPolicy, options.DenyPolicy, options.DelegatePolicy:
// Do nothing for valid options
default:
msgs = append(msgs, fmt.Sprintf("request rule %q has invalid policy (%s): policy must be one of %s, %s or %s", ruleID, policy, options.AllowPolicy, options.DenyPolicy, options.DelegatePolicy))
}
return msgs
}
// validateRequestRulePath validates paths for path/regex based conditions
func validateRequestRulePath(ruleID string, path string) []string {
msgs := []string{}
_, err := regexp.Compile(path)
if err != nil {
msgs = append(msgs, fmt.Sprintf("error compiling path regex (%s) for rule %q: %v", path, ruleID, err))
}
return msgs
}
// validateRequestRuleIPs validates IP/CIDRs for IP based conditions.
func validateRequestRuleIPs(ruleID string, ips []string, reverseProxy bool) []string {
msgs := []string{}
if len(ips) > 0 && reverseProxy {
_, err := fmt.Fprintln(os.Stderr, "WARNING: mixing IP authorization with --reverse-proxy is a potential security vulnerability. An attacker can inject a trusted IP into an X-Real-IP or X-Forwarded-For header if they aren't properly protected outside of oauth2-proxy")
if err != nil {
panic(err)
}
}
for i, ipStr := range ips {
if nil == ip.ParseIPNet(ipStr) {
msgs = append(msgs, fmt.Sprintf("rule %q IP [%d] (%s) could not be recognized", ruleID, i, ipStr))
}
}
return msgs
}
+1 -3
View File
@@ -20,6 +20,7 @@ import (
// are of the correct format
func Validate(o *options.Options) error {
msgs := validateCookie(o.Cookie)
msgs = append(msgs, validateAuthorization(o.Authorization, o.ReverseProxy)...)
msgs = append(msgs, validateSessionCookieMinimal(o)...)
msgs = append(msgs, validateRedisSessionStore(o)...)
msgs = append(msgs, prefixValues("injectRequestHeaders: ", validateHeaders(o.InjectRequestHeaders)...)...)
@@ -96,9 +97,6 @@ func Validate(o *options.Options) error {
})
}
// Do this after ReverseProxy validation for TrustedIP coordinated checks
msgs = append(msgs, validateAllowlists(o)...)
if len(msgs) != 0 {
return fmt.Errorf("invalid configuration:\n %s",
strings.Join(msgs, "\n "))