git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add request authorization ruleset
commit8a2e32a655ef0c3cee17ffd0612c1d27d5647590 authorJoel Speed <joel.speed@hotmail.co.uk> dateSun 19 Jul 2020, 09:37 (6 years ago) parente02f99e
3 files changed+209
pkg/apis/options/authorization.go+17 pkg/authorization/conditions.go+86 pkg/authorization/rules.go+106
pkg/apis/options/authorization.gonew file+17
@@ -0,0 +1,17 @@
1+package options
2+
3+type AuthorizationPolicy string
4+
5+const (
6+ AllowPolicy AuthorizationPolicy = "Allow"
7+ DenyPolicy AuthorizationPolicy = "Deny"
8+)
9+
10+type AuthorizationRule struct {
11+ Policy AuthorizationPolicy
12+ Path string
13+ Methods []string
14+ IPs []string
15+}
16+
17+type RequestRules []AuthorizationRule
pkg/authorization/conditions.gonew file+86
@@ -0,0 +1,86 @@
1+package authorization
2+
3+import (
4+ "errors"
5+ "fmt"
6+ "net"
7+ "net/http"
8+ "regexp"
9+ "strings"
10+
11+ "github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
12+)
13+
14+type condition interface {
15+ matches(*http.Request) bool
16+}
17+
18+type methodCondition struct {
19+ methods map[string]struct{}
20+}
21+
22+func (m methodCondition) matches(req *http.Request) bool {
23+ _, ok := m.methods[strings.ToUpper(req.Method)]
24+ return ok
25+}
26+
27+func newMethodCondition(methods []string) condition {
28+ methodMap := make(map[string]struct{})
29+ for _, method := range methods {
30+ methodMap[strings.ToUpper(method)] = struct{}{}
31+ }
32+ return methodCondition{
33+ methods: methodMap,
34+ }
35+}
36+
37+type pathCondition struct {
38+ pathRegexp *regexp.Regexp
39+}
40+
41+func (p pathCondition) matches(req *http.Request) bool {
42+ return p.pathRegexp.MatchString(req.URL.Path)
43+}
44+
45+func newPathCondition(path string) (condition, error) {
46+ exp, err := regexp.Compile(path)
47+ if err != nil {
48+ return nil, err
49+ }
50+ return pathCondition{
51+ pathRegexp: exp,
52+ }, nil
53+}
54+
55+type ipCondition struct {
56+ netSet *ip.NetSet
57+ getClientIP func(req *http.Request) net.IP
58+}
59+
60+func (i ipCondition) matches(req *http.Request) bool {
61+ ip := i.getClientIP(req)
62+ if ip == nil {
63+ return false
64+ }
65+ return i.netSet.Has(ip)
66+}
67+
68+func newIPCondition(rawIPs []string, getClientIPFunc func(req *http.Request) net.IP) (condition, error) {
69+ if getClientIPFunc == nil {
70+ return nil, errors.New("client IP function required for IP condition")
71+ }
72+
73+ netSet := ip.NewNetSet()
74+ for _, rawIP := range rawIPs {
75+ ipNet := ip.ParseIPNet(rawIP)
76+ if ipNet == nil {
77+ return nil, fmt.Errorf("could not parse IP network: %s", rawIP)
78+ }
79+ netSet.AddIPNet(*ipNet)
80+ }
81+
82+ return ipCondition{
83+ netSet: netSet,
84+ getClientIP: getClientIPFunc,
85+ }, nil
86+}
pkg/authorization/rules.gonew file+106
@@ -0,0 +1,106 @@
1+package authorization
2+
3+import (
4+ "net"
5+ "net/http"
6+
7+ "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
8+)
9+
10+type AuthorizationPolicy int
11+
12+const (
13+ NonePolicy AuthorizationPolicy = iota
14+ AllowPolicy
15+ DenyPolicy
16+)
17+
18+type RuleSet interface {
19+ Matches(req *http.Request) AuthorizationPolicy
20+}
21+
22+type rule struct {
23+ conditions []condition
24+ policy AuthorizationPolicy
25+}
26+
27+func (r rule) matches(req *http.Request) AuthorizationPolicy {
28+ for _, condition := range r.conditions {
29+ if !condition.matches(req) {
30+ // One of the conditions didn't match so this rule does not apply
31+ return NonePolicy
32+ }
33+ }
34+ // If all conditions match, return the configured rule policy
35+ return r.policy
36+}
37+
38+func newRule(authRule options.AuthorizationRule, getClientIPFunc func(*http.Request) net.IP) (rule, error) {
39+ // This function should add the conditions in order of complexity, least complex first
40+ conditions := []condition{}
41+
42+ if len(authRule.Methods) > 0 {
43+ conditions = append(conditions, newMethodCondition(authRule.Methods))
44+ }
45+
46+ if len(authRule.Path) > 0 {
47+ condition, err := newPathCondition(authRule.Path)
48+ if err != nil {
49+ return rule{}, err
50+ }
51+ conditions = append(conditions, condition)
52+ }
53+
54+ if len(authRule.IPs) > 0 {
55+ condition, err := newIPCondition(authRule.IPs, getClientIPFunc)
56+ if err != nil {
57+ return rule{}, err
58+ }
59+ conditions = append(conditions, condition)
60+ }
61+
62+ var policy AuthorizationPolicy
63+ switch authRule.Policy {
64+ case options.AllowPolicy:
65+ policy = AllowPolicy
66+ case options.DenyPolicy:
67+ policy = DenyPolicy
68+ default:
69+ // This shouldn't be the case and should be prevented by validation
70+ policy = NonePolicy
71+ }
72+
73+ return rule{
74+ conditions: conditions,
75+ policy: policy,
76+ }, nil
77+}
78+
79+type ruleSet struct {
80+ rules []rule
81+}
82+
83+func (r ruleSet) Matches(req *http.Request) AuthorizationPolicy {
84+ for _, rule := range r.rules {
85+ if policy := rule.matches(req); policy != NonePolicy {
86+ // The rule applies to this request, return its policy
87+ return policy
88+ }
89+ }
90+ // No rules matched
91+ return NonePolicy
92+}
93+
94+func NewRuleSet(requestRules options.RequestRules, getClientIPFunc func(*http.Request) net.IP) (RuleSet, error) {
95+ rules := []rule{}
96+ for _, requestRule := range requestRules {
97+ r, err := newRule(requestRule, getClientIPFunc)
98+ if err != nil {
99+ return nil, err
100+ }
101+ rules = append(rules, r)
102+ }
103+ return ruleSet{
104+ rules: rules,
105+ }, nil
106+}