mirror of https://github.com/hashicorp/consul
153 lines
4.9 KiB
Go
153 lines
4.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package acl
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// These error constants define the standard ACL error types. The values
|
|
// must not be changed since the error values are sent via RPC calls
|
|
// from older clients and may not have the correct type.
|
|
const (
|
|
errNotFound = "ACL not found"
|
|
errRootDenied = "Cannot resolve root ACL"
|
|
errDisabled = "ACL support disabled"
|
|
errPermissionDenied = "Permission denied"
|
|
errInvalidParent = "Invalid Parent"
|
|
)
|
|
|
|
var (
|
|
// ErrNotFound indicates there is no matching ACL.
|
|
ErrNotFound = errors.New(errNotFound)
|
|
|
|
// ErrRootDenied is returned when attempting to resolve a root ACL.
|
|
ErrRootDenied = errors.New(errRootDenied)
|
|
|
|
// ErrDisabled is returned when ACL changes are not permitted since
|
|
// they are disabled.
|
|
ErrDisabled = errors.New(errDisabled)
|
|
|
|
// ErrPermissionDenied is returned when an ACL based rejection
|
|
// happens.
|
|
ErrPermissionDenied = PermissionDeniedError{}
|
|
|
|
// ErrInvalidParent is returned when a remotely resolve ACL
|
|
// token claims to have a non-root parent
|
|
ErrInvalidParent = errors.New(errInvalidParent)
|
|
)
|
|
|
|
// IsErrNotFound checks if the given error message is comparable to
|
|
// ErrNotFound.
|
|
func IsErrNotFound(err error) bool {
|
|
return err != nil && strings.Contains(err.Error(), errNotFound)
|
|
}
|
|
|
|
// IsErrRootDenied checks if the given error message is comparable to
|
|
// ErrRootDenied.
|
|
func IsErrRootDenied(err error) bool {
|
|
return err != nil && strings.Contains(err.Error(), errRootDenied)
|
|
}
|
|
|
|
// IsErrDisabled checks if the given error message is comparable to
|
|
// ErrDisabled.
|
|
func IsErrDisabled(err error) bool {
|
|
return err != nil && strings.Contains(err.Error(), errDisabled)
|
|
}
|
|
|
|
// IsErrPermissionDenied checks if the given error message is comparable
|
|
// to ErrPermissionDenied.
|
|
func IsErrPermissionDenied(err error) bool {
|
|
return err != nil && strings.Contains(err.Error(), errPermissionDenied)
|
|
}
|
|
|
|
// Arguably this should be some sort of union type.
|
|
// The usage of Cause and the rest of the fields is entirely disjoint.
|
|
type PermissionDeniedError struct {
|
|
Cause string
|
|
|
|
// Accessor contains information on the accessor used e.g. "token <GUID>"
|
|
Accessor string
|
|
// Resource (e.g. Service)
|
|
Resource Resource
|
|
// Access level (e.g. Read)
|
|
AccessLevel AccessLevel
|
|
// e.g. "sidecar-proxy-1"
|
|
ResourceID ResourceDescriptor
|
|
}
|
|
|
|
// Initially we may not have attribution information; that will become more complete as we work this change through
|
|
// There are generally three classes of errors
|
|
// 1) Named entities without a context
|
|
// 2) Unnamed entities with a context
|
|
// 3) Completely context free checks (global permissions)
|
|
// 4) Errors that only have a cause (for example bad token)
|
|
func (e PermissionDeniedError) Error() string {
|
|
var message strings.Builder
|
|
message.WriteString(errPermissionDenied)
|
|
|
|
// Type 4)
|
|
if e.Cause != "" {
|
|
fmt.Fprintf(&message, ": %s", e.Cause)
|
|
return message.String()
|
|
}
|
|
// Should only be empty when default struct is used.
|
|
if e.Resource == "" {
|
|
return message.String()
|
|
}
|
|
|
|
if e.Accessor == AnonymousTokenID {
|
|
message.WriteString(": anonymous token")
|
|
} else {
|
|
fmt.Fprintf(&message, ": token with AccessorID '%s'", e.Accessor)
|
|
}
|
|
|
|
fmt.Fprintf(&message, " lacks permission '%s:%s'", e.Resource, e.AccessLevel.String())
|
|
|
|
if e.ResourceID.Name != "" {
|
|
fmt.Fprintf(&message, " on %s", e.ResourceID.ToString())
|
|
}
|
|
|
|
if e.Accessor == AnonymousTokenID {
|
|
message.WriteString(". The anonymous token is used implicitly when a request does not specify a token.")
|
|
}
|
|
return message.String()
|
|
}
|
|
|
|
func PermissionDenied(msg string, args ...interface{}) PermissionDeniedError {
|
|
cause := fmt.Sprintf(msg, args...)
|
|
return PermissionDeniedError{Cause: cause}
|
|
}
|
|
|
|
// TODO Extract information from Authorizer
|
|
func PermissionDeniedByACL(authz Authorizer, context *AuthorizerContext, resource Resource, accessLevel AccessLevel, resourceID string) PermissionDeniedError {
|
|
desc := NewResourceDescriptor(resourceID, context)
|
|
return PermissionDeniedError{Accessor: extractAccessorID(authz), Resource: resource, AccessLevel: accessLevel, ResourceID: desc}
|
|
}
|
|
|
|
func PermissionDeniedByACLUnnamed(authz Authorizer, context *AuthorizerContext, resource Resource, accessLevel AccessLevel) PermissionDeniedError {
|
|
desc := NewResourceDescriptor("", context)
|
|
return PermissionDeniedError{Accessor: extractAccessorID(authz), Resource: resource, AccessLevel: accessLevel, ResourceID: desc}
|
|
}
|
|
|
|
func extractAccessorID(authz interface{}) string {
|
|
var accessor string
|
|
switch v := authz.(type) {
|
|
case AllowAuthorizer:
|
|
accessor = v.AccessorID
|
|
case string:
|
|
accessor = v
|
|
}
|
|
return accessor
|
|
}
|
|
|
|
func ACLResourceNotExistError(resourceType string, entMeta EnterpriseMeta) error {
|
|
if ns := entMeta.NamespaceOrEmpty(); ns != "" {
|
|
return fmt.Errorf("Requested %s not found in namespace %s: %w", resourceType, ns, ErrNotFound)
|
|
}
|
|
return fmt.Errorf("Requested %s does not exist: %w", resourceType, ErrNotFound)
|
|
}
|