mirror of
https://github.com/gravitational/teleport.git
synced 2026-08-28 21:12:20 +08:00
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:
@@ -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
|
||||
|
||||
+23
@@ -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
|
||||
@@ -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|
|
||||
|
||||
+1
-1
@@ -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`
|
||||
|
||||
+1
-1
@@ -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.
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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,
|
||||
}}),
|
||||
|
||||
Reference in New Issue
Block a user