diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index 2ee6770d44..62cc047535 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -2123,10 +2123,15 @@ func (a *ACL) AuthMethodSet(args *structs.ACLAuthMethodSetRequest, reply *struct // Instantiate a validator but do not cache it yet. This will validate the // configuration. - if _, err := authmethod.NewValidator(a.srv.logger, method); err != nil { + validator, err := authmethod.NewValidator(a.srv.logger, method) + if err != nil { return fmt.Errorf("Invalid Auth Method: %v", err) } + if err := enterpriseAuthMethodValidation(method, validator); err != nil { + return err + } + if err := a.srv.fsm.State().ACLAuthMethodUpsertValidateEnterprise(method, existing); err != nil { return err } @@ -2301,7 +2306,10 @@ func (a *ACL) Login(args *structs.ACLLoginRequest, reply *structs.ACLToken) erro } // This always will return a valid pointer - targetMeta := method.TargetEnterpriseMeta(verifiedIdentity.EnterpriseMeta) + targetMeta, err := computeTargetEnterpriseMeta(method, verifiedIdentity) + if err != nil { + return err + } // 3. send map through role bindings serviceIdentities, roleLinks, err := a.srv.evaluateRoleBindings(validator, verifiedIdentity, &auth.EnterpriseMeta, targetMeta) diff --git a/agent/consul/acl_endpoint_oss.go b/agent/consul/acl_endpoint_oss.go new file mode 100644 index 0000000000..f4331f0d68 --- /dev/null +++ b/agent/consul/acl_endpoint_oss.go @@ -0,0 +1,19 @@ +// +build !consulent + +package consul + +import ( + "github.com/hashicorp/consul/agent/consul/authmethod" + "github.com/hashicorp/consul/agent/structs" +) + +func enterpriseAuthMethodValidation(method *structs.ACLAuthMethod, validator authmethod.Validator) error { + return nil +} + +func computeTargetEnterpriseMeta( + method *structs.ACLAuthMethod, + verifiedIdentity *authmethod.Identity, +) (*structs.EnterpriseMeta, error) { + return method.TargetEnterpriseMeta(verifiedIdentity.EnterpriseMeta), nil +} diff --git a/agent/consul/acl_endpoint_test.go b/agent/consul/acl_endpoint_test.go index 7c27237a8e..0512a605c9 100644 --- a/agent/consul/acl_endpoint_test.go +++ b/agent/consul/acl_endpoint_test.go @@ -5087,6 +5087,7 @@ func TestACLEndpoint_Login_with_MaxTokenTTL(t *testing.T) { got.SecretID = "" got.Hash = nil + defaultEntMeta := structs.DefaultEnterpriseMeta() expect := &structs.ACLToken{ AuthMethod: method.Name, Description: `token created via login: {"pod":"pod1"}`, @@ -5096,7 +5097,9 @@ func TestACLEndpoint_Login_with_MaxTokenTTL(t *testing.T) { ServiceIdentities: []*structs.ACLServiceIdentity{ {ServiceName: "web"}, }, + EnterpriseMeta: *defaultEntMeta, } + expect.ACLAuthMethodEnterpriseMeta.FillWithEnterpriseMeta(defaultEntMeta) require.Equal(t, got, expect) } diff --git a/agent/consul/authmethod/kubeauth/k8s_oss.go b/agent/consul/authmethod/kubeauth/k8s_oss.go index f73069c780..f87ca502fe 100644 --- a/agent/consul/authmethod/kubeauth/k8s_oss.go +++ b/agent/consul/authmethod/kubeauth/k8s_oss.go @@ -6,6 +6,10 @@ import "github.com/hashicorp/consul/agent/structs" type enterpriseConfig struct{} +func enterpriseValidation(method *structs.ACLAuthMethod, config *Config) error { + return nil +} + func (v *Validator) k8sEntMetaFromFields(fields map[string]string) *structs.EnterpriseMeta { return nil } diff --git a/agent/structs/acl.go b/agent/structs/acl.go index 35918315e8..e58287266d 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -1060,6 +1060,8 @@ type ACLAuthMethod struct { // Embedded Enterprise ACL Meta EnterpriseMeta `mapstructure:",squash"` + ACLAuthMethodEnterpriseFields `mapstructure:",squash"` + // Embedded Raft Metadata RaftIndex `hash:"ignore"` } diff --git a/agent/structs/acl_oss.go b/agent/structs/acl_oss.go index ca30f57750..43c11c691b 100644 --- a/agent/structs/acl_oss.go +++ b/agent/structs/acl_oss.go @@ -28,6 +28,8 @@ node_prefix "" { }` ) +type ACLAuthMethodEnterpriseFields struct{} + type ACLAuthMethodEnterpriseMeta struct{} func (_ *ACLAuthMethodEnterpriseMeta) FillWithEnterpriseMeta(_ *EnterpriseMeta) { diff --git a/api/acl.go b/api/acl.go index 6971d9a89b..44fc29ad67 100644 --- a/api/acl.go +++ b/api/acl.go @@ -195,6 +195,10 @@ type ACLAuthMethod struct { CreateIndex uint64 ModifyIndex uint64 + // NamespaceRules apply only on auth methods defined in the default namespace. + // Namespacing is a Consul Enterprise feature. + NamespaceRules []*ACLAuthMethodNamespaceRule `json:",omitempty"` + // Namespace is the namespace the ACLAuthMethod is associated with. // Namespacing is a Consul Enterprise feature. Namespace string `json:",omitempty"` @@ -237,6 +241,18 @@ func (m *ACLAuthMethod) UnmarshalJSON(data []byte) error { return nil } +type ACLAuthMethodNamespaceRule struct { + // Selector is an expression that matches against verified identity + // attributes returned from the auth method during login. + Selector string `json:",omitempty"` + + // BindNamespace is the target namespace of the binding. Can be lightly + // templated using HIL ${foo} syntax from available field names. + // + // If empty it's created in the same namespace as the auth method. + BindNamespace string `json:",omitempty"` +} + type ACLAuthMethodListEntry struct { Name string Type string diff --git a/website/pages/api-docs/acl/auth-methods.mdx b/website/pages/api-docs/acl/auth-methods.mdx index 9e10b944be..adcba3fb78 100644 --- a/website/pages/api-docs/acl/auth-methods.mdx +++ b/website/pages/api-docs/acl/auth-methods.mdx @@ -70,6 +70,31 @@ The table below shows this endpoint's support for If not provided at all, the namespace will be inherited from the request's ACL token or will default to the `default` namespace. Added in Consul 1.7.0. +- `NamespaceRules` `(array)` - A set + of rules that can control which namespace tokens created via this auth method + will be created within. Note that assigning namespaces via rules requires the + auth method to reside within the `default` namespace. Unlike binding rules, + the **first** matching namespace rule wins. Added in Consul 1.8.0. + + - `Selector` `(string: "")` - Specifies the expression used to match this + namespace rule against valid identities returned from an auth method + validation. If empty this namespace rule matches all valid identities + returned from the auth method. For example: + + ```text + serviceaccount.namespace==default and serviceaccount.name!=vault + ``` + + - `BindNamespace` `(string: )` - If the namespace rule's `Selector` + matches then this is used to control the namespace where the token is + created. This can either be a plain string or lightly templated + using [HIL syntax](https://github.com/hashicorp/hil) to interpolate the + same values that are usable by the `Selector` syntax. For example: + + ```text + prefixed-${serviceaccount.name} + ``` + ### Sample Payload ```json @@ -218,6 +243,31 @@ The table below shows this endpoint's support for If not provided at all, the namespace will be inherited from the request's ACL token or will default to the `default` namespace. Added in Consul 1.7.0. +- `NamespaceRules` `(array)` - A set + of rules that can control which namespace tokens created via this auth method + will be created within. Note that assigning namespaces via rules requires the + auth method to reside within the `default` namespace. Unlike binding rules, + the **first** matching namespace rule wins. Added in Consul 1.8.0. + + - `Selector` `(string: "")` - Specifies the expression used to match this + namespace rule against valid identities returned from an auth method + validation. If empty this namespace rule matches all valid identities + returned from the auth method. For example: + + ```text + serviceaccount.namespace==default and serviceaccount.name!=vault + ``` + + - `BindNamespace` `(string: )` - If the namespace rule's `Selector` + matches then this is used to control the namespace where the token is + created. This can either be a plain string or lightly templated + using [HIL syntax](https://github.com/hashicorp/hil) to interpolate the + same values that are usable by the `Selector` syntax. For example: + + ```text + prefixed-${serviceaccount.name} + ``` + ### Sample Payload ```json diff --git a/website/pages/docs/acl/auth-methods/kubernetes.mdx b/website/pages/docs/acl/auth-methods/kubernetes.mdx index c9a65d44de..43a226b9ca 100644 --- a/website/pages/docs/acl/auth-methods/kubernetes.mdx +++ b/website/pages/docs/acl/auth-methods/kubernetes.mdx @@ -37,17 +37,20 @@ parameters are required to properly configure an auth method of type ([JWT](https://jwt.io/ 'JSON Web Token')) used by the Consul leader to validate application JWTs during login. -- `MapNamespaces` `(bool: )` - Indicates whether - the auth method should attempt to map the Kubernetes namespace to a Consul +- `MapNamespaces` `(bool: )` - + **Deprecated in Consul 1.8.0 in favor of [namespace rules](/api/acl/auth-methods#namespacerules).** + Indicates whether the auth method should attempt to map the Kubernetes namespace to a Consul namespace instead of creating tokens in the auth methods own namespace. Note that mapping namespaces requires the auth method to reside within the `default` namespace. -- `ConsulNamespacePrefix` `(string: )` - When - `MapNamespaces` is enabled, this value will be prefixed to the Kubernetes +- `ConsulNamespacePrefix` `(string: )` - + **Deprecated in Consul 1.8.0 in favor of [namespace rules](/api/acl/auth-methods#namespacerules).** + When `MapNamespaces` is enabled, this value will be prefixed to the Kubernetes namespace to determine the Consul namespace to create the new token within. - `ConsulNamespaceOverrides` `(map: )` - + **Deprecated in Consul 1.8.0 in favor of [namespace rules](/api/acl/auth-methods#namespacerules).** This field is a mapping of Kubernetes namespace names to Consul namespace names. If a Kubernetes namespace is present within this map, the value will be used without adding the `ConsulNamespacePrefix`. If the value in the map @@ -133,11 +136,11 @@ roleRef: The authentication step returns the following trusted identity attributes for use in binding rule selectors and bind name interpolation. -| Attributes | Supported Selector Operations | Can be Interpolated | -| -------------------------- | ----------------------------- | ------------------- | -| `serviceaccount.namespace` | Equal, Not Equal | yes | -| `serviceaccount.name` | Equal, Not Equal | yes | -| `serviceaccount.uid` | Equal, Not Equal | yes | +| Attributes | Supported Selector Operations | Can be Interpolated | +| -------------------------- | -------------------------------------------------- | ------------------- | +| `serviceaccount.namespace` | Equal, Not Equal, In, Not In, Matches, Not Matches | yes | +| `serviceaccount.name` | Equal, Not Equal, In, Not In, Matches, Not Matches | yes | +| `serviceaccount.uid` | Equal, Not Equal, In, Not In, Matches, Not Matches | yes | ## Kubernetes Authentication Details