Skip to content
Merged
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
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ OS := $(shell go env GOOS)
KUBE_MAJOR_VERSION := 1
KUBE_MINOR_VERSION := $(shell go mod edit -json | jq '.Require[] | select(.Path == "k8s.io/client-go") | .Version' --raw-output | sed "s/v[0-9]*\.\([0-9]*\).*/\1/")
GIT_COMMIT := $(shell git rev-parse --short HEAD || echo 'local')
GIT_DIRTY := $(shell git diff --quiet && echo 'clean' || echo 'dirty')
# --quiet would still produces output when files are deleted
GIT_DIRTY := $(shell git diff --quiet >/dev/null && echo 'clean' || echo 'dirty')
GIT_VERSION := $(shell go mod edit -json | jq '.Require[] | select(.Path == "k8s.io/client-go") | .Version' --raw-output | sed 's/v0/v1/')+kube-bind-$(shell git describe --tags --match='v*' --abbrev=14 "$(GIT_COMMIT)^{commit}" 2>/dev/null || echo v0.0.0-$(GIT_COMMIT))
BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
LDFLAGS := \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ $ kubectl krew index add bind https://github.com/kube-bind/krew-index.git
$ kubectl krew install bind/bind
$ kubectl bind login https://mangodb
$ kubectl bind
Redirect to the brower to authenticate via OIDC.
Redirect to the browser to authenticate via OIDC.
BOOM – the MangoDB API is available in the local cluster,
without anything MangoDB-specific running.
$ kubectl get mangodbs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ func (r *reconciler) ensureBoundSchemas(ctx context.Context, cl client.Client, c
continue
}

// If namespaced isolation is configured for cluster-scoped objects,
// we need to rewrite the BoundSchema's scope accordingly. For all
// other isolation strategies, as well as for namespaced schemas,
// no changes are necessary.
if boundSchema.Spec.Scope == apiextensionsv1.NamespaceScoped && r.clusterScopedIsolation == kubebindv1alpha2.IsolationNamespaced {
boundSchema.Spec.Scope = apiextensionsv1.ClusterScoped
}

