Add docs for workload_cluster (#65812)

* docs: add reference for workload-cluster

* docs: add workload clusters guide

* fix(docs): add words to ignore

* feat(build.assets): use resource-ref-generator

* fix(docs): clarify replacing account and arn

* docs(zta/wc): add How it works and clarify child cluster

* docs(zta/wc): make feature availability callout a warning

* docs(zta/wc): move IAM callout to paragraph

* docs(zta/wc): indent snippets

* docs(zta/wc): split up Go example

* docs(zta/wc): update example ARN to use assumed-role

* docs(refs/wc): update example to use assumed-role

* fix typo

Co-authored-by: Paul Gottschling <paul.gottschling@goteleport.com>

* docs(zta/wc): fix snippet indent

* docs(zta/wc): move wc Go to examples dir

* fix(examples/wc): uppercase log messages

* refactor(examples/wc): rename to timeoutCtx

* fix(examples/wc): log os.RemoveAll error

* fix(examples/wc): use fmt.Printf instead for actual output

* refactor(examples/wc): move replaceable values to vars block

* refactor(examples/wc): use ticker instead of timer

* docs(zta/wc): reword example usage

* fix(docs/wc): update example name

* fix(docs/zta/wc): fix cd command

* docs(zta/wc): clarify address

* fix(examples/wc): add license header

* refactor(examples/wc): move wc name to a var

* fix(api/proto/wc): improve godoc for bot.name

* docs(zta/wc): use company-organization as name

* fix(examples/wc): use deffered func to delete wc

* refactor(examples/wc): use consistent import naming

* refactor(examples/wc): avoid panics

* fix(examples/wc): fix log message

---------

Co-authored-by: Paul Gottschling <paul.gottschling@goteleport.com>
This commit is contained in:
Dustin Specker
2026-04-29 16:10:24 -05:00
committed by GitHub
parent a6c49af437
commit 7bb55db115
14 changed files with 1137 additions and 5 deletions
@@ -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
@@ -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;
}
@@ -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
@@ -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
+5
View File
@@ -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",
@@ -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. |
@@ -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`<br/>
**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|
@@ -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`
@@ -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`
@@ -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.
<Admonition type="warning" title="Feature availability">
Teleport Cloud customers must contact support at
<a href="mailto:support@goteleport.com">support@goteleport.com</a> to enable
the workload cluster feature.
</Admonition>
- `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
```
<Admonition type="note" title="Immutable configuration">
After creation, workload cluster configuration cannot be modified.
</Admonition>
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
<a href="mailto:support@goteleport.com">support@goteleport.com</a> 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.
+48
View File
@@ -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
)
+144
View File
@@ -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=
+367
View File
@@ -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()
}
}
}
@@ -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,
}}),