Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions config/crd/bases/operator.kcp.io_frontproxies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,26 @@ spec:
CertificateTemplates allows to customize the properties on the generated
certificates for this front-proxy.
type: object
clientCABundleRef:
description: |-
ClientCABundleRef references a v1.Secret object that contains an additional client CA bundle
that should be trusted by the front-proxy for client certificate authentication.
The secret must contain a key named `tls.crt` that holds the PEM encoded CA certificate(s).
This CA bundle will be merged with the root shard's client CA, allowing the front-proxy
to accept client certificates signed by either CA. This is useful for backwards compatibility
during upgrades when migrating from a separate front-proxy client CA to the shared root shard client CA.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
deploymentTemplate:
description: 'Optional: DeploymentTemplate configures the Kubernetes
Deployment created for this shard.'
Expand Down
20 changes: 20 additions & 0 deletions config/crd/bases/operator.kcp.io_rootshards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,26 @@ spec:
- name
type: object
type: object
clientCABundleRef:
description: |-
ClientCABundleRef references a v1.Secret containing an additional client CA bundle
for client certificate authentication. The secret must contain a key named `tls.crt`.
This CA bundle will be merged with the root shard's client CA.
If configured on a RootShard, this bundle is automatically inherited by all FrontProxies,
Shards, and VirtualWorkspaces connected to it. Each of those components can additionally
specify their own ClientCABundleRef, which will be merged on top.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
clusterDomain:
description: ClusterDomain is the DNS domain for services in the cluster.
Defaults to "cluster.local" if not set.
Expand Down
20 changes: 20 additions & 0 deletions config/crd/bases/operator.kcp.io_shards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,26 @@ spec:
CertificateTemplates allows to customize the properties on the generated
certificates for this shard.
type: object
clientCABundleRef:
description: |-
ClientCABundleRef references a v1.Secret containing an additional client CA bundle
for client certificate authentication. The secret must contain a key named `tls.crt`.
This CA bundle will be merged with the root shard's client CA.
If configured on a RootShard, this bundle is automatically inherited by all FrontProxies,
Shards, and VirtualWorkspaces connected to it. Each of those components can additionally
specify their own ClientCABundleRef, which will be merged on top.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
clusterDomain:
description: ClusterDomain is the DNS domain for services in the cluster.
Defaults to "cluster.local" if not set.
Expand Down
18 changes: 18 additions & 0 deletions config/crd/bases/operator.kcp.io_virtualworkspaces.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,24 @@ spec:
CertificateTemplates allows to customize the properties on the generated
certificates for this server.
type: object
clientCABundleRef:
description: |-
ClientCABundleRef references a v1.Secret containing an additional client CA bundle
for client certificate authentication. The secret must contain a key named `tls.crt`.
This CA bundle will be merged with the root shard's client CA and its ClientCABundleRef
(if configured).
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
clusterDomain:
description: ClusterDomain is the DNS domain for services in the cluster.
Defaults to "cluster.local" if not set.
Expand Down
2 changes: 1 addition & 1 deletion docs/content/architecture/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ For developing controllers against kcp, it is often necessary to access the shar

Kubeconfigs allow the easy creation of credentials to access kcp. As a sharded system, kcp relies on client certificate authentication and the kcp-operator will ensure the correct certificates are generated and then neatly wrapped up in ready-to-use kubeconfig Secrets.

Kubeconfigs can be configured to point to a specific shard or to a front-proxy instance, which affects which client CA is used to generate the certificates.
Kubeconfigs can be configured to point to a specific shard or to a front-proxy instance. All kubeconfigs use the root shard's client CA for certificate generation, ensuring consistent authentication across the entire kcp installation. Additional client CAs can be configured via `clientCABundleRef` – see [Certificate Management](pki.md#client-ca-bundle) for details.

## Cross-Namespace/Cluster References

Expand Down
2 changes: 2 additions & 0 deletions docs/content/architecture/front-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ The kcp front-proxy will build up a runtime map of all shards, workspaces and lo

The proxy can optionally perform authentication, for example using OIDC, and pass authentication information (like username and groups) on to the shard (via HTTP headers). This can be configured for each front-proxy individually. If no authentication is performed, the requests will be passed unauthenticated to their target shards, where then authentication happens.

