{"token_count": 7221}

# Deploying tbot with any OIDC provider via Generic OIDC

While Teleport natively supports many cloud platforms and identity providers, it is still often necessary to join bots using identity providers that do not have a dedicated join method. Generic OIDC allows you to join bots from any platform or provider where workloads are issued OIDC compatible JWTs, and allows you to define custom rules to allow only the intended workloads to authenticate to Teleport.

In this guide, you will create a Machine & Workload Identity Bot and configure Teleport to allow it to join from a hypothetical provider that does not have a Teleport join method, and without using any long-lived credentials.

## How it works

Instead of using shared secrets, the `generic_oidc` join method allows Teleport Agents and Bots to authenticate to the Teleport Auth Service using a JWT issued by any OIDC-compatible provider.

Many CI/CD platforms and cloud providers act as OIDC providers and can issue short-lived JWTs to workloads that serve as proof of the workload's identity. In fact, this is how many of Teleport's join methods work today, including the `github`, `kubernetes` (in `oidc` mode), and `gcp` join methods, among others.

The `generic_oidc` join method extends this support to any provider that can issue compatible JWTs, allowing you to freely specify any compatible provider and a set of free-form joining rules to select exactly which workloads are allowed to join, without requiring a dedicated join method for the provider.

Be aware that due to the nature of `generic_oidc`, this guide is meaningfully less directed than other deployment guides. The configuration shown here is only useful as an example or reference, and you will need to thoroughly customize and verify any configuration made before deploying it into a production environment.

---

WARNING

Be aware that not all OIDC providers are equally secure. When using `generic_oidc`, you are responsible for both properly vetting the provider and writing secure rules that will not allow unintended clients to join.

Where possible, one of Teleport's [dedicated join methods](https://goteleport.com/docs/ver/19.x/reference/deployment/join-methods.md) should be used instead of `generic_oidc`, which benefit from dedicated documentation and vetted, predefined rules.

Additionally, due to its generic nature, this document cannot fully guide you in setting up the `tbot` client to run seamlessly on arbitrary platforms. Teleport's dedicated join methods have guides and supporting documentation tailored specifically for use with those platforms; with `generic_oidc` you will need to determine the best deployment method yourself.

---

## Prerequisites

