diff --git a/api/gen/proto/go/teleport/workloadcluster/v1/workloadcluster.pb.go b/api/gen/proto/go/teleport/workloadcluster/v1/workloadcluster.pb.go
index 632f1de67fe..11719c3d61e 100644
--- a/api/gen/proto/go/teleport/workloadcluster/v1/workloadcluster.pb.go
+++ b/api/gen/proto/go/teleport/workloadcluster/v1/workloadcluster.pb.go
@@ -231,7 +231,7 @@ func (x *Region) GetName() string {
// Bot configures a bot for the child Teleport Cloud cluster
type Bot struct {
state protoimpl.MessageState `protogen:"open.v1"`
- // name is the name of the bot, token, and role that will be created
+ // name of the bot, token, and role that will be created
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
diff --git a/api/proto/teleport/workloadcluster/v1/workloadcluster.proto b/api/proto/teleport/workloadcluster/v1/workloadcluster.proto
index 1799fb36769..757fa94abf8 100644
--- a/api/proto/teleport/workloadcluster/v1/workloadcluster.proto
+++ b/api/proto/teleport/workloadcluster/v1/workloadcluster.proto
@@ -46,7 +46,7 @@ message Region {
// Bot configures a bot for the child Teleport Cloud cluster
message Bot {
- // name is the name of the bot, token, and role that will be created
+ // name of the bot, token, and role that will be created
string name = 1;
}
diff --git a/build.assets/tooling/cmd/resource-ref-generator/config.yaml b/build.assets/tooling/cmd/resource-ref-generator/config.yaml
index 26cccc7e3c2..9d4f2659d27 100644
--- a/build.assets/tooling/cmd/resource-ref-generator/config.yaml
+++ b/build.assets/tooling/cmd/resource-ref-generator/config.yaml
@@ -133,6 +133,10 @@ resources:
package: github.com/gravitational/teleport/api/types
yaml_kind: node
yaml_version: v2
+ - type: WorkloadCluster
+ package: github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadcluster/v1
+ yaml_kind: workload_cluster
+ yaml_version: v1
camel_case_exceptions:
- AWS
diff --git a/build.assets/tooling/cmd/resource-ref-generator/resource_examples/workload_cluster.yaml b/build.assets/tooling/cmd/resource-ref-generator/resource_examples/workload_cluster.yaml
new file mode 100644
index 00000000000..a1e9e6bced2
--- /dev/null
+++ b/build.assets/tooling/cmd/resource-ref-generator/resource_examples/workload_cluster.yaml
@@ -0,0 +1,23 @@
+kind: workload_cluster
+version: v1
+metadata:
+ name: company-organization
+spec:
+ regions:
+ - name: us-west-2
+ bot:
+ # Teleport Cloud creates a bot, token, and role with this name
+ # in the child cluster.
+ name: example-iam
+ token:
+ # Allow tbot to join the child cluster using the below IAM assumed role.
+ join_method: iam
+ allow:
+ - aws_account: "123456789012"
+ aws_arn: "arn:aws:sts::123456789012:assumed-role/example-tbot-role/session-name"
+# Status will be set by Teleport with status from Teleport Cloud.
+status:
+ # The Teleport Proxy address of the child cluster.
+ domain: company-organization.teleport.sh
+ # The current state of the child cluster in Teleport Cloud.
+ state: active
diff --git a/docs/cspell.json b/docs/cspell.json
index 33e32c50fc5..31b06e3d337 100644
--- a/docs/cspell.json
+++ b/docs/cspell.json
@@ -525,6 +525,7 @@
"enzos",
"errcode",
"errgroup",
+ "errorf",
"etcdctl",
"evtx",
"exadata",
@@ -595,6 +596,7 @@
"gworkspace",
"hashfile",
"hashicorp",
+ "headerv",
"healthcheck",
"healthchecks",
"healthz",
@@ -837,6 +839,7 @@
"pagerduty",
"pagerdutyapikey",
"pagerdutyapikeyfromsecret",
+ "panicf",
"paramiko",
"parquetlog",
"pastable",
@@ -1057,6 +1060,7 @@
"timechart",
"timedatectl",
"timekey",
+ "timeoutctx",
"timesearch",
"timesync",
"timesyncd",
@@ -1136,6 +1140,7 @@
"winscp",
"winserver",
"workgroups",
+ "workloadcluster",
"wtmp",
"xample",
"xauth",
diff --git a/docs/pages/reference/infrastructure-as-code/teleport-resources/teleport-resources.mdx b/docs/pages/reference/infrastructure-as-code/teleport-resources/teleport-resources.mdx
index 9ac2b1534c4..4e2463caf2b 100644
--- a/docs/pages/reference/infrastructure-as-code/teleport-resources/teleport-resources.mdx
+++ b/docs/pages/reference/infrastructure-as-code/teleport-resources/teleport-resources.mdx
@@ -70,3 +70,4 @@ Here's the list of resources currently exposed via [`tctl`](../../cli/tctl.mdx):
| [inference_secret](inference-secret.mdx) | Session summarization AI provider secret (API key) |
| [inference_policy](inference-policy.mdx)| Matches sessions to inference models using session kind and other metadata |
| [discovery_config](discovery-config.mdx)| Auto discovery configurations |
+| [workload_cluster](workload-cluster.mdx) | A child Teleport Cloud cluster managed from a parent Teleport Cloud cluster. |
diff --git a/docs/pages/reference/infrastructure-as-code/teleport-resources/workload-cluster.mdx b/docs/pages/reference/infrastructure-as-code/teleport-resources/workload-cluster.mdx
new file mode 100644
index 00000000000..ce6b57680da
--- /dev/null
+++ b/docs/pages/reference/infrastructure-as-code/teleport-resources/workload-cluster.mdx
@@ -0,0 +1,198 @@
+---
+title: Workload Cluster Reference
+description: Provides a reference of fields within the Workload Cluster resource, which you can manage with tctl.
+sidebar_label: Workload Cluster
+---
+{/* vale 3rd-party-products.former-names = NO */}
+{/* vale messaging.capitalization = NO */}
+{/* Automatically generated from: gen/proto/go/teleport/workloadcluster/v1/workloadcluster.pb.go */}
+{/* DO NOT EDIT */}
+
+**Kind**: `workload_cluster`
+**Version**: `v1`
+
+Represents a child Teleport Cloud cluster
+
+Example definition:
+```yaml
+kind: workload_cluster
+version: v1
+metadata:
+ name: company-organization
+spec:
+ regions:
+ - name: us-west-2
+ bot:
+ # Teleport Cloud creates a bot, token, and role with this name
+ # in the child cluster.
+ name: example-iam
+ token:
+ # Allow tbot to join the child cluster using the below IAM assumed role.
+ join_method: iam
+ allow:
+ - aws_account: "123456789012"
+ aws_arn: "arn:aws:sts::123456789012:assumed-role/example-tbot-role/session-name"
+# Status will be set by Teleport with status from Teleport Cloud.
+status:
+ # The Teleport Proxy address of the child cluster.
+ domain: company-organization.teleport.sh
+ # The current state of the child cluster in Teleport Cloud.
+ state: active
+
+```
+
+## Top-level fields
+
+Example:
+
+```yaml
+kind: "string"
+sub_kind: "string"
+version: "string"
+metadata: # [...]
+spec: # [...]
+status: # [...]
+```
+|Field Name|Description|Type|
+|---|---|---|
+|kind||string|
+|metadata||[Metadata](#metadata)|
+|spec||[Workload Cluster Spec](#workload-cluster-spec)|
+|status||[Workload Cluster Status](#workload-cluster-status)|
+|sub_kind||string|
+|version||string|
+
+## Allow
+
+Configures rules for using the corresponding join method
+
+
+Example:
+
+```yaml
+aws_account: "string"
+aws_arn: "string"
+```
+
+|Field Name|Description|Type|
+|---|---|---|
+|aws_account||string|
+|aws_arn||string|
+
+## Bot
+
+Configures a bot for the child Teleport Cloud cluster
+
+
+Example:
+
+```yaml
+name: "string"
+```
+
+|Field Name|Description|Type|
+|---|---|---|
+|name|Name of the bot, token, and role that will be created|string|
+
+## Metadata
+
+Resource metadata.
+
+
+Example:
+
+```yaml
+name: "string"
+namespace: "string"
+description: "string"
+labels:
+ "string": "string"
+ "string": "string"
+ "string": "string"
+expires: # See description
+revision: "string"
+```
+
+|Field Name|Description|Type|
+|---|---|---|
+|description|Description is object description.|string|
+|expires|Expires is a global expiry time header can be set on any resource in the system.||
+|labels|Labels is a set of labels.|map[string]string|
+|name|Name is an object name.|string|
+|namespace|Namespace is object namespace. The field should be called "namespace" when it returns in Teleport 2.4.|string|
+|revision|Revision is an opaque identifier which tracks the versions of a resource over time. Clients should ignore and not alter its value but must return the revision in any updates of a resource.|string|
+
+## Region
+
+Configures a region for deploying Teleport services
+
+
+Example:
+
+```yaml
+name: "string"
+```
+
+|Field Name|Description|Type|
+|---|---|---|
+|name|Name is the region's name|string|
+
+## Token
+
+Configures a token for a bot
+
+
+Example:
+
+```yaml
+join_method: "string"
+allow:
+ - # [...]
+ - # [...]
+ - # [...]
+```
+
+|Field Name|Description|Type|
+|---|---|---|
+|allow|Allow defines the AWS Accounts and ARNs to allow joining from|[][Allow](#allow)|
+|join_method|Join_method is type of join method to allow for the token|string|
+
+## Workload Cluster Spec
+
+Configures where a child Teleport Cloud cluster should be created and the initial bot
+
+
+Example:
+
+```yaml
+regions:
+ - # [...]
+ - # [...]
+ - # [...]
+bot: # [...]
+token: # [...]
+```
+
+|Field Name|Description|Type|
+|---|---|---|
+|bot||[Bot](#bot)|
+|regions||[][Region](#region)|
+|token||[Token](#token)|
+
+## Workload Cluster Status
+
+Populated with status from Teleport Cloud about the child Teleport Cloud cluster
+
+
+Example:
+
+```yaml
+state: "string"
+domain: "string"
+```
+
+|Field Name|Description|Type|
+|---|---|---|
+|domain|Domain is the Teleport Proxy address of the child Teleport Cloud cluster|string|
+|state|State is the status of the child cluster running in Teleport Cloud|string|
+
diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/workload_cluster.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/workload_cluster.mdx
index b03b08887cf..a1605399081 100644
--- a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/workload_cluster.mdx
+++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/workload_cluster.mdx
@@ -57,7 +57,7 @@ Optional:
Optional:
-- `name` (String) name is the name of the bot, token, and role that will be created
+- `name` (String) name of the bot, token, and role that will be created
### Nested Schema for `spec.regions`
diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/workload_cluster.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/workload_cluster.mdx
index b4cbaa2f942..37aa649afe1 100644
--- a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/workload_cluster.mdx
+++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/workload_cluster.mdx
@@ -88,7 +88,7 @@ Optional:
Optional:
-- `name` (String) name is the name of the bot, token, and role that will be created
+- `name` (String) name of the bot, token, and role that will be created
### Nested Schema for `spec.regions`
diff --git a/docs/pages/zero-trust-access/management/workload-clusters.mdx b/docs/pages/zero-trust-access/management/workload-clusters.mdx
new file mode 100644
index 00000000000..99369d5b8b2
--- /dev/null
+++ b/docs/pages/zero-trust-access/management/workload-clusters.mdx
@@ -0,0 +1,342 @@
+---
+title: Workload Clusters
+sidebar_label: Workload Clusters
+description: How to create and manage Teleport Cloud workload clusters using tctl and Machine & Workload Identity
+tags:
+ - how-to
+ - mwi
+ - infrastructure-identity
+---
+
+`workload_cluster` resources can be used to automatically provision and
+deprovision Teleport Cloud clusters. When a workload cluster is created,
+Teleport Cloud also creates a Bot, token, and role in the provisioned Teleport
+Cloud cluster so that automation can create, read, and update users and roles
+in that cluster.
+
+This guide covers how to:
+
+- Create a `workload_cluster` resource in a parent Teleport Cloud cluster.
+- Wait for the child Teleport Cloud cluster to become active.
+- Configure `tbot` to join the child Teleport Cloud cluster.
+- Use the identity generated by `tbot` to create, read, and update users and
+ roles in the child Teleport Cloud cluster with `tctl`.
+
+## How it works
+
+Creating a `workload_cluster` resource in a parent Teleport Cloud cluster tells
+Teleport Cloud to provision a child Teleport Cloud cluster.
+
+A child Teleport Cloud cluster is a separate Teleport Cloud cluster whose
+lifecycle is managed from a parent Teleport Cloud cluster. It is not the same
+thing as a trusted cluster.
+
+Changes to the parent Teleport Cloud cluster do not affect the child Teleport
+Cloud cluster. The parent Teleport Cloud cluster can only provision and
+deprovision child Teleport Cloud clusters.
+
+When the child Teleport Cloud cluster is provisioned, Teleport Cloud also
+creates:
+
+- A Bot in the child Teleport Cloud cluster
+- A join token for that Bot
+- A role for that Bot
+
+That Bot can then join the child Teleport Cloud cluster using IAM joining via
+`tbot`. The identity generated by `tbot` can be used to create, read, and
+update users and roles in the child Teleport Cloud cluster.
+
+## Prerequisites
+
+This guide uses AWS IAM joining for the child Teleport Cloud cluster Bot because
+`workload_cluster` currently supports IAM joining only. Learn more
+about IAM joining in the
+[join methods reference](../../reference/deployment/join-methods.mdx#aws-iam-role-iam).
+
+- A Teleport Cloud cluster to use as the parent cluster.
+
+
+Teleport Cloud customers must contact support at
+support@goteleport.com to enable
+the workload cluster feature.
+
+
+- `tctl` and `tbot` installed. For installation and deployment guidance, see
+ the [Machine & Workload Identity deployment guides](../../machine-workload-identity/deployment/deployment.mdx).
+- An AWS identity that matches the IAM join token rules configured for the
+ child Teleport Cloud cluster Bot.
+
+## Step 1/5. Configure RBAC in the parent cluster
+
+To create and delete `workload_cluster` resources, the Teleport user in the
+parent cluster needs permission to manage that resource type.
+
+If the user already has access to manage `workload_cluster` resources, skip
+this step.
+
+Create a file called `workload-cluster-editor.yaml`:
+
+ ```yaml
+ kind: role
+ version: v8
+ metadata:
+ name: workload-cluster-editor
+ spec:
+ allow:
+ rules:
+ - resources:
+ - workload_cluster
+ verbs:
+ - create
+ - delete
+ - list
+ - read
+ - update
+ ```
+
+Use `tctl` to create the role:
+
+ ```code
+ $ tctl create -f workload-cluster-editor.yaml
+ ```
+
+Assign the role to a user in the parent cluster. Replace `alice` with the
+Teleport username and include any roles that user already needs:
+
+ ```code
+ $ tctl users update --set-roles editor,workload-cluster-editor alice
+ ```
+
+## Step 2/5. Create a workload cluster
+
+Create a file called `workload-cluster.yaml`:
+
+ ```yaml
+ kind: workload_cluster
+ version: v1
+ metadata:
+ name: company-organization
+ spec:
+ regions:
+ - name: us-west-2
+ bot:
+ name: example-iam
+ token:
+ join_method: iam
+ allow:
+ - aws_account: "123456789012"
+ aws_arn: "arn:aws:sts::123456789012:assumed-role/example-tbot-role/session-name"
+ ```
+
+Replace:
+
+- `company-organization` with the name of the Teleport Cloud cluster that Teleport Cloud
+ should create.
+- `us-west-2` with the Teleport Cloud region for the child Teleport Cloud
+ cluster. See [Teleport Cloud architecture](../../reference/architecture/teleport-cloud-architecture.mdx#auth-service)
+ for available auth regions.
+- `example-iam` with the Bot and token name that `tbot` will use.
+- `123456789012` and `arn:aws:sts::123456789012:assumed-role/example-tbot-role/session-name`
+ with values that match the AWS identity from which `tbot` will join.
+
+Use `tctl` to create the resource in the parent cluster:
+
+ ```code
+ $ tctl --auth-server parent.teleport.sh:443 create -f workload-cluster.yaml
+ ```
+
+
+After creation, workload cluster configuration cannot be modified.
+
+
+The Teleport Cloud cluster domain is derived from the resource name. In this
+example, the child Teleport Cloud cluster will use the domain
+`company-organization.teleport.sh`.
+
+## Step 3/5. Wait for the child Teleport Cloud cluster to become active
+
+Retrieve the resource until the `status.state` field is `active`:
+
+ ```code
+ $ tctl --auth-server parent.teleport.sh:443 get workload_cluster/company-organization
+ ```
+
+Example output:
+
+ ```yaml
+ kind: workload_cluster
+ version: v1
+ metadata:
+ name: company-organization
+ spec:
+ regions:
+ - name: us-west-2
+ bot:
+ name: example-iam
+ token:
+ join_method: iam
+ allow:
+ - aws_account: "123456789012"
+ aws_arn: "arn:aws:sts::123456789012:assumed-role/example-tbot-role/session-name"
+ status:
+ domain: company-organization.teleport.sh
+ state: active
+ ```
+
+Once the resource is active, use the `status.domain` value when connecting
+`tbot` and `tctl` to the child Teleport Cloud cluster.
+
+## Step 4/5. Use `tbot` to join the child Teleport Cloud cluster
+
+Create a file called `tbot.yaml`:
+
+ ```yaml
+ version: v2
+ oneshot: true
+ proxy_server: company-organization.teleport.sh:443
+ onboarding:
+ join_method: iam
+ token: example-iam
+ storage:
+ type: memory
+ services:
+ - type: identity
+ destination:
+ type: directory
+ path: ./tbot-output
+ ```
+
+Replace:
+
+- `company-organization.teleport.sh:443` with the child Teleport Cloud cluster's proxy
+ address from
+ `status.domain`.
+- `example-iam` with the Bot name from `spec.bot.name`.
+
+Before running `tbot`, authenticate to AWS using an identity that satisfies the
+token's IAM join rules.
+
+Then run `tbot`:
+
+ ```code
+ $ tbot start -c ./tbot.yaml
+ ```
+
+This writes an identity file to `./tbot-output/identity`. Use that
+identity with `tctl` to create, read, and update users and roles in the child
+Teleport Cloud cluster.
+
+## Step 5/5. Manage users and roles in the child Teleport Cloud cluster
+
+Create a role definition for the child Teleport Cloud cluster in
+`child-role.yaml`:
+
+ ```yaml
+ kind: role
+ version: v8
+ metadata:
+ name: child-auditor
+ spec:
+ allow:
+ rules:
+ - resources:
+ - role
+ - user
+ verbs:
+ - list
+ - read
+ ```
+
+Create the role in the child Teleport Cloud cluster using the identity
+produced by `tbot`:
+
+ ```code
+ $ tctl --auth-server company-organization.teleport.sh:443 -i ./tbot-output/identity create -f child-role.yaml
+ ```
+
+Now create a user in the child Teleport Cloud cluster and assign the new role:
+
+ ```code
+ $ tctl --auth-server company-organization.teleport.sh:443 -i ./tbot-output/identity users add --roles child-auditor example-user
+ ```
+
+The same identity can also inspect roles in the child Teleport Cloud cluster:
+
+ ```code
+ $ tctl --auth-server company-organization.teleport.sh:443 -i ./tbot-output/identity get role/child-auditor
+ ```
+
+## Delete the workload cluster
+
+When the child Teleport Cloud cluster is no longer needed, remove the
+`workload_cluster` resource from the parent cluster:
+
+ ```code
+ $ tctl --auth-server parent.teleport.sh:443 rm workload_cluster/company-organization
+ ```
+
+After the `workload_cluster` resource is deleted, Teleport Cloud keeps the
+child Teleport Cloud cluster in a 14-day grace period. During that period,
+the cluster name cannot be reused. Contact support at
+support@goteleport.com to restore
+access to the cluster or to request an expedited deletion.
+
+## Use the Go SDK instead of `tctl`
+
+Teleport's Go SDK can be used to automate this workflow instead of relying on
+client tools such as `tctl`.
+
+For general Teleport API guidance, see
+[Using the Teleport API](../api/api.mdx) and
+[Getting Started with the Teleport API](../api/getting-started.mdx).
+
+Teleport's repository includes an example using the Go SDK that does the following:
+
+- Creates a Teleport API client connected to the parent Teleport Cloud cluster.
+- Creates a `workload_cluster` resource in the parent Teleport Cloud cluster.
+- Waits for the `workload_cluster` resource to report being `active`.
+- Uses the `tbot` binary to retrieve an identity file for the child Teleport
+ Cloud cluster.
+- Creates a role and user in the child Teleport Cloud cluster.
+- Creates an invite link for the new user.
+- Deletes the `workload_cluster` resource in the parent Teleport Cloud cluster.
+
+Please follow the below steps to run the workload cluster Go example.
+
+1. Log in to the parent Teleport Cloud cluster (replace `parent.teleport.sh` with the Teleport Cloud cluster's address):
+
+ ```code
+ $ tsh login --proxy parent.teleport.sh
+ ```
+
+2. Clone the Teleport repository:
+
+ ```code
+ $ git clone --depth=1 https://github.com/gravitational/teleport
+ ```
+
+3. Navigate to the workload clusters example:
+
+ ```code
+ $ cd ./teleport/examples/workload-clusters/
+ ```
+
+4. Replace `parent.teleport.sh`, `company-organization`, `account`, and `arn`
+with the parent Teleport Cloud cluster address, desired workload cluster name, a valid AWS account ID, and
+a valid AWS ARN that `tbot` can use for IAM joining in `main.go`.
+
+5. Authenticate to AWS using the identity referenced in the
+`workload_cluster` resource, then run the Go program:
+
+ ```code
+ $ go run ./
+ ```
+
+## Next steps
+
+- Read the [Workload Cluster resource reference](../../reference/infrastructure-as-code/teleport-resources/workload-cluster.mdx)
+ for the complete schema.
+- Read the [Machine & Workload Identity configuration reference](../../reference/machine-workload-identity/configuration.mdx)
+ for more `tbot` options.
+- Review the [role resource reference](../../reference/infrastructure-as-code/teleport-resources/role.mdx)
+ to define more precise RBAC in parent and child Teleport Cloud clusters.
diff --git a/examples/workload-clusters/go.mod b/examples/workload-clusters/go.mod
new file mode 100644
index 00000000000..ca69add6096
--- /dev/null
+++ b/examples/workload-clusters/go.mod
@@ -0,0 +1,48 @@
+module workload-clusters
+
+go 1.25.9
+
+require github.com/gravitational/teleport/api v0.0.0-20260428152919-24b2608bf3f5
+
+require (
+ github.com/beevik/etree v1.6.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/charlievieth/strcase v0.0.5 // indirect
+ github.com/coreos/go-semver v0.3.1 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-piv/piv-go/v2 v2.5.0 // indirect
+ github.com/gobwas/httphead v0.1.0 // indirect
+ github.com/gobwas/pool v0.2.1 // indirect
+ github.com/gobwas/ws v1.4.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/gravitational/trace v1.5.3 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
+ github.com/jonboulle/clockwork v0.5.0 // indirect
+ github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
+ github.com/russellhaering/gosaml2 v0.11.0 // indirect
+ github.com/russellhaering/goxmldsig v1.6.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
+ go.opentelemetry.io/otel v1.43.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
+ go.opentelemetry.io/otel/metric v1.43.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.43.0 // indirect
+ go.opentelemetry.io/otel/trace v1.43.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.10.0 // indirect
+ golang.org/x/crypto v0.49.0 // indirect
+ golang.org/x/net v0.52.0 // indirect
+ golang.org/x/sys v0.42.0 // indirect
+ golang.org/x/term v0.41.0 // indirect
+ golang.org/x/text v0.35.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
+ google.golang.org/grpc v1.80.0 // indirect
+ google.golang.org/protobuf v1.36.11 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+)
diff --git a/examples/workload-clusters/go.sum b/examples/workload-clusters/go.sum
new file mode 100644
index 00000000000..9353c511f76
--- /dev/null
+++ b/examples/workload-clusters/go.sum
@@ -0,0 +1,144 @@
+github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE=
+github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/charlievieth/strcase v0.0.5 h1:gV4iXVyD6eI5KdfOV+/vIVCKXZwtCWOmDMcu7Uy00Rs=
+github.com/charlievieth/strcase v0.0.5/go.mod h1:FIOYY1aDBMSIOFqmVomHBpoK+bteGlESRsgsdWjrhx8=
+github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
+github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-piv/piv-go/v2 v2.5.0 h1:w4KZ3GytEGZt8zm+S7olcIHZk0giL23xVqCa2HgwuqA=
+github.com/go-piv/piv-go/v2 v2.5.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI=
+github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
+github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
+github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
+github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
+github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gravitational/teleport/api v0.0.0-20260428152919-24b2608bf3f5 h1:RRp06ZBvr/dVI3p3l+6hnToZhQdHzGA8uG4R+1qwr50=
+github.com/gravitational/teleport/api v0.0.0-20260428152919-24b2608bf3f5/go.mod h1:wC4lfXzs0yzFoKu2PJ6E6EDQr+nV3uJ3MZ6KhHG8yMk=
+github.com/gravitational/trace v1.5.3 h1:qP/BP7xFJBr83D5ctTClNiJUVQKlg6Q4qVPviK7sMfo=
+github.com/gravitational/trace v1.5.3/go.mod h1:/uCbC3ukVU8Pdrh8+3vNLyyE1aGheBWpGGrMnwIK80E=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
+github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
+github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
+github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/russellhaering/gosaml2 v0.11.0 h1:wlWm7dWMrpJBzh0xEOZof70nVen4f/2BEF8ZXaidJ9o=
+github.com/russellhaering/gosaml2 v0.11.0/go.mod h1:GmL5LeCP7PBYzSkkFxtmHuRzC2eUZ/6JSLYQd5fzKK4=
+github.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks=
+github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
+go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
+go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
+go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
+go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
+go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
+go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
+go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
+go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
+go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
+go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
+go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
+go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
+gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
+google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
+google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
+google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/examples/workload-clusters/main.go b/examples/workload-clusters/main.go
new file mode 100644
index 00000000000..ee30abf2e1d
--- /dev/null
+++ b/examples/workload-clusters/main.go
@@ -0,0 +1,367 @@
+/*
+Copyright 2026 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "time"
+
+ "github.com/gravitational/teleport/api/client"
+ "github.com/gravitational/teleport/api/client/proto"
+ headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
+ workloadclusterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadcluster/v1"
+ "github.com/gravitational/teleport/api/types"
+)
+
+var (
+ // awsAccount is the AWS account to allow tbot to use for joining
+ awsAccount = "account"
+ // awsARN is the AWS ARN to allow tbot to use for joining
+ awsARN = "arn"
+ // parentClusterProxyAddress is the parent Teleport Cloud cluster's proxy address
+ parentClusterProxyAddress = "parent.teleport.sh"
+ // workloadClusterName is the desired name for the new child Teleport Cloud cluster
+ workloadClusterName = "company-organization"
+)
+
+// TbotConfig defines a configuration for running tbot.
+type TbotConfig struct {
+ // Version is the configuration version.
+ Version string `json:"version"`
+ // Oneshot determines if tbot runs as a service.
+ Oneshot bool `json:"oneshot"`
+ // ProxyServer is the Teleport Proxy to run tbot against.
+ ProxyServer string `json:"proxy_server"`
+ // Onboarding defines how tbot should attempt to join the Teleport cluster.
+ Onboarding Onboarding `json:"onboarding"`
+ // Storage instructs tbot where to save its internal certificates.
+ Storage Storage `json:"storage"`
+ // Services defines which services for tbot to run.
+ Services []Service `json:"services"`
+}
+
+// Onboarding defines how tbot should attempt to join the Teleport cluster.
+type Onboarding struct {
+ // JoinMethod is how to join, such as iam.
+ JoinMethod string `json:"join_method"`
+ // Token is which token in the Teleport cluster to use.
+ Token string `json:"token"`
+}
+
+// Storage instructs tbot where to save its internal certificates.
+type Storage struct {
+ // Type is the storage type, such as "memory" for in-memory storage.
+ Type string `json:"type"`
+}
+
+// Service defines which services for tbot to run.
+type Service struct {
+ // Type is the service type, such as "identity".
+ Type string `json:"type"`
+ // Destination is used by the identity service to save retrieved identity files and certs.
+ Destination Destination `json:"destination"`
+}
+
+// Destination is used by the identity service to save retrieved identity files and certs.
+type Destination struct {
+ // Type is the type of storage, such as "path".
+ Type string `json:"type"`
+ // Path is the filepath to use.
+ Path string `json:"path"`
+}
+
+func main() {
+ if err := run(); err != nil {
+ log.Fatalf("Failed running workload_cluster lifecycle: %v", err)
+ }
+}
+
+func run() (err error) {
+ ctx := context.Background()
+
+ /**********************************************
+ * Create a Teleport workload_cluster resource *
+ **********************************************/
+
+ // parentClient is a Teleport client connected to the parent cluster, which
+ // assumes the identity used has access for creating, reading, and deleting
+ // workload_cluster resources.
+ parentClient, err := client.New(ctx, client.Config{
+ Addrs: []string{
+ parentClusterProxyAddress,
+ // Note: port is optional.
+ },
+ Credentials: []client.Credentials{
+ // this loads the credential from tsh
+ client.LoadProfile("", ""),
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create client: %w", err)
+ }
+ defer parentClient.Close()
+
+ // wc defines a workload_cluster to create in us-west-2 with a bot named
+ // example-iam and configuration for a token to use IAM joining.
+ // The created child Teleport Cloud cluster will have a bot, role, and token
+ // each named example-iam. The bot will have access to create, read, and
+ // update users and roles.
+ wc := &workloadclusterv1.WorkloadCluster{
+ Kind: types.KindWorkloadCluster,
+ Version: "v1",
+ Metadata: &headerv1.Metadata{
+ Name: workloadClusterName,
+ },
+ Spec: &workloadclusterv1.WorkloadClusterSpec{
+ Regions: []*workloadclusterv1.Region{
+ {
+ Name: "us-west-2",
+ },
+ },
+ Bot: &workloadclusterv1.Bot{
+ Name: "example-iam",
+ },
+ Token: &workloadclusterv1.Token{
+ JoinMethod: "iam",
+ Allow: []*workloadclusterv1.Allow{
+ {
+ AwsAccount: awsAccount,
+ AwsArn: awsARN,
+ },
+ },
+ },
+ },
+ }
+
+ // Create a workload_cluster resource within the parent Teleport Cloud cluster.
+ if _, err := parentClient.CreateWorkloadCluster(ctx, wc); err != nil {
+ return fmt.Errorf("failed to create workload cluster: %w", err)
+ }
+
+ defer func() {
+ /****************************
+ * Delete a workload cluster *
+ ****************************/
+
+ // Delete the workload cluster if any error is encountered or if whole lifecycle
+ // completes successfully.
+
+ // Clean up the previously created workload_cluster resource in the parent
+ // Teleport Cloud cluster.
+ if deleteErr := parentClient.DeleteWorkloadCluster(ctx, wc.Metadata.Name); deleteErr != nil {
+ err = errors.Join(err, fmt.Errorf("error deleting workload cluster: %w", deleteErr))
+ }
+ }()
+
+ // Wait for the created workload cluster to reach an active state.
+ timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Minute)
+ defer cancel()
+ wc, err = waitForActiveWorkloadCluster(timeoutCtx, parentClient, wc.Metadata.Name, 30*time.Second)
+ if err != nil {
+ return fmt.Errorf("failed waiting for workload cluster to be active: %w", err)
+ }
+
+ /************************************************
+ * Run tbot against child Teleport Cloud cluster *
+ ************************************************/
+
+ // Create a directory that will be used for tbot's configuration and saving a
+ // retrieved identity file for interacting with the child Teleport Cloud cluster.
+ tbotDir, err := os.MkdirTemp("", "")
+ if err != nil {
+ return fmt.Errorf("error creating directory for tbot: %w", err)
+ }
+ defer func() {
+ if err := os.RemoveAll(tbotDir); err != nil {
+ log.Printf("Error removing tbot directory %s: %v", tbotDir, err)
+ }
+ }()
+
+ // Create the tbot configuration.
+ tbotConfig := TbotConfig{
+ // Version must be v2.
+ Version: "v2",
+ // Oneshot should be true to avoid running tbot as a daemon.
+ Oneshot: true,
+ // ProxyServer should be the Proxy Server including the port 443 for the
+ // new child Teleport Cloud cluster.
+ ProxyServer: fmt.Sprintf("%s:443", wc.Status.Domain),
+ Onboarding: Onboarding{
+ // Only iam join method will be supported in the short term for workload_clusters.
+ JoinMethod: "iam",
+ // Token must match the same name provided in the workload_cluster's
+ // Spec.Bot.Name.
+ Token: "example-iam",
+ },
+ Storage: Storage{
+ // Configure tbot to use in-memory storage.
+ Type: "memory",
+ },
+ Services: []Service{
+ {
+ Type: "identity",
+ Destination: Destination{
+ Type: "directory",
+ // A file named identity will be created in the provided path.
+ // This identity file may be provided to tctl or Teleport clients
+ // for interacting with a Teleport cluster.
+ Path: tbotDir,
+ },
+ },
+ },
+ }
+
+ // Write the tbot configuration to a `tbot.json` file.
+ tbotConfigContent, err := json.Marshal(tbotConfig)
+ if err != nil {
+ return fmt.Errorf("error marshalling tbot configuration: %w", err)
+ }
+ tbotConfigPath := filepath.Join(tbotDir, "tbot.json")
+ if err := os.WriteFile(tbotConfigPath, tbotConfigContent, 0600); err != nil {
+ return fmt.Errorf("error writing tbot configuration: %w", err)
+ }
+
+ // Run the tbot binary. Teleport does not expose programmatic access to
+ // tbot, so the binary must be used.
+ // Once tbot start has successfully completed then an identity file
+ // will be populated at the provided path in the tbot configuration.
+ var bufErr bytes.Buffer
+ tbotCmd := exec.Command("tbot", "start", "-c", tbotConfigPath)
+ tbotCmd.Stderr = &bufErr
+ if err := tbotCmd.Run(); err != nil {
+ return fmt.Errorf("error running tbot: %w\n\n%s", err, bufErr.String())
+ }
+
+ /*************************************************************
+ * Manage roles and users in the child Teleport Cloud cluster *
+ *************************************************************/
+
+ // Create a new Teleport client to interact with the child Teleport Cloud cluster.
+ // This client will use the identify file retrieved by tbot.
+ childClient, err := client.New(ctx, client.Config{
+ Addrs: []string{
+ // This is the child Teleport Cloud cluster's proxy address:
+ wc.Status.Domain,
+ // Note: port is optional.
+ },
+ Credentials: []client.Credentials{
+ // This uses an identity file instead of using a credential from tsh.
+ client.LoadIdentityFile(filepath.Join(tbotDir, "identity")),
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create client: %w", err)
+ }
+ defer childClient.Close()
+
+ // The following section includes examples of creating and deleting
+ // a role and a user.
+
+ // Create a new role named example in the child Teleport Cloud cluster.
+ newRole := types.RoleV6{
+ Metadata: types.Metadata{
+ Name: "example",
+ },
+ Spec: types.RoleSpecV6{
+ Allow: types.RoleConditions{
+ Rules: []types.Rule{
+ {
+ Resources: []string{
+ "workload_cluster",
+ },
+ Verbs: []string{
+ "read",
+ },
+ },
+ },
+ },
+ },
+ }
+ if _, err := childClient.CreateRole(ctx, &newRole); err != nil {
+ return fmt.Errorf("error creating role: %w", err)
+ }
+
+ // Create user named "example" that has the new "example" role assigned.
+ newUser := types.UserV2{
+ Metadata: types.Metadata{
+ Name: "example",
+ },
+ Spec: types.UserSpecV2{
+ Roles: []string{
+ "example",
+ },
+ },
+ }
+ if _, err := childClient.CreateUser(ctx, &newUser); err != nil {
+ return fmt.Errorf("error creating user: %w", err)
+ }
+
+ // create an invite URL for user to activate account and setup MFA
+ resetPasswordToken := proto.CreateResetPasswordTokenRequest{
+ Name: newUser.Metadata.Name,
+ TTL: proto.Duration(2 * time.Hour),
+ Type: "invite",
+ }
+ resetToken, err := childClient.CreateResetPasswordToken(ctx, &resetPasswordToken)
+ if err != nil {
+ return fmt.Errorf("error creating reset token: %w", err)
+ }
+
+ ttl := resetToken.Expiry().Sub(time.Now().UTC())
+ fmt.Printf("User %q has been created but requires a password. Share this URL with the user to complete user setup, link is valid for %v:\n%v\n\n", newUser.Metadata.Name, ttl, resetToken.GetURL())
+
+ /****************************
+ * Delete a workload cluster *
+ ****************************/
+ // Deferred function above will execute and delete the workload_cluster resource in the parent
+ // Teleport Cloud cluster.
+
+ return nil
+}
+
+func waitForActiveWorkloadCluster(ctx context.Context, client *client.Client, workloadClusterName string, pollingInterval time.Duration) (*workloadclusterv1.WorkloadCluster, error) {
+ ticker := time.NewTicker(pollingInterval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ticker.C:
+ wc, err := client.GetWorkloadCluster(ctx, workloadClusterName)
+ if err != nil {
+ return nil, fmt.Errorf("error getting workload cluster: %w", err)
+ }
+
+ if wc.Status == nil {
+ continue
+ }
+
+ if wc.Status.State == "active" {
+ return wc, nil
+ }
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ }
+ }
+}
diff --git a/integrations/terraform/tfschema/workloadcluster/v1/workloadcluster_terraform.go b/integrations/terraform/tfschema/workloadcluster/v1/workloadcluster_terraform.go
index b0ef25cff75..28661381d5a 100644
--- a/integrations/terraform/tfschema/workloadcluster/v1/workloadcluster_terraform.go
+++ b/integrations/terraform/tfschema/workloadcluster/v1/workloadcluster_terraform.go
@@ -104,7 +104,7 @@ func GenSchemaWorkloadCluster(ctx context.Context) (github_com_hashicorp_terrafo
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{
"bot": {
Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"name": {
- Description: "name is the name of the bot, token, and role that will be created",
+ Description: "name of the bot, token, and role that will be created",
Optional: true,
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
}}),