Additionally, front-proxies can be configured to accept client certificates signed by additional CAs via the `clientCABundleRef` field. This is useful when integrating with external identity systems. See [Certificate Management](pki.md#client-ca-bundle) for more details.

## RootShard Proxy

The kcp-operator will deploy an internal front-proxy for every `RootShard` (i.e. one for each kcp installation). This internal proxy is solely used by the operator itself to allow it to resolve workspace paths to logicalclusters and provision resources inside those workspaces.
Expand Down
71 changes: 68 additions & 3 deletions docs/content/architecture/pki.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,20 @@ graph TB

C --> D(kcp-etcd-client-ca):::ca
C --> E(kcp-etcd-peer-ca):::ca
C --> F($rootshard-front-proxy-client-ca):::ca
C --> G($rootshard-server-ca):::ca
C --> H($rootshard-requestheaer-client-ca):::ca
C --> I($rootshard-client-ca):::ca
C --> J(kcp-service-account-ca):::ca

D --> K([kcp-etcd-client-issuer]):::issuer
E --> L([kcp-etcd-peer-issuer]):::issuer
F --> M([$rootshard-front-proxy-client-ca]):::issuer
G --> N([$rootshard-server-ca]):::issuer
H --> O([$rootshard-requestheader-client-ca]):::issuer
I --> P([$rootshard-client-ca]):::issuer
J --> Q([kcp-service-account-issuer]):::issuer

K --- K1(kcp-etcd):::cert --> K2(kcp-etcd-client):::cert
L --> L1(kcp-etcd-peer):::cert
M --> M1($rootshard-$frontproxy-admin-kubeconfig):::cert
N --- N1(kcp):::cert --- N2($rootshard-$frontproxy-server):::cert --> N3(kcp-virtual-workspaces):::cert
O --- O1($rootshard-$frontproxy-requestheader):::cert --> O2("(kcp-front-proxy-vw-client)"):::cert
P --- P1($rootshard-$frontproxy-kubeconfig):::cert --> P2(kcp-internal-admin-kubeconfig):::cert
Expand All @@ -43,3 +40,71 @@ graph TB
classDef ca color:#F77
classDef cert color:orange
```

## Client CA Bundle

By default, all components in a kcp installation use the root shard's Client CA (`$rootshard-client-ca`) to authenticate client certificates. The kcp-operator generates this CA and uses it to sign all client certificates created via `Kubeconfig` objects.

However, in some scenarios you may want to accept client certificates signed by additional CAs – for example, when integrating with external identity systems or when migrating from another certificate infrastructure.

### Adding Additional Client CAs

You can configure additional client CA bundles using the `clientCABundleRef` field on various resources. This field references a Secret containing additional CA certificates that should be trusted for client authentication.

The inheritance model works as follows:

| Component | Trusted Client CAs |
|-----------|-------------------|
| **RootShard** | Root Client CA + RootShard's `clientCABundleRef` |
| **Shard** | Root Client CA + RootShard's `clientCABundleRef` + Shard's `clientCABundleRef` |
| **FrontProxy** | Root Client CA + RootShard's `clientCABundleRef` + FrontProxy's `clientCABundleRef` |
| **VirtualWorkspace** | Root Client CA + RootShard's `clientCABundleRef` + VirtualWorkspace's `clientCABundleRef` |

This means a `clientCABundleRef` configured on the `RootShard` automatically propagates to all shards, front-proxies, and virtual workspaces connected to it. Each of those components can additionally specify their own `clientCABundleRef` to trust even more CAs.

### Example

To add an additional client CA to your kcp installation:

1. Create a Secret containing the additional CA certificate:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: external-client-ca
namespace: my-kcp
type: Opaque
data:
tls.crt: <base64-encoded-ca-certificate>
```

2. Reference it in your RootShard (to apply to all components):

```yaml
apiVersion: operator.kcp.io/v1alpha1
kind: RootShard
metadata:
name: root
namespace: my-kcp
spec:
# ... other configuration ...
clientCABundleRef:
name: external-client-ca
```

Or reference it on a specific component (e.g., a FrontProxy) to only trust it there:

```yaml
apiVersion: operator.kcp.io/v1alpha1
kind: FrontProxy
metadata:
name: my-frontproxy
namespace: my-kcp
spec:
# ... other configuration ...
clientCABundleRef:
name: external-client-ca
```

The Secret must contain a key named `tls.crt` with PEM-encoded CA certificate(s). Multiple certificates can be concatenated in a single PEM bundle.
3 changes: 0 additions & 3 deletions internal/controller/bundle/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ func getBundleObjectsForShard(shard *operatorv1alpha1.Shard, rootShardName strin

objects := []operatorv1alpha1.BundleObject{
// CA certificates from RootShard (shared)
{GVR: secretGVR, Name: fmt.Sprintf("%s-front-proxy-client-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-requestheader-client-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-server-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-ca", rootShardName), Namespace: namespace},
Expand Down Expand Up @@ -128,7 +127,6 @@ func getBundleObjectsForRootShard(rootShard *operatorv1alpha1.RootShard) []opera
{GVR: secretGVR, Name: fmt.Sprintf("%s-requestheader-client-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-client-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-service-account-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-front-proxy-client-ca", rootShardName), Namespace: namespace},

// RootShard certificates
{GVR: secretGVR, Name: fmt.Sprintf("%s-server", rootShardName), Namespace: namespace},
Expand Down Expand Up @@ -181,7 +179,6 @@ func getBundleObjectsForFrontProxy(frontProxy *operatorv1alpha1.FrontProxy, root

return []operatorv1alpha1.BundleObject{
// CA certificates from RootShard (shared)
{GVR: secretGVR, Name: fmt.Sprintf("%s-front-proxy-client-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-requestheader-client-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-server-ca", rootShardName), Namespace: namespace},
{GVR: secretGVR, Name: fmt.Sprintf("%s-ca", rootShardName), Namespace: namespace},
Expand Down
Loading
Loading