- A running Teleport (v18.11.0 or higher) cluster accessible at a hostname with a valid TLS certificate. 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 `tctl` and `tsh` clients.

  Installing `tctl` and `tsh` clients

  1. Determine the version of your Teleport cluster. The `tctl` and `tsh` 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 `tctl` and `tsh` clients:

     **Mac**

     Download the signed macOS .pkg installer for Teleport, which includes the `tctl` and `tsh` 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 `tctl` and `tsh` clients to your %PATH%
     NOTE: Do not place the `tctl` and `tsh` 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 `tctl` and `tsh` 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
     ```

  Connecting with TLS routing disabled

  This guide's commands assume your Teleport cluster uses TLS routing (`proxy_listener_mode: multiplex`), where the `tctl` and `tsh` clients reach every Teleport service through the Proxy Service's web address on port `443`. If you're not sure whether this applies to your cluster, check with whoever manages it.

  If your cluster uses separate listener ports instead, adjust ports as follows:

  - **`tsh` commands** (e.g., `tsh login --proxy=...`): continue using the Proxy Service web address on port `3080` (or `443` if behind a load balancer). Do not change these to port `3025`.

  - **Direct `tctl` or Auth Service API commands**: use port `3025` for the Auth Service gRPC listener:

    ```
    $ tctl status --auth-server=teleport.example.com:3025
    ```

* Check that you can connect to your Teleport cluster and verify that you can run `tctl` and `tsh` commands using your current credentials.

  1. Assign teleport.example.com to the domain name of the Teleport Proxy Service in your cluster and email\@example.com to your Teleport username.

  2. Authenticate to your Teleport cluster. This depends on whether your shell is interactive or not.

     **In an interactive shell:** Run the following command. By default, this triggers a multi-factor authentication prompt:

     ```
     $ tsh login --proxy=teleport.example.com --user=email@example.com
     $ tctl status
     Cluster  teleport.example.com
     Version  19.0.0-dev
     CA pin   sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678
     ```

     **On non-interactive environments:** If you are running `tsh` and `tctl` as an AI agent, in a CI/CD environment, or similar, make sure the `TELEPORT_IDENTITY_FILE` environment variable is assigned to a valid file path with credentials for your cluster. `tsh` and `tctl` read the file path from the environment variable and do not require a separate authentication step. If there is no identity file available, we recommend that you [set up Machine ID](https://goteleport.com/docs/ver/19.x/machine-workload-identity/getting-started.md) to provision one automatically.

     When executing `tctl` commands with an identity file, you must pass the `--auth-server` flag to provide the Teleport Auth Service address, which is not included in the identity file. If you provide the Proxy Service address, `tctl` connects to the Proxy Service, which forwards traffic to and from the Teleport Auth Service. Update 443 to `3025` if you are contacting the Auth Service directly with `tctl`:

     ```
     $ tctl status --auth-server=teleport.example.com:443
     ```

     For `tsh` commands that read an identity file, you must pass the `--proxy` flag, which points `tsh` to the address of the Teleport Proxy Service:

     ```
     $ tsh status --proxy=teleport.example.com
     ```

     Ensure client commands can access your identity file. Replace path/to/identity/file with the path to your identity file:

     ```
     $ export TELEPORT_IDENTITY_FILE="${TELEPORT_IDENTITY_FILE:-path/to/identity/file}"
     ```

     Add the `--auth-server` or `--proxy` flags to all subsequent `tctl` and `tsh` commands.

  If you can connect to the cluster and run the `tctl status` command, you can use your current credentials to run subsequent `tctl` commands from your workstation. If you host your own Teleport cluster, you can also run `tctl` commands on the computer that hosts the Teleport Auth Service for full permissions.

* Your user should have the privileges to create token resources.

* You must already have an OIDC-compliant identity provider that can issue JWTs to your bot environment

## Security considerations and limitations

Before getting started with `generic_oidc`, it is important to consider the potential security impacts associated with trusting arbitrary identity providers, as not all providers are created equal.

When Teleport adds support for a new identity provider, care is taken to ensure they meet minimum security requirements and that joining rules ensure a minimum bar for security. This includes:

- Ensuring the provider is actually OIDC compliant and issues sane credentials to workloads
- Ensuring those issued credentials contain useful identifying claims, and that one or more of those claims are required

When using `generic_oidc`, you should ensure any providers whose tokens you decide to trust provide at least the above properties.

As a hypothetical example, consider Google Cloud Platform. Note that Teleport has a dedicated [`gcp` join method](https://goteleport.com/docs/ver/19.x/reference/deployment/join-methods.md#gcp-service-account-gcp) which should be used instead of `generic_oidc`, but it makes for a useful example. If you naively issue a token using the `gcloud` tool it contains very few useful claims:

```
$ gcloud auth print-identity-token --audiences=example.teleport.sh | jwt decode -
[...]
{
  "aud": "example.teleport.sh",
  "azp": "115001032080118155850",
  "exp": 1784168534,
  "iat": 1784164934,
  "iss": "https://accounts.google.com",
  "sub": "115001032080118155850"
}
```

Note the following:

- There are no custom claims to match against
- Only `sub` contains any identifying information at all
- The issuer (`iss`) is global (<https://accounts.google.com>) and applies to all GCP users
- The audience (`aud`) is determined by the client and is generally not a claim that should be used for client verification

Given this, if you configured a `generic_oidc` join token to trust these GCP tokens, it would be trivially easy to write a rule that accidentally allowed *any* GCP user to authenticate to your Teleport cluster if your rule for the single useful claim (`sub`) was written incorrectly. As such, we would not recommend using `generic_oidc` to allow clients to join with a token this limited.

In GCP's case, you can pass an additional flag (`--token-format=full`) to the `gcloud` tool to include a larger set of useful claims in the issued JWT, like service account, instance, and project information - this is what Teleport's [dedicated `gcp` join method](https://goteleport.com/docs/ver/19.x/reference/deployment/join-methods.md#gcp-service-account-gcp) ensures - and is why built-in join methods are generally preferred where available.

### Limitations

Be aware that not all possible rules can be configured at this time. The `generic_oidc` join method has 3 different rule types, but it still may not always possible to encode arbitrarily complex logic into the token to account for especially unusual providers.

Limitations to be aware of include:

- Teleport's predicate language currently cannot compare floating point numbers, so `expression` rules within `allow_any` cannot validate numeric fields. Use `must_match_fields` or `conditions` if you need to compare a number field. Note that numbers *encoded as strings* in the JWT can be compared as strings without issue.
- Teleport's predicate language currently has limited support for lists and unary strings within its built-in functions, so string and list values must be wrapped using the `set()` helper to be used certain functions like `regexp.match()`, `contains_all()`, or `contains_any()`.
- The `generic_oidc` join method will refuse to compare integers encoded in number fields if they are larger than 2^53-1. This is because JSON numbers (64 bit IEEE floating point values) cannot uniquely represent numbers larger than 2^53-1, meaning multiple values would would match your expected number beyond this limit. If your token tries to compare a field with an integer value larger than this - on either side of the comparison - the join attempt will be rejected.
- Teleport's Terraform provider currently does not support `must_match_fields` and will **drop any values** you enter into the field.
- Teleport's Kubernetes operator currently has limited support for `must_match_fields` and only top-level claims fields (i.e. not nested fields) can be validated with this rule type.

### Identity provider requirements

Additionally, there are a number of minimum security requirements that identity providers must meet to work with `generic_oidc`:

1. They must use a modern signature algorithm, including: `RS256` or larger, `PS256` or larger, `ES256` or larger, or `EdDSA`
2. They must provide a `kid` in the token header
3. They must issue JWTs with standard fields set, including `exp`, `iat`, `sub`, `aud`, and `iss`
4. They must be able to serve OIDC discovery and JWKS resources within 10 seconds (unless `static_jwks` is used)
5. They must serve OIDC discovery and JWKS resources via HTTPS (unless `insecure_allow_http_issuer` is set in the token) at the standard path: `$ISSUER/.well-known/openid-configuration`

## Step 1/3. Create a Bot

Next, you need to create a Bot. A Bot is a Teleport identity for a machine or group of machines. Like users, bots have a set of roles and traits which define what they can access.

Create `bot.yaml`:

```
kind: bot
version: v1
metadata:
  # name is a unique identifier for the Bot in the cluster.
  name: example
