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 } type ConsulPolicy struct { Services []*ConsulServiceRule `hcl:"service,expand"` ServicePrefixes []*ConsulServiceRule `hcl:"service_prefix,expand"` } func (cp *ConsulPolicy) IsEmpty() bool { if cp == nil { return true } return len(cp.Services) == 0 && len(cp.ServicePrefixes) == 0 } 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 } // policyAllowsServiceWrite 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 c.allowsServiceWrite(task, cp) { return true, nil } return false, nil } const ( serviceNameWildcard = "*" ) func (_ *consulACLsAPI) allowsServiceWrite(task string, cp *ConsulPolicy) 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 }