130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
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
|
|
}
|