Skip to content

Commit 73febc8

Browse files
authored
Merge pull request #405 from mjudeikis/mjudeikis/grab.a.bag.v0.6.0
2 parents 573bae5 + 1820696 commit 73febc8

10 files changed

Lines changed: 276 additions & 13 deletions

File tree

deploy/charts/backend/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ version: 0.1.0
2121
# incremented each time you make changes to the application. Versions are not expected to
2222
# follow Semantic Versioning. They should reflect the version the application is using.
2323
# It is recommended to use it with quotes.
24-
appVersion: "v0.5.1"
24+
appVersion: "v0.6.0"

docs/content/setup/helm.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ helm upgrade \
6464
--set 'gatewayApi.route.hostnames[0]=kube-bind.example.com' \
6565
--set gatewayApi.route.path=/ \
6666
--set gatewayApi.route.pathType=PathPrefix \
67-
--set image.tag=${VERSION} \
68-
kube-bind \
67+
--set image.tag=v${VERSION} \
6968
kube-bind oci://ghcr.io/kube-bind/charts/backend --version ${VERSION}
7069
```
7170

docs/content/usage/.pages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ nav:
22
- index.md
33
- api-concepts.md
44
- synchronization.md
5+
- integrations

docs/content/usage/index.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ If you're new to kube-bind:
8888

8989
1. **Start with the [Quickstart Guide](../setup/quickstart.md)** for a hands-on introduction
9090
2. **Review [API Concepts](api-concepts.md)** to understand the fundamental types
91-
3. **Explore [Template References](template-references.md)** for advanced use cases
92-
4. **Check the [Reference Documentation](../reference/)** for complete API specifications
91+
3. **Check the [Reference Documentation](../reference/)** for complete API specifications
9392

9493
The konnector agents establish a secure, authenticated connection that allows:
9594

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
nav:
2+
- cert-manager.md
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
---
2+
title: Cert-Manager
3+
description: |
4+
Guide on integrating kube-bind with cert-manager for automated TLS certificate management.
5+
weight: 10
6+
---
7+
8+
# Cert-Manager Integration
9+
10+
1. **Install cert-manager** in your Kubernetes cluster, where kube-bind backend is running, if you haven't already. You can follow the official installation guide [here](https://cert-manager.io/docs/installation/kubernetes/).
11+
12+
13+
2. **Create a `kube-bind` template for `Certificate` resources** to allow service consumers to request TLS certificates. Below is an example template:
14+
15+
```yaml
16+
kubectl apply -f - <<EOF
17+
apiVersion: kube-bind.io/v1alpha2
18+
kind: APIServiceExportTemplate
19+
metadata:
20+
labels:
21+
provider: cert-manager
22+
name: certificate
23+
spec:
24+
permissionClaims:
25+
- group: ""
26+
resource: secrets
27+
selector:
28+
references:
29+
- resource: certificates
30+
group: cert-manager.io
31+
jsonPath:
32+
name: 'spec.secretName'
33+
resources:
34+
- group: cert-manager.io
35+
resource: certificates
36+
versions:
37+
- v1
38+
scope: Namespaced
39+
EOF
40+
```
41+
42+
3. **Login into the kube-bind CLI** and request a binding to the `certificate` template created above. This will allow you to create `Certificate` resources in your consumer cluster.
43+
44+
```bash
45+
kubectl bind login https://kube-bind.example.com
46+
# you will get redirected to UI to authenticate and pick the template
47+
kubectl bind
48+
```
49+
50+
4. **Wait for the binding to be established.** Once the binding is active, you can create `Certificate` resources in your consumer cluster, and you will get `Certificate` objects synced from the provider cluster.
51+
52+
```bash
53+
kubectl bind
54+
🌐 Opening kube-bind UI in your browser...
55+
https://kube-bind.genericcontrolplane.io?redirect_url=....
56+
57+
Browser opened successfully
58+
Waiting for binding completion from UI...
59+
(Press Ctrl+C to cancel)
60+
61+
Binding completed successfully!
62+
Created kube-bind namespace.
63+
🔒 Created secret kube-bind/kubeconfig-p6mfh for host https://api.kcp-prod.kcp.internal.canary.k8s.ondemand.com:443, namespace kube-bind-dkxkx
64+
🚀 Deploying konnector v0.6.0 to namespace kube-bind with custom image "ghcr.io/kube-bind/konnector:v0.6.0-rc1".
65+
Waiting for the ...................
66+
✅ Created APIServiceBinding certificate for 1 resources
67+
Created 1 APIServiceBinding(s):
68+
- certificate
69+
Resources bound successfully!
70+
```
71+
72+
5. **Create a `Certificate` resource** in your consumer cluster. The cert-manager in the provider cluster will handle the issuance and management of the TLS certificate.
73+
74+
!!! note
75+
my-selfsigned-issuer must be present in the provider cluster for this example to work.
76+
77+
```yaml
78+
kubectl apply -f - <<EOF
79+
apiVersion: cert-manager.io/v1
80+
kind: Certificate
81+
metadata:
82+
name: my-tls-cert
83+
namespace: default
84+
spec:
85+
commonName: my-ca
86+
isCA: true
87+
issuerRef:
88+
kind: ClusterIssuer
89+
name: my-selfsigned-issuer
90+
secretName: my-tls-cert
91+
EOF
92+
```
93+
94+
6. Observe that the `Certificate` resource is created in the consumer cluster and the corresponding TLS secret is generated.
95+
96+
```bash
97+
kubectl get certificates
98+
NAME READY SECRET AGE
99+
my-tls-cert True my-tls-cert 6m55s
100+
kubectl get secrets
101+
NAME TYPE DATA AGE
102+
my-tls-cert kubernetes.io/tls 3 6m33s
103+
```

pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ func (c *controller) enqueueProvider(logger klog.Logger, obj interface{}) {
294294
return
295295
}
296296
if !c.isClaimed(logger, o, false) {
297+
logger.V(4).Info("object is not claimed, skipping", "object", o.GetObjectKind().GroupVersionKind(), "name", o.GetName())
297298
return
298299
}
299300

pkg/konnector/controllers/cluster/serviceexport/status/status_controller.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ func (c *controller) enqueueProvider(logger klog.Logger, obj any) {
185185
// Try to map the provider name back to the consumer name,
186186
// but only to check if we "own" the object; we will actually
187187
// enqueue the provider key after all.
188-
consumerKey, err := c.reconciler.isolationStrategy.ToConsumerKey(types.NamespacedName{
188+
consumerKey, err := c.isolationStrategy.ToConsumerKey(types.NamespacedName{
189189
Namespace: ns,
190190
Name: name,
191191
})
@@ -216,7 +216,7 @@ func (c *controller) enqueueConsumer(logger klog.Logger, obj any) {
216216
return
217217
}
218218

219-
providerKey, err := c.reconciler.isolationStrategy.ToProviderKey(types.NamespacedName{
219+
providerKey, err := c.isolationStrategy.ToProviderKey(types.NamespacedName{
220220
Namespace: ns,
221221
Name: name,
222222
})
@@ -250,7 +250,10 @@ func (c *controller) enqueueServiceNamespace(logger klog.Logger, obj any) {
250250
return // not for us
251251
}
252252

253-
strategy := c.reconciler.isolationStrategy.(*isolation.ServiceNamespacedStrategy)
253+
strategy, ok := c.isolationStrategy.(*isolation.ServiceNamespacedStrategy)
254+
if !ok { // should not happen... but...
255+
return
256+
}
254257
nsOnProviderCluster, err := strategy.ProviderNamespace(name)
255258
if err != nil {
256259
runtime.HandleError(err)
@@ -289,7 +292,7 @@ func (c *controller) Start(ctx context.Context, numThreads int) {
289292
// APIServiceNamespaces are only of interest when syncing namespaced
290293
// objects, and since these event handlers need the appropriate isolation
291294
// strategy, we only start them when necessary.
292-
if _, ok := c.reconciler.isolationStrategy.(*isolation.ServiceNamespacedStrategy); ok {
295+
if _, ok := c.isolationStrategy.(*isolation.ServiceNamespacedStrategy); ok {
293296
c.serviceNamespaceInformer.Informer().AddDynamicEventHandler(ctx, controllerName, cache.ResourceEventHandlerFuncs{
294297
AddFunc: func(obj any) {
295298
c.enqueueServiceNamespace(logger, obj)

pkg/resources/resources.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ func matchesReferences(logger klog.Logger, references []kubebindv1alpha2.Selecto
8585
return false
8686
}
8787

88+
logger.V(4).Info("checking references", "references", references, "object", obj, "potentiallyReferencedResources", potentiallyReferencedResources)
89+
8890
if potentiallyReferencedResources == nil {
8991
return false
9092
}
@@ -156,6 +158,7 @@ func IsClaimed(logger klog.Logger, selector kubebindv1alpha2.Selector, obj *unst
156158
}
157159
// Empty selector selects nothing
158160
if selector.LabelSelector == nil && len(selector.NamedResources) == 0 && len(selector.References) == 0 {
161+
logger.V(4).Info("no selector provided")
159162
return false
160163
}
161164

@@ -178,7 +181,7 @@ func IsClaimedWithReference(
178181
consumerClient dynamicclient.Interface,
179182
serviceNamespaceLister ServiceNamespaceLister,
180183
) bool {
181-
logger = logger.V(4).WithValues("gvr", obj.GroupVersionKind().String(), "namespace", obj.GetNamespace(), "name", obj.GetName())
184+
logger = logger.V(4).WithValues("gvr", obj.GroupVersionKind().String(), "namespace", obj.GetNamespace(), "name", obj.GetName(), "claim", claim)
182185
logger.Info("checking if object is claimed")
183186
copy := obj.DeepCopy()
184187

@@ -208,11 +211,14 @@ func IsClaimedWithReference(
208211
defer cancel()
209212

210213
for _, version := range versions {
211-
objs, err := consumerClient.Resource(schema.GroupVersionResource{
214+
gvr := schema.GroupVersionResource{
212215
Group: ref.Group,
213216
Resource: ref.Resource,
214217
Version: version,
215-
}).List(ctx, metav1.ListOptions{})
218+
}
219+
logger.V(4).Info("listing objects for reference claim", "gvr", gvr)
220+
221+
objs, err := consumerClient.Resource(gvr).List(ctx, metav1.ListOptions{})
216222
if err != nil {
217223
logger.Error(err, "failed to list objects for reference claim. Invalidating all claim.", "group", ref.Group, "resource", ref.Resource, "version", version)
218224
return false
@@ -248,10 +254,11 @@ func IsClaimedWithReference(
248254
}
249255
}
250256
if sn != nil {
257+
logger.V(4).Info("remapping namespace for provider side claimed check", "originalNamespace", copy.GetNamespace(), "remappedNamespace", sn.Name)
251258
copy.SetNamespace(sn.Name)
252259
}
253260

254261
result := IsClaimed(logger, claim.Selector, copy, potentiallyReferencedResources)
255-
logger.Info("IsClaimed result (provider side)", "result", result)
262+
logger.V(4).Info("IsClaimed result (provider side)", "result", result)
256263
return result
257264
}

pkg/resources/resources_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,154 @@ func TestSelector_IsClaimed(t *testing.T) {
788788
},
789789
want: false, // Should NOT match because it doesn't have app=sheriff label and is not referenced by Sheriff
790790
},
791+
{
792+
name: "cert-manager secret reference - should match when certificate references secret via secretName",
793+
potentiallyReferencedResources: &unstructured.UnstructuredList{
794+
Items: []unstructured.Unstructured{
795+
{
796+
Object: map[string]any{
797+
"apiVersion": "cert-manager.io/v1",
798+
"kind": "Certificate",
799+
"metadata": map[string]any{
800+
"name": "my-tls-cert",
801+
"namespace": "default",
802+
},
803+
"spec": map[string]any{
804+
"secretName": "my-tls-cert",
805+
"commonName": "my-ca",
806+
"isCA": true,
807+
"issuerRef": map[string]any{
808+
"kind": "ClusterIssuer",
809+
"name": "kcp-ca",
810+
},
811+
},
812+
},
813+
},
814+
},
815+
},
816+
selector: kubebindv1alpha2.Selector{
817+
References: []kubebindv1alpha2.SelectorReference{
818+
{
819+
GroupResource: kubebindv1alpha2.GroupResource{
820+
Group: "cert-manager.io",
821+
Resource: "certificates",
822+
},
823+
JSONPath: &kubebindv1alpha2.JSONPath{
824+
Name: "spec.secretName",
825+
},
826+
},
827+
},
828+
},
829+
obj: &unstructured.Unstructured{
830+
Object: map[string]any{
831+
"apiVersion": "v1",
832+
"kind": "Secret",
833+
"metadata": map[string]any{
834+
"name": "my-tls-cert",
835+
"namespace": "default",
836+
"annotations": map[string]any{
837+
"cert-manager.io/certificate-name": "my-tls-cert",
838+
},
839+
},
840+
"type": "kubernetes.io/tls",
841+
},
842+
},
843+
want: true, // Should match because certificate's .spec.secretName equals secret name and no namespace JSONPath means namespace matching is handled by caller
844+
},
845+
{
846+
name: "cert-manager secret reference - should not match when certificate references different secret",
847+
potentiallyReferencedResources: &unstructured.UnstructuredList{
848+
Items: []unstructured.Unstructured{
849+
{
850+
Object: map[string]any{
851+
"apiVersion": "cert-manager.io/v1",
852+
"kind": "Certificate",
853+
"metadata": map[string]any{
854+
"name": "my-tls-cert",
855+
"namespace": "default",
856+
},
857+
"spec": map[string]any{
858+
"secretName": "other-secret",
859+
"commonName": "my-ca",
860+
"isCA": true,
861+
},
862+
},
863+
},
864+
},
865+
},
866+
selector: kubebindv1alpha2.Selector{
867+
References: []kubebindv1alpha2.SelectorReference{
868+
{
869+
GroupResource: kubebindv1alpha2.GroupResource{
870+
Group: "cert-manager.io",
871+
Resource: "certificates",
872+
},
873+
JSONPath: &kubebindv1alpha2.JSONPath{
874+
Name: "spec.secretName",
875+
},
876+
},
877+
},
878+
},
879+
obj: &unstructured.Unstructured{
880+
Object: map[string]any{
881+
"apiVersion": "v1",
882+
"kind": "Secret",
883+
"metadata": map[string]any{
884+
"name": "my-tls-cert",
885+
"namespace": "default",
886+
},
887+
"type": "kubernetes.io/tls",
888+
},
889+
},
890+
want: false, // Should not match because certificate's .spec.secretName is "other-secret", not "my-tls-cert"
891+
},
892+
{
893+
name: "cert-manager secret reference - cross-namespace scenario with no namespace JSONPath",
894+
potentiallyReferencedResources: &unstructured.UnstructuredList{
895+
Items: []unstructured.Unstructured{
896+
{
897+
Object: map[string]any{
898+
"apiVersion": "cert-manager.io/v1",
899+
"kind": "Certificate",
900+
"metadata": map[string]any{
901+
"name": "my-tls-cert",
902+
"namespace": "default",
903+
},
904+
"spec": map[string]any{
905+
"secretName": "my-tls-cert",
906+
"commonName": "my-ca",
907+
"isCA": true,
908+
},
909+
},
910+
},
911+
},
912+
},
913+
selector: kubebindv1alpha2.Selector{
914+
References: []kubebindv1alpha2.SelectorReference{
915+
{
916+
GroupResource: kubebindv1alpha2.GroupResource{
917+
Group: "cert-manager.io",
918+
Resource: "certificates",
919+
},
920+
JSONPath: &kubebindv1alpha2.JSONPath{
921+
Name: "spec.secretName",
922+
},
923+
},
924+
},
925+
},
926+
obj: &unstructured.Unstructured{
927+
Object: map[string]any{
928+
"apiVersion": "v1",
929+
"kind": "Secret",
930+
"metadata": map[string]any{
931+
"name": "my-tls-cert",
932+
"namespace": "remapped-namespace", // Different namespace simulating provider-side remapping
933+
},
934+
"type": "kubernetes.io/tls",
935+
},
936+
},
937+
want: true, // Should match because when no namespace JSONPath is provided, namespace matching is delegated to caller
938+
},
791939
}
792940

793941
for _, tt := range tests {

0 commit comments

Comments
 (0)