cdc4b20afa
A Node Identity is very similar to a service identity. Its main targeted use is to allow creating tokens for use by Consul agents that will grant the necessary permissions for all the typical agent operations (node registration, coordinate updates, anti-entropy). Half of this commit is for golden file based tests of the acl token and role cli output. Another big updates was to refactor many of the tests in agent/consul/acl_endpoint_test.go to use the same style of tests and the same helpers. Besides being less boiler plate in the tests it also uses a common way of starting a test server with ACLs that should operate without any warnings regarding deprecated non-uuid master tokens etc.
1775 lines
48 KiB
Go
1775 lines
48 KiB
Go
package structs
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"hash/fnv"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/lib"
|
|
"golang.org/x/crypto/blake2b"
|
|
)
|
|
|
|
type ACLMode string
|
|
|
|
const (
|
|
// ACLs are disabled by configuration
|
|
ACLModeDisabled ACLMode = "0"
|
|
// ACLs are enabled
|
|
ACLModeEnabled ACLMode = "1"
|
|
// DEPRECATED (ACL-Legacy-Compat) - only needed while legacy ACLs are supported
|
|
// ACLs are enabled and using legacy ACLs
|
|
ACLModeLegacy ACLMode = "2"
|
|
// DEPRECATED (ACL-Legacy-Compat) - only needed while legacy ACLs are supported
|
|
// ACLs are assumed enabled but not being advertised
|
|
ACLModeUnknown ACLMode = "3"
|
|
)
|
|
|
|
// ACLOp is used in RPCs to encode ACL operations.
|
|
type ACLOp string
|
|
|
|
type ACLTokenIDType string
|
|
|
|
const (
|
|
ACLTokenSecret ACLTokenIDType = "secret"
|
|
ACLTokenAccessor ACLTokenIDType = "accessor"
|
|
)
|
|
|
|
type ACLPolicyIDType string
|
|
|
|
const (
|
|
ACLPolicyName ACLPolicyIDType = "name"
|
|
ACLPolicyID ACLPolicyIDType = "id"
|
|
)
|
|
|
|
const (
|
|
// All policy ids with the first 120 bits set to all zeroes are
|
|
// reserved for builtin policies. Policy creation will ensure we
|
|
// dont accidentally create them when autogenerating uuids.
|
|
|
|
// This policy gives unlimited access to everything. Users
|
|
// may rename if desired but cannot delete or modify the rules.
|
|
ACLPolicyGlobalManagementID = "00000000-0000-0000-0000-000000000001"
|
|
ACLPolicyGlobalManagement = `
|
|
acl = "write"
|
|
agent_prefix "" {
|
|
policy = "write"
|
|
}
|
|
event_prefix "" {
|
|
policy = "write"
|
|
}
|
|
key_prefix "" {
|
|
policy = "write"
|
|
}
|
|
keyring = "write"
|
|
node_prefix "" {
|
|
policy = "write"
|
|
}
|
|
operator = "write"
|
|
query_prefix "" {
|
|
policy = "write"
|
|
}
|
|
service_prefix "" {
|
|
policy = "write"
|
|
intentions = "write"
|
|
}
|
|
session_prefix "" {
|
|
policy = "write"
|
|
}` + EnterpriseACLPolicyGlobalManagement
|
|
|
|
// This is the policy ID for anonymous access. This is configurable by the
|
|
// user.
|
|
ACLTokenAnonymousID = "00000000-0000-0000-0000-000000000002"
|
|
|
|
ACLReservedPrefix = "00000000-0000-0000-0000-0000000000"
|
|
)
|
|
|
|
func ACLIDReserved(id string) bool {
|
|
return strings.HasPrefix(id, ACLReservedPrefix)
|
|
}
|
|
|
|
const (
|
|
// ACLSet creates or updates a token.
|
|
ACLSet ACLOp = "set"
|
|
|
|
// ACLDelete deletes a token.
|
|
ACLDelete ACLOp = "delete"
|
|
)
|
|
|
|
// ACLBootstrapNotAllowedErr is returned once we know that a bootstrap can no
|
|
// longer be done since the cluster was bootstrapped
|
|
var ACLBootstrapNotAllowedErr = errors.New("ACL bootstrap no longer allowed")
|
|
|
|
// ACLBootstrapInvalidResetIndexErr is returned when bootstrap is requested with a non-zero
|
|
// reset index but the index doesn't match the bootstrap index
|
|
var ACLBootstrapInvalidResetIndexErr = errors.New("Invalid ACL bootstrap reset index")
|
|
|
|
type ACLIdentity interface {
|
|
// ID returns a string that can be used for logging and telemetry. This should not
|
|
// contain any secret data used for authentication
|
|
ID() string
|
|
SecretToken() string
|
|
PolicyIDs() []string
|
|
RoleIDs() []string
|
|
EmbeddedPolicy() *ACLPolicy
|
|
ServiceIdentityList() []*ACLServiceIdentity
|
|
NodeIdentityList() []*ACLNodeIdentity
|
|
IsExpired(asOf time.Time) bool
|
|
IsLocal() bool
|
|
EnterpriseMetadata() *EnterpriseMeta
|
|
}
|
|
|
|
type ACLTokenPolicyLink struct {
|
|
ID string
|
|
Name string `hash:"ignore"`
|
|
}
|
|
|
|
type ACLTokenRoleLink struct {
|
|
ID string
|
|
Name string `hash:"ignore"`
|
|
}
|
|
|
|
// ACLServiceIdentity represents a high-level grant of all necessary privileges
|
|
// to assume the identity of the named Service in the Catalog and within
|
|
// Connect.
|
|
type ACLServiceIdentity struct {
|
|
ServiceName string
|
|
|
|
// Datacenters that the synthetic policy will be valid within.
|
|
// - No wildcards allowed
|
|
// - If empty then the synthetic policy is valid within all datacenters
|
|
//
|
|
// Only valid for global tokens. It is an error to specify this for local tokens.
|
|
Datacenters []string `json:",omitempty"`
|
|
}
|
|
|
|
func (s *ACLServiceIdentity) Clone() *ACLServiceIdentity {
|
|
s2 := *s
|
|
s2.Datacenters = cloneStringSlice(s.Datacenters)
|
|
return &s2
|
|
}
|
|
|
|
func (s *ACLServiceIdentity) AddToHash(h hash.Hash) {
|
|
h.Write([]byte(s.ServiceName))
|
|
for _, dc := range s.Datacenters {
|
|
h.Write([]byte(dc))
|
|
}
|
|
}
|
|
|
|
func (s *ACLServiceIdentity) EstimateSize() int {
|
|
size := len(s.ServiceName)
|
|
for _, dc := range s.Datacenters {
|
|
size += len(dc)
|
|
}
|
|
return size
|
|
}
|
|
|
|
func (s *ACLServiceIdentity) SyntheticPolicy(entMeta *EnterpriseMeta) *ACLPolicy {
|
|
// Given that we validate this string name before persisting, we do not
|
|
// have to escape it before doing the following interpolation.
|
|
rules := aclServiceIdentityRules(s.ServiceName, entMeta)
|
|
|
|
hasher := fnv.New128a()
|
|
hashID := fmt.Sprintf("%x", hasher.Sum([]byte(rules)))
|
|
|
|
policy := &ACLPolicy{}
|
|
policy.ID = hashID
|
|
policy.Name = fmt.Sprintf("synthetic-policy-%s", hashID)
|
|
policy.Description = "synthetic policy"
|
|
policy.Rules = rules
|
|
policy.Syntax = acl.SyntaxCurrent
|
|
policy.Datacenters = s.Datacenters
|
|
policy.EnterpriseMeta.Merge(entMeta)
|
|
policy.SetHash(true)
|
|
return policy
|
|
}
|
|
|
|
// ACLNodeIdentity represents a high-level grant of all privileges
|
|
// necessary to assume the identity of that node and manage it.
|
|
type ACLNodeIdentity struct {
|
|
// NodeName identities the Node that this identity authorizes access to
|
|
NodeName string
|
|
|
|
// Datacenter is required and specifies the datacenter of the node.
|
|
Datacenter string
|
|
}
|
|
|
|
func (s *ACLNodeIdentity) Clone() *ACLNodeIdentity {
|
|
s2 := *s
|
|
return &s2
|
|
}
|
|
|
|
func (s *ACLNodeIdentity) AddToHash(h hash.Hash) {
|
|
h.Write([]byte(s.NodeName))
|
|
h.Write([]byte(s.Datacenter))
|
|
}
|
|
|
|
func (s *ACLNodeIdentity) EstimateSize() int {
|
|
return len(s.NodeName) + len(s.Datacenter)
|
|
}
|
|
|
|
func (s *ACLNodeIdentity) SyntheticPolicy() *ACLPolicy {
|
|
// Given that we validate this string name before persisting, we do not
|
|
// have to escape it before doing the following interpolation.
|
|
rules := fmt.Sprintf(aclPolicyTemplateNodeIdentity, s.NodeName)
|
|
|
|
hasher := fnv.New128a()
|
|
hashID := fmt.Sprintf("%x", hasher.Sum([]byte(rules)))
|
|
|
|
policy := &ACLPolicy{}
|
|
policy.ID = hashID
|
|
policy.Name = fmt.Sprintf("synthetic-policy-%s", hashID)
|
|
policy.Description = "synthetic policy"
|
|
policy.Rules = rules
|
|
policy.Syntax = acl.SyntaxCurrent
|
|
policy.Datacenters = []string{s.Datacenter}
|
|
policy.EnterpriseMeta = *DefaultEnterpriseMeta()
|
|
policy.SetHash(true)
|
|
return policy
|
|
}
|
|
|
|
type ACLToken struct {
|
|
// This is the UUID used for tracking and management purposes
|
|
AccessorID string
|
|
|
|
// This is the UUID used as the api token by clients
|
|
SecretID string
|
|
|
|
// Human readable string to display for the token (Optional)
|
|
Description string
|
|
|
|
// List of policy links - nil/empty for legacy tokens or if service identities are in use.
|
|
// Note this is the list of IDs and not the names. Prior to token creation
|
|
// the list of policy names gets validated and the policy IDs get stored herein
|
|
Policies []ACLTokenPolicyLink `json:",omitempty"`
|
|
|
|
// List of role links. Note this is the list of IDs and not the names.
|
|
// Prior to token creation the list of role names gets validated and the
|
|
// role IDs get stored herein
|
|
Roles []ACLTokenRoleLink `json:",omitempty"`
|
|
|
|
// List of services to generate synthetic policies for.
|
|
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
|
|
|
// The node identities that this token should be allowed to manage.
|
|
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
|
|
|
// Type is the V1 Token Type
|
|
// DEPRECATED (ACL-Legacy-Compat) - remove once we no longer support v1 ACL compat
|
|
// Even though we are going to auto upgrade management tokens we still
|
|
// want to be able to have the old APIs operate on the upgraded management tokens
|
|
// so this field is being kept to identify legacy tokens even after an auto-upgrade
|
|
Type string `json:"-"`
|
|
|
|
// Rules is the V1 acl rules associated with
|
|
// DEPRECATED (ACL-Legacy-Compat) - remove once we no longer support v1 ACL compat
|
|
Rules string `json:",omitempty"`
|
|
|
|
// Whether this token is DC local. This means that it will not be synced
|
|
// to the ACL datacenter and replicated to others.
|
|
Local bool
|
|
|
|
// AuthMethod is the name of the auth method used to create this token.
|
|
AuthMethod string `json:",omitempty"`
|
|
|
|
// ACLAuthMethodEnterpriseMeta is the EnterpriseMeta for the AuthMethod that this token was created from
|
|
ACLAuthMethodEnterpriseMeta
|
|
|
|
// ExpirationTime represents the point after which a token should be
|
|
// considered revoked and is eligible for destruction. The zero value
|
|
// represents NO expiration.
|
|
//
|
|
// This is a pointer value so that the zero value is omitted properly
|
|
// during json serialization. time.Time does not respect json omitempty
|
|
// directives unfortunately.
|
|
ExpirationTime *time.Time `json:",omitempty"`
|
|
|
|
// ExpirationTTL is a convenience field for helping set ExpirationTime to a
|
|
// value of CreateTime+ExpirationTTL. This can only be set during
|
|
// TokenCreate and is cleared and used to initialize the ExpirationTime
|
|
// field before being persisted to the state store or raft log.
|
|
//
|
|
// This is a string version of a time.Duration like "2m".
|
|
ExpirationTTL time.Duration `json:",omitempty"`
|
|
|
|
// The time when this token was created
|
|
CreateTime time.Time `json:",omitempty"`
|
|
|
|
// Hash of the contents of the token
|
|
//
|
|
// This is needed mainly for replication purposes. When replicating from
|
|
// one DC to another keeping the content Hash will allow us to avoid
|
|
// unnecessary calls to the authoritative DC
|
|
Hash []byte
|
|
|
|
// Embedded Enterprise Metadata
|
|
EnterpriseMeta `mapstructure:",squash"`
|
|
|
|
// Embedded Raft Metadata
|
|
RaftIndex
|
|
}
|
|
|
|
func (t *ACLToken) UnmarshalJSON(data []byte) (err error) {
|
|
type Alias ACLToken
|
|
aux := &struct {
|
|
ExpirationTTL interface{}
|
|
Hash string
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(t),
|
|
}
|
|
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
if aux.ExpirationTTL != nil {
|
|
switch v := aux.ExpirationTTL.(type) {
|
|
case string:
|
|
if t.ExpirationTTL, err = time.ParseDuration(v); err != nil {
|
|
return err
|
|
}
|
|
case float64:
|
|
t.ExpirationTTL = time.Duration(v)
|
|
}
|
|
|
|
}
|
|
if aux.Hash != "" {
|
|
t.Hash = []byte(aux.Hash)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *ACLToken) Clone() *ACLToken {
|
|
t2 := *t
|
|
t2.Policies = nil
|
|
t2.Roles = nil
|
|
t2.ServiceIdentities = nil
|
|
t2.NodeIdentities = nil
|
|
|
|
if len(t.Policies) > 0 {
|
|
t2.Policies = make([]ACLTokenPolicyLink, len(t.Policies))
|
|
copy(t2.Policies, t.Policies)
|
|
}
|
|
if len(t.Roles) > 0 {
|
|
t2.Roles = make([]ACLTokenRoleLink, len(t.Roles))
|
|
copy(t2.Roles, t.Roles)
|
|
}
|
|
if len(t.ServiceIdentities) > 0 {
|
|
t2.ServiceIdentities = make([]*ACLServiceIdentity, len(t.ServiceIdentities))
|
|
for i, s := range t.ServiceIdentities {
|
|
t2.ServiceIdentities[i] = s.Clone()
|
|
}
|
|
}
|
|
if len(t.NodeIdentities) > 0 {
|
|
t2.NodeIdentities = make([]*ACLNodeIdentity, len(t.NodeIdentities))
|
|
for i, n := range t.NodeIdentities {
|
|
t2.NodeIdentities[i] = n.Clone()
|
|
}
|
|
}
|
|
|
|
return &t2
|
|
}
|
|
|
|
func (t *ACLToken) ID() string {
|
|
return t.AccessorID
|
|
}
|
|
|
|
func (t *ACLToken) SecretToken() string {
|
|
return t.SecretID
|
|
}
|
|
|
|
func (t *ACLToken) PolicyIDs() []string {
|
|
if len(t.Policies) == 0 {
|
|
return nil
|
|
}
|
|
|
|
ids := make([]string, 0, len(t.Policies))
|
|
for _, link := range t.Policies {
|
|
ids = append(ids, link.ID)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func (t *ACLToken) RoleIDs() []string {
|
|
if len(t.Roles) == 0 {
|
|
return nil
|
|
}
|
|
|
|
ids := make([]string, 0, len(t.Roles))
|
|
for _, link := range t.Roles {
|
|
ids = append(ids, link.ID)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func (t *ACLToken) ServiceIdentityList() []*ACLServiceIdentity {
|
|
if len(t.ServiceIdentities) == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := make([]*ACLServiceIdentity, 0, len(t.ServiceIdentities))
|
|
for _, s := range t.ServiceIdentities {
|
|
out = append(out, s.Clone())
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (t *ACLToken) IsExpired(asOf time.Time) bool {
|
|
if asOf.IsZero() || !t.HasExpirationTime() {
|
|
return false
|
|
}
|
|
return t.ExpirationTime.Before(asOf)
|
|
}
|
|
|
|
func (t *ACLToken) IsLocal() bool {
|
|
return t.Local
|
|
}
|
|
|
|
func (t *ACLToken) HasExpirationTime() bool {
|
|
return t.ExpirationTime != nil && !t.ExpirationTime.IsZero()
|
|
}
|
|
|
|
func (t *ACLToken) UsesNonLegacyFields() bool {
|
|
return len(t.Policies) > 0 ||
|
|
len(t.ServiceIdentities) > 0 ||
|
|
len(t.NodeIdentities) > 0 ||
|
|
len(t.Roles) > 0 ||
|
|
t.Type == "" ||
|
|
t.HasExpirationTime() ||
|
|
t.ExpirationTTL != 0 ||
|
|
t.AuthMethod != ""
|
|
}
|
|
|
|
func (t *ACLToken) EmbeddedPolicy() *ACLPolicy {
|
|
// DEPRECATED (ACL-Legacy-Compat)
|
|
//
|
|
// For legacy tokens with embedded rules this provides a way to map those
|
|
// rules to an ACLPolicy. This function can just return nil once legacy
|
|
// acl compatibility is no longer needed.
|
|
//
|
|
// Additionally for management tokens we must embed the policy rules
|
|
// as well
|
|
policy := &ACLPolicy{}
|
|
if t.Type == ACLTokenTypeManagement {
|
|
hasher := fnv.New128a()
|
|
policy.ID = fmt.Sprintf("%x", hasher.Sum([]byte(ACLPolicyGlobalManagement)))
|
|
policy.Name = "legacy-management"
|
|
policy.Rules = ACLPolicyGlobalManagement
|
|
policy.Syntax = acl.SyntaxCurrent
|
|
} else if t.Rules != "" || t.Type == ACLTokenTypeClient {
|
|
hasher := fnv.New128a()
|
|
policy.ID = fmt.Sprintf("%x", hasher.Sum([]byte(t.Rules)))
|
|
policy.Name = fmt.Sprintf("legacy-policy-%s", policy.ID)
|
|
policy.Rules = t.Rules
|
|
policy.Syntax = acl.SyntaxLegacy
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
policy.SetHash(true)
|
|
return policy
|
|
}
|
|
|
|
func (t *ACLToken) EnterpriseMetadata() *EnterpriseMeta {
|
|
return &t.EnterpriseMeta
|
|
}
|
|
|
|
func (t *ACLToken) SetHash(force bool) []byte {
|
|
if force || t.Hash == nil {
|
|
// Initialize a 256bit Blake2 hash (32 bytes)
|
|
hash, err := blake2b.New256(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Any non-immutable "content" fields should be involved with the
|
|
// overall hash. The IDs are immutable which is why they aren't here.
|
|
// The raft indices are metadata similar to the hash which is why they
|
|
// aren't incorporated. CreateTime is similarly immutable
|
|
//
|
|
// The Hash is really only used for replication to determine if a token
|
|
// has changed and should be updated locally.
|
|
|
|
// Write all the user set fields
|
|
hash.Write([]byte(t.Description))
|
|
hash.Write([]byte(t.Type))
|
|
hash.Write([]byte(t.Rules))
|
|
|
|
if t.Local {
|
|
hash.Write([]byte("local"))
|
|
} else {
|
|
hash.Write([]byte("global"))
|
|
}
|
|
|
|
for _, link := range t.Policies {
|
|
hash.Write([]byte(link.ID))
|
|
}
|
|
|
|
for _, link := range t.Roles {
|
|
hash.Write([]byte(link.ID))
|
|
}
|
|
|
|
for _, srvid := range t.ServiceIdentities {
|
|
srvid.AddToHash(hash)
|
|
}
|
|
|
|
for _, nodeID := range t.NodeIdentities {
|
|
nodeID.AddToHash(hash)
|
|
}
|
|
|
|
t.EnterpriseMeta.addToHash(hash, false)
|
|
|
|
// Finalize the hash
|
|
hashVal := hash.Sum(nil)
|
|
|
|
// Set and return the hash
|
|
t.Hash = hashVal
|
|
}
|
|
return t.Hash
|
|
}
|
|
|
|
func (t *ACLToken) EstimateSize() int {
|
|
// 41 = 16 (RaftIndex) + 8 (Hash) + 8 (ExpirationTime) + 8 (CreateTime) + 1 (Local)
|
|
size := 41 + len(t.AccessorID) + len(t.SecretID) + len(t.Description) + len(t.Type) + len(t.Rules) + len(t.AuthMethod)
|
|
for _, link := range t.Policies {
|
|
size += len(link.ID) + len(link.Name)
|
|
}
|
|
for _, link := range t.Roles {
|
|
size += len(link.ID) + len(link.Name)
|
|
}
|
|
for _, srvid := range t.ServiceIdentities {
|
|
size += srvid.EstimateSize()
|
|
}
|
|
for _, nodeID := range t.NodeIdentities {
|
|
size += nodeID.EstimateSize()
|
|
}
|
|
return size + t.EnterpriseMeta.estimateSize()
|
|
}
|
|
|
|
// ACLTokens is a slice of ACLTokens.
|
|
type ACLTokens []*ACLToken
|
|
|
|
type ACLTokenListStub struct {
|
|
AccessorID string
|
|
Description string
|
|
Policies []ACLTokenPolicyLink `json:",omitempty"`
|
|
Roles []ACLTokenRoleLink `json:",omitempty"`
|
|
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
|
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
|
Local bool
|
|
AuthMethod string `json:",omitempty"`
|
|
ExpirationTime *time.Time `json:",omitempty"`
|
|
CreateTime time.Time `json:",omitempty"`
|
|
Hash []byte
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
Legacy bool `json:",omitempty"`
|
|
EnterpriseMeta
|
|
}
|
|
|
|
type ACLTokenListStubs []*ACLTokenListStub
|
|
|
|
func (token *ACLToken) Stub() *ACLTokenListStub {
|
|
return &ACLTokenListStub{
|
|
AccessorID: token.AccessorID,
|
|
Description: token.Description,
|
|
Policies: token.Policies,
|
|
Roles: token.Roles,
|
|
ServiceIdentities: token.ServiceIdentities,
|
|
NodeIdentities: token.NodeIdentities,
|
|
Local: token.Local,
|
|
AuthMethod: token.AuthMethod,
|
|
ExpirationTime: token.ExpirationTime,
|
|
CreateTime: token.CreateTime,
|
|
Hash: token.Hash,
|
|
CreateIndex: token.CreateIndex,
|
|
ModifyIndex: token.ModifyIndex,
|
|
Legacy: token.Rules != "",
|
|
EnterpriseMeta: token.EnterpriseMeta,
|
|
}
|
|
}
|
|
|
|
func (tokens ACLTokens) Sort() {
|
|
sort.Slice(tokens, func(i, j int) bool {
|
|
return tokens[i].AccessorID < tokens[j].AccessorID
|
|
})
|
|
}
|
|
|
|
func (tokens ACLTokenListStubs) Sort() {
|
|
sort.Slice(tokens, func(i, j int) bool {
|
|
return tokens[i].AccessorID < tokens[j].AccessorID
|
|
})
|
|
}
|
|
|
|
type ACLPolicy struct {
|
|
// This is the internal UUID associated with the policy
|
|
ID string
|
|
|
|
// Unique name to reference the policy by.
|
|
// - Valid Characters: [a-zA-Z0-9-]
|
|
// - Valid Lengths: 1 - 128
|
|
Name string
|
|
|
|
// Human readable description (Optional)
|
|
Description string
|
|
|
|
// The rule set (using the updated rule syntax)
|
|
Rules string
|
|
|
|
// DEPRECATED (ACL-Legacy-Compat) - This is only needed while we support the legacy ACLs
|
|
Syntax acl.SyntaxVersion `json:"-"`
|
|
|
|
// Datacenters that the policy is valid within.
|
|
// - No wildcards allowed
|
|
// - If empty then the policy is valid within all datacenters
|
|
Datacenters []string `json:",omitempty"`
|
|
|
|
// Hash of the contents of the policy
|
|
// This does not take into account the ID (which is immutable)
|
|
// nor the raft metadata.
|
|
//
|
|
// This is needed mainly for replication purposes. When replicating from
|
|
// one DC to another keeping the content Hash will allow us to avoid
|
|
// unnecessary calls to the authoritative DC
|
|
Hash []byte
|
|
|
|
// Embedded Enterprise ACL Metadata
|
|
EnterpriseMeta `mapstructure:",squash"`
|
|
|
|
// Embedded Raft Metadata
|
|
RaftIndex `hash:"ignore"`
|
|
}
|
|
|
|
func (t *ACLPolicy) UnmarshalJSON(data []byte) error {
|
|
type Alias ACLPolicy
|
|
aux := &struct {
|
|
Hash string
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(t),
|
|
}
|
|
|
|
if err := lib.UnmarshalJSON(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
if aux.Hash != "" {
|
|
t.Hash = []byte(aux.Hash)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *ACLPolicy) Clone() *ACLPolicy {
|
|
p2 := *p
|
|
p2.Datacenters = cloneStringSlice(p.Datacenters)
|
|
return &p2
|
|
}
|
|
|
|
type ACLPolicyListStub struct {
|
|
ID string
|
|
Name string
|
|
Description string
|
|
Datacenters []string
|
|
Hash []byte
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
EnterpriseMeta
|
|
}
|
|
|
|
func (p *ACLPolicy) Stub() *ACLPolicyListStub {
|
|
return &ACLPolicyListStub{
|
|
ID: p.ID,
|
|
Name: p.Name,
|
|
Description: p.Description,
|
|
Datacenters: p.Datacenters,
|
|
Hash: p.Hash,
|
|
CreateIndex: p.CreateIndex,
|
|
ModifyIndex: p.ModifyIndex,
|
|
EnterpriseMeta: p.EnterpriseMeta,
|
|
}
|
|
}
|
|
|
|
type ACLPolicies []*ACLPolicy
|
|
type ACLPolicyListStubs []*ACLPolicyListStub
|
|
|
|
func (p *ACLPolicy) SetHash(force bool) []byte {
|
|
if force || p.Hash == nil {
|
|
// Initialize a 256bit Blake2 hash (32 bytes)
|
|
hash, err := blake2b.New256(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Any non-immutable "content" fields should be involved with the
|
|
// overall hash. The ID is immutable which is why it isn't here. The
|
|
// raft indices are metadata similar to the hash which is why they
|
|
// aren't incorporated. CreateTime is similarly immutable
|
|
//
|
|
// The Hash is really only used for replication to determine if a policy
|
|
// has changed and should be updated locally.
|
|
|
|
// Write all the user set fields
|
|
hash.Write([]byte(p.Name))
|
|
hash.Write([]byte(p.Description))
|
|
hash.Write([]byte(p.Rules))
|
|
for _, dc := range p.Datacenters {
|
|
hash.Write([]byte(dc))
|
|
}
|
|
|
|
p.EnterpriseMeta.addToHash(hash, false)
|
|
|
|
// Finalize the hash
|
|
hashVal := hash.Sum(nil)
|
|
|
|
// Set and return the hash
|
|
p.Hash = hashVal
|
|
}
|
|
return p.Hash
|
|
}
|
|
|
|
func (p *ACLPolicy) EstimateSize() int {
|
|
// This is just an estimate. There is other data structure overhead
|
|
// pointers etc that this does not account for.
|
|
|
|
// 64 = 36 (uuid) + 16 (RaftIndex) + 8 (Hash) + 4 (Syntax)
|
|
size := 64 + len(p.Name) + len(p.Description) + len(p.Rules)
|
|
for _, dc := range p.Datacenters {
|
|
size += len(dc)
|
|
}
|
|
|
|
return size + p.EnterpriseMeta.estimateSize()
|
|
}
|
|
|
|
// HashKey returns a consistent hash for a set of policies.
|
|
func (policies ACLPolicies) HashKey() string {
|
|
cacheKeyHash, err := blake2b.New256(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for _, policy := range policies {
|
|
cacheKeyHash.Write([]byte(policy.ID))
|
|
// including the modify index prevents a policy set from being
|
|
// cached if one of the policies has changed
|
|
binary.Write(cacheKeyHash, binary.BigEndian, policy.ModifyIndex)
|
|
}
|
|
return fmt.Sprintf("%x", cacheKeyHash.Sum(nil))
|
|
}
|
|
|
|
func (policies ACLPolicies) Sort() {
|
|
sort.Slice(policies, func(i, j int) bool {
|
|
return policies[i].ID < policies[j].ID
|
|
})
|
|
}
|
|
|
|
func (policies ACLPolicyListStubs) Sort() {
|
|
sort.Slice(policies, func(i, j int) bool {
|
|
return policies[i].ID < policies[j].ID
|
|
})
|
|
}
|
|
|
|
func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, entConf *acl.Config) ([]*acl.Policy, error) {
|
|
// Parse the policies
|
|
parsed := make([]*acl.Policy, 0, len(policies))
|
|
for _, policy := range policies {
|
|
policy.SetHash(false)
|
|
cacheKey := fmt.Sprintf("%x", policy.Hash)
|
|
cachedPolicy := cache.GetParsedPolicy(cacheKey)
|
|
if cachedPolicy != nil {
|
|
// policies are content hashed so no need to check the age
|
|
parsed = append(parsed, cachedPolicy.Policy)
|
|
continue
|
|
}
|
|
|
|
p, err := acl.NewPolicyFromSource(policy.ID, policy.ModifyIndex, policy.Rules, policy.Syntax, entConf, policy.EnterprisePolicyMeta())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse %q: %v", policy.Name, err)
|
|
}
|
|
|
|
cache.PutParsedPolicy(cacheKey, p)
|
|
parsed = append(parsed, p)
|
|
}
|
|
|
|
return parsed, nil
|
|
}
|
|
|
|
func (policies ACLPolicies) Compile(cache *ACLCaches, entConf *acl.Config) (acl.Authorizer, error) {
|
|
// Determine the cache key
|
|
cacheKey := policies.HashKey()
|
|
entry := cache.GetAuthorizer(cacheKey)
|
|
if entry != nil {
|
|
// the hash key takes into account the policy contents. There is no reason to expire this cache or check its age.
|
|
return entry.Authorizer, nil
|
|
}
|
|
|
|
parsed, err := policies.resolveWithCache(cache, entConf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse the ACL policies: %v", err)
|
|
}
|
|
|
|
// Create the ACL object
|
|
authorizer, err := acl.NewPolicyAuthorizer(parsed, entConf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to construct ACL Authorizer: %v", err)
|
|
}
|
|
|
|
// Update the cache
|
|
cache.PutAuthorizer(cacheKey, authorizer)
|
|
return authorizer, nil
|
|
}
|
|
|
|
func (policies ACLPolicies) Merge(cache *ACLCaches, entConf *acl.Config) (*acl.Policy, error) {
|
|
parsed, err := policies.resolveWithCache(cache, entConf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return acl.MergePolicies(parsed), nil
|
|
}
|
|
|
|
type ACLRoles []*ACLRole
|
|
|
|
// HashKey returns a consistent hash for a set of roles.
|
|
func (roles ACLRoles) HashKey() string {
|
|
cacheKeyHash, err := blake2b.New256(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for _, role := range roles {
|
|
cacheKeyHash.Write([]byte(role.ID))
|
|
// including the modify index prevents a role set from being
|
|
// cached if one of the roles has changed
|
|
binary.Write(cacheKeyHash, binary.BigEndian, role.ModifyIndex)
|
|
}
|
|
return fmt.Sprintf("%x", cacheKeyHash.Sum(nil))
|
|
}
|
|
|
|
func (roles ACLRoles) Sort() {
|
|
sort.Slice(roles, func(i, j int) bool {
|
|
return roles[i].ID < roles[j].ID
|
|
})
|
|
}
|
|
|
|
type ACLRolePolicyLink struct {
|
|
ID string
|
|
Name string `hash:"ignore"`
|
|
}
|
|
|
|
type ACLRole struct {
|
|
// ID is the internal UUID associated with the role
|
|
ID string
|
|
|
|
// Name is the unique name to reference the role by.
|
|
Name string
|
|
|
|
// Description is a human readable description (Optional)
|
|
Description string
|
|
|
|
// List of policy links.
|
|
// Note this is the list of IDs and not the names. Prior to role creation
|
|
// the list of policy names gets validated and the policy IDs get stored herein
|
|
Policies []ACLRolePolicyLink `json:",omitempty"`
|
|
|
|
// List of services to generate synthetic policies for.
|
|
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
|
|
|
// List of nodes to generate synthetic policies for.
|
|
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
|
|
|
// Hash of the contents of the role
|
|
// This does not take into account the ID (which is immutable)
|
|
// nor the raft metadata.
|
|
//
|
|
// This is needed mainly for replication purposes. When replicating from
|
|
// one DC to another keeping the content Hash will allow us to avoid
|
|
// unnecessary calls to the authoritative DC
|
|
Hash []byte
|
|
|
|
// Embedded Enterprise ACL metadata
|
|
EnterpriseMeta `mapstructure:",squash"`
|
|
|
|
// Embedded Raft Metadata
|
|
RaftIndex `hash:"ignore"`
|
|
}
|
|
|
|
func (t *ACLRole) UnmarshalJSON(data []byte) error {
|
|
type Alias ACLRole
|
|
aux := &struct {
|
|
Hash string
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(t),
|
|
}
|
|
|
|
if err := lib.UnmarshalJSON(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
if aux.Hash != "" {
|
|
t.Hash = []byte(aux.Hash)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *ACLRole) Clone() *ACLRole {
|
|
r2 := *r
|
|
r2.Policies = nil
|
|
r2.ServiceIdentities = nil
|
|
r2.NodeIdentities = nil
|
|
|
|
if len(r.Policies) > 0 {
|
|
r2.Policies = make([]ACLRolePolicyLink, len(r.Policies))
|
|
copy(r2.Policies, r.Policies)
|
|
}
|
|
if len(r.ServiceIdentities) > 0 {
|
|
r2.ServiceIdentities = make([]*ACLServiceIdentity, len(r.ServiceIdentities))
|
|
for i, s := range r.ServiceIdentities {
|
|
r2.ServiceIdentities[i] = s.Clone()
|
|
}
|
|
}
|
|
if len(r.NodeIdentities) > 0 {
|
|
r2.NodeIdentities = make([]*ACLNodeIdentity, len(r.NodeIdentities))
|
|
for i, n := range r.NodeIdentities {
|
|
r2.NodeIdentities[i] = n.Clone()
|
|
}
|
|
}
|
|
return &r2
|
|
}
|
|
|
|
func (r *ACLRole) SetHash(force bool) []byte {
|
|
if force || r.Hash == nil {
|
|
// Initialize a 256bit Blake2 hash (32 bytes)
|
|
hash, err := blake2b.New256(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Any non-immutable "content" fields should be involved with the
|
|
// overall hash. The ID is immutable which is why it isn't here. The
|
|
// raft indices are metadata similar to the hash which is why they
|
|
// aren't incorporated. CreateTime is similarly immutable
|
|
//
|
|
// The Hash is really only used for replication to determine if a role
|
|
// has changed and should be updated locally.
|
|
|
|
// Write all the user set fields
|
|
hash.Write([]byte(r.Name))
|
|
hash.Write([]byte(r.Description))
|
|
for _, link := range r.Policies {
|
|
hash.Write([]byte(link.ID))
|
|
}
|
|
for _, srvid := range r.ServiceIdentities {
|
|
srvid.AddToHash(hash)
|
|
}
|
|
for _, nodeID := range r.NodeIdentities {
|
|
nodeID.AddToHash(hash)
|
|
}
|
|
|
|
r.EnterpriseMeta.addToHash(hash, false)
|
|
|
|
// Finalize the hash
|
|
hashVal := hash.Sum(nil)
|
|
|
|
// Set and return the hash
|
|
r.Hash = hashVal
|
|
}
|
|
return r.Hash
|
|
}
|
|
|
|
func (r *ACLRole) EstimateSize() int {
|
|
// This is just an estimate. There is other data structure overhead
|
|
// pointers etc that this does not account for.
|
|
|
|
// 60 = 36 (uuid) + 16 (RaftIndex) + 8 (Hash)
|
|
size := 60 + len(r.Name) + len(r.Description)
|
|
for _, link := range r.Policies {
|
|
size += len(link.ID) + len(link.Name)
|
|
}
|
|
for _, srvid := range r.ServiceIdentities {
|
|
size += srvid.EstimateSize()
|
|
}
|
|
for _, nodeID := range r.NodeIdentities {
|
|
size += nodeID.EstimateSize()
|
|
}
|
|
|
|
return size + r.EnterpriseMeta.estimateSize()
|
|
}
|
|
|
|
const (
|
|
// BindingRuleBindTypeService is the binding rule bind type that
|
|
// assigns a Service Identity to the token that is created using the value
|
|
// of the computed BindName as the ServiceName like:
|
|
//
|
|
// &ACLToken{
|
|
// ...other fields...
|
|
// ServiceIdentities: []*ACLServiceIdentity{
|
|
// &ACLServiceIdentity{
|
|
// ServiceName: "<computed BindName>",
|
|
// },
|
|
// },
|
|
// }
|
|
BindingRuleBindTypeService = "service"
|
|
|
|
// BindingRuleBindTypeRole is the binding rule bind type that only allows
|
|
// the binding rule to function if a role with the given name (BindName)
|
|
// exists at login-time. If it does the token that is created is directly
|
|
// linked to that role like:
|
|
//
|
|
// &ACLToken{
|
|
// ...other fields...
|
|
// Roles: []ACLTokenRoleLink{
|
|
// { Name: "<computed BindName>" }
|
|
// }
|
|
// }
|
|
//
|
|
// If it does not exist at login-time the rule is ignored.
|
|
BindingRuleBindTypeRole = "role"
|
|
|
|
// BindingRuleBindTypeNode is the binding rule bind type that assigns
|
|
// a Node Identity to the token that is created using the value of
|
|
// the computed BindName as the NodeName like:
|
|
//
|
|
// &ACLToken{
|
|
// ...other fields...
|
|
// NodeIdentities: []*ACLNodeIdentity{
|
|
// &ACLNodeIdentity{
|
|
// NodeName: "<computed BindName>",
|
|
// Datacenter: "<local datacenter of the binding rule>"
|
|
// }
|
|
// }
|
|
// }
|
|
BindingRuleBindTypeNode = "node"
|
|
)
|
|
|
|
type ACLBindingRule struct {
|
|
// ID is the internal UUID associated with the binding rule
|
|
ID string
|
|
|
|
// Description is a human readable description (Optional)
|
|
Description string
|
|
|
|
// AuthMethod is the name of the auth method for which this rule applies.
|
|
AuthMethod string
|
|
|
|
// Selector is an expression that matches against verified identity
|
|
// attributes returned from the auth method during login.
|
|
Selector string
|
|
|
|
// BindType adjusts how this binding rule is applied at login time. The
|
|
// valid values are:
|
|
//
|
|
// - BindingRuleBindTypeService = "service"
|
|
// - BindingRuleBindTypeRole = "role"
|
|
BindType string
|
|
|
|
// BindName is the target of the binding. Can be lightly templated using
|
|
// HIL ${foo} syntax from available field names. How it is used depends
|
|
// upon the BindType.
|
|
BindName string
|
|
|
|
// Embedded Enterprise ACL metadata
|
|
EnterpriseMeta `mapstructure:",squash"`
|
|
|
|
// Embedded Raft Metadata
|
|
RaftIndex `hash:"ignore"`
|
|
}
|
|
|
|
func (r *ACLBindingRule) Clone() *ACLBindingRule {
|
|
r2 := *r
|
|
return &r2
|
|
}
|
|
|
|
type ACLBindingRules []*ACLBindingRule
|
|
|
|
func (rules ACLBindingRules) Sort() {
|
|
sort.Slice(rules, func(i, j int) bool {
|
|
return rules[i].ID < rules[j].ID
|
|
})
|
|
}
|
|
|
|
type ACLAuthMethodListStub struct {
|
|
Name string
|
|
Type string
|
|
DisplayName string `json:",omitempty"`
|
|
Description string `json:",omitempty"`
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
EnterpriseMeta
|
|
}
|
|
|
|
func (p *ACLAuthMethod) Stub() *ACLAuthMethodListStub {
|
|
return &ACLAuthMethodListStub{
|
|
Name: p.Name,
|
|
Type: p.Type,
|
|
DisplayName: p.DisplayName,
|
|
Description: p.Description,
|
|
CreateIndex: p.CreateIndex,
|
|
ModifyIndex: p.ModifyIndex,
|
|
EnterpriseMeta: p.EnterpriseMeta,
|
|
}
|
|
}
|
|
|
|
type ACLAuthMethods []*ACLAuthMethod
|
|
type ACLAuthMethodListStubs []*ACLAuthMethodListStub
|
|
|
|
func (methods ACLAuthMethods) Sort() {
|
|
sort.Slice(methods, func(i, j int) bool {
|
|
return methods[i].Name < methods[j].Name
|
|
})
|
|
}
|
|
|
|
func (methods ACLAuthMethodListStubs) Sort() {
|
|
sort.Slice(methods, func(i, j int) bool {
|
|
return methods[i].Name < methods[j].Name
|
|
})
|
|
}
|
|
|
|
type ACLAuthMethod struct {
|
|
// Name is a unique identifier for this specific auth method.
|
|
//
|
|
// Immutable once set and only settable during create.
|
|
Name string
|
|
|
|
// Type is the type of the auth method this is.
|
|
//
|
|
// Immutable once set and only settable during create.
|
|
Type string
|
|
|
|
// DisplayName is an optional name to use instead of the Name field when
|
|
// displaying information about this auth method in any kind of user
|
|
// interface.
|
|
DisplayName string `json:",omitempty"`
|
|
|
|
// Description is just an optional bunch of explanatory text.
|
|
Description string `json:",omitempty"`
|
|
|
|
// MaxTokenTTL this is the maximum life of a token created by this method.
|
|
MaxTokenTTL time.Duration `json:",omitempty"`
|
|
|
|
// TokenLocality defines the kind of token that this auth method produces.
|
|
// This can be either 'local' or 'global'. If empty 'local' is assumed.
|
|
TokenLocality string `json:",omitempty"`
|
|
|
|
// Configuration is arbitrary configuration for the auth method. This
|
|
// should only contain primitive values and containers (such as lists and
|
|
// maps).
|
|
Config map[string]interface{}
|
|
|
|
// Embedded Enterprise ACL Meta
|
|
EnterpriseMeta `mapstructure:",squash"`
|
|
|
|
ACLAuthMethodEnterpriseFields `mapstructure:",squash"`
|
|
|
|
// Embedded Raft Metadata
|
|
RaftIndex `hash:"ignore"`
|
|
}
|
|
|
|
func (m *ACLAuthMethod) MarshalJSON() ([]byte, error) {
|
|
type Alias ACLAuthMethod
|
|
exported := &struct {
|
|
MaxTokenTTL string `json:",omitempty"`
|
|
*Alias
|
|
}{
|
|
MaxTokenTTL: m.MaxTokenTTL.String(),
|
|
Alias: (*Alias)(m),
|
|
}
|
|
if m.MaxTokenTTL == 0 {
|
|
exported.MaxTokenTTL = ""
|
|
}
|
|
|
|
return json.Marshal(exported)
|
|
}
|
|
|
|
func (m *ACLAuthMethod) UnmarshalJSON(data []byte) (err error) {
|
|
type Alias ACLAuthMethod
|
|
aux := &struct {
|
|
MaxTokenTTL interface{}
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(m),
|
|
}
|
|
if err = lib.UnmarshalJSON(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
if aux.MaxTokenTTL != nil {
|
|
switch v := aux.MaxTokenTTL.(type) {
|
|
case string:
|
|
if m.MaxTokenTTL, err = time.ParseDuration(v); err != nil {
|
|
return err
|
|
}
|
|
case float64:
|
|
m.MaxTokenTTL = time.Duration(v)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type ACLReplicationType string
|
|
|
|
const (
|
|
ACLReplicateLegacy ACLReplicationType = "legacy"
|
|
ACLReplicatePolicies ACLReplicationType = "policies"
|
|
ACLReplicateRoles ACLReplicationType = "roles"
|
|
ACLReplicateTokens ACLReplicationType = "tokens"
|
|
)
|
|
|
|
func (t ACLReplicationType) SingularNoun() string {
|
|
switch t {
|
|
case ACLReplicateLegacy:
|
|
return "legacy"
|
|
case ACLReplicatePolicies:
|
|
return "policy"
|
|
case ACLReplicateRoles:
|
|
return "role"
|
|
case ACLReplicateTokens:
|
|
return "token"
|
|
default:
|
|
return "<UNKNOWN>"
|
|
}
|
|
}
|
|
|
|
// ACLReplicationStatus provides information about the health of the ACL
|
|
// replication system.
|
|
type ACLReplicationStatus struct {
|
|
Enabled bool
|
|
Running bool
|
|
SourceDatacenter string
|
|
ReplicationType ACLReplicationType
|
|
ReplicatedIndex uint64
|
|
ReplicatedRoleIndex uint64
|
|
ReplicatedTokenIndex uint64
|
|
LastSuccess time.Time
|
|
LastError time.Time
|
|
}
|
|
|
|
// ACLTokenSetRequest is used for token creation and update operations
|
|
// at the RPC layer
|
|
type ACLTokenSetRequest struct {
|
|
ACLToken ACLToken // Token to manipulate - I really dislike this name but "Token" is taken in the WriteRequest
|
|
Create bool // Used to explicitly mark this request as a creation
|
|
Datacenter string // The datacenter to perform the request within
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLTokenSetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLTokenGetRequest is used for token read operations at the RPC layer
|
|
type ACLTokenGetRequest struct {
|
|
TokenID string // id used for the token lookup
|
|
TokenIDType ACLTokenIDType // The Type of ID used to lookup the token
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLTokenGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLTokenDeleteRequest is used for token deletion operations at the RPC layer
|
|
type ACLTokenDeleteRequest struct {
|
|
TokenID string // ID of the token to delete
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLTokenDeleteRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLTokenListRequest is used for token listing operations at the RPC layer
|
|
type ACLTokenListRequest struct {
|
|
IncludeLocal bool // Whether local tokens should be included
|
|
IncludeGlobal bool // Whether global tokens should be included
|
|
Policy string // Policy filter
|
|
Role string // Role filter
|
|
AuthMethod string // Auth Method filter
|
|
Datacenter string // The datacenter to perform the request within
|
|
ACLAuthMethodEnterpriseMeta
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLTokenListRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLTokenListResponse is used to return the secret data free stubs
|
|
// of the tokens
|
|
type ACLTokenListResponse struct {
|
|
Tokens ACLTokenListStubs
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLTokenBatchGetRequest is used for reading multiple tokens, this is
|
|
// different from the the token list request in that only tokens with the
|
|
// the requested ids are returned
|
|
type ACLTokenBatchGetRequest struct {
|
|
AccessorIDs []string // List of accessor ids to fetch
|
|
Datacenter string // The datacenter to perform the request within
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLTokenBatchGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLTokenBatchSetRequest is used only at the Raft layer
|
|
// for batching multiple token creation/update operations
|
|
//
|
|
// This is particularly useful during token replication and during
|
|
// automatic legacy token upgrades.
|
|
type ACLTokenBatchSetRequest struct {
|
|
Tokens ACLTokens
|
|
CAS bool
|
|
AllowMissingLinks bool
|
|
ProhibitUnprivileged bool
|
|
}
|
|
|
|
// ACLTokenBatchDeleteRequest is used only at the Raft layer
|
|
// for batching multiple token deletions.
|
|
//
|
|
// This is particularly useful during token replication when
|
|
// multiple tokens need to be removed from the local DCs state.
|
|
type ACLTokenBatchDeleteRequest struct {
|
|
TokenIDs []string // Tokens to delete
|
|
}
|
|
|
|
// ACLTokenBootstrapRequest is used only at the Raft layer
|
|
// for ACL bootstrapping
|
|
//
|
|
// The RPC layer will use a generic DCSpecificRequest to indicate
|
|
// that bootstrapping must be performed but the actual token
|
|
// and the resetIndex will be generated by that RPC endpoint
|
|
type ACLTokenBootstrapRequest struct {
|
|
Token ACLToken // Token to use for bootstrapping
|
|
ResetIndex uint64 // Reset index
|
|
}
|
|
|
|
// ACLTokenResponse returns a single Token + metadata
|
|
type ACLTokenResponse struct {
|
|
Token *ACLToken
|
|
Redacted bool // whether the token's secret was redacted
|
|
SourceDatacenter string
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLTokenBatchResponse returns multiple Tokens associated with the same metadata
|
|
type ACLTokenBatchResponse struct {
|
|
Tokens []*ACLToken
|
|
Redacted bool // whether the token secrets were redacted.
|
|
Removed bool // whether any tokens were completely removed
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLPolicySetRequest is used at the RPC layer for creation and update requests
|
|
type ACLPolicySetRequest struct {
|
|
Policy ACLPolicy // The policy to upsert
|
|
Datacenter string // The datacenter to perform the request within
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLPolicySetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLPolicyDeleteRequest is used at the RPC layer deletion requests
|
|
type ACLPolicyDeleteRequest struct {
|
|
PolicyID string // The id of the policy to delete
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLPolicyDeleteRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLPolicyGetRequest is used at the RPC layer to perform policy read operations
|
|
type ACLPolicyGetRequest struct {
|
|
PolicyID string // id used for the policy lookup (one of PolicyID or PolicyName is allowed)
|
|
PolicyName string // name used for the policy lookup (one of PolicyID or PolicyName is allowed)
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLPolicyGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLPolicyListRequest is used at the RPC layer to request a listing of policies
|
|
type ACLPolicyListRequest struct {
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLPolicyListRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
type ACLPolicyListResponse struct {
|
|
Policies ACLPolicyListStubs
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLPolicyBatchGetRequest is used at the RPC layer to request a subset of
|
|
// the policies associated with the token used for retrieval
|
|
type ACLPolicyBatchGetRequest struct {
|
|
PolicyIDs []string // List of policy ids to fetch
|
|
Datacenter string // The datacenter to perform the request within
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLPolicyBatchGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLPolicyResponse returns a single policy + metadata
|
|
type ACLPolicyResponse struct {
|
|
Policy *ACLPolicy
|
|
QueryMeta
|
|
}
|
|
|
|
type ACLPolicyBatchResponse struct {
|
|
Policies []*ACLPolicy
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLPolicyBatchSetRequest is used at the Raft layer for batching
|
|
// multiple policy creations and updates
|
|
//
|
|
// This is particularly useful during replication
|
|
type ACLPolicyBatchSetRequest struct {
|
|
Policies ACLPolicies
|
|
}
|
|
|
|
// ACLPolicyBatchDeleteRequest is used at the Raft layer for batching
|
|
// multiple policy deletions
|
|
//
|
|
// This is particularly useful during replication
|
|
type ACLPolicyBatchDeleteRequest struct {
|
|
PolicyIDs []string
|
|
}
|
|
|
|
func cloneStringSlice(s []string) []string {
|
|
if len(s) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]string, len(s))
|
|
copy(out, s)
|
|
return out
|
|
}
|
|
|
|
// ACLRoleSetRequest is used at the RPC layer for creation and update requests
|
|
type ACLRoleSetRequest struct {
|
|
Role ACLRole // The role to upsert
|
|
Datacenter string // The datacenter to perform the request within
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLRoleSetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLRoleDeleteRequest is used at the RPC layer deletion requests
|
|
type ACLRoleDeleteRequest struct {
|
|
RoleID string // id of the role to delete
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLRoleDeleteRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLRoleGetRequest is used at the RPC layer to perform role read operations
|
|
type ACLRoleGetRequest struct {
|
|
RoleID string // id used for the role lookup (one of RoleID or RoleName is allowed)
|
|
RoleName string // name used for the role lookup (one of RoleID or RoleName is allowed)
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLRoleGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLRoleListRequest is used at the RPC layer to request a listing of roles
|
|
type ACLRoleListRequest struct {
|
|
Policy string // Policy filter
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLRoleListRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
type ACLRoleListResponse struct {
|
|
Roles ACLRoles
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLRoleBatchGetRequest is used at the RPC layer to request a subset of
|
|
// the roles associated with the token used for retrieval
|
|
type ACLRoleBatchGetRequest struct {
|
|
RoleIDs []string // List of role ids to fetch
|
|
Datacenter string // The datacenter to perform the request within
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLRoleBatchGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLRoleResponse returns a single role + metadata
|
|
type ACLRoleResponse struct {
|
|
Role *ACLRole
|
|
QueryMeta
|
|
}
|
|
|
|
type ACLRoleBatchResponse struct {
|
|
Roles []*ACLRole
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLRoleBatchSetRequest is used at the Raft layer for batching
|
|
// multiple role creations and updates
|
|
//
|
|
// This is particularly useful during replication
|
|
type ACLRoleBatchSetRequest struct {
|
|
Roles ACLRoles
|
|
AllowMissingLinks bool
|
|
}
|
|
|
|
// ACLRoleBatchDeleteRequest is used at the Raft layer for batching
|
|
// multiple role deletions
|
|
//
|
|
// This is particularly useful during replication
|
|
type ACLRoleBatchDeleteRequest struct {
|
|
RoleIDs []string
|
|
}
|
|
|
|
// ACLBindingRuleSetRequest is used at the RPC layer for creation and update requests
|
|
type ACLBindingRuleSetRequest struct {
|
|
BindingRule ACLBindingRule // The rule to upsert
|
|
Datacenter string // The datacenter to perform the request within
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLBindingRuleSetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLBindingRuleDeleteRequest is used at the RPC layer deletion requests
|
|
type ACLBindingRuleDeleteRequest struct {
|
|
BindingRuleID string // id of the rule to delete
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLBindingRuleDeleteRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLBindingRuleGetRequest is used at the RPC layer to perform rule read operations
|
|
type ACLBindingRuleGetRequest struct {
|
|
BindingRuleID string // id used for the rule lookup
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLBindingRuleGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLBindingRuleListRequest is used at the RPC layer to request a listing of rules
|
|
type ACLBindingRuleListRequest struct {
|
|
AuthMethod string // optional filter
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLBindingRuleListRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
type ACLBindingRuleListResponse struct {
|
|
BindingRules ACLBindingRules
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLBindingRuleResponse returns a single binding + metadata
|
|
type ACLBindingRuleResponse struct {
|
|
BindingRule *ACLBindingRule
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLBindingRuleBatchSetRequest is used at the Raft layer for batching
|
|
// multiple rule creations and updates
|
|
type ACLBindingRuleBatchSetRequest struct {
|
|
BindingRules ACLBindingRules
|
|
}
|
|
|
|
// ACLBindingRuleBatchDeleteRequest is used at the Raft layer for batching
|
|
// multiple rule deletions
|
|
type ACLBindingRuleBatchDeleteRequest struct {
|
|
BindingRuleIDs []string
|
|
}
|
|
|
|
// ACLAuthMethodSetRequest is used at the RPC layer for creation and update requests
|
|
type ACLAuthMethodSetRequest struct {
|
|
AuthMethod ACLAuthMethod // The auth method to upsert
|
|
Datacenter string // The datacenter to perform the request within
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLAuthMethodSetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLAuthMethodDeleteRequest is used at the RPC layer deletion requests
|
|
type ACLAuthMethodDeleteRequest struct {
|
|
AuthMethodName string // name of the auth method to delete
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLAuthMethodDeleteRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLAuthMethodGetRequest is used at the RPC layer to perform rule read operations
|
|
type ACLAuthMethodGetRequest struct {
|
|
AuthMethodName string // name used for the auth method lookup
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLAuthMethodGetRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
// ACLAuthMethodListRequest is used at the RPC layer to request a listing of auth methods
|
|
type ACLAuthMethodListRequest struct {
|
|
Datacenter string // The datacenter to perform the request within
|
|
EnterpriseMeta
|
|
QueryOptions
|
|
}
|
|
|
|
func (r *ACLAuthMethodListRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
type ACLAuthMethodListResponse struct {
|
|
AuthMethods ACLAuthMethodListStubs
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLAuthMethodResponse returns a single auth method + metadata
|
|
type ACLAuthMethodResponse struct {
|
|
AuthMethod *ACLAuthMethod
|
|
QueryMeta
|
|
}
|
|
|
|
// ACLAuthMethodBatchSetRequest is used at the Raft layer for batching
|
|
// multiple auth method creations and updates
|
|
type ACLAuthMethodBatchSetRequest struct {
|
|
AuthMethods ACLAuthMethods
|
|
}
|
|
|
|
// ACLAuthMethodBatchDeleteRequest is used at the Raft layer for batching
|
|
// multiple auth method deletions
|
|
type ACLAuthMethodBatchDeleteRequest struct {
|
|
AuthMethodNames []string
|
|
// While it may seem odd that AuthMethodNames is associated with a single
|
|
// EnterpriseMeta, it is okay as this struct is only ever used to
|
|
// delete a single entry. This is because AuthMethods unlike tokens, policies
|
|
// and roles are not replicated between datacenters and therefore never
|
|
// batch applied.
|
|
EnterpriseMeta
|
|
}
|
|
|
|
type ACLLoginParams struct {
|
|
AuthMethod string
|
|
BearerToken string
|
|
Meta map[string]string `json:",omitempty"`
|
|
EnterpriseMeta
|
|
}
|
|
|
|
type ACLLoginRequest struct {
|
|
Auth *ACLLoginParams
|
|
Datacenter string // The datacenter to perform the request within
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLLoginRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
type ACLLogoutRequest struct {
|
|
Datacenter string // The datacenter to perform the request within
|
|
WriteRequest
|
|
}
|
|
|
|
func (r *ACLLogoutRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
type RemoteACLAuthorizationRequest struct {
|
|
Datacenter string
|
|
Requests []ACLAuthorizationRequest
|
|
QueryOptions
|
|
}
|
|
|
|
type ACLAuthorizationRequest struct {
|
|
Resource acl.Resource
|
|
Segment string `json:",omitempty"`
|
|
Access string
|
|
EnterpriseMeta
|
|
}
|
|
|
|
type ACLAuthorizationResponse struct {
|
|
ACLAuthorizationRequest
|
|
Allow bool
|
|
}
|
|
|
|
func (r *RemoteACLAuthorizationRequest) RequestDatacenter() string {
|
|
return r.Datacenter
|
|
}
|
|
|
|
func CreateACLAuthorizationResponses(authz acl.Authorizer, requests []ACLAuthorizationRequest) ([]ACLAuthorizationResponse, error) {
|
|
responses := make([]ACLAuthorizationResponse, len(requests))
|
|
var ctx acl.AuthorizerContext
|
|
|
|
for idx, req := range requests {
|
|
req.FillAuthzContext(&ctx)
|
|
decision, err := acl.Enforce(authz, req.Resource, req.Segment, req.Access, &ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
responses[idx].ACLAuthorizationRequest = req
|
|
responses[idx].Allow = decision == acl.Allow
|
|
}
|
|
|
|
return responses, nil
|
|
}
|