spec:
  # roles is a list of roles to grant to the Bot. Don't worry if you don't know
  # what roles you need to specify here, the Access Guides will walk you through
  # creating and assigning roles to the already created Bot.
  roles: []

```

Make sure you replace `example` with a unique, descriptive name for your Bot.

Use `tctl` to apply this file:

```
$ tctl create bot.yaml
```

## Step 2/3. Create a join token for Generic OIDC

In order to allow your workload to authenticate with your Teleport cluster, you'll need to create a join token. These tokens define criteria by which the Auth Service decides whether to allow a bot or node to join.

### Retrieving a reference token

To create a `generic_oidc` join token, you'll first need to determine which OIDC provider you're using and then find or generate a reference JWT to use as a template for building your join token and rules.

Many providers provide documented token examples, for example:

- [GitHub Actions](https://docs.github.com/en/actions/concepts/security/openid-connect#understanding-the-oidc-token)
- [BuildKite](https://buildkite.com/docs/agent/cli/reference/oidc)
- [GitLab CI](https://docs.gitlab.com/ci/secrets/id_token_authentication/#token-payload)

Note that many of these providers (like GitHub) have dedicated Teleport join methods which should be used instead of `generic_oidc`.

Alternatively, if you can fetch a JWT inside your existing workload, then you can decode it with a tool like [`jwt-cli`](https://github.com/mike-engel/jwt-cli):

```
$ echo "$EXAMPLE_TOKEN" | jwt decode -
```

Different providers issue tokens to workloads in different ways, for example:

- Many CI/CD providers insert a token into a job's environment as an environment variable before the job starts. Note that many providers require a config parameter to enable this for a given step or workflow, or require a project-level setting to enable OIDC token issuance.
- Some platforms have a CLI tool that can fetch tokens and write them to stdout.
- Other platforms have an internal private HTTP API you can query to request a token.

Regardless of your provider, to continue you'll need to determine which of the two options you'll use to configure the `tbot` client to use the token:

1. An environment variable that contains a JWT
2. A command to run that fetches a JWT

For this example, we'll use a hypothetical provider named ExampleCI. They provide a command to run to fetch tokens with a given audience (`aud`) value, and their tokens look like this:

```
$ example-ci issue-token --audience=example.teleport.sh/example-bot | jwt decode -
{
  "namespace_id": "123",
  "namespace_path": "acme-corp",
  "project_id": "456",
  "project_path": "acme-corp/example-project",
  "user_id": "1",
  "user_login": "alice",
  "user_email": "alice@example.com",
  "job_id": "100",
  "ref": "feature-branch-1",
  "ref_type": "branch",
  "ref_path": "refs/heads/feature-branch-1",
  "ref_protected": "false",
  "runner": {
    "environment": "self-hosted",
    "protected": "false",
    "action": "start"
  },
  "job_source": "push",
  "jti": "235b3a54-b797-45c7-ae9a-f72d7bc6ef5b",
  "iss": "https://example.com",
  "iat": 1681395193,
  "nbf": 1681395188,
  "exp": 1681398793,
  "sub": "project_path:acme-corp/example-project:ref_type:branch:ref:feature-branch-1",
  "aud": "example.teleport.sh/example-bot"
}
```

### Creating a join token

With this JWT template in mind, you will create a join token that grants access to any CI/CD workflow runs within the `acme-corp/example-project` repository, with some additional restrictions included for demonstration purposes. You can find a full list of the available rules and syntax on the [join token reference page.](https://goteleport.com/docs/ver/19.x/reference/deployment/join-methods.md#generic-oidc-generic_oidc)

Create a file named `bot-token.yaml`:

```
kind: token
version: v2
metadata:
  name: example-bot
