-
Notifications
You must be signed in to change notification settings - Fork 0
/
abac.go
153 lines (136 loc) · 4.97 KB
/
abac.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package policy
import (
"encoding/json"
"fmt"
"os"
)
// PolicySpec is a specification for a policy.
type PolicySpec struct {
// Role is the role of the user making the request.
// "*" matches all roles.
Role string `json:"role"`
// User is the user-id this rule applies to.
// Either user or group is required to match the request.
// "*" matches all users.
User string `json:"user"`
// Group is the group-id this rule applies to.
// Either user or group is required to match the request.
// "*" matches all groups.
Group string `json:"group"`
// Resource is the name of a resource. Resource, and Namespace are required to match resource requests.
// "*" matches all resources
Resource string `json:"resource"`
// Namespace is the name of a namespace. APIGroup, Resource, and Namespace are required to match resource requests.
// "*" matches all namespaces (including unnamespaced requests)
Namespace string `json:"namespace"`
// Readonly matches readonly requests when true, and all requests when false
ReadOnly bool `json:"readonly"`
// NonResourcePath matches non-resource request paths.
// "*" matches all paths
// "/foo/*" matches all subpaths of foo
NonResourcePath string `json:"nonResourcePath"`
}
// UserAttributes holds user-related attributes.
type UserAttributes struct {
// UserID is the user-id of the user making the request.
UserID string `json:"userID"`
// GroupID is the group-id the user belongs to.
GroupID string `json:"groupID"`
// Roles is the list of roles the user has.
Roles []string `json:"roles"`
}
// ResourceAttributes holds resource-related attributes.
type ResourceAttributes struct {
// Resource is the name of a resource.
Resource string `json:"resource"`
// Namespace is the name of a namespace.
Namespace string `json:"namespace"`
// ReadOnly is true for read-only requests.
ReadOnly bool `json:"readOnly"`
}
// WithDefaultPolicies adds default policies to the PolicyManager.
func WithDefaultPolicies() PolicyOption {
return func(p *PolicyManager) {
p.policies = append(p.policies, defaultPolicies()...)
}
}
// WithPolicies adds additional policies to the PolicyManager.
func WithPolicies(policies ...PolicySpec) PolicyOption {
return func(p *PolicyManager) {
p.policies = append(p.policies, policies...)
}
}
// WithPoliciesFromFile loads policies from a JSON file and adds them to the PolicyManager.
func WithPoliciesFromFile(filename string) PolicyOption {
return func(p *PolicyManager) {
file, err := os.Open(filename)
if err != nil {
panic(fmt.Sprintf("failed to open policies file: %v", err))
}
defer file.Close()
var newPolicies []PolicySpec
if err := json.NewDecoder(file).Decode(&newPolicies); err != nil {
panic(fmt.Sprintf("failed to decode policies from file: %v\n", err))
}
p.policies = append(p.policies, newPolicies...)
}
}
// defaultPolicies returns the default set of policies.
func defaultPolicies() []PolicySpec {
// Return an empty slice or define default policies here.
return []PolicySpec{
{Role: "*", User: "*", Group: "*", Resource: "*", Namespace: "*", ReadOnly: true, NonResourcePath: "*"},
{Role: "admin", User: "*", Group: "*", Resource: "*", Namespace: "*", ReadOnly: false, NonResourcePath: "*"},
}
}
// PolicyManager is an ABAC policy engine.
//
// We use ABAC (Attribute-Based Access Control) to define policies.
type PolicyManager struct {
policies []PolicySpec
}
// PolicyOption defines a function that applies a configuration to the PolicyManager.
type PolicyOption func(*PolicyManager)
// NewPolicyManager initializes a new policy manager with optional policies.
func NewPolicyManager(opts ...PolicyOption) *PolicyManager {
p := &PolicyManager{}
for _, opt := range opts {
opt(p)
}
return p
}
// Evaluate checks if any policy allows the action based on user and resource attributes.
func (p *PolicyManager) Evaluate(user *UserAttributes, resource *ResourceAttributes) bool {
for _, policy := range p.policies {
if p.matchPolicy(policy, user, resource) {
return true
}
}
return false
}
// matchPolicy checks if a policy matches the provided user and resource attributes.
func (p *PolicyManager) matchPolicy(policy PolicySpec, user *UserAttributes, resource *ResourceAttributes) bool {
return matchesSlice(policy.Role, user.Roles) &&
matchesString(policy.User, user.UserID) &&
matchesString(policy.Group, user.GroupID) &&
matchesString(policy.Resource, resource.Resource) &&
matchesString(policy.Namespace, resource.Namespace) &&
matchesString(policy.NonResourcePath, resource.Resource) &&
(policy.ReadOnly == resource.ReadOnly)
}
// matchesSlice checks if a value is in a list or matches a wildcard "*".
func matchesSlice(pattern string, list []string) bool {
if pattern == "*" || pattern == "" {
return true
}
for _, v := range list {
if v == pattern {
return true
}
}
return false
}
// matchesString checks if a single value matches a specified pattern or wildcard "*".
func matchesString(pattern, value string) bool {
return pattern == "*" || pattern == "" || pattern == value
}