# Joining Services with Bound Keypair

This guide explains how to use the **Bound Keypair join method** to configure Teleport processes to join your Teleport cluster.

The Bound Keypair join method supports two modes of operation for Teleport agents:

1. **Joining via a registration secret**, where a single-use secret is provided to or generated by Teleport and copied to the joining agent, much like the traditional `token` join method.
2. **Joining with a static key**, where a keypair is generated on the joining agent and shared out-of-band to Teleport, which is then configured to trust the agent's public key.

This guide covers the use of **registration secrets**, which is best used in environments with long-lived persistent storage for the Agent's data directory (e.g. `/var/lib/teleport`). If you wish to join an agent using Bound Keypair Static Keys, see the [dedicated guide](https://goteleport.com/docs/ver/19.x/installation/agents/bound-keypair-static-keys.md).

---

BOUND KEYPAIR FOR BOTS

This guide applies to joining agents with the standard `teleport` binary. For information on joining bots with the `bound_keypair` join method, refer to the [dedicated bot guide](https://goteleport.com/docs/ver/19.x/reference/machine-workload-identity/bound-keypair/getting-started.md).

---

## How it works

With Bound Keypair Joining, Teleport agents generate a unique keypair which is written to their internal per-node identity storage. Teleport is then configured to trust this public key for future joining attempts.

Later, when the agent attempts to join the cluster, Teleport issues it a challenge that can only be completed using its private key. The agent returns the solved challenge, attesting to its own identity, and is conditionally allowed to join the cluster. In the unlikely event the agent needs to retrieve new certificates, it repeats the challenge process using its stored key to reauthenticate and fetch new certificates.

Refer to the [reference page](https://goteleport.com/docs/ver/19.x/reference/machine-workload-identity/bound-keypair.md) for further details on how this join method works and how to use it in production.

Note that there are a number of limitations inherent to using Bound Keypair joining for non-bot agents:

- Teleport Agents joining a cluster with the Bound Keypair join method generally exercise Bound Keypair features only once during their initial join attempt. Unlike the `tbot` client which authenticates to Teleport frequently, Agents are issued long-lived certificates when they join which are not normally refreshed. This means join state verification, keypair rotation, and other Bound Keypair features will not be exercised during normal Agent operation.
- Standard preregistered keys are not currently supported.

For more information on agent joining limitations, see the [admin guide](https://goteleport.com/docs/ver/19.x/reference/machine-workload-identity/bound-keypair/admin-guide.md#limitations-of-bound-keypair-joining-for-teleport-agents).

## Prerequisites

- A running Teleport (v18.8.0 or higher) cluster. If you want to get started with Teleport, [sign up](https://goteleport.com/signup) for a free trial or [set up a demo environment](https://goteleport.com/docs/ver/19.x/get-started/deploy-community.md).

- The `tsh`, `tctl`, and `tbot` clients.

  Installing `tsh`, `tctl`, and `tbot` clients

  1. Determine the version of your Teleport cluster. The `tsh`, `tctl`, and `tbot` clients must be at most one major version behind your Teleport cluster version. Send a GET request to the Proxy Service at `/v1/webapi/find` and use a JSON query tool to obtain your cluster version. Replace teleport.example.com:443 with the web address of your Teleport Proxy Service:

     **Mac/Linux**

     ```
     $ TELEPORT_DOMAIN=teleport.example.com:443
     $ TELEPORT_VERSION="$(curl -s https://$TELEPORT_DOMAIN/v1/webapi/find | jq -r '.server_version')"
     ```

     **Windows - Powershell**

     ```
     $ $TELEPORT_DOMAIN = "teleport.example.com:443"
     $ $TELEPORT_VERSION = (Invoke-RestMethod -Uri "https://${TELEPORT_DOMAIN}/v1/webapi/find").server_version
     ```

  2. Follow the instructions for your platform to install `tsh`, `tctl`, and `tbot` clients:

     **Mac**

     Download the signed macOS .pkg installer for Teleport, which includes the `tsh`, `tctl`, and `tbot` clients:

     ```
     $ curl -O https://cdn.teleport.dev/teleport-${TELEPORT_VERSION?}.pkg
     ```

     In Finder double-click the `pkg` file to begin installation.

     ---

     DANGER

     Using Homebrew to install Teleport is not supported. The Teleport package in Homebrew is not maintained by Teleport and we can't guarantee its reliability or security.

     ---

     **Windows - Powershell**

     ```
     $ curl.exe -O https://cdn.teleport.dev/teleport-v$TELEPORT_VERSION-windows-amd64-bin.zip
     Unzip the archive and move the `tsh`, `tctl`, and `tbot` clients to your %PATH%
     NOTE: Do not place the `tsh`, `tctl`, and `tbot` clients in the System32 directory, as this can cause issues when using WinSCP.
     Use %SystemRoot% (C:\Windows) or %USERPROFILE% (C:\Users\<username>) instead.
     ```

     **Linux**

     All of the Teleport binaries in Linux installations include the `tsh`, `tctl`, and `tbot` clients. For more options (including RPM/DEB packages and downloads for i386/ARM/ARM64) see our [installation page](https://goteleport.com/docs/ver/19.x/installation/single-machine.md).

     ```
     $ curl -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ tar -xzf teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ cd teleport
     $ sudo ./install
     Teleport binaries have been copied to /usr/local/bin
     ```

* A Linux host, either bare-metal or a VM with persistent storage

## Step 1/5. Prepare the bound keypair token

**tctl**

Create the following `token.yaml` file with a `bound_keypair` section specifying your desired `bound_keypair` configuration. Most of the time, the `standard` recovery mode with a `recovery.limit` of 1 (the defaults) are sufficient for agent joining.

```
kind: token
version: v2
metadata:
  name: example-token
spec:
  # The role list, either `[Bot]` for bots, or a minimal set of roles (e.g.
  # Node, Proxy, App, Kube, DB, WindowsDesktop).
  roles: [Node]

  # Configures this token as a bound keypair token.
  join_method: bound_keypair

  # A bot name is required if `roles: [Bot]` is set above. It must be unset for
  # other node types.
  # bot_name: example

  # Fields related to the bound keypair joining process.
  bound_keypair:
    # Fields related to the initial join attempt.
    onboarding:
      # If set to a public key in SSH authorized_keys format, the
      # joining client must have the corresponding private key to join. This
      # keypair may be created using `tbot keypair create`. If set,
      # `registration_secret` and `must_register_before` are ignored.
      initial_public_key: ""

      # If set to a secret string value, a client may use this secret to perform
      # the first join without pre-registering a public key in
      # `initial_public_key`. If unset and no `initial_public_key` is provided,
      # a random value will be generated automatically into
      # `.status.bound_keypair.registration_secret`.
      registration_secret: ""

      # If set to an RFC 3339 timestamp, attempts to register via
      # `registration_secret` will be denied once the timestamp has elapsed. If
      # more time is needed, this field can be edited to extend the registration
      # period.
      must_register_before: ""

    # Fields related to recovery after certificates have expired.
    recovery:
      # The maximum number of allowed recovery attempts. This value may
      # be raised or lowered after creation to allow additional recovery
      # attempts should the initial limit be exhausted. If `mode` is set to
      # `standard`, recovery attempts will only be allowed if
      # `.status.bound_keypair.recovery_count` is less than this limit. This
      # limit is not enforced if `mode` is set to `relaxed` or `insecure`. This
      # value must be at least 1 to allow for the initial join during
      # onboarding, which counts as a recovery.
      limit: 1

      # The recovery rule enforcement mode. Valid values:
      # - standard (or unset): all configured rules enforced. The recovery limit
      #   and client join state are required and verified. This is the most
      #   secure recovery mode.
      # - relaxed: recovery limit is not enforced, but client join state is
      #   still required. This effectively allows unlimited recovery attempts,
      #   but client join state still helps mitigate stolen credentials.
      # - insecure: neither the recovery limit nor client join state are
      #   enforced. This allows any client with the private key to join freely.
      #   This is less secure, but can be useful in certain situations, like in
      #   otherwise unsupported CI/CD providers. This mode should be used with
      #   care, and RBAC rules should be configured to heavily restrict which
      #   resources this identity can access.
      mode: "standard"

    # If set to an RFC 3339 timestamp, once elapsed, a keypair rotation will be
    # forced on next join if it has not already been rotated. The most recent
    # rotation is recorded in `.status.bound_keypair.last_rotated_at`.
    rotate_after: ""

```

Run the following command to create the token:

```
$ tctl create token.yaml
```

**Terraform**

Add the following resource to your Terraform configuration, replacing values as indicated:

```
resource "teleport_provision_token" "example" {
  version = "v2"
  metadata = {
    name = "example-token"
  }

  labels = {
    // This label is added on the Teleport side by default
    "teleport.dev/origin" = "dynamic"
  }

  spec = {
    // The role list, either `[Bot]` for bots, or a minimal set of roles (e.g.
    // Node, Proxy, App, Kube, DB, WindowsDesktop).
    roles       = ["Node"]

    // Configures this token as a bound keypair token.
    join_method = "bound_keypair"

    // A bot name is required if roles contains 'Bot', otherwise it must be
    // unset.
    // bot_name    = "example"

    // Fields related to the bound keypair joining process.
    bound_keypair = {
      // Fields related to the initial join attempt.
      onboarding = {
        // If set to a public key in SSH authorized_keys format, the
        // joining client must have the corresponding private key to join.
        // This keypair may be created using `tbot keypair create`. If set,
        // `registration_secret` and `must_register_before` are ignored.
        // initial_public_key = ""

        // If set to a secret string value, a client may use this secret to
        // perform the first join without pre-registering a public key in
        // `initial_public_key`. If unset and no `initial_public_key` is
        // provided, a random value will be generated automatically into
        // `.status.bound_keypair.registration_secret`.
        // Note that any generated values must currently be fetched via `tctl`,
        // a random value should be provided here if required for a dependency.
        // registration_secret = ""

        // If set to an RFC 3339 timestamp, attempts to register via
        // `registration_secret` will be denied once the timestamp has
        // elapsed. If more time is needed, this field can be edited to
        // extend the registration period.
        // must_register_before = "2025-01-01T00:00:00Z"
      }

      // Fields related to recovery after certificates have expired.
      recovery = {
        // The maximum number of allowed recovery attempts. This value may
        // be raised or lowered after creation to allow additional recovery
        // attempts should the initial limit be exhausted. If `mode` is set
        // to `standard`, recovery attempts will only be allowed if
        // `.status.bound_keypair.recovery_count` is less than this limit.
        // This limit is not enforced if `mode` is set to `relaxed` or
        // `insecure`. This value must be at least 1 to allow for the
        // initial join during onboarding, which counts as a recovery.
        limit = 1

        // The recovery rule enforcement mode. Valid values:
        // - standard (or unset): all configured rules enforced. The
        //   recovery limit and client join state are required and
        //   verified. This is the most secure recovery mode.
        // - relaxed: recovery limit is not enforced, but client join state
        //   is still required. This effectively allows unlimited recovery
        //   attempts, but client join state still helps mitigate stolen
        //   credentials.
        // - insecure: neither the recovery limit nor client join state are
        //   enforced. This allows any client with the private key to join
        //   freely. This is less secure, but can be useful in certain
        //   situations, like in otherwise unsupported CI/CD providers.
        //   This mode should be used with care, and RBAC rules should be
        //   configured to heavily restrict which resources this identity
        //   can access.
        mode = "standard"
      }

      // If set to an RFC 3339 timestamp, once elapsed, a keypair rotation
      // will be forced on next join if it has not already been rotated.
      // The most recent rotation is recorded in
      // `.status.bound_keypair.last_rotated_at`.
      // rotate_after = "2025-01-01T00:00:00Z"
    }
  }
}

```

**Kubernetes**

Add the following Kubernetes resource manifest, replacing values as indicated:

```
apiVersion: "resources.teleport.dev/v2"
kind: TeleportProvisionToken
metadata:
  name: example-token
  labels:
    # This label is added on the Teleport side by default
    "teleport.dev/origin": "dynamic"
spec:
  # The role list, either `[Bot]` for bots, or a minimal set of roles (e.g.
  # Node, Proxy, App, Kube, DB, WindowsDesktop).
  roles: [Node]

  # Configures this token as a bound keypair token.
  join_method: bound_keypair

  # A bot name is required if `roles: [Bot]` is set above. It must be unset for
  # other node types.
  # bot_name: example

  # Fields related to the bound keypair joining process.
  bound_keypair:
    # Fields related to the initial join attempt.
    onboarding:
      # If set to a public key in SSH authorized_keys format, the joining
      # client must have the corresponding private key to join. This
      # keypair may be created using `tbot keypair create`. If set,
      # `registration_secret` and `must_register_before` are ignored.
      initial_public_key: ""

      # If set to a secret string value, a client may use this secret to
      # perform the first join without pre-registering a public key in
      # `initial_public_key`. If unset and no `initial_public_key` is
      # provided, a random value will be generated automatically into
      # `.status.bound_keypair.registration_secret`. Note that generated values
      # must currently be fetched via `tctl`, so a random value should be
      # explicitly provided if required for a dependency.
      registration_secret: ""

      # If set to an RFC 3339 timestamp, attempts to register via
      # `registration_secret` will be denied once the timestamp has
      # elapsed. If more time is needed, this field can be edited to
      # extend the registration period.
      must_register_before: ""

    # Fields related to recovery after certificates have expired.
    recovery:
      # The maximum number of allowed recovery attempts. This value may be
      # raised or lowered after creation to allow additional recovery
      # attempts should the initial limit be exhausted. If `mode` is set
      # to `standard`, recovery attempts will only be allowed if
      # `.status.bound_keypair.recovery_count` is less than this limit.
      # This limit is not enforced if `mode` is set to `relaxed` or
      # `insecure`. This value must be at least 1 to allow for the initial
      # join during onboarding, which counts as a recovery.
      limit: 1

      # The recovery rule enforcement mode. Valid values:
      # - standard (or unset): all configured rules enforced. The recovery
      #   limit and client join state are required and verified. This is
      #   the most secure recovery mode.
      # - relaxed: recovery limit is not enforced, but client join state
      #   is still required. This effectively allows unlimited recovery
      #   attempts, but client join state still helps mitigate stolen
      #   credentials.
      # - insecure: neither the recovery limit nor client join state are
      #   enforced. This allows any client with the private key to join
      #   freely. This is less secure, but can be useful in certain
      #   situations, like in otherwise unsupported CI/CD providers. This
      #   mode should be used with care, and RBAC rules should be
      #   configured to heavily restrict which resources this identity can
      #   access.
      mode: "standard"

    # If set to an RFC 3339 timestamp, once elapsed, a keypair rotation
    # will be forced on next join if it has not already been rotated. The
    # most recent rotation is recorded in
    # `.status.bound_keypair.last_rotated_at`.
    rotate_after: ""

```

## Step 2/5. Fetch the registration secret

Using the `tctl` client on your local machine, fetch the registration secret for the token you created in step 1:

```
$ tctl get token/example-token --with-secrets
kind: token
metadata:
  name: example-token
spec:
  bound_keypair:
    onboarding: {}
    recovery:
      limit: 1
      mode: standard
  join_method: bound_keypair
  roles:
  - Node
status:
  bound_keypair:
    bound_bot_instance_id: ""
    bound_public_key: ""
    recovery_count: 0
    registration_secret: example-value
version: v2
```

Make a note of the `status.bound_keypair.registration_secret` field value - you'll need to include this in your node's `teleport.yaml` in a future step.

## Step 3/5. Install Teleport

Install Teleport on your Linux VM or bare-metal host.

To install Teleport binaries on your Linux server, the recommended installation method is the cluster install script. It will select the correct version, edition, and installation mode for your cluster.

1. Assign teleport.example.com:443 to your Teleport cluster hostname and port, but not the scheme (https\://).

2. Run your cluster's install script:

   ```
   $ curl "https://teleport.example.com:443/scripts/install.sh" | sudo bash
   ```

## Step 4/5. Configure your services

The Bound Keypair join method can be used for Teleport processes running the SSH (`Node`), Proxy, Kubernetes, Application, Database, or Windows Desktop Services. The Teleport process should be run directly on your Linux host.

Configure your Teleport process with a custom `teleport.yaml` file. Use the `join_params` section with `token_name` matching your token created in Step 1 and `method: bound_keypair` as shown in the following example config:

```
# /etc/teleport.yaml
version: v3
teleport:
  join_params:
    token_name: example-token
    method: bound_keypair
    bound_keypair:
      registration_secret_value: <secret value here>

      # Alternatively, specify a path to a file containing the registration
      # secret.
      # registration_secret_path: /path/to/secret
  proxy_server: teleport.example.com:443
ssh_service:
  enabled: true
auth_service:
  enabled: false
proxy_service:
  enabled: false

```

Make sure to configure a registration secret under the `bound_keypair` field:

- Set `registration_secret_value` to a string containing the registration secret, or
- Set `registration_secret_path` to the path of a file containing the registration secret

Only one of the fields may be specified.

## Step 5/5. Launch your Teleport process

Configure your Teleport instance to start automatically when the host boots up by creating a systemd service for it. The instructions depend on how you installed your Teleport instance.

**Package Manager**

On the host where you will run your Teleport instance, enable and start Teleport:

```
$ sudo systemctl enable teleport
$ sudo systemctl start teleport
```

**TAR Archive**

On the host where you will run your Teleport instance, create a systemd service configuration for Teleport, enable the Teleport service, and start Teleport:

```
$ sudo teleport install systemd -o /etc/systemd/system/teleport.service
$ sudo systemctl enable teleport
$ sudo systemctl start teleport
```

You can check the status of your Teleport instance with `systemctl status teleport` and view its logs with `journalctl -fu teleport`.

Once you have started Teleport, confirm that your service is able to connect to and join your cluster.