if err := r.createBoundSchema(ctx, cl, boundSchema); err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion backend/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (s *Server) Config(callbackURL, issuerURL string) (*Config, error) {
c := &Config{
ClientID: s.server.Config().ClientID,
ClientSecret: s.server.Config().ClientSecret,
Issuer: issuerURL, // This overrided default fake OIDC issuer URL. Must match what it is served at.
Issuer: issuerURL, // This overrides default fake OIDC issuer URL. Must match what it is served at.

AccessTTL: s.server.Config().AccessTTL,
RefreshTTL: s.server.Config().RefreshTTL,
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/kubectl-bind/cmd/kubectlBind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestKubectlBindCommand(t *testing.T) {

require.Equal(t, "kubectl-bind", rootCmd.Use, "Unexpected one-line command description")
require.Equal(t, "kubectl plugin for kube-bind, bind different remote types into the current cluster.", rootCmd.Short, "Unexpected short command description")
require.Contains(t, rootCmd.Long, "To bind a remote service, use the 'kubectl bind' command.", "Unexpected lond command Long")
require.Contains(t, rootCmd.Long, "To bind a remote service, use the 'kubectl bind' command.", "Unexpected long command")
require.Equal(t, rootCmd.Example, fmt.Sprintf(bindcmd.BindExampleUses, "kubectl"), "Unexpected command Example")
}

Expand Down
2 changes: 1 addition & 1 deletion cli/pkg/kubectl/bind-login/plugin/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type LoginOptions struct {
}

// TokenResponse represents the response from the OAuth callback
// Important: this stuct must match one on backend/auth/types.go
// Important: this struct must match one on backend/auth/types.go
type TokenResponse struct {
// OAuth2 token fields
AccessToken string `json:"access_token"`
Expand Down
2 changes: 1 addition & 1 deletion contrib/kcp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ replace (
// Can use versioned when v0.28.2 releases
replace github.com/kcp-dev/kcp/sdk => github.com/kcp-dev/kcp/sdk v0.28.1-0.20251003164010-742ce0ea6b8c

// k/k 1.34 is leaking from main repo. This pins some deps to force depdendency tree to be on 1.34
// k/k 1.34 is leaking from main repo. This pins some deps to force dependency tree to be on 1.34
replace (
github.com/google/gnostic-models => github.com/google/gnostic-models v0.6.9
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
Expand Down
2 changes: 1 addition & 1 deletion contrib/kcp/test/e2e/kcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func testKcpIntegration(t *testing.T, name string, scope kubebindv1alpha2.Inform
// Can assume that the last entry is now the cluster-id, grab it and
// sanity check that it's not empty
providerClusterID := providerClusterSplit[len(providerClusterSplit)-1]
require.NotEmpty(t, providerClusterID, "Retreived cluster id is empty, source URL: %s", providerCluster.Status.URL)
require.NotEmpty(t, providerClusterID, "Retrieved cluster id is empty, source URL: %s", providerCluster.Status.URL)

// kube-bind process
t.Log("Perform binding process with browser")
Expand Down
4 changes: 3 additions & 1 deletion docs/content/usage/.pages
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
nav:
- api-concepts.md
- index.md
- api-concepts.md
- synchronization.md
22 changes: 17 additions & 5 deletions docs/content/usage/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,60 +14,72 @@ This section provides comprehensive documentation on how to use kube-bind's core
kube-bind operates on three fundamental concepts:

### Service Provider

The cluster that **exports** APIs and resources, making them available for other clusters to consume. Service providers create templates and handle permission claims.

### Service Consumer
### Service Consumer

The cluster that **imports** and uses APIs from service providers. Consumers bind to templates and get access to resources through a secure, controlled process.

### Konnector Agent

The component that establishes and maintains the secure connection between provider and consumer clusters, synchronizing resources and handling permissions.

## Key API Types

### APIServiceExportTemplate

**Purpose**: Defines a reusable service template that groups related CRDs and permission claims.
**Used by**: Service providers
**Scope**: Template definition for multiple consumers

### APIServiceExport

**Purpose**: Represents an active export of a specific CRD to consumer clusters.
**Used by**: Automatically created by konnector agents
**Scope**: Per-CRD export instance

### APIServiceExportRequest

**Purpose**: Consumer's request to bind to a specific service template.
**Used by**: Service consumers (via CLI/UI)
**Scope**: Per-binding request

### APIServiceNamespace

**Purpose**: Manages namespace mapping and isolation between provider and consumer clusters.
**Used by**: Automatically managed by konnector agents
**Scope**: Per-namespace sync

## Documentation Structure

### [API Concepts](api-concepts.md)

Deep dive into the core API types, their relationships, and how they work together in the kube-bind ecosystem.

### [Template References](template-references.md)

Advanced guide for using dynamic resource selection through references in templates.

## Common Workflows

### For Service Providers

1. **Create templates** defining what APIs and resources to export, including permission claims
2. **Implement service** to act on the synced/bound objects so it can be returned to the consumer/user.

### For Service Consumers
### For Service Consumers

1. **Authenticate** to the kube-bind backend
1. **Discover available templates** through the web UI or CLI
2. **Request bindings** to specific templates
3. **Authenticate and authorize** access through OAuth2 flows
4. **Use imported APIs** in their local cluster

### For Platform Operators

1. **Deploy kube-bind infrastructure** on both provider and consumer sides (if using GitOps)
2. **Configure authentication** and security policies
2. **Configure authentication** and security policies
3. **Monitor connections** and resource synchronization

## Getting Started
Expand All @@ -79,9 +91,9 @@ If you're new to kube-bind:
3. **Explore [Template References](template-references.md)** for advanced use cases
4. **Check the [Reference Documentation](../reference/)** for complete API specifications


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

- **API schema synchronization** from provider to consumer
- **Resource data flow** based on permission claims
- **Namespace isolation** and mapping
- **Authentication and authorization** enforcement
- **Authentication and authorization** enforcement
173 changes: 173 additions & 0 deletions docs/content/usage/synchronization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
title: Resource Synchronization
description: |
Overview over the general resource synchronization logic and different isolation modes.
weight: 220
---

# Resource Synchronization

This document describes the way kube-bind synchronizes Kubernetes resources across clusters.

## Overview

kube-bind synchronizes objects between two kinds of clusters:

* The **provider cluster** is run by a service provider and hosts a service, operator, or any other kind of Kubernetes API. This is also where there kube-bind **backend** is running in order to offer these APIs to consumers.
* The **consumer clusters** are where endusers consume services/APIs offered by providers.

There exists a 1:n relationship, where one provider cluster can be connected to from many different consumer clusters.

### Cluster Namespaces

In kube-bind, each successful `bind` operation yields a new, so-called "cluster namespace" on the provider cluster. This namespace contains all kube-bind-related resources, like `APIServiceExports` or `BoundSchemas` and is communicated to the consumer by being included in the provided kubeconfig.

By default the cluster namespaces are named `kube-bind-[random string]`, like `kube-bind-hd73s`. Their name also serves as a unique identifier for this "contract" between consumer and provider and is used in other places, for example as a prefix in the `prefixed` cluster isolation mode.

### Sync Direction

In the kube-bind architecture, the consumer clusters represent the source of truth (the actual desired state by the enduser) and the provider clusters merely contain copies of those objects.

During the synchronization,

* the **spec** (desired state) of an object is copied from the consumer cluster to the provider and
* the **status** (if any) is copied in the opposite direction, to the consumer.

kube-bind will continuously watch the object and its copy on both clusters and update the other side as needed.

### Connectivity

The object synchronization logic lives in the kube-bind **konnector**, a Kubernetes agent that runs on each *consumer cluster*. It reads a local Secret that contains a kubeconfig pointing to **a specific namespace** on the *provider cluster*. In this namespace the konnector will find all resources describing the resources to sync (chiefly `APIServiceExports`, `APIServiceNamespaces` and `BoundSchemas`).

!!! note
This kubeconfig is automatically generated as part of the `kubectl bind` handshake with a service provider.

This design allows consumer clusters to be mostly firewalled off, but requires provider clusters to not only be reachable from all consumers, but also to ensure multiple consumers do not conflict with each other. This is achieved by a combination of RBAC and isolation modes, which are described further down in this document.

### RBAC

Great care must be taken to ensure multiple consumers do not collide with each other on a single provider cluster. To achieve this, kube-bind offers two different informer scoping options:

* `namespaced` will make the konnector watch and inform on each relevant namespace on the provider cluster individually.
* `cluster` will instead make the konnector use a cluster-scoped (global) informer. This scoping method requires the konnector to have much wider permissions, but is more performant.

Regardless of scoping mode, the konnector itself will take care to not overwrite/touch other consumers' objects. This however does not mean that an attacker with access to the konnector kubeconfig could not exploit an RBAC policy that is too wide.

## Cluster Isolation

This section outlines how kube-bind deals with differently scoped Kubernetes objects.

In Kubernetes, an object can be either namespaced or cluster-scoped. This scope greatly affects how kube-bind processes the object.

### Namespaced

For namespaced objects (like a `Deployment`), kube-bind will map the consumer-side namespace to a unique, but random provider-side namespace: first the konnector will create an `APIServiceNamespace` object on the provider cluster (inside the cluster namespace), with the name of the consumer-side namespace (so a namespace `app1` will lead to an `APIServiceNamespace` `app1` in the cluster namespace). After this, the konnector waits until the backend has assigned this `APIServiceNamespace` a provider-side namespace to use. Once that namespace is present in the `APIServiceNamespace`'s status, the konnector will use it for all objects originating in the same consumer-side namespace.

In YAML, this means

```yaml
# consumer-side object

apiVersion: provider.example.com/v1
kind: MangoDB
metadata:
name: my-first-db
namespace: team1
spec:
size: large
```

will lead to

```yaml
# provider-side objects

apiVersion: kube-bind.io/v1alpha2
kind: APIServiceNamespace
metadata:
name: team1 # name of the namespace from the consumer
namespace: kube-bind-hd73d # cluster namespace
spec: {}
status:
# a common implementation in the backend is to construct the provider-side
# namespace by just concatenating the two values, like so:
namespace: kube-bind-hd73d-team1

---
apiVersion: v1
kind: Namespace
metadata:
name: kube-bind-hd73d-team1

---
apiVersion: provider.example.com/v1
kind: MangoDB
metadata:
name: my-first-db
namespace: kube-bind-hd73d-team1
spec:
size: large
```

!!! note
For natively namespaced resources (those that are namespaced in both the provider and consumer cluster), this is always the strategy being used by the konnector. The other isolation modes only influence how the konnector deals with cluster-scoped objects in the consumer cluster.

### Cluster-Scoped

Cluster-scoped objects require a different approach to isolation than namespaced objects. kube-bind offers three different so-called isolation strategies to deal with them. The strategy to use is configured globally via the `--cluster-scoped-isolation` CLI flag on the kube-bind backend and will from there affect all services offered by that backend.

#### None Strategy

The `none` strategy provides no consumer-separation at all. Any cluster-scoped object on the consumer side is copied 1:1 to the provider side.

!!! warning
Due to the obvious downsides of this approach, `none` should be used only in special circumstances.

#### Prefixed Strategy

The `prefixed` strategy is kube-bind's default strategy and will use the name of the cluster namespace as a prefix for object names.

In YAML, this means

```yaml
# consumer-side
apiVersion: provider.example.com/v1
kind: MangoDB
metadata:
name: my-first-db
spec:
size: large
```

will lead to

```yaml
# provider-side
apiVersion: provider.example.com/v1
kind: MangoDB
metadata:
name: kube-bind-hd73d-my-first-db
spec:
size: large
```

This strategy works well for separating objects, but

* requires a broad RBAC policy, which will always grant too many permissions, and
* would technically break for Kubernetes objects with names close to the maximum allowed length (253 characters).

#### Namespaced Strategy

!!! note
Not to be confused with the strategy used for natively namespaced resources, described earlier.

The `namespaced` strategy will convert cluster-scoped objects into namespaced ones.

For this to work, the original CRD on the provider cluster has to be **namespaced**. The backend will turn it into a cluster-scoped CRD (stored in the `BoundSchema`), so on the consumer cluster, all objects are cluster-scoped.

During synchronization, the konnector will then place each cluster-scoped object into the cluster namespace (`kube-bind-hd73d`) on the provider side.

This strategy provides excellent isolation between consumers, but requires that the original CRD from the provider still makes sense to the consumer when it's suddenly cluster-scoped. For example, if references were to be used, especially to Secrets in the same namespace, this concept would get mangled to some degree during the synchronization.

!!! warning
At the moment, when the backend is started with `--cluster-scoped-isolation=namespaced`, it will convert **all namespaced CRDs** in all ServiceExports to become cluster-scoped on the consumer side, even if you intended for a namespaced CRD to stay namespaced on both sides of the sync.
3 changes: 3 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ mkdocs-macros-plugin==1.0.5
mkdocs-material==9.5.49
mkdocs-material-extensions==1.3.1
mkdocs-static-i18n==1.2.2

# https://github.com/mkdocs/mkdocs/issues/4032
click<=8.2.1
Loading