diff --git a/docs/config.json b/docs/config.json
index efad6942bd51c..3bed63d29e165 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -565,6 +565,11 @@
"destination": "/reference/workload-identity/workload-identity-api-and-workload-attestation/",
"permanent": true
},
+ {
+ "source": "/machine-workload-identity/machine-id/deployment/bound-keypair/",
+ "destination": "/reference/machine-id/bound-keypair/getting-started/",
+ "permanent": true
+ },
{
"source": "/enroll-resources/workload-identity/",
"destination": "/machine-workload-identity/workload-identity/"
@@ -1045,4 +1050,4 @@
"permanent": true
}
]
-}
+}
\ No newline at end of file
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 4d90fcab49ffc..c94df51712f25 100644
--- a/docs/pages/machine-workload-identity/machine-id/deployment/deployment.mdx
+++ b/docs/pages/machine-workload-identity/machine-id/deployment/deployment.mdx
@@ -55,15 +55,15 @@ and [Architecture](../../../reference/architecture/machine-id-architecture.mdx)
Read the following guides for how to deploy Machine ID on your cloud platform or
on-prem infrastructure.
-| Platform | Installation method | Join method |
-|--------------------------------------------|-------------------------------------------------|-----------------------------------------------------|
-| [Linux](linux.mdx) | Package manager or TAR archive | Static join token |
-| [Linux (TPM)](linux-tpm.mdx) | Package manager or TAR archive | Attestation from TPM 2.0 |
-| [Linux (Bound Keypair)](bound-keypair.mdx) | Package manager or TAR archive | Bound Keypair |
-| [GCP](gcp.mdx) | Package manager, TAR archive, or Kubernetes pod | Identity document signed by GCP |
-| [AWS](aws.mdx) | Package manager, TAR archive, or Kubernetes pod | Identity document signed by AWS |
-| [Azure](azure.mdx) | Package manager or TAR archive | Identity document signed by Azure |
-| [Kubernetes](kubernetes.mdx) | Kubernetes pod | Identity document signed by your Kubernetes cluster |
+| Platform | Installation method | Join method |
+|----------------------------------------|-------------------------------------------------|-----------------------------------------------------|
+| [Linux](linux.mdx) | Package manager or TAR archive | Static join token |
+| [Linux (TPM)](linux-tpm.mdx) | Package manager or TAR archive | Attestation from TPM 2.0 |
+| [Linux (Bound Keypair)][bound-keypair] | Package manager or TAR archive | Bound Keypair |
+| [GCP](gcp.mdx) | Package manager, TAR archive, or Kubernetes pod | Identity document signed by GCP |
+| [AWS](aws.mdx) | Package manager, TAR archive, or Kubernetes pod | Identity document signed by AWS |
+| [Azure](azure.mdx) | Package manager or TAR archive | Identity document signed by Azure |
+| [Kubernetes](kubernetes.mdx) | Kubernetes pod | Identity document signed by your Kubernetes cluster |
### CI/CD
@@ -81,3 +81,5 @@ integration and continuous deployment platform
| [Spacelift](../../../zero-trust-access/infrastructure-as-code/terraform-provider/spacelift.mdx) | Docker Image | Spacelift-signed identity document |
| [Terraform Cloud](../../../zero-trust-access/infrastructure-as-code/terraform-provider/terraform-cloud.mdx) | Teleport Terraform Provider via Teleport's Terraform Registry | Terraform Cloud-signed identity document |
+
+[bound-keypair]: ../../../reference/machine-id/bound-keypair/getting-started.mdx
diff --git a/docs/pages/reference/cli/tbot.mdx b/docs/pages/reference/cli/tbot.mdx
index 888601834c0a7..25fa49a717948 100644
--- a/docs/pages/reference/cli/tbot.mdx
+++ b/docs/pages/reference/cli/tbot.mdx
@@ -750,12 +750,8 @@ To register the keypair with Teleport, include this public key in the token's
This public key, including the algorithm identifier (`ssh-ed25519`, but may vary
depending on your cluster configuration) can then be copied into a Bound Keypair
-join token to be used as a preregistered key.
-
-{/*
-TODO: Replace with a link into the admin guide once the follow up PR has merged.
-[preregistered key](../machine-id/bound-keypair.mdx#preregistered-key-example).
-*/}
+join token to be used as a
+[preregistered key](../machine-id/bound-keypair/concepts.mdx#onboarding).
Note that the Teleport Proxy Service address is required to fetch the currently
enabled [signature suite](../signature-algorithms.mdx). No authentication takes
diff --git a/docs/pages/reference/join-methods.mdx b/docs/pages/reference/join-methods.mdx
index b55a243a80075..0a8c9d7db9158 100644
--- a/docs/pages/reference/join-methods.mdx
+++ b/docs/pages/reference/join-methods.mdx
@@ -280,7 +280,7 @@ New Machine & Workload Identity bot deployments should consider upgrading to the
Bound Keypair tokens are an alternative to
[secret-based join methods](#secret-based-join-methods) that improve security
and flexibility. They are best used on platforms with persistent storage, but
-can be configured for use in any environment.
+can be configured for use in nearly any environment.
This join method is recommended for on-prem environments
[without TPMs](#trusted-platform-module-tpm) or cloud platforms
@@ -289,13 +289,8 @@ without a specialized [delegated join method](#delegated-join-methods).
(!docs/pages/includes/provision-token/bound-keypair-spec.mdx!)
-- [Deploying Machine ID with Bound Keypair joining](../machine-workload-identity/machine-id/deployment/bound-keypair.mdx)
-
-{/*
-TODO: Uncomment after follow-up PR with admin guide has merged.
-- [Bound Keypair Reference and Admin Guide](./machine-id/bound-keypair.mdx)
-*/}
-
+- [Deploying Machine ID with Bound Keypair joining](./machine-id/bound-keypair/getting-started.mdx)
+- [Bound Keypair Reference](./machine-id/bound-keypair/bound-keypair.mdx)
### AWS IAM role: `iam`
diff --git a/docs/pages/reference/machine-id/bound-keypair/admin-guide.mdx b/docs/pages/reference/machine-id/bound-keypair/admin-guide.mdx
new file mode 100644
index 0000000000000..e80dbf91a0249
--- /dev/null
+++ b/docs/pages/reference/machine-id/bound-keypair/admin-guide.mdx
@@ -0,0 +1,279 @@
+---
+title: Bound Keypair Joining Admin Guide
+description: "How to deploy and maintain bots in production with Bound Keypair Joining"
+---
+
+This guide discusses various tasks users administering bots using Bound Keypair
+Joining may need to perform over the lifespan of the bot.
+
+## Allowing additional recovery attempts
+
+When using the `standard` recovery mode, only a configured number of recovery
+attempts can be made. If the limit is reached, no further recovery attempts can
+be made until the limit is increased.
+
+To increase this limit and allow an expired bot to join again, edit the token
+using `tctl edit`:
+```code
+$ tctl edit token/example-token
+```
+
+Find the `spec.bound_keypair.recovery.limit` field and increment the limit by
+the desired amount. You are free to select any desired threshold. For example,
+consider these use cases:
+- If human intervention is desired for each join attempt you can increase this
+ value by 1. This single recovery attempt will be immediately consumed, so
+ future recoveries will again require human intervention, and may result in
+ downtime.
+
+ While this approach makes downtime likely, it does ensure a human verifies the
+ state of the bot host on each recovery.
+
+- If you want human intervention for each recovery, but want to avoid downtime,
+ you can increase this value by 2. The first attempt will be consumed
+ immediately, but the bot will have one recovery attempt for automatic future
+ use.
+
+ A human user can periodically audit the recovery count and bot host to ensure
+ a recovery attempt is always available and the host is behaving as expected.
+
+- Any larger value will increase the amount of time required between human
+ intervention. You can select your tolerance for automatic bot recoveries as
+ desired.
+
+Alternatively, if you wish to allow an unlimited number of automatic recovery
+attempts, [refer to the entry below](#allowing-unlimited-recovery-attempts) on
+the `relaxed` recovery mode.
+
+Note that the recovery limit is always relative to the recovery counter (in the
+`status.bound_keypair.recovery_count` field in the token resource). It is valid
+to decrease the limit or set it to zero, however doing so may prevent future
+bot recovery attempts until the limit is increased again.
+
+Additionally, note that [join state verification](concepts.mdx#join-state-verification)
+is still required, and will prevent multiple concurrent uses of the same keypair
+and token. In other words, increasing the recovery limit will not allow multiple
+clients to join.
+
+## Allowing unlimited recovery attempts
+
+To allow unlimited recovery attempts, the `spec.bound_keypair.recovery.mode`
+field should be set to `relaxed`. To do this, use `tctl edit` to edit the token:
+```code
+$ tctl edit token/example-token
+```
+
+Find or create the `spec.bound_keypair.recovery.mode` field and set the value to
+`relaxed`. Save the file and quit your editor to update the token.
+
+When the recovery mode is set to `relaxed`, the `limit` field is ignored and the
+`status.bound_keypair.recovery_count` field may increase beyond the written
+limit. If the mode is later changed back to `standard`, be aware that future
+recovery attempts will fail unless the `limit` is increased to accommodate the
+current value of `recovery_count`.
+
+Note that when `relaxed` mode is in use,
+[join state verification](concepts.mdx#join-state-verification) is still required and will
+prevent multiple concurrent uses of the same keypair and token. If your use case
+requires this, you can
+[disable join state verification](#disabling-join-state-verification), but doing
+so does impact the security of the token.
+
+## Requesting a keypair rotation
+
+To request a keypair rotation, set the `.spec.bound_keypair.rotate_after` field
+to contain a timestamp. On the next authentication attempt after that timestamp
+has elapsed, the bot will automatically rotate its keypair.
+
+To simplify this process, you can use the `tctl bound-keypair rotate` helper:
+```code
+$ tctl bound-keypair rotate token-name
+```
+
+This sets the timestamp to the current time. Note that by default bots only
+reauthenticate every 20 minutes, so it may take some time for the request to be
+acknowledged. You can monitor the rotation status by watching the token's
+`.status.bound_keypair.last_rotated_at` field.
+
+If you want to force an early rotation and have access to the bot host, you can
+restart the `tbot` process, or send it a signal with `pkill -usr1 tbot` to
+request an early rotation.
+
+Note that the previous 10 keypairs are retained on the client for use in case of
+a cluster rollback; refer to the
+[cluster rollback](#recovery-after-a-cluster-rollback) section for additional
+information.
+
+## Locking a `bound_keypair` bot or bot instance
+
+The simplest way to lock out a bot that joined using the `bound_keypair` join
+method is to use a join token lock target:
+
+```code
+$ tctl lock --join-token=token-name
+```
+
+As a bound keypair token is linked to a single bot, this will effectively lock
+the bot. It will not be able to reauthenticate, recover, interact with the
+Teleport API, or otherwise use its credentials until the lock is removed.
+
+Note that if a bot is locked for long enough - bots have a 1 hour certificate
+TTL by default - its certificates will expire. If you intend to remove this lock
+and reinstate the bot, you may also need to increase the recovery limit
+(`.spec.bound_keypair.recovery.limit`) to accommodate the additional recovery
+attempt.
+
+Other lock targets can also be used, but are not preferred:
+- Bot instance (`tctl lock --bot-instance-id ...`): will lock only a single
+ instance of the bot. Note that if the recovery limit allows for it, the
+ [automatic recovery process](concepts.mdx#recovery) will attempt to rejoin and, if
+ successful, will generate a new bot instance ID.
+- Bot name (`tctl lock --user bot-`): will lock all bots using the same
+ bot / user. This may be overly broad and lock other instances running under
+ this bot user.
+
+## Recovering a locked `bound_keypair` bot instance
+
+Bots joined with the `bound_keypair` join method can become automatically locked
+under various conditions, including:
+- Failing to correctly complete [join state verification](concepts.mdx#join-state-verification)
+- Connecting with certificates that have an invalid [generation counter][ephemeral]
+- Locked manually by a cluster admin
+
+To recover a bot that has become locked, first ensure the bot's internal storage
+(`storage`) has not been compromised. These locking conditions are designed to
+trigger if more than one client tries to join using a copy of the same
+certificates and private key. This can occur due to a misconfiguration or due
+to an attacker copying a bot's credentials, so ideally the latter should be
+ruled out before unlocking a bot.
+
+Next, determine the name (UUID) of the lock or locks targeting the bot:
+```code
+$ tctl get lock
+kind: lock
+metadata:
+ name: 372af058-76d1-4e64-93da-3b04d7d03ac2
+spec:
+ target:
+ user: bot-example
+version: v2
+---
+kind: lock
+metadata:
+ name: 791d0b1d-01b4-4752-8a99-9b2908aebfae
+spec:
+ target:
+ bot_instance_id: e7d494ae-a0ff-4d12-b935-de5e2025f667
+version: v2
+---
+kind: lock
+metadata:
+ name: a69fdbb2-8e53-406a-b453-48b2cda6991d
+spec:
+ target:
+ join_token: example-token-name
+version: v2
+```
+
+Note the different locks and lock targets shown above: bots can be targeted by
+any of their Teleport user name (`bot-example`), the bot instance ID (a UUID),
+or the join token name. Locks created automatically for bots using Bound Keypair
+Joining will typically use a `join_token` target, but a lock targeting any of
+these values could be created manually.
+
+Note that locks may have a message field containing details about why the lock
+was created.
+
+Once the lock name(s) have been determined, remove each using `tctl rm`:
+```code
+$ tctl rm lock/372af058-76d1-4e64-93da-3b04d7d03ac2
+```
+
+Next, join state should be reset. Use `tctl edit` to set the token's recovery
+mode to `insecure`, but make a note of the current value (`standard` or
+`relaxed`):
+```code
+$ tctl edit token/example-token
+```
+
+Change the `.spec.bound_keypair.recovery.mode` field to `insecure`, save, and
+quit the editor.
+
+The bot can now be allowed to rejoin. Given sufficient time it will retry on its
+own, but if you have access to the host, `systemctl restart tbot` or similar can
+be used to restart the bot process.
+
+The bot should now be able to join successfully. You can monitor progress by
+watching for new audit events in Teleport's web UI, or by waiting for the
+recovery counter to increase:
+```code
+$ tctl get token/example-token --format=json | jq '.[].status.bound_keypair.recovery_count'
+```
+
+Once the bot has joined successfully, reset the recovery mode to its previous
+value using `tctl edit`:
+```code
+$ tctl edit token/example-token
+```
+
+If you do suspect the bot's credentials may have been compromised, you may also
+want to [request a keypair rotation](#requesting-a-keypair-rotation) in
+addition to taking other steps to ensure the host is properly secured.
+
+## Disabling join state verification
+
+It is occasionally useful to intentionally disable join state verification. For
+example, this can enable use with:
+- CI/CD providers without an explicit [delegated join method][delegated].
+- Nodes with immutable storage that cannot store an updated join state document
+ after each join.
+
+Before continuing, be aware that disabling join state verification will prevent
+Teleport from detecting if multiple clients are joining using the same bound
+keypair token. In other words, if the private key is copied by an attacker, they
+will be able to join indefinitely. Take care to protect the keypair, and make
+certain to limit access from the bot identity using Teleport's
+[RBAC system][rbac].
+
+When ready, use `tctl edit` to modify the Bound Keypair token:
+```code
+$ tctl edit token/example-token
+```
+
+Find or add the `spec.bound_keypair.recovery.mode` field and set it to
+`insecure`. Save and quit your editor to update the token.
+
+With the mode set to `insecure`, the `recovery.limit` is ignored, allowing
+unlimited reuse of the token, and join state verification is disabled, allowing
+concurrent or stateless reuse.
+
+## Recovery after a cluster rollback
+
+If your Teleport cluster is rolled back for any reason, joining bots may fail
+[join state verification](concepts.mdx#join-state-verification) as their local join state
+document may not match the values currently (or previously) known to Teleport.
+
+The simplest workaround is to temporarily set all bound keypair tokens to
+`insecure` recovery mode for the first join attempt following a cluster restore.
+Once they've joined once, they will once again have a valid join state, so the
+recovery mode can be restored to its previous value.
+
+To change the recovery mode, use `tctl edit` to modify the token resource:
+```code
+$ tctl edit token/example-token
+```
+
+Find the `spec.bound_keypair.recovery.mode` field, and set the value to
+"insecure". Repeat this for each bound keypair token. Wait for all bound keypair
+bots to reauthenticate, and repeat this process to restore the recovery mode to
+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
+the client-side history, or if the client-side history has been lost or deleted.
+
+[rbac]: ../../access-controls/roles.mdx
+[ephemeral]: ../../architecture/machine-id-architecture.mdx#ephemeral-token
+[delegated]: ../../join-methods.mdx#delegated-join-methods
diff --git a/docs/pages/reference/machine-id/bound-keypair/bound-keypair.mdx b/docs/pages/reference/machine-id/bound-keypair/bound-keypair.mdx
new file mode 100644
index 0000000000000..3a6d8c1fc7455
--- /dev/null
+++ b/docs/pages/reference/machine-id/bound-keypair/bound-keypair.mdx
@@ -0,0 +1,78 @@
+---
+title: Bound Keypair Joining Reference
+description: "Bound Keypair Joining: Reference and admin guide"
+---
+
+Bound Keypair is a join method designed to provide the best features of
+[delegated join methods][s-vs-d] - like the AWS, GCP, or Azure join
+methods - but in on-prem or otherwise unsupported environments where no external
+verification is available.
+
+Specifically, this join method:
+- Does not require dedicated TPM hardware or external identity attestation
+- Does not require long-lived shared secrets
+- Allows for limited automatic recovery if certificates expire
+- Allows recovery restrictions to be relaxed or lifted to accommodate different
+ use cases and deployment scenarios
+- Ensures failed bots can be recovered without client-side intervention in most
+ cases
+
+
+Bound Keypair Joining is available in v18.1.0 and is intended to replace `token`
+joining as the default recommended join method in Teleport v19.0.0.
+
+
+## Use cases
+
+Bound Keypair Joining can be used in any environment and is designed to function
+as a drop-in replacement for the traditional [`token`][ephemeral] join method in
+all situations where it is used today. This includes bare-metal and on-prem
+hardware where TPMs are not available, or cloud providers not currently
+supported by a [delegated join method][delegated].
+
+Similar to `token` joining, Bound Keypair Joining is also a good replacement for
+local experimentation for testing, with minimal configuration needed to onboard
+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.
+
+## Limitations
+
+While Bound Keypair Joining does enable or simplify a number use cases, it does
+have limitations that may make it unfit in some instances.
+
+In particular, the [secure recovery modes](concepts.mdx#recovery) introduce some deployment
+restrictions:
+- Each bot deployment must be issued a unique token. For deployment at scale,
+ use of Teleport's
+ [Terraform provider](../../terraform-provider/terraform-provider.mdx) is
+ recommended to create tokens in bulk for each deployment.
+- Each bot deployment must be able to store client-side state (used for
+ [join state verification](concepts.mdx#join-state-verification)).
+
+This limitation can be worked around using the
+[`insecure` recovery mode](admin-guide.mdx#disabling-join-state-verification), but doing so
+does meaningfully reduce the join method's security protections and should be
+used with care.
+
+## Next steps
+
+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
+- [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
+ and maintain bots in production with Bound Keypair Joining
+- [Bound Keypair Provision Token Reference](../../join-methods.mdx#bound-keypair-bound_keypair): Learn about the options that can be configured for a `bound_keypair` token
+
+[delegated]: ../../join-methods.mdx#delegated-join-methods
+[ephemeral]: ../../architecture/machine-id-architecture.mdx#ephemeral-token
+[token]: ../../join-methods.mdx#ephemeral-tokens
+[s-vs-d]: ../../join-methods.mdx#secret-vs-delegated
diff --git a/docs/pages/reference/machine-id/bound-keypair/concepts.mdx b/docs/pages/reference/machine-id/bound-keypair/concepts.mdx
new file mode 100644
index 0000000000000..a9f6b4db6f43b
--- /dev/null
+++ b/docs/pages/reference/machine-id/bound-keypair/concepts.mdx
@@ -0,0 +1,145 @@
+---
+title: Bound Keypair Joining Concepts
+description: "Learn the key components of Bound Keypair Joining"
+---
+
+This page discusses the main concepts needed to understand Bound Keypair
+Joining.
+
+## Bound keypair
+
+The term "bound keypair" refers to both the type of credential and way it's used
+in Teleport: first, a bot generates a standard public/private keypair; next,
+Teleport is configured to trust the public key, binding it to a token.
+
+While bots using Bound Keypair Joining will receive regular, short-lived
+Teleport certificates, and will refresh them regularly in a similar manner to
+[renewable certificates][renewable], they also make use of an additional
+public/private keypair that is not tied directly to a certificate. Critically,
+this means the keypair does not have an expiration date, and that any trust
+relationship established between Teleport and a Bound Keypair bot is controlled
+by server-side configuration within Teleport and not due to the passage of time.
+
+Once this trust relationship has been established, bots can - in a sense - serve
+as their own identity authority, similar to how cloud providers can attest to
+the identity of a host or CI workflow run. Teleport issues the joining bot a
+unique cryptographic challenge which the bot signs using its private key, and
+the result is verified using the key registered with Teleport.
+
+As this self attestation is not by itself a sufficient security guarantee, Bound
+Keypair Joining makes use of additional server-side rules to control whether or
+not a join attempt will be allowed, including:
+- Limiting the number of [recovery attempts](#recovery) and optionally requiring
+ (server-side) human intervention for each recovery.
+- Preventing reuse or theft of private keys using
+ [join state verification](#join-state-verification).
+
+## Onboarding
+
+Onboarding takes place at first join and is the stage at which a public key
+becomes bound to a token. There are two methods bots may use to complete this
+step:
+- Preregistered keys: a private key is generated locally on the bot host, and
+ the public key is copied out-of-band to Teleport by a human user or a script.
+ This method ensures no secret values are ever copied between systems.
+- Registration secrets: a one time use random secret is generated by Teleport
+ and provided to the bot host via either a human user or a script, similar to
+ the [ephemeral `token` join method][token].
+
+If a registration secret is used, the bot uses the registration secret to prove
+its identity for this first connection, generates a keypair on the fly, and
+registers the public key with Teleport.
+
+Registration secrets are used by default: if a token is created and no initial
+(preregistered) public key is provided, a registration secret will be randomly
+generated by Teleport and written to the token's
+`status.bound_keypair.registration_secret` field.
+
+In both cases - once registered, or when using preregistered keys - the joining
+bot is then required to complete a joining challenge. A unique cryptographic
+challenge is generated by Teleport which the bot must sign and return. If the
+signature matches the registered public key, onboarding has succeeded: the
+public key is permanently bound to the token, a certificate bundle is issued,
+and a join state document is provided for use on the next join attempt.
+
+## Recovery
+
+Recovery takes place when a bot uses its private key to request new
+certificates. This can occur in two situations:
+- On first join, when the bot has no certificates.
+- When the bot's certificates expire before they can be renewed, such as when a
+ bot is offline for longer than its configured `certificate_ttl`.
+
+So long as the bot has and maintains valid Teleport credentials - by default,
+bot certificates are valid for an hour and renewed every 20 minutes, but these
+values are configurable - no recovery will be needed after the initial join.
+Recovery is only required when a bot is unable to refresh its certificates for
+longer than its configured certificate TTL (e.g. 1 hour).
+
+The behavior of the recovery process depends on two values configured in the
+token resource: the recovery mode (`spec.bound_keypair.recovery.mode`), and the
+recovery limit (`spec.bound_keypair.recovery.limit`). The mode may have these
+values:
+- `standard` (default): only `limit` recovery attempts can be automatically
+ attempted.
+
+ This mode is recommended for most situations. Bots can recover automatically
+ so long as the recovery count (`status.bound_keypair.recovery_count`) doesn't
+ exceed the configured limit, which allows cluster administrators to select
+ their tolerance for automatic bot recovery attempts.
+
+ [Join state verification](#join-state-verification) is enabled, which helps
+ prevent keypair reuse, but does require clients to store additional on each
+ join.
+
+ Note that as the initial join counts as a recovery attempt, `limit` must
+ always be at least `1` for the initial join to succeed.
+
+- `relaxed`: `limit` is ignored, but
+ [join state verification](#join-state-verification) remains enabled.
+
+ This mode allows for unlimited recovery attempts and is useful for lower
+ security impact bots, or deployments where bots are regularly expected to go
+ offline for extended periods.
+
+ Like `standard` mode, join state verification is enabled to help prevent
+ keypair reuse, but does require clients to store additional state on each
+ join.
+
+- `insecure`: `limit` is ignored, and join state verification is **disabled**.
+
+ This mode is the most flexible, as bots can rejoin repeatedly and can operate
+ without mutable client-side state. However, this disables most additional
+ security checks and should be used with care.
+ [See the admin guide](admin-guide.mdx#disabling-join-state-verification) for
+ more information on using `insecure` mode in practice.
+
+## Join state verification
+
+A **join state document** is an additional piece of information provided to
+joining bots alongside their usual Teleport certificate bundle. It contains
+signed information about the state of the joining process, including a sequence
+number that uniquely identifies each join attempt.
+
+**Join state verification** both ensures that the bot can provide a valid and
+signed join state document, and that the document contains the expected sequence
+number.
+* If successful, the join attempt can proceed and a new join state document will
+ be issued with an incremented sequence counter.
+* If the sequence number presented by the client is outdated, the join
+ attempt is rejected, and a lock is created to ensure existing clients will be
+ denied further access to the Teleport cluster.
+
+This system takes advantage of Teleport's short lived certificates and frequent
+bot reauthentication. If an attacker somehow obtains a copy of a bot's keypair
+and attempts to use it to retrieve Teleport certificates, they will initially
+succeed. However, the original bot will eventually make another authentication
+attempt, using its now-outdated join state document. This join attempt with an
+outdated document will trigger a lockout and block both the original bot and any
+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`.
+
+[renewable]: ../../join-methods.mdx#renewable-vs-non-renewable
+[token]: ../../join-methods.mdx#ephemeral-tokens
diff --git a/docs/pages/machine-workload-identity/machine-id/deployment/bound-keypair.mdx b/docs/pages/reference/machine-id/bound-keypair/getting-started.mdx
similarity index 57%
rename from docs/pages/machine-workload-identity/machine-id/deployment/bound-keypair.mdx
rename to docs/pages/reference/machine-id/bound-keypair/getting-started.mdx
index fa012d0cf61e3..40d738bb102d1 100644
--- a/docs/pages/machine-workload-identity/machine-id/deployment/bound-keypair.mdx
+++ b/docs/pages/reference/machine-id/bound-keypair/getting-started.mdx
@@ -16,8 +16,9 @@ perform an automated recovery to ensure the bot can rejoin even after an
extended outage.
Note that platform-specific join methods may be available that are better suited
-to your environment; refer to the [deployment guides](./deployment.mdx) for a
-full list of options.
+to your environment; refer to the
+[deployment guides](../../../machine-workload-identity/machine-id/deployment/deployment.mdx)
+for a full list of options.
## How it works
@@ -48,8 +49,8 @@ can be reconfigured at any time, and bots that expire or go offline can be
recovered by making a server-side exemption without any client-side
intervention.
-Refer to the [admin guide][guide] for further details on how this join method
-works.
+Refer to the [reference page][reference] for further details on how this join
+method works and how to use it in production.
## Prerequisites
@@ -61,7 +62,7 @@ works.
- This guide assumes the bot host has mutable persistent storage for internal
bot data. While it is possible to use Bound Keypair Joining can on immutable
hosts (like CI runs), doing so will reduce security guarantees; see the
- [admin guide][guide] for further information.
+ [reference page][reference] for further information.
## Step 1/5. Install `tbot`
@@ -123,9 +124,10 @@ If desired, it is also possible to generate a key on the bot host first and
register it with Teleport out-of-band, avoiding the need to copy secrets between
hosts.
-To learn more about preregistering public keys and Bound Keypair Joining's other
-onboarding and recovery options, refer to the
-[Reference and Admin Guide][guide].
+To learn more about preregistering public keys, see the
+[alternative flow](#alternative-preregistered-keys) below. For more information
+on Bound Keypair Joining's other onboarding and recovery options, refer to the
+[reference page][reference].
Use `tctl` to apply this file:
@@ -176,21 +178,116 @@ Replace the following:
(!docs/pages/includes/machine-id/configure-outputs.mdx!)
+## Alternative: preregistered keys
+
+This guide makes use of a registration secret - a single use shared secret
+that's consumed on the first bot join and allows a bot to automatically register
+its public key with Teleport. If you don't want to use any shared secrets, you
+can instead opt to generate the bot's keypair ahead of time and inform Teleport
+of the public key yourself.
+
+To generate a public key, run the following command on the bot host:
+```code
+## If needed, create the bot's storage directory
+$ mkdir -p /var/lib/teleport/bot
+$ tbot keypair create --storage /var/lib/teleport/bot --proxy-server=example.teleport.sh:443
+2025-07-08T16:31:48.000-00:00 INFO [TBOT] keypair has been written to storage storage:directory: /var/lib/teleport/bot tbot/keypair.go:135
+
+To register the keypair with Teleport, include this public key in the token's
+`spec.bound_keypair.onboarding.initial_public_key`:
+
+ ssh-ed25519
+```
+
+Note the SSH-style public key written to the console - you'll need this value in
+the next step.
+
+
+Be aware that if a keypair already exists within the specified storage directory
+it will not be overwritten by default. If an existing key is found, a warning
+will be logged and the existing public key will be printed to the console.
+
+If you explicitly want to create a new public key, pass the `--overwrite` flag
+to `tbot keypair create`. A warning will also be logged if any keys are actually
+overwritten.
+
+If you'd like to automate this process, the `--format=json` flag will write the
+public key string in a JSON document for use in scripts.
+
+
+Note that while a proxy server address must be provided, this is only used to
+ping the cluster to determine its configured
+[signature algorithms](../../signature-algorithms.mdx). Once the keypair has been
+generated, the public key will be printed to the console.
+
+Next, on your local machine, create a file named `token-example.yaml`:
+```yaml
+kind: token
+version: v2
+metadata:
+ name: example
+spec:
+ roles: [Bot]
+ join_method: bound_keypair
+ bot_name: example
+ bound_keypair:
+ onboarding:
+ initial_public_key: "ssh-ed25519 "
+ recovery:
+ mode: standard
+ limit: 1
+```
+
+The SSH-style public key generated by the bot should be copied into the
+`initial_public_key` field. As shown above, the value should be quoted to ensure
+the YAML remains valid.
+
+Create the token with `tctl`:
+```code
+$ tctl create -f token-example.yaml
+```
+
+Back on the bot machine, configure the bot as in the original guide, but this
+time with no `registration_secret` field set. Create a `tbot.yaml` with this
+content:
+```yaml
+version: v2
+proxy_server: example.teleport.sh:443
+onboarding:
+ join_method: bound_keypair
+ token: example
+storage:
+ type: directory
+ path: /var/lib/teleport/bot
+# outputs will be filled in during the completion of an access guide.
+outputs: []
+```
+
+As in the original example, replace:
+- `example.teleport.sh:443` with the address of your Teleport Proxy.
+- `example` with the name of the token created in the previous step, if you
+ changed it from `example`.
+
+Note that the `storage.path` must point to the same storage directory you passed
+to `tbot keypair create` above.
+
+Once the config file has been created, you can start the bot as usual:
+```code
+$ tbot start -c tbot.yaml
+```
+
+For additional information on how onboarding a new bot works with Bound Keypair
+Joining, refer to the [onboarding reference](./concepts.mdx#onboarding).
+
## Next steps
-- Read the [Bound Keypair Joining Reference andĀ Admin Guide][guide]
+- Read the [Bound Keypair Joining Reference][reference]
for more details about the join method and the available configuration options.
-- Follow the [access guides](../access-guides/access-guides.mdx) to finish configuring `tbot` for
- your environment.
-- Read the [configuration reference](../../../reference/machine-id/configuration.mdx) to explore
+- 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`.](../../../reference/machine-id/telemetry.mdx)
-
-{/*
-TODO: guide link above is a placeholder, link to the real guide once merged in
-follow-up PR.
-[guide]: ../../../reference/machine-id/bound-keypair.mdx
-*/}
+- [More information about `TELEPORT_ANONYMOUS_TELEMETRY`.](../telemetry.mdx)
-[secret]: ../../../reference/join-methods.mdx#secret-vs-delegated
-[guide]: ../../../reference/machine-id/machine-id.mdx
+[secret]: ../../join-methods.mdx#secret-vs-delegated
+[reference]: ./bound-keypair.mdx
diff --git a/docs/pages/reference/machine-id/machine-id.mdx b/docs/pages/reference/machine-id/machine-id.mdx
index e4db496e92d01..8e6d08a292729 100644
--- a/docs/pages/reference/machine-id/machine-id.mdx
+++ b/docs/pages/reference/machine-id/machine-id.mdx
@@ -13,3 +13,4 @@ labels:
- [Telemetry](telemetry.mdx)
- [V16 Upgrade Guide](v16-upgrade-guide.mdx)
- [Bot Terraform Resource](../../reference/terraform-provider/resources/bot.mdx)
+- [Bound Keypair Joining](bound-keypair/bound-keypair.mdx)