github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/consul_policy.go (about)

     1  package nomad
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/hashicorp/consul/api"
     7  	"github.com/hashicorp/hcl"
     8  	"github.com/pkg/errors"
     9  )
    10  
    11  // ConsulServiceRule represents a policy for a service.
    12  type ConsulServiceRule struct {
    13  	Name   string `hcl:",key"`
    14  	Policy string
    15  }
    16  
    17  // ConsulPolicy represents the parts of a ConsulServiceRule Policy that are
    18  // relevant to Service Identity authorizations.
    19  type ConsulPolicy struct {
    20  	Services        []*ConsulServiceRule `hcl:"service,expand"`
    21  	ServicePrefixes []*ConsulServiceRule `hcl:"service_prefix,expand"`
    22  }
    23  
    24  // IsEmpty returns true if there are no Services or ServicePrefixes defined for
    25  // the ConsulPolicy.
    26  func (cp *ConsulPolicy) IsEmpty() bool {
    27  	if cp == nil {
    28  		return true
    29  	}
    30  	return len(cp.Services) == 0 && len(cp.ServicePrefixes) == 0
    31  }
    32  
    33  // ParseConsulPolicy parses raw string s into a ConsulPolicy. An error is
    34  // returned if decoding the policy fails, or if the decoded policy has no
    35  // Services or ServicePrefixes defined.
    36  func ParseConsulPolicy(s string) (*ConsulPolicy, error) {
    37  	cp := new(ConsulPolicy)
    38  	if err := hcl.Decode(cp, s); err != nil {
    39  		return nil, errors.Wrap(err, "failed to parse ACL policy")
    40  	}
    41  	if cp.IsEmpty() {
    42  		// the only use case for now, may as well validate asap
    43  		return nil, errors.New("consul policy contains no service rules")
    44  	}
    45  	return cp, nil
    46  }
    47  
    48  func (c *consulACLsAPI) hasSufficientPolicy(task string, token *api.ACLToken) (bool, error) {
    49  	// check each policy directly attached to the token
    50  	for _, policyRef := range token.Policies {
    51  		if allowable, err := c.policyAllowsServiceWrite(task, policyRef.ID); err != nil {
    52  			return false, err
    53  		} else if allowable {
    54  			return true, nil
    55  		}
    56  	}
    57  
    58  	// check each policy on each role attached to the token
    59  	for _, roleLink := range token.Roles {
    60  		role, _, err := c.aclClient.RoleRead(roleLink.ID, &api.QueryOptions{
    61  			AllowStale: false,
    62  		})
    63  		if err != nil {
    64  			return false, err
    65  		}
    66  
    67  		for _, policyLink := range role.Policies {
    68  			allowable, err := c.policyAllowsServiceWrite(task, policyLink.ID)
    69  			if err != nil {
    70  				return false, err
    71  			}
    72  			if allowable {
    73  				return true, nil
    74  			}
    75  		}
    76  	}
    77  
    78  	return false, nil
    79  }
    80  
    81  func (c *consulACLsAPI) policyAllowsServiceWrite(task string, policyID string) (bool, error) {
    82  	policy, _, err := c.aclClient.PolicyRead(policyID, &api.QueryOptions{
    83  		AllowStale: false,
    84  	})
    85  	if err != nil {
    86  		return false, err
    87  	}
    88  
    89  	// compare policy to the necessary permission for service write
    90  	// e.g. service "db" { policy = "write" }
    91  	// e.g. service_prefix "" { policy == "write" }
    92  	cp, err := ParseConsulPolicy(policy.Rules)
    93  	if err != nil {
    94  		return false, err
    95  	}
    96  
    97  	if cp.allowsServiceWrite(task) {
    98  		return true, nil
    99  	}
   100  
   101  	return false, nil
   102  }
   103  
   104  const (
   105  	serviceNameWildcard = "*"
   106  )
   107  
   108  func (cp *ConsulPolicy) allowsServiceWrite(task string) bool {
   109  	for _, service := range cp.Services {
   110  		name := strings.ToLower(service.Name)
   111  		policy := strings.ToLower(service.Policy)
   112  		if policy == ConsulPolicyWrite {
   113  			if name == task || name == serviceNameWildcard {
   114  				return true
   115  			}
   116  		}
   117  	}
   118  
   119  	for _, servicePrefix := range cp.ServicePrefixes {
   120  		prefix := strings.ToLower(servicePrefix.Name)
   121  		policy := strings.ToLower(servicePrefix.Policy)
   122  		if policy == ConsulPolicyWrite {
   123  			if strings.HasPrefix(task, prefix) {
   124  				return true
   125  			}
   126  		}
   127  	}
   128  	return false
   129  }