diff --git a/docs/pages/machine-workload-identity/machine-id/deployment/deployment.mdx b/docs/pages/machine-workload-identity/machine-id/deployment/deployment.mdx
index ec0a2214137cd..d620332406cba 100644
--- a/docs/pages/machine-workload-identity/machine-id/deployment/deployment.mdx
+++ b/docs/pages/machine-workload-identity/machine-id/deployment/deployment.mdx
@@ -42,7 +42,7 @@ authentication methods:
## Deployment guides
-The guides in this section show you how to deploy Machine ID and join it to your cluster.
+The guides in this section show you how to deploy Machine ID and join it to your cluster.
Choose a guide based on the platform where you intend to run Machine ID.
If a specific guide does not exist for your platform, the [Linux
@@ -114,9 +114,9 @@ and [Architecture](../../../reference/architecture/machine-id-architecture.mdx)
},
{
icon: ,
- to: "../../reference/machine-workload-identity/machine-id/bound-keypair/getting-started",
+ to: "../../../reference/machine-workload-identity/machine-id/bound-keypair/getting-started",
name: "Bound Keypair Joining",
- }
+ }
]}
/>
@@ -165,6 +165,18 @@ Read the following guides for how to deploy Machine ID on a continuous integrati
icon: ,
to: "../../zero-trust-access/infrastructure-as-code/terraform-provider/terraform-cloud",
name: "Terraform Cloud",
+ },
+ {
+ icon: ,
+ to: "../../../reference/machine-workload-identity/machine-id/bound-keypair/static-keys",
+ name: "Bound Keypair static keys (Generic)",
}
]}
/>
+
+
+If your CI/CD provider does not have a dedicated join method listed above,
+consider using [Bound Keypair static keys][bound-keypair-static] as a fallback.
+
+
+[bound-keypair-static]: ../../../reference/machine-workload-identity/machine-id/bound-keypair/static-keys.mdx
diff --git a/docs/pages/reference/cli/tbot.mdx b/docs/pages/reference/cli/tbot.mdx
index f8a2db9dbd6ed..0829763f4a13a 100644
--- a/docs/pages/reference/cli/tbot.mdx
+++ b/docs/pages/reference/cli/tbot.mdx
@@ -288,7 +288,9 @@ specific section for details when using a YAML config file or legacy output.
| `--[no-]oneshot` | If set, quit after the first renewal. |
| `--diag-addr` | If set and the bot is in debug mode, a diagnostics service will listen on specified address. |
| `--storage` | A destination URI for tbot's internal storage, e.g. `file:///foo/bar`. See [Destination URIs](#destination-uris) for more info. |
-| `--registration-secret` | An optional joining secret to use on first join with the `bound_keypair` join method. |
+| `--registration-secret` | An optional joining secret to use on first join with the `bound_keypair` join method. This can also be provided via the `TBOT_REGISTRATION_SECRET` environment variable. |
+| `--registration-secret-path` | An optional path to a file containing a joining secret to use on first join with the `bound_keypair` join method. |
+| `--static-key-path` | An optional path to a file containing a static private key for use with the `bound_keypair` join method. A base64-encoded key can also be provided via the `TBOT_BOUND_KEYPAIR_STATIC_KEY` environment variable. |
## tbot start legacy
@@ -727,9 +729,12 @@ new keypair.
| Flag | Description |
|------|-------------|
-| `--storage` | A destination URI to be used for bot internal storage. Required. |
-| `--proxy-server` | A Teleport Proxy Service address. Required. |
-| `--overwrite` | If set, always generate a new key. If unset, the existing public key will be printed if one already exists in the destination specified by `--storage` |
+| `--storage` | A destination URI to be used for bot internal storage. Required. |
+| `--proxy-server` | A Teleport Proxy Service address. Required. |
+| `--overwrite` | If set, always generate a new key. If unset, the existing public key will be printed if one already exists in the destination specified by `--storage`. |
+| `--static` | If set, generates a static keypair. For more information, see [the static key guide](../machine-workload-identity/machine-id/bound-keypair/static-keys.mdx). |
+| `--static-key-path` | If set with `--static`, writes the static keypair to a file. |
+| `--format` | Override the output format, supported values: `text`, `json`. |
### Examples
diff --git a/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/admin-guide.mdx b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/admin-guide.mdx
index 71a3ac32ec158..2771d4946f853 100644
--- a/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/admin-guide.mdx
+++ b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/admin-guide.mdx
@@ -274,10 +274,65 @@ its previous value.
If [bot keypairs were rotated](#requesting-a-keypair-rotation) between the
snapshot and restore of the Teleport cluster, note that bots only keep a record
-of the previous 10 keypairs. This means server-side recovery may impossible if
-the keypair expected by the restored Teleport cluster has been rotated out of
+of the previous 10 keypairs. This means server-side recovery may be impossible
+if the keypair expected by the restored Teleport cluster has been rotated out of
the client-side history, or if the client-side history has been lost or deleted.
+## Manually rotating static keys
+
+Static keys prevent automatic key rotation as the `tbot` client cannot update
+keys in an arbitrary keystore. However, it may still possible to automate
+rotation if your environment or secret store allows you to update secrets
+through an API.
+
+The specific steps needed to automate these will vary based on your environment,
+but the general steps are:
+
+1. Generate a new keypair on any node using the tbot client:
+
+ ```code
+ $ tbot keypair create --proxy-server example.teleport.sh:443 --static --format json
+ ```
+
+2. Parse the `.public_key` and `.private_key` values using your tool of choice,
+ like `jq` or any other JSON parser.
+
+3. Replace the token in Teleport to trust the new public key, using the value in
+ the `.public_key` field:
+ ```code
+ $ cat my-token.yaml
+ version: v2
+ kind: token
+ metadata:
+ name: my-token
+ spec:
+ bot_name: example-bot
+ bound_keypair:
+ onboarding:
+ initial_public_key:
+ recovery:
+ mode: insecure
+ join_method: bound_keypair
+ roles:
+ - Bot
+ $ tctl create -f my-token.yaml
+ ```
+
+3. Insert the new private key into your keystore. This will vary depending on
+ which keystore or provider you are using.
+ * If passing the private key via an environment variable, copy the value directly
+ * If passing the private key via file, decode the base64-encoded private key first:
+ ```code
+ $ tbot keypair create --proxy-server example.teleport.sh:443 --static --format json | jq -r .private_key | base64 -d
+ ```
+ ...and store the result as needed.
+
+4. Future jobs should now use the new keypair.
+
+Frequently rotating static keys can help to mitigate the security tradeoffs of
+`insecure` recovery. See the [concepts page](concepts.mdx#static-keys) for more
+information about static keys.
+
[rbac]: ../../../access-controls/roles.mdx
[ephemeral]: ../../../architecture/machine-id-architecture.mdx#ephemeral-token
[delegated]: ../../../deployment/join-methods.mdx#delegated-join-methods
diff --git a/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/bound-keypair.mdx b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/bound-keypair.mdx
index 64b107a8ec928..534a42d7303fa 100644
--- a/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/bound-keypair.mdx
+++ b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/bound-keypair.mdx
@@ -45,10 +45,10 @@ a bot initially. When ready to deploy to production, it's trivial to adjust
onboarding and recovery settings to select your desired balance between
resiliency and security.
-Additionally, with [`insecure` recovery](admin-guide.mdx#disabling-join-state-verification)
-and in situations that can accommodate the security complications, Bound Keypair
-Joining can be used to join bots in otherwise unsupported CI/CD providers by
-persisting the bot's keypair in the platform keystore.
+Additionally, with [static keys](static-keys.mdx) and in situations that can
+accommodate the security complications, Bound Keypair Joining can be used to
+join bots in otherwise unsupported CI/CD providers by persisting the bot's
+keypair in a platform keystore.
## Limitations
@@ -75,6 +75,9 @@ You can read step-by-step guides on using Bound Keypair Joining with Machine ID:
- [Using Machine ID with Bound Keypair Joining](./getting-started.mdx):
How to install and configure Machine ID with Bound Keypair Joining
+- [Using Machine ID with Bound Keypair static keys](./static-keys.mdx): How to
+ use Bound Keypair static keys with stateless hosts, like otherwise
+ unsupported CI/CD providers
- [Bound Keypair Joining Concepts](./concepts.mdx): Learn more about the
components and architecture of Bound Keypair Joining
- [Bound Keypair Joining Admin Guide](./admin-guide.mdx): Learn how to deploy
diff --git a/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/concepts.mdx b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/concepts.mdx
index 1d65d8946c0c7..1653a46349e12 100644
--- a/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/concepts.mdx
+++ b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/concepts.mdx
@@ -118,6 +118,10 @@ values:
[See the admin guide](admin-guide.mdx#disabling-join-state-verification) for
more information on using `insecure` mode in practice.
+ Static key joining (`tbot keypair create --static ...`) requires use of
+ `insecure` mode as it disables client-side credential storage, meaning bots
+ cannot complete [join state verification](#join-state-verification).
+
## Join state verification
A **join state document** is an additional piece of information provided to
@@ -145,5 +149,42 @@ credentials in use by the attacker.
Note that join state verification is disabled when the token's
`spec.bound_keypair.recovery.mode` is set to `insecure`.
+## Static Keys
+
+Static keys trade some security guarantees for flexibility, and can help enable
+use of `tbot` in environments where bots otherwise could not authenticate, such
+as:
+- CI/CD providers for which Teleport has no dedicated join method
+- Ephemeral bare-metal nodes without TPMs
+- Any other environment where persistent storage is not available
+- Any environment where an arbitrary number of instances may join, like
+ ephemeral CI runners.
+
+However, static keys have disadvantages:
+- `insecure` mode means any client with the private key can join without
+ restrictions. If the key is stolen, the attacker will have access to Teleport
+ and any resources the bot is allowed to access.
+- `insecure` mode additionally means you cannot limit the number of uses of the
+ bound keypair token (`spec.bound_keypair.recovery.limit`).
+- Keypair rotation is not supported, as client storage is not writable, and
+ attempting to require a rotation will prevent bots from joining.
+
+When using static keys, a keypair is generated ahead of time and [preregistered
+with Teleport](#onboarding). You can then provide the keypair to the `tbot`
+client using a file or the `TBOT_BOUND_KEYPAIR_STATIC_KEY` environment variable.
+
+The `insecure` recovery mode is required when using static keys (with
+`tbot keypair create --static ...`) as this mode disables client-side storage
+for join state documents. With no previous state, bots will not be able to
+complete join state verification, and will be unable to recover after their
+first authentication attempt; as such, join state verification must be disabled.
+
+With this context in mind, when using static keys, be aware that any client with
+knowledge of the private key can authenticate to Teleport with no additional
+restrictions. Before deploying a bot with static keys, take additional care to
+fully understand your environment's threat model and security needs, and reduce
+access using [Teleport's RBAC](../../../access-controls/roles.mdx) to ensure the
+minimum possible blast radius in the event the keypair is compromised.
+
[renewable]: ../../../deployment/join-methods.mdx#renewable-vs-non-renewable
[token]: ../../../deployment/join-methods.mdx#ephemeral-tokens
diff --git a/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/static-keys.mdx b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/static-keys.mdx
new file mode 100644
index 0000000000000..9193d3af66945
--- /dev/null
+++ b/docs/pages/reference/machine-workload-identity/machine-id/bound-keypair/static-keys.mdx
@@ -0,0 +1,298 @@
+---
+title: Deploying Machine ID with Bound Keypair Static Keys
+description: "How to install and configure Machine ID with Bound Keypair static keys"
+tags:
+ - mwi
+ - how-to
+ - infrastructure-identity
+---
+
+In this guide, you will install Machine and Workload Identity's agent, `tbot`,
+on an arbitrary stateless environment using Bound Keypair static keys. This
+might be a CI/CD job on a provider not yet supported by one of Teleport's
+[dedicated join methods](../../../deployment/join-methods.mdx), an ephemeral
+bare-metal node, or any other environment without writable persistent storage.
+
+Bound Keypair Joining is a more flexible alternative to [secret-based join
+methods][secret], and static keys allow arbitrary nodes to join by relaxing some
+of its built-in security requirements.
+
+
+If your provider has writable persistent storage, like a Kubernetes PVC or
+similar writable persistent storage, you should instead follow the [standard
+Bound Keypair guide](./getting-started.mdx).
+
+
+## How it works
+
+Bound Keypair joining typically assumes you have access to writable client-side
+storage to store additional identity proofs, but this requirement is lifted when
+using Bound Keypair static keys.
+
+Normally, the actual Bound Keypair keys are managed internally by the `tbot`
+client, which allows them to be generated and rotated on demand. With static
+keys, you take direct ownership of the private key and can store it as you see
+fit - like in a platform keystore. The `tbot` client is then configured to use
+your static private key instead of managing it automatically.
+
+
+Static key joining relaxes certain security checks otherwise performed when
+client-side storage is available. This is designed to be used where no other
+join methods are possible. Before continuing, consider:
+- Using a [dedicated join method](../../../deployment/join-methods.mdx) for your
+ platform if available.
+- Using [standard Bound Keypair joining](./getting-started.mdx) if your
+ environment has writable persistent storage.
+
+[Read more about static keys and what tradeoffs they
+require](./concepts.mdx#static-keys) before using them in a production
+environment.
+
+
+## Prerequisites
+
+{/* note: consider edition-prereqs-tabs.mdx include for v19; it is misleading due to the minor launch release */}
+
+{/* TODO: replace version with version containing `tbot keypair create --static` */}
+
+- A running Teleport cluster version 18.2.0 or above.
+- The `tsh`, `tctl`, and `tbot` clients.
+- (!docs/pages/includes/tctl.mdx!)
+
+## Step 1/7. Install `tbot`
+
+**This step is completed on the bot host.**
+
+First, `tbot` needs to be installed on the host that you wish to use Machine ID
+on.
+
+Download and install the appropriate Teleport package for your platform:
+
+(!docs/pages/includes/install-linux.mdx!)
+
+## Step 2/7. Create a Bot
+
+**This step is completed on your local machine.**
+
+(!docs/pages/includes/machine-id/create-a-bot.mdx!)
+
+## Step 3/7. Create a keypair and a join token
+
+Next, we'll need to generate a keypair, consisting of:
+* A private key that will need to be available on your bot host at runtime
+* A public key that will need to be configured in the Teleport join token
+
+We'll use a helper in the `tbot` client to generate a static keypair. This
+step can be run anywhere and does not require any configuration, but you will
+need to decide how the bot will access the key, which may vary depending on your
+environment or secret store:
+1. Via an environment variable, useful for providers or environments that inject
+ secrets as environment variables
+2. Via a standard file which you transfer to the bot host
+
+With that in mind, if you intend to make the key available to `tbot` via an
+environment variable, run the following:
+
+```code
+$ tbot keypair create --proxy-server :443 --static
+```
+
+Otherwise, if using a file, run the following:
+```code
+$ tbot keypair create --proxy-server :443 --static --static-key-path ./path/to/key
+```
+
+Adjust the `--static-key-path` value as desired. If you didn't run this command
+on the bot host directly, be prepared to transfer this to the bot host in the
+next step.
+
+In either case, the command will generate a keypair and print some instructions.
+For example, this is shown when using an environment variable key:
+```code
+To register the keypair with Teleport, include this public key in the token's
+'spec.bound_keypair.onboarding.initial_public_key' field:
+
+
+
+Refer to this token example as a reference:
+
+ kind: token
+ metadata:
+ name: example-token
+ spec:
+ bot_name: example
+ bound_keypair:
+ onboarding:
+ initial_public_key:
+ recovery:
+ limit: 0
+ mode: insecure
+ join_method: bound_keypair
+ roles:
+ - Bot
+ version: v2
+
+Configure your bot to use this static key by inserting the following private key
+value into the bot's environment, ideally via a platform-specific keystore if
+available:
+
+ export TBOT_BOUND_KEYPAIR_STATIC_KEY="<...encoded private key...>"
+
+Note that bots joined with static tokens do not support keypair rotation and
+will be unable to join if a rotation is requested server-side via the token's
+'rotate_after' field. Additionally, 'insecure' recovery mode must be used, as
+shown above. Read more at:
+
+ https://goteleport.com/docs/reference/machine-workload-identity/machine-id/bound-keypair/concepts/#recovery
+```
+
+This command will print the public key, a join token example, and the private
+key needed to configure your bot. Keep this output available as you'll need it
+through the next step.
+
+Using the example token printed by the above command as a template,
+create `token.yaml` containing the following content:
+```yaml
+kind: token
+metadata:
+ name: example-token
+spec:
+ bot_name: example
+ bound_keypair:
+ onboarding:
+ initial_public_key:
+ recovery:
+ limit: 0
+ mode: insecure
+ join_method: bound_keypair
+ roles:
+ - Bot
+version: v2
+```
+
+Be sure to set `bot_name` to match the bot created in the previous step, and
+ensure the public key matches the value printed by `tbot keypair create ...`.
+
+When ready, create the join token in your Teleport cluster using `tctl`:
+```code
+$ tctl create -f token.yaml
+```
+
+## Step 4/7. Store the private key
+
+Now that you've generated the private key, it needs to be stored and made
+available to your job. Exactly how to do this will depend on your provider and
+environment, as well as whether you'll be making it available via an environment
+variable or a file.
+
+### Storing the key in a file on the bot host
+
+Ensure the private key file you generated is made available on the bot host. If
+you generated it on your local machine, this might mean copying it via `scp`,
+provisioning the key via Ansible, or transferring it to the bot host through
+whichever means you prefer.
+
+Take care to ensure the resulting file on the bot host will only be readable by
+the account that will run the `tbot` process.
+
+### Storing the key in an environment variable
+
+If using an environment variable, depending on your platform and environment,
+you'll likely want to store the key in a platform-specific keystore - such that
+the environment variable is set on the bot host when it starts - rather than
+on the bot host directly.
+
+Regardless of backend, set the variable as follows:
+- Name: `TBOT_BOUND_KEYPAIR_STATIC_KEY`
+- Value: the base64-encoded value as printed by `tbot keypair create --static ...`
+
+The value is the same content that would otherwise be written to a file if you'd
+used `tbot keypair create ... --static --static-key-path /path/to/key`, but the
+content has been base64 encoded to simplify use as an environment variable
+value.
+
+## Step 5/7. Configure `tbot`
+
+**This step is completed on the bot host.**
+
+### With an environment variable key
+
+If exposing the secret via an environment variable, ensure it's available in the
+`$TBOT_BOUND_KEYPAIR_STATIC_KEY` variable.
+
+Create `/etc/tbot.yaml` with the following content:
+
+```yaml
+version: v2
+proxy_server: :443
+onboarding:
+ join_method: bound_keypair
+ token: example-token
+storage:
+ type: directory
+ path: /var/lib/teleport/bot
+# outputs will be filled in during the completion of an access guide.
+outputs: []
+```
+
+Aside from specifying the `join_method` and `token`, no additional configuration
+is needed; the key will be read from the environment as needed during the join
+process.
+
+### With a file key
+
+Copy the key to the bot environment or otherwise make sure it's available, and
+write the following to `/etc/tbot.yaml`:
+
+```yaml
+version: v2
+proxy_server: :443
+onboarding:
+ join_method: bound_keypair
+ token: example-token
+ bound_keypair:
+ static_private_key_path: /path/to/key
+storage:
+ type: directory
+ path: /var/lib/teleport/bot
+# outputs will be filled in during the completion of an access guide.
+outputs: []
+```
+
+Set `static_private_key_path` to point to the location the key will be
+available and save the file.
+
+## Step 6/7. Verify `tbot` can authenticate to Teleport
+
+Run `tbot` using the `tbot.yaml` you created, and ensure the private key is
+available as expected, either via file (at the path you configured earlier) or
+via the environment in `$TBOT_BOUND_KEYPAIR_STATIC_KEY`:
+
+```code
+$ tbot start -c /etc/tbot.yaml --oneshot
+```
+
+If everything has been setup correctly, `tbot` should run, authenticate with
+Teleport, and exit cleanly. In production, you can remove the `--oneshot` flag
+if you want `tbot` to continually provide updated certificates for
+longer-running jobs, otherwise the certificates issued will expire eventually
+(1 hour by default).
+
+## Step 7/7. Configure outputs
+
+(!docs/pages/includes/machine-id/configure-outputs.mdx!)
+
+## Next Steps
+
+- Read more about [Bound Keypair static keys](./concepts.mdx#static-keys).
+- Read about [static key rotation](./admin-guide.mdx#manually-rotating-static-keys)
+- Read the [Bound Keypair Joining Reference][reference]
+ for more details about the join method and the available configuration options.
+- Follow the [access guides](../../../../machine-workload-identity/machine-id/access-guides/access-guides.mdx)
+ to finish configuring `tbot` for your environment.
+- Read the [configuration reference](../configuration.mdx) to explore
+ all the available configuration options.
+- [More information about `TELEPORT_ANONYMOUS_TELEMETRY`.](../telemetry.mdx)
+
+[secret]: ../../../deployment/join-methods.mdx#secret-vs-delegated
+[reference]: ./bound-keypair.mdx
diff --git a/docs/pages/reference/machine-workload-identity/machine-id/configuration.mdx b/docs/pages/reference/machine-workload-identity/machine-id/configuration.mdx
index 941b1c09d1cf2..0951b40bd0c14 100644
--- a/docs/pages/reference/machine-workload-identity/machine-id/configuration.mdx
+++ b/docs/pages/reference/machine-workload-identity/machine-id/configuration.mdx
@@ -128,9 +128,18 @@ onboarding:
# bound_keypair holds parameters specific to the "bound_keypair" join method
bound_keypair:
# registration_secret is an optional secret to use on first join in lieu of
- # a preregistered keypair.
+ # a preregistered keypair. You can also set this in the
+ # `TBOT_REGISTRATION_SECRET` environment variable.
registration_secret: "secret"
+ # registration_secret_path is an optional path to a file containing a
+ # registration secret; conflicts with `registration_secret`
+ registration_secret_path: ./path/to/secret
+
+ # static_key_path is an optional path to a file containing a static private
+ # key.
+ static_key_path: ./path/to/secret
+
# storage specifies the destination that `tbot` should use to store its
# internal state. This state is sensitive, and you should ensure that the
# destination you specify here can only be accessed by `tbot`.