forked from hashicorp/nomad
/
consul_policy.go
129 lines (112 loc) · 3.23 KB
/
consul_policy.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
package nomad
import (
"strings"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/hcl"
"github.com/pkg/errors"
)
// ConsulServiceRule represents a policy for a service.
type ConsulServiceRule struct {
Name string `hcl:",key"`
Policy string
}
// ConsulPolicy represents the parts of a ConsulServiceRule Policy that are
// relevant to Service Identity authorizations.
type ConsulPolicy struct {
Services []*ConsulServiceRule `hcl:"service,expand"`
ServicePrefixes []*ConsulServiceRule `hcl:"service_prefix,expand"`
}
// IsEmpty returns true if there are no Services or ServicePrefixes defined for
// the ConsulPolicy.
func (cp *ConsulPolicy) IsEmpty() bool {
if cp == nil {
return true
}
return len(cp.Services) == 0 && len(cp.ServicePrefixes) == 0
}
// ParseConsulPolicy parses raw string s into a ConsulPolicy. An error is
// returned if decoding the policy fails, or if the decoded policy has no
// Services or ServicePrefixes defined.
func ParseConsulPolicy(s string) (*ConsulPolicy, error) {
cp := new(ConsulPolicy)
if err := hcl.Decode(cp, s); err != nil {
return nil, errors.Wrap(err, "failed to parse ACL policy")
}
if cp.IsEmpty() {
// the only use case for now, may as well validate asap
return nil, errors.New("consul policy contains no service rules")
}
return cp, nil
}
func (c *consulACLsAPI) hasSufficientPolicy(task string, token *api.ACLToken) (bool, error) {
// check each policy directly attached to the token
for _, policyRef := range token.Policies {
if allowable, err := c.policyAllowsServiceWrite(task, policyRef.ID); err != nil {
return false, err
} else if allowable {
return true, nil
}
}
// check each policy on each role attached to the token
for _, roleLink := range token.Roles {
role, _, err := c.aclClient.RoleRead(roleLink.ID, &api.QueryOptions{
AllowStale: false,
})
if err != nil {
return false, err
}
for _, policyLink := range role.Policies {
allowable, err := c.policyAllowsServiceWrite(task, policyLink.ID)
if err != nil {
return false, err
}
if allowable {
return true, nil
}
}
}
return false, nil
}
func (c *consulACLsAPI) policyAllowsServiceWrite(task string, policyID string) (bool, error) {
policy, _, err := c.aclClient.PolicyRead(policyID, &api.QueryOptions{
AllowStale: false,
})
if err != nil {
return false, err
}
// compare policy to the necessary permission for service write
// e.g. service "db" { policy = "write" }
// e.g. service_prefix "" { policy == "write" }
cp, err := ParseConsulPolicy(policy.Rules)
if err != nil {
return false, err
}
if cp.allowsServiceWrite(task) {
return true, nil
}
return false, nil
}
const (
serviceNameWildcard = "*"
)
func (cp *ConsulPolicy) allowsServiceWrite(task string) bool {
for _, service := range cp.Services {
name := strings.ToLower(service.Name)
policy := strings.ToLower(service.Policy)
if policy == ConsulPolicyWrite {
if name == task || name == serviceNameWildcard {
return true
}
}
}
for _, servicePrefix := range cp.ServicePrefixes {
prefix := strings.ToLower(servicePrefix.Name)
policy := strings.ToLower(servicePrefix.Policy)
if policy == ConsulPolicyWrite {
if strings.HasPrefix(task, prefix) {
return true
}
}
}
return false
}