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: "", // }, // }, // } 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: "" } // } // } // // 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: "", // Datacenter: "" // } // } // } 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 "" } } // 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 FromReplication 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 }