spec:
  # The Bot role indicates that this token grants access to a bot user, rather
  # than allowing a node to join. This role is built in to Teleport.
  roles: [Bot]
  join_method: generic_oidc

  # The bot_name indicates which bot user this token grants access to. This
  # should match the name of the bot that you created in the previous step.
  bot_name: example

  generic_oidc:
    # Issuer is the JWT issuer, and must match the `iss` value in the token
    # template. Additionally, it must serve OIDC discovery and JWKS documents,
    # unless `static_jwks` is configured as well.
    issuer: https://example.com

    # The token audience, or `aud` value. If your provider lets you set or
    # request this value, it should uniquely identify the Teleport cluster and
    # token, like the example below. You can use a random value like a UUID
    # if you prefer not to use the token.
    # If your provider does not allow you to specify the audience, enter the
    # value they provide here directly. Additionally, some providers include an
    # array of audiences; if so, the value specified here must be present in the
    # array.
    # The audience is always validated regardless of any custom rules you
    # specify.
    audience: example.teleport.sh/example-bot

    # These "field"-style matchers are "global" and are applied to all join
    # attempts using this token. They're useful for ensuring all `allow_any`
    # rules still check important fields, like the organization or namespace,
    # and ensure that even a mistake in an `allow_any` rule that might otherwise
    # allow joins from an unintended party still check at least that field.
    #
    # These match against the structure of the incoming JWT, so you can freely
    # specify even nested fields. Note that only strings, numbers, and booleans
    # can be used here. To check
    # against lists, you must use `allow_any` rules.
    #
    # This field is optional if you only want to use `allow_any` rules; at least
    # one rule is required between both rule categories.
    must_match_fields:
      # This ensures all joins at least come from the "acme-corp" namespace. Be
      # cautious of only matching against bare repository names: they may not be
      # adequately namespaced, so attackers might be able to make a repository
      # with the same name in a different namespace. A `must_match_fields` check
      # ensures all `allow_any` rules that allow different repositories or other
      # sub-resources at least must fall within this namespace, even if a given
      # expression accidentally allows unintended clients.
      namespace_path: acme-corp

      # Note the datatype: a plain 123 in YAML would parse a number and would
      # not match the datatype in the template JWT (a number inside a string).
      # As a security tip, for providers that provide both forms, it may be
      # helpful to match both a name and unique ID:
      # - Unique IDs help prevent name-reuse attacks if a repository or
      #   namespace is deleted
      # - Named identifiers help give a readable name to an arbitrary number or
      #   UUID
      # Combining both can help keep tokens both secure and auditable.
      namespace_id: "123"

      # You can nest fields freely. If a parent field doesn't exist, the join
      # attempt is rejected.
      runner:
        environment: self-hosted

    # These rules are evaluated after `must_match_fields` (if any), and only one
    # needs to match for the join to be allowed. This lets you allow joins from
    # multiple different repositories within the same token, if desired.
    #
    # This field is optional if you are satisfied with the "AND"-only rules of
    # `must_match_fields`; at least one rule is required between both rule
    # categories.
    allow_any:
      # This rule uses a Teleport Predicate expression to allow joins where the
      # user email matches a given suffix via regex.
      # All claim fields are available under the `claims` variable. Note that
      # the `set()` helper must be used to wrap any list or string values for
      # functions that expect a Set, like `regexp.match()` or `contains_all()`.
      # Note that the expression is wrapped in single quotes ('...') due to the
      # double quotes inside the expression itself.
      - expression: 'regexp.match(set(claims.user_email), "^.+@example.com$")'

      # This allows workflows from the `acme-corp/example-project` repo to join
      # without further restrictions.
      - conditions:
        - attribute: project_path
          eq:
            value: acme-corp/example-project

      # This allows workflows from `acme-corp/other-project`, but only if they
      # are run against the `feature-branch-1` branch.
      - conditions:
        - attribute: project_path
          eq:
            value: acme-corp/other-project
        - attribute: ref_type
          eq:
            value: branch
        - attribute: ref
          eq:
            value: feature-branch-1


```

As these are explicitly demonstration values, make sure to replace all of these rules with values tailored to your provider and environment. Broadly, we recommend the following minimum rules:

- Define some "global" requirements in `must_match_fields`, like an organization name or ID. These are always required, and are cheap insurance to protect against an `allow_any` rule that might accidentally let unintended clients in. At worst, these can limit the blast radius to within your organization.

- Define one or more `allow_any` rules to allow individual repositories, workloads, or other units that should be granted access, using either `expression` or `conditions`.

  - The `conditions` rule allows you to specify a list of simple conditions (`eq`, `not_eq`, `in`, `not_in`) that claim attributes must match.

  - The `expression` rule type uses Teleport's [predicate language](https://goteleport.com/docs/ver/19.x/reference/access-controls/predicate-language.md) to evaluate claims based on complex logic.

    See the [limitations](#limitations) section above for some specific compatibility notes when using some built-in predicate functions.

Additionally, be aware of the rule evaluation order:

- First, all `must_match_fields` checks are executed. All values specified here must both exist and match the corresponding values on the incoming JWT. If `must_match_fields` is empty, this is skipped.
- Next, each rule listed in `allow_any` is evaluated sequentially and evaluation stops after the first matching rule (either an `expression` or `conditions`).
- You may combine as many rules as you'd like between either `must_match_fields` or `allow_any`, or skip either variant entirely if you prefer. At least one rule or value must be matched between both rule types.

Once you've written your token YAML, create it with `tctl`:

```
$ tctl create -f bot-token.yaml
```

Check that token `example-bot` has been created with the following command:

```
$ tctl tokens ls
Token       Type Labels Expiry Time (UTC)
----------- ---- ------ ----------------------------------------------
example-bot Bot         01 Jan 00 00:00 UTC (2562047h47m16.854775807s)
```

## Step 3/3. Configure `tbot` within your workflow

Due to the wide variation in potential providers or platforms you might use `generic_oidc` on, we can only provide generalized advice for running `tbot`.

First, to install `tbot`, you can use your Teleport cluster's `install.sh` to download and install the proper version:

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

Next, `tbot` needs to be configured to connect to your Teleport cluster, and must also to be configured to actually fetch the JWT. This can be done inside `tbot.yaml`, but you might use a slightly different starting template depending on the type of workload or provider in use.

### Ephemeral workloads

For ephemeral workloads like CI/CD runs, there's no need to specify a long-lived `storage` directory, so the `memory` type can be used. Start with this `tbot.yaml`:

```
version: v2
proxy_server: example.teleport.sh:443
onboarding:
  join_method: generic_oidc
  token: example-bot
  generic_oidc:
    # Specify a command to run. The first value must be the executable to run,
    # followed by arguments, one per list entry.
    command: ["example-ci", "issue-token", "--audience=example.teleport.sh/example-bot"]

    # By default, commands timeout after 1 minute. If your OIDC provider needs
    # a different value, specify it in `timeout`
    # timeout: 1m

    # Alternatively, if the token can be found inside an environment variable,
    # you can simply specify the name of the variable containing the token:
    # env: "ENV_VAR_WITH_JWT"

storage:
  # For ephemeral workloads, there is no need to store data persistently
  type: memory

# Configure one or more services. Refer to the Access Guides to set up your
# desired services.
services: []

```

### Long-lived workloads

For deployments where you expect a single `tbot` instance to last a long time, and if it has a persistent filesystem, you might instead start with this `tbot.yaml`:

```
version: v2
proxy_server: example.teleport.sh:443
onboarding:
  join_method: generic_oidc
  token: example-bot
  generic_oidc:
    # Specify a command to run. The first value must be the executable to run,
    # followed by arguments, one per list entry.
    command: ["example-ci", "issue-token", "--audience=example.teleport.sh/example-bot"]

    # By default, commands timeout after 1 minute. If your OIDC provider needs
    # a different value, specify it in `timeout`
    # timeout: 1m

    # Alternatively, if the token can be found inside an environment variable,
    # you can simply specify the name of the variable containing the token:
    # env: "ENV_VAR_WITH_JWT"

storage:
  # This writes long-lived bot data to the configured directory. Note that the
  # Unix user that runs `tbot` must have write access to this path.
  type: directory
  path: /var/lib/teleport/bot

# Configure one or more services. Refer to the Access Guides to set up your
# desired services.
services: []

```

### Running `tbot`

Once configured, run the `tbot` client:

```
$ tbot start -c tbot.yaml
```

Note that the exact mechanics for running `tbot`, especially if you need it to run in the background to serve proxies or other long-lived services, will vary between different providers and platforms.

## Next steps

- Follow the [access guides](https://goteleport.com/docs/ver/19.x/machine-workload-identity/access-guides.md) to finish configuring `tbot` for your environment by assigning roles and configuring services.
- For more information about the `generic_oidc` join method, read the [join token reference page](https://goteleport.com/docs/ver/19.x/reference/deployment/join-methods.md#generic-oidc-generic_oidc)
- [More information about `anonymous-telemetry`.](https://goteleport.com/docs/ver/19.x/reference/machine-workload-identity/telemetry.md)
