diff --git a/design/proposals/namespace_label_authenticator.md b/design/proposals/namespace_label_authenticator.md index 82660aaf99..a7e8517d55 100644 --- a/design/proposals/namespace_label_authenticator.md +++ b/design/proposals/namespace_label_authenticator.md @@ -1,4 +1,4 @@ -# Solution Design - Namespace Label Selector for the Kubernetes Authenticator +# Solution Design - Namespace Label Identity Scope for the Kubernetes Authenticator [//]: # "Change the title above from 'Template' to your design's title" [//]: # "General notes:" @@ -10,7 +10,7 @@ ## Table of Contents [//]: # "You can use this tool to generate a TOC - https://ecotrust-canada.github.io/markdown-toc/" -- [Solution Design - Namespace Label Selector for the Kubernetes Authenticator](#solution-design---namespace-label-selector-for-the-kubernetes-authenticator) +- [Solution Design - Namespace-Label Identity Scope for the Kubernetes Authenticator](#solution-design---namespace-label-identity-scope-for-the-kubernetes-authenticator) * [Table of Contents](#table-of-contents) * [Glossary](#glossary) * [Useful Links](#useful-links) @@ -32,9 +32,9 @@ * [Backwards Compatibility](#backwards-compatibility) * [Affected Components](#affected-components) * [Work in Parallel](#work-in-parallel) + + [Development Tasks](#development-tasks) * [Test Plan](#test-plan) + [Test Environments](#test-environments) - + [Prerequisites](#prerequisites) + [Test Cases (Including Performance)](#test-cases--including-performance-) - [Functional Tests](#functional-tests) - [Security Tests](#security-tests) @@ -49,6 +49,7 @@ Table of contents generated with markdown-toc + ## Glossary [//]: # "Describe terms that will be used throughout the design" [//]: # "You can use this tool to generate a table - https://www.tablesgenerator.com/markdown_tables#" @@ -65,7 +66,8 @@ |-------------|----------| | Feature Doc | | | Issue | | -| POC branch | https://github.com/cyberark/conjur/compare/master...authn-k8s-label-selector | +| Kubernetes Docs: Labels and Selectors | https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ | +| Proof-of-Concept branch | https://github.com/cyberark/conjur/compare/master...authn-k8s-label-selector | ## Background [//]: # "Give relevant background for the designed feature. What is the motivation for this solution?" @@ -184,42 +186,20 @@ The minimum subset of label selector flavors we need to support are an identity to scope to a single group of similarly-labeled namespaces. Should this feature also support: -- **Multiple** positive equality-based selectors? - - **Nice to have, but not required.** Multiple equality-based selectors can be - used to reduce scope from a single equality-based selector. An identity could - authenticate workloads in a subset of namespaces in a single group based on - additional labels. - -- **Negative** selectors (`!=`, `notin`, and `!`)? - - **No.** This feature should not support negative selectors, which would serve as a - denylist in this context. Theoretically, new workloads would be able to - authenticate by default, unless they reside in a denylisted namespace. An - identity's scope should be meaningfully curated, not open-ended. - -- Positive **set-based** selectors (`in`)? - - **No.** Set-based selectors can be used to increase scope from a single equality-based - selector. Support for this selector flavor would imply that a single host - identity could authenticate on behalf of any number of namespace groups. This - runs counter to having intentionally-scoped host identities - if two workloads - are in separate namespaces, and those namespaces are designated to different - sets, they should authenticate with separate identities. - -- Positive **existence-based** selectors? - - **No.** Existence based selectors filter results based on label keys, and does no - validation on label values. While this could technically be leveraged to mimic - positive equality-based selectors by using unique label keys, this would mean - thoroughly polluting the label namespace. - -Under the hood, Kubernetes' native label selector implementation lists all -namespaces in a given cluster, then reduces the result scope to only those that -satisfy the condition. Using this implementation with the Kubernetes -Authenticator requires permitting its identity to `list` all namespaces in the -target cluster. This operation could be negatively affected if the target -cluster is host to so many namespaces that the query operation becomes costly. +| **Selector Type** | **Support?** | **Comments** | +|-------------------|--------------|--------------| +| **Multiple** positive equality-based selectors | **Nice to have, but not required** | Multiple equality-based selectors can be used to reduce scope from a single equality-based selector. An identity could authenticate workloads in a subset of namespaces in a single group based on additional labels. | +| **Negative** selectors (`!=`, `notin`, and `!`) | **No** | This feature should not support negative selectors, which would serve as a denylist in this context. Theoretically, new workloads would be able to authenticate by default, unless they reside in a denylisted namespace. An identity's scope should be meaningfully curated, not open-ended. | +| Positive **set-based** selectors (`in`) | **No** | Set-based selectors can be used to increase scope from a single equality-based selector. Support for this selector flavor would imply that a single host identity could authenticate on behalf of any number of namespace groups. This runs counter to having intentionally-scoped host identities - if two workloads are in separate namespaces, and those namespaces are designated to different sets, they should authenticate with separate identities. | +| Positive **existence-based** selectors | **No** | Existence based selectors filter results based on label keys, and does no validation on label values. While this could technically be leveraged to mimic positive equality-based selectors by using unique label keys, this would mean thoroughly polluting the label namespace. | + +In Kubernetes, label selectors can only be applied to functions that list +instances of a resource type - for example, a client can query a list of +namespaces in a cluster, but limit the results to only those namespaces that +match a given selector. Using label selectors with the Kubernetes Authenticator +will require permitting its identity to `list` all namespaces in the target +cluster. This operation could be negatively affected if the target cluster is +host to so many namespaces that the list operation becomes costly. ```rb # Retrieves all namespaces in the cluster, enforcing field selection based on @@ -265,7 +245,7 @@ example: - !host id: test-app annotations: - authn-k8s/namespace-label-selector: "conjur.org/authn-k8s-project=dev" + authn-k8s/namespace-label: "conjur.org/authn-k8s-project=dev" authn-k8s/service-account: "test-app-sa" ``` @@ -311,7 +291,7 @@ policy. All other factors in Authenticator setup are unchanged. - !host id: test-app annotations: - authn-k8s/namespace-label-selector: "key1=value1" + authn-k8s/namespace-label: "key1=value1" ``` Rancher creates a unique ID for Projects at creation. This ID needs to inform @@ -341,7 +321,7 @@ This value then informs host configuration: - !host id: test-app annotations: - authn-k8s/namespace-label-selector: "field.cattle.io/projectId=${PROJECT_ID}" + authn-k8s/namespace-label: "field.cattle.io/projectId=${PROJECT_ID}" ``` ## Design @@ -391,28 +371,43 @@ following modules: ### Development Tasks -- Implement and test a new Constraint, where exactly one of many restrictions is - required. -- Use the new Constraint to add a resource restriction to an AuthnK8s host, - where exactly one of either `namespace` or `namespace-label-selector` is - required. -- Implement namespace retrieval and label parsing to validate the new - restriction. -- Test the public interface with in-process K8s API mock: hosts configured with - the new restriction, when authenticating from a properly labeled namespace, - should be authenticated. +- [ ] Community and Integrations team ramp-up: current Kubernetes Authenticator + behavior, new Authn-K8s and existing project testing strategies, solution + details. +- [ ] Given the name of a namespace, retrieve its labels from the K8s API server. +- [ ] Given a map of a namespace's labels and a desired label selector, validate + that the label map adheres to the selector. +- [ ] Create and test a new `Authentication::Constraint` class that can enforce + logical XOR on two or more resource restrictions. +- [ ] Update Authn-K8s request validation logic to use the new `Constraint` + class to validate a request's origin namespace against a desired label. +- [ ] Test the new Authn-K8s request validation end-to-end, using either a mock + K8s API server or live infrastructure. +- [ ] [Spike]: How should we suggest customers secure their labels? +- [ ] Perform final security review. +- [ ] Assist TW with updating Kubernetes Authenticator documentation. This + should include: + 1. Updates to the Kubernetes/OpenShift integration documentation. This should + include only general language about using labels to group namespaces and + assign identity scope. + 2. Updates to the Rancher integration documentation. This can include + Rancher-specific language regarding scoping identities to Projects. +- [ ] Perform UX review by manually following the new documentation. This will + include creating a label-scoped identity, creating a labeled namespace, + and authenticating a workload using labels. ## Test Plan ### Test Environments [//]: # "Including build number, platforms etc. Considering the OS and version of PAS (PVWA, CPM), Conjur, Synchronizer etc." -| **Feature** | **OS** | **Version Number** | -|-------------|--------|--------------------| -| | | | +End-to-end tests should be run against resources deployed in full-fledged +Kubernetes environments, to best replicate the typical user experience. -### Prerequisites -[//]: # "List any expected infrastructure requirements here" +| **Platform** | **Versions** | +|--------------|--------------| +| OpenShift | v4.8 & 9 | +| GKE | >= v1.21 | While this solution is designed to easily extend to a Rancher use-case, Rancher infrastructure is not required. Testing against labeled namespaces in standard @@ -433,8 +428,8 @@ for infrastructure setup and long CI builds. | **Title** | **Given** | **When** | **Then** | **Comment** | |-----------|-----------|----------|----------|-------------| -| **Login Happy Path** | Given the Kubernetes Authenticator `${service_id}` is configured to validate namespaces with label `x=y` | When a certificate injection request is received from a namespace labeled `x=y` | The the Authenticator responds with a 202 status and injects the certificate | | -| **Authentication Happy Path** | Given the Kubernetes Authenticator `${service_id}` is configured to validate namespaces with label `x=y`` | When an authentication request is received from a namespace labeled `x=y` | Then the Authenticator responds with a 200 status and an access token | | +| **Login Happy Path** | Given the Kubernetes Authenticator `${service_id}` is configured to validate namespaces with label `x=y` | When a certificate injection request is received from a namespace labeled `x=y` | Then authentication succeeds with a 202 status, certificate injection, and [new log **4**](#logs) | | +| **Authentication Happy Path** | Given the Kubernetes Authenticator `${service_id}` is configured to validate namespaces with label `x=y` | When an authentication request is received from a namespace labeled `x=y` | Then authentication succeeds with a 200 status, an access token, and [new log **4**](#logs) | | #### Security Tests @@ -453,9 +448,10 @@ for infrastructure setup and long CI builds. | **Title** | **Given** | **When** | **Then** | **Comment** | |-----------|-----------|----------|----------|-------------| -| **Misconfigured Authenticator** | Given that a policy file defines a Kubernetes Authenticator instance with both `authn-k8s.[namespace\|namespace-label-selector]` annotations | When the identity authenticates with Conjur | Authentication should fail with a 401 response and an `Errors::Authentication::Constraints` log. | | -| **Misconfigured Identity** | Given that the Kubernetes Authenticator is configured to validate a namespace label, and its identity does not have permission to `get` namespaces in a cluster | When an authentication request is received | Then the request fails with a 500 response | | -| **Misconfigured Namespace** | Given that a Kubernetes Authenticator is configured to authenticate requests from namespaces with the label `x=y` | When a requesting namespace does not have the label `x=y` | Then the request fails with a 401 response | | +| **Misconfigured Authenticator** | Given that a policy file defines a Kubernetes Authenticator identity with both `authn-k8s.[namespace\|namespace-label]` annotations | When the identity authenticates with Conjur | Authentication should fail with a 401 response and [new log **0**](#logs). | | +| **Misconfigured Identity** | Given that the Kubernetes Authenticator is configured to validate a namespace label, and its identity does not have permission to `get` namespaces in a cluster | When an authentication request is received | Then the request fails with a 404 response and a KubeClient log indicating the namespace was not found | | +| **Misconfigured Namespace** | Given that a Kubernetes Authenticator is configured to authenticate requests from namespaces with the label `x=y` | When a requesting namespace does not have the label `x=y` | Then the request fails with a 401 response and [new log **1**](#logs) | | +| **Misconfigured Label** | Given that a policy file defines a Kubernetes Authenticator identity with the `authn-k8s/namespace-label` annotation, but its value does not adhere to the format `=` | When the identity authenticates with Conjur | Authentication should fail with a 403 response and [new log **2**](#logs) | | #### Performance Tests @@ -470,13 +466,13 @@ for infrastructure setup and long CI builds. [//]: # "If the logs are listed in the feature doc, add a link to that section. If not, list them here." [//]: # "You can use this tool to generate a table - https://www.tablesgenerator.com/markdown_tables#" -| **Scenario** | **Log message** | -|--------------|-----------------| -| An authenticating host is configured with either both or none of `authn-k8s/namespace` or `authn-k8s/namespace-label-selector` | Role must have exactly one of the following required constraints: {0} | -| An authenticating host is configured with `authn-k8s/namespace-label-selector`, but the authenticating namespace does not conform | Kubernetes namespace {0} does not match label-selector {1} | -| An authenticating host is configured with `authn-k8s/namespace-label-selector`, but its format does not match `"="` | Invalid namespace label selector {0}: must adhere to format "\=\" | -| Kubernetes Authenticator begins validating `authn-k8s/namespace-label-selector` restriction | Validating resource restriction on request: 'namespace-label-selector' | -| Kubernetes Authenticator successfully validates `authn-k8s/namespace-label-selector` restriction | Validated K8s resource. Type:'namespace-label-selector', Selector:'{0}', Namespace:'{1}' | +| **ID** | **Scenario** | **Log message** | +|--------|--------------|-----------------| +| 0 | An authenticating host is configured with either both or none of `authn-k8s/namespace` or `authn-k8s/namespace-label` | Role must have exactly one of the following required constraints: {0} | +| 1 | An authenticating host is configured with `authn-k8s/namespace-label`, but the authenticating namespace does not conform | Kubernetes namespace {0} does not match label-selector {1} | +| 2 | An authenticating host is configured with `authn-k8s/namespace-label`, but its format does not match `"="` | Invalid namespace label selector {0}: must adhere to format "\=\" | +| 3 | Kubernetes Authenticator begins validating `authn-k8s/namespace-label` restriction | Validating resource restriction on request: 'namespace-label' | +| 4 | Kubernetes Authenticator successfully validates `authn-k8s/namespace-label` restriction | Validated K8s resource. Type:'namespace-label', Selector:'{0}', Namespace:'{1}' | ## Documentation [//]: # "Add notes on what should be documented in this solution. Elaborate on where this should be documented, including GitHub READMEs and/or official documentation." @@ -517,7 +513,7 @@ live currently. - !host id: test-app annotations: - authn-k8s/namespace-label-selector: "conjur.org/project=devNamespaces" + authn-k8s/namespace-label: "conjur.org/project=devNamespaces" authn-k8s/authentication-container-name: "authenticator" authn-k8s/service-account: "test-app-sa" ``` @@ -545,6 +541,17 @@ live currently. This restriction may not be a barrier to acceptance, and shouldn't obstruct a complete solution. +2. Rancher uses + [field management](https://kubernetes.io/docs/reference/using-api/server-side-apply/#field-management) + to maintain labels describing Project memberships server-side. + + For our users that want to use labeled namespace groups in vanilla + Kubernetes, should we suggest field management to secure labels? Should we + provide a suggested implementation? + + Is this something that can be achieved by simply restricting `create`, + `update` and `patch` permissions on namespace resources? + ## Definition of Done - Solution designed is approved