{"token_count": 10892}

# Joining Services with any OIDC provider via Generic OIDC

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

This guide will explain how to use the **Generic OIDC join method** to configure Teleport services to join your Teleport cluster by establishing trust with an OIDC-compatible issuer of your choice.

## 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
    ```

* An OIDC-compatible issuer of your choice (see below for specific requirements)

* An environment or workload that can fetch JWTs from your OIDC-compatible issuer

* One of the following client tools for managing Teleport resources:

  - The `tctl` CLI, which you can install along with Teleport on your workstation ([documentation](https://goteleport.com/docs/ver/19.x/installation/single-machine.md)) on your workstation.
  - [Teleport Terraform provider](https://goteleport.com/docs/ver/19.x/configuration/terraform-provider.md)
  - [Teleport Kubernetes operator](https://goteleport.com/docs/ver/19.x/configuration/teleport-operator.md)

## 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/2. Create a Generic OIDC join token

To allow your new agent to authenticate with Teleport, you'll need to create a join token. Join tokens define the criteria by which the Teleport Auth Service decides whether a join attempt will be allowed or rejected. In this step, you'll create a `generic_oidc`-type join token.

### 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)
- [Google Cloud](https://docs.cloud.google.com/compute/docs/instances/verifying-instance-identity)

Note that many of these providers (like GitHub, GitLab, and Google Cloud) 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 providers that provide an HTTP endpoint, you may need to provide your own script that fetches the token, for example using `curl`.

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=teleport.example.com/example-agent | 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": "teleport.example.com/example-agent"
}
```

### 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)

To do so, decide how you wish to create the token - directly via `tctl`, via Teleport's Terraform provider, or via Teleport's Kubernetes operator - and refer to the matching token template to use as a starting point:

**tctl**

Create `token.yaml` with this initial content:

```
kind: token
version: v2
metadata:
  # name identifies the token. When configuring a bot or node to join using this
  # token, this name should be specified.
  name: generic-oidc-token
spec:
  # For Machine & Workload Identity bots, roles will always be "Bot". For
  # standard Teleport agents, roles may include "Node", "Kube", "App", "db", or
  # similar, depending on the desired services.
  roles: [Node]

  # This field is always "generic_oidc" for Generic OIDC joining.
  join_method: generic_oidc

  # bot_name specifies the name of the bot that this token will grant access to
  # when it is used. If joining a standard Teleport agent, this field should be
  # omitted. It is required for bots.
  # bot_name: generic-oidc-demo

  generic_oidc:
    # The OIDC issuer. Must exactly match the `iss` value in incoming OIDC
    # tokens. This value is always required.
    #
    # Unless `static_jwks` is configured, this issuer must be accessible over
    # HTTPS to the Teleport cluster and must serve valid OIDC metadata,
    # including discovery configuration and JWKS keys.
    issuer: https://example.com

    # If set, this flag allows the use of HTTP-only issuers. Not recommended for
    # production use.
    # insecure_allow_http_issuer: false

    # The audience (`aud`) value to require in the incoming JWTs. This value is
    # always required.
    #
    # Note that not all issuers allow you to configure or request an audience
    # value of your choosing. For issuers that require use of prescribed `aud`
    # values, that value should be entered here exactly.
    #
    # Otherwise, for providers that do allow you to configure or request a
    # value, we recommend making the value unique to this cluster and join
    # token. You can include your Teleport cluster name (teleport.example.com)
    # and the name of the token, as shown below, or use a random UUID if you
    # prefer.
    audience: teleport.example.com/generic-oidc-token

    # The TLS CA certificate or certificates, if your OIDC provider's TLS
    # certificates are not trusted by the standard system trust store. If set,
    # this value replaces the system CA store outright, so you can include
    # multiple concatenated certificates if necessary.
    #
    # The CAs specified here are only used to validate requests using this token
    # and will not be used to verify any other join attempts or TLS connections
    # for other Teleport features. Be aware that if the issuer's TLS certificate
    # is rotated such that it is no longer trusted by the CA certs specified
    # here, join attempts will be rejected and this token will need to be
    # updated to include the updated CA certificates.
    #
    # Most users will not need to configure this value.
    #
    # tls_ca: |
    #   -----BEGIN CERTIFICATE-----
    #   ...
    #   -----END CERTIFICATE-----

    # These fields perform simple equality matches against the incoming JWT
    # using "AND" semantics. Rules are written by mirroring the structure of the
    # JWT, and each written value must be equal to the value in the JWT
    # presented by the client.
    #
    # These field rules can be used as simple "global" matchers that apply to
    # all join attempts using this token. For example, you can use this to
    # ensure all `allow_any` rules also check `organization_name` to ensure they
    # can never accidentally skip an important check.
    #
    # If any rules are specified here, all must match. If any rules are
    # specified in `allow_any`, these rules are evaluated first, if any exist,
    # and `allow_any` is only evaluated if all of these checks pass. If no rules
    # are specified here, only `allow_any` rules are evaluated. At least one
    # rule must exist between `must_match_fields` and `allow_any`.
    #
    # Note the following limitations for field rules:
    # - This field currently cannot be configured via the Teleport Terraform
    #   provider and `allow_any` must be used instead.
    # - Strings, numbers, booleans, and nested objects are supported.
    # - List values are currently not supported.
    # - Integer comparisons must be less than 2^53-1, as integers larger than
    #   this cannot be accurately represented in JSON's 64-bit floating point
    #   number type. If either a rule or incoming value is larger than this, the
    #   join attempt will be rejected.
    #
    # NOTE: must_match_fields is currently NOT SUPPORTED in Teleport's Terraform
    # provider, and any rules you specify here will be SILENTLY IGNORED. If
    # using the Terraform provider, you must rely exclusively on `allow_any`
    # rules instead.
    #
    # NOTE: must_match_fields currently has limited support if using Teleport's
    # Kubernetes operator to provision resources. Nested fields are currently
    # not supported and will result in an error at creation time. Top-level
    # fields (direct children of `must_match_fields`) will work as expected.
    must_match_fields:
      organization_name: "acme-corp"

      # Fields can be nested freely to match the structure of the incoming JWT.
      example:
        project_id: example-1234567

        # Numeric comparisons are supported, but be mindful of the datatype as
        # determined by YAML parsing rules.
        # This will be parsed as a number, and the datatype of the equivalent
        # field on the incoming JWT must also be a number. To compare as a
        # string, it must be quoted, e.g.: "1234"
        number_field: 1234

        # Boolean values are supported, but as above, this also requires that
        # the equivalent field on the incoming JWT is a boolean. To compare it
        # as a string, the value must be quoted, e.g.: "true"
        boolean_field: true

    # If specified, at least one of these rules must match. These are evaluated
    # with "OR" semantics, after all `must_match_fields` rules have been
    # evaluated successfully (if any were written). At least one rule must be
    # written between `must_match_fields` and `allow_any`.
    #
    # Note that for a given entry, at most one of `expression` or `conditions`
    # can be configured.
    allow_any:
      # A Teleport predicate expression. Claims are available under the `claims`
      # object.
      # Note the following limitations with Teleport's predicate language and
      # claim evaluation:
      # - Numbers cannot be compared; use `conditions` or `must_match_fields`
      #   for numeric comparisons instead.
      # - Lists must be wrapped in `set()` for use with most predicate
      #   functions.
      - expression: 'claims.organization_name == "acme-corp"'

      # A list of simple field conditions. Unlike expressions, attributes are
      # not prefixed with "claims" and instead are a dot-separated list of
      # fields starting from the top level of the JWT.
      #
      # Valid operators include: eq, not_eq, in, not_in
      - conditions:
        - attribute: email_verified
          eq:
            value: "true"

        # Nested fields are supported. This denies join requests where the
        # project named "invalid-project" is present in the JWT.
        - attribute: example.project_name
          not_eq:
            value: "invalid-project"

        # `in` can be used to accept one from a set of values, for example this
        # allows join attempts where `project_name` is any of "foo", "bar", or
        # "baz".
        - attribute: example.project_name
          in:
            values: [foo, bar, baz]

        # `not_in` can be used to exclude a set of values, for example this
        # denies join attempts where `zone` is `us-central1-a|b|c`
        - attribute: zone
          not_in:
            values: ["us-central1-a", "us-central1-b", "us-central1-c"]

```

Customize it as necessary, and when ready, create the token:

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

Finally, validate the token was created:

```
$ tctl get token/generic-oidc-token

kind: token
metadata:
  expires: "3000-01-01T00:00:00Z"
  name: generic-oidc-token
spec:
  join_method: generic_oidc
  roles:
  - Node
version: v2
```

**Terraform**

---

MUST\_MATCH\_FIELDS LIMITATION

Note that `must_match_fields` is currently not supported in Teleport's Terraform provider and any rules you specify in that field **will be silently ignored**.

For now, either write all rules instead within `allow_any`, or create your `generic_oidc` tokens via `tctl`.

---

Add the following resource to your Terraform configuration, adjusting values and rules as necessary:

```
resource "teleport_provision_token" "generic-oidc-token" {
  version = "v2"
  metadata = {
    # name identifies the token. When configuring a bot or node to join using
    # this token, this name should be specified.
    name = "generic-oidc-token"

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

  spec = {
    // For Machine & Workload Identity joining, roles will always be "Bot". For
    // standard Teleport agents, roles may include "Node", "Kube", "App", "db",
    // or similar, depending on the desired services.
    roles = ["Node"]

    // This field is always "generic_oidc" for Generic OIDC joining.
    join_method = "generic_oidc"

    // bot_name specifies the name of the bot that this token will grant access
    // to when it is used. If joining a standard Teleport agent, this field
    // should be omitted. This field is required for bots
    // bot_name = "generic-oidc-demo"

    generic_oidc = {
      // The OIDC issuer. Must exactly match the `iss` value in incoming OIDC
      // tokens. This value is always required.
      //
      // Unless `static_jwks` is configured, this issuer must be accessible over
      // HTTPS to the Teleport cluster and must serve valid OIDC metadata,
      // including discovery configuration and JWKS keys.
      issuer = "https://example.com"

      // If set, this flag allows the use of HTTP-only issuers. Not recommended
      // for production use.
      // insecure_allow_http_issuer = false

      // The audience (`aud`) value to require in the incoming JWTs. This value
      // is always required.
      //
      // Note that not all issuers allow you to configure or request an audience
      // value of your choosing. For issuers that require use of prescribed
      // `aud` values, that value should be entered here exactly.
      //
      // Otherwise, for providers that do allow you to configure or request a
      // value, we recommend making the value unique to this cluster and join
      // token. You can include your Teleport cluster name (example.teleport.sh)
      // and the name of the token, as shown below, or use a random UUID if you
      // prefer.
      audience = "teleport.example.com/generic-oidc-token"

      // The TLS CA certificate or certificates, if your OIDC provider's TLS
      // certificates are not trusted by the standard system trust store. If
      // set, this value replaces the system CA store outright, so you can
      // include multiple concatenated certificates if necessary.
      //
      // The CAs specified here are only used to validate requests using this
      // token and will not be used to verify any other join attempts or TLS
      // connections for other Teleport features. Be aware that if the issuer's
      // TLS certificate is rotated such that it is no longer trusted by the CA
      // certs specified here, join attempts will be rejected and this token
      // will need to be updated to include the updated CA certificates.
      //
      // Most users will not need to configure this value.
      //
      // tls_ca = <<-EOT
      //   -----BEGIN CERTIFICATE-----
      //   ...
      //   -----END CERTIFICATE-----
      // EOT

      // NOTE: `must_match_fields` is currently NOT SUPPORTED in Teleport's
      // Terraform provider, and any rules you specify here will be SILENTLY
      // IGNORED. When using the Terraform provider, you must rely exclusively
      // on `allow_any` rules instead.
      // must_match_fields = { ... }

      // If specified, at least one of these rules must match. These are
      // evaluated with "OR" semantics, after all `must_match_fields` rules have
      // been evaluated successfully (if any were written). At least one rule
      // must be written between `must_match_fields` and `allow_any`. Because the
      // Terraform provider does not support `must_match_fields`, `allow_any`
      // must contain at least one rule here.
      //
      // Note that for a given entry, at most one of `expression` or
      // `conditions` can be configured.
      allow_any = [
        {
          // A Teleport predicate expression. Claims are available under the
          // `claims` object.
          // Note the following limitations with Teleport's predicate language
          // and claim evaluation:
          // - Numbers cannot be compared; use `conditions` for numeric
          //   comparisons instead.
          // - Lists must be wrapped in `set()` for use with most predicate
          //   functions.
          expression = "claims.organization_name == \"acme-corp\""
        },
        {
          // A list of simple field conditions. Unlike expressions, attributes
          // are not prefixed with "claims" and instead are a dot-separated list
          // of fields starting from the top level of the JWT.
          //
          // Valid operators include: eq, not_eq, in, not_in
          conditions = [
            {
              attribute = "email_verified"
              eq = {
                value = "true"
              }
            },
            {
              // Nested fields are supported. This denies join requests where
              // the project named "invalid-project" is present in the JWT.
              attribute = "example.project_name"
              not_eq = {
                value = "invalid-project"
              }
            },
            {
              // `in` can be used to accept one from a set of values, for
              // example this allows join attempts where `project_name` is any
              // of "foo", "bar", or "baz".
              attribute = "example.project_name"
              in = {
                values = ["foo", "bar", "baz"]
              }
            },
            {
              // `not_in` can be used to exclude a set of values, for example
              // this denies join attempts where `zone` is `us-central1-a|b|c`
              attribute = "zone"
              not_in = {
                values = ["us-central1-a", "us-central1-b", "us-central1-c"]
              }
            },
          ]
        },
      ]
    }
  }
}

```

**Kubernetes**

---

MUST\_MATCH\_FIELDS LIMITATION

Note that `must_match_fields` has limited support in Teleport's Kubernetes operator: rules can only be written against root-level claims on the incoming JWT. Attempting to check nested claim fields will result in an error.

If you need to check nested fields, either write those checks within `allow_any` rules, or create your `generic_oidc` token via `tctl` which does not have this limitation.

---

Add the following Kubernetes resource manifest, adjusting values and rules as necessary:

```
apiVersion: "resources.teleport.dev/v2"
kind: TeleportProvisionToken
metadata:
  # name identifies the token. When configuring a bot or node to join using this
  # token, this name should be specified.
  name: generic-oidc-token
  labels:
    # This label is added on the Teleport side by default
    "teleport.dev/origin": "dynamic"
spec:
  # For Machine & Workload Identity bots, roles will always be "Bot". For
  # standard Teleport agents, roles may include "Node", "Kube", "App", "db", or
  # similar, depending on the desired services.
  roles: [Node]

  # This field is always "generic_oidc" for Generic OIDC joining.
  join_method: generic_oidc

  # bot_name specifies the name of the bot that this token will grant access to
  # when it is used. If joining a standard Teleport agent, this field should be
  # omitted. This field is required for bots.
  # bot_name: generic-oidc-demo

  generic_oidc:
    # The OIDC issuer. Must exactly match the `iss` value in incoming OIDC
    # tokens. This value is always required.
    #
    # Unless `static_jwks` is configured, this issuer must be accessible over
    # HTTPS to the Teleport cluster and must serve valid OIDC metadata,
    # including discovery configuration and JWKS keys.
    issuer: https://example.com

    # If set, this flag allows the use of HTTP-only issuers. Not recommended for
    # production use.
    # insecure_allow_http_issuer: false

    # The audience (`aud`) value to require in the incoming JWTs. This value is
    # always required.
    #
    # Note that not all issuers allow you to configure or request an audience
    # value of your choosing. For issuers that require use of prescribed `aud`
    # values, that value should be entered here exactly.
    #
    # Otherwise, for providers that do allow you to configure or request a
    # value, we recommend making the value unique to this cluster and join
    # token. You can include your Teleport cluster name (example.teleport.sh)
    # and the name of the token, as shown below, or use a random UUID if you
    # prefer.
    audience: teleport.example.com/generic-oidc-token

    # The TLS CA certificate or certificates, if your OIDC provider's TLS
    # certificates are not trusted by the standard system trust store. If set,
    # this value replaces the system CA store outright, so you can include
    # multiple concatenated certificates if necessary.
    #
    # The CAs specified here are only used to validate requests using this token
    # and will not be used to verify any other join attempts or TLS connections
    # for other Teleport features. Be aware that if the issuer's TLS certificate
    # is rotated such that it is no longer trusted by the CA certs specified
    # here, join attempts will be rejected and this token will need to be
    # updated to include the updated CA certificates.
    #
    # Most users will not need to configure this value.
    #
    # tls_ca: |
    #   -----BEGIN CERTIFICATE-----
    #   ...
    #   -----END CERTIFICATE-----

    # These fields perform simple equality matches against the incoming JWT
    # using "AND" semantics. Rules are written by mirroring the structure of the
    # JWT, and each written value must be equal to the value in the JWT
    # presented by the client.
    #
    # These field rules can be used as simple "global" matchers that apply to
    # all join attempts using this token. For example, you can use this to
    # ensure all `allow_any` rules also check `organization_name` to ensure they
    # can never accidentally skip an important check.
    #
    # If any rules are specified here, all must match. If any rules are
    # specified in `allow_any`, these rules are evaluated first, if any exist,
    # and `allow_any` is only evaluated if all of these checks pass. If no rules
    # are specified here, only `allow_any` rules are evaluated. At least one
    # rule must exist between `must_match_fields` and `allow_any`.
    #
    # Note the following limitations for field rules:
    # - Strings, numbers, booleans, and nested objects are supported.
    # - List values are currently not supported.
    # - Integer comparisons must be less than 2^53-1, as integers larger than
    #   this cannot be accurately represented in JSON's 64-bit floating point
    #   number type. If either a rule or incoming value is larger than this, the
    #   join attempt will be rejected.
    #
    # NOTE: must_match_fields currently has limited support when using
    # Teleport's Kubernetes operator to provision resources. Nested fields are
    # currently not supported and will result in an error at creation time.
    # Only top-level fields (direct children of `must_match_fields`) will work
    # as expected. The nested `example` block below is included for parity with
    # the source token, but will be rejected by the operator as written; remove
    # it or flatten it to top-level fields when using the Kubernetes operator.
    must_match_fields:
      organization_name: "acme-corp"

      # Fields can be nested freely to match the structure of the incoming JWT.
      example:
        project_id: example-1234567

        # Numeric comparisons are supported, but be mindful of the datatype as
        # determined by YAML parsing rules.
        # This will be parsed as a number, and the datatype of the equivalent
        # field on the incoming JWT must also be a number. To compare as a
        # string, it must be quoted, e.g.: "1234"
        number_field: 1234

        # Boolean values are supported, but as above, this also requires that
        # the equivalent field on the incoming JWT is a boolean. To compare it
        # as a string, the value must be quoted, e.g.: "true"
        boolean_field: true

    # If specified, at least one of these rules must match. These are evaluated
    # with "OR" semantics, after all `must_match_fields` rules have been
    # evaluated successfully (if any were written). At least one rule must be
    # written between `must_match_fields` and `allow_any`.
    #
    # Note that for a given entry, at most one of `expression` or `conditions`
    # can be configured.
    allow_any:
      # A Teleport predicate expression. Claims are available under the `claims`
      # object.
      # Note the following limitations with Teleport's predicate language and
      # claim evaluation:
      # - Numbers cannot be compared; use `conditions` or `must_match_fields`
      #   for numeric comparisons instead.
      # - List support is currently limited.
      - expression: 'claims.organization_name == "acme-corp"'

      # A list of simple field conditions. Unlike expressions, attributes are
      # not prefixed with "claims" and instead are a dot-separated list of
      # fields starting from the top level of the JWT.
      #
      # Valid operators include: eq, not_eq, in, not_in
      - conditions:
        # Condition values are always compared as strings: the incoming
        # attribute is cast to a string before it is compared, so the value must
        # be quoted (e.g. "true" rather than true).
        - attribute: email_verified
          eq:
            value: "true"

        # Nested fields are supported. This denies join requests where the
        # project named "invalid-project" is present in the JWT.
        - attribute: example.project_name
          not_eq:
            value: "invalid-project"

        # `in` can be used to accept one from a set of values, for example this
        # allows join attempts where `project_name` is any of "foo", "bar", or
        # "baz".
        - attribute: example.project_name
          in:
            values: [foo, bar, baz]

        # `not_in` can be used to exclude a set of values, for example this
        # denies join attempts where `zone` is `us-central1-a|b|c`
        - attribute: zone
          not_in:
            values: ["us-central1-a", "us-central1-b", "us-central1-c"]

```

Be aware that the examples shown above are for demonstrative purposes. Make sure to replace all rules with values tailored to your provider and environment, using the reference token you fetched as a guide.

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.

  In the example JWT shown above, `namespace_id` and `namespace_path` might be good global rules to configure for all joining clients.

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

  With the example JWT in mind, `project_id` and `project_path` might be useful fields to perform an equality check on, to ensure join attempts are coming from an authorized project.

  - 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.

## Step 2/2. Set up Teleport on your provider

The Generic OIDC join method can be used for Teleport processes running the SSH, Proxy, Kubernetes, Application, Database, or Desktop Service.

1. Install Teleport on your target environment (VM or physical machine)

   To install Teleport binaries on your Linux server, the recommended installation method is the cluster install script. This script is served by your Teleport cluster's Proxy Service and automatically selects the correct version, edition, and installation mode to match your cluster.

   1. Remove any existing Teleport binaries on your system:

      ```
      $ sudo rm -f /usr/local/bin/{tsh,teleport,tctl,tbot,fdpass-teleport,teleport-update}
      ```

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

   3. Run your cluster's install script:

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

2. 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: generic_oidc` as shown in the following example config:

   ```
   # /etc/teleport.yaml
   version: v3
   teleport:
     join_params:
       token_name: generic-oidc-token
       method: generic_oidc
       generic_oidc:
         # Specify a command to run. The first value must be the executable to run,
         # followed by arguments, one per list entry. This example calls
         # `example-provider issue-token ...` with a client-specified audience
         # request. Your provider will differ.
         command: ["example-ci", "issue-token", "--audience=teleport.example.com/example-agent"]

         # 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. This cannot be combined with `command`.
         # env: "ENV_VAR_WITH_JWT"
     proxy_server: teleport.example.com:443
   ssh_service:
     enabled: true
   auth_service:
     enabled: false
   proxy_service:
     enabled: false

   ```

3. Start Teleport:

   Start your Teleport instance. The instructions depend on how you installed your Teleport instance and whether your system supports systemd:

   **Package Manager**

   Configure your Teleport instance to start automatically when the host boots up by creating a systemd service for it. On the host where you will run your Teleport instance, enable and start Teleport:

   ```
   $ 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`.

   **TAR Archive**

   Configure your Teleport instance to start automatically when the host boots up by creating a systemd service for it. 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`.

   **No systemd**

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

   ```
   $ sudo teleport start --config=/etc/teleport.yaml
   ```

   Teleport runs in the foreground and outputs logs for the services it is running.

4. Confirm that your Teleport process is able to connect to and join your cluster. You're all set!
