{"token_count": 15428}

# Scoped RBAC

**Scopes** let you divide a Teleport cluster into a hierarchy of isolated administrative boundaries. Use scopes when you want to delegate management of one part of a cluster without granting access to unrelated resources.

## How it works

A scope is a path-like attribute (for example `/staging/west` or `/prod`) that Teleport attaches to supported resources, permission grants, and issued credentials. Permissions assigned at a scope apply to resources within that scope and its descendants, but not to parent or sibling scopes. Scopes are an *attribute*, not an object, so a scope path does not need to be created before resources or permissions are assigned to it.

Cluster administrators create scoped roles, assign those roles to users at a specific scope, and create scoped join tokens for Agents. When a user logs in with a scope, Teleport issues credentials pinned to that scope. Scoped RBAC checks then authorize access only to resources in that scope or one of its descendants. Agents that join with a scoped token register their supported resources at the token's assigned scope.

For example, a cluster administrator can assign a scoped admin role to Alice at `/staging/west`. Alice can then manage scoped roles, scoped tokens, and SSH Services in `/staging/west` and descendant scopes such as `/staging/west/team-a`, but she cannot affect `/staging/east`, `/prod`, or unscoped cluster resources.

Scopes complement labels, but they solve a different problem. Labels describe individual resources and are used by roles to choose matching resources. Scopes define the administrative boundary and the scope of issued credentials. Label selectors can further restrict which resources a scoped role can access, but they cannot grant access outside the role assignment's scope.

## Use cases

Scopes are designed to enable:

- **Hierarchical isolation.** Permissions granted within a scope cannot affect resources or permissions in parent or sibling scopes.
- **Delegated administration.** Cluster administrators can grant powerful administrative capabilities to "scope admins" without those admins being able to affect anything outside their scope.
- **Reduced blast radius.** Users can pin a login session to a specific scope, limiting the privileges of the resulting credentials to that scope and its descendants.
- **Mixed permissiveness.** Different access controls (such as session recording, port forwarding, or idle timeouts) can apply to the same user in different scopes.

---

ACTIVE DEVELOPMENT

Scopes are an actively developed feature of Teleport. Not every Teleport feature is supported within a scope yet, and breaking changes may still be introduced. Scoped resources and credentials created with one Teleport version may not work with another.

When operating in a scoped session, commands and APIs that have not yet been updated to understand scopes will commonly fail with a `scoped identities not supported` error. This is expected for features outside the currently supported set listed below.

---

## Prerequisites

- A running Teleport 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/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/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/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.

* All Teleport instances (Auth Service, Proxy Service, and Agents) must be running the same Teleport version, and must have the `TELEPORT_UNSTABLE_SCOPES=yes` environment variable set.

* The Auth Service must also have the `TELEPORT_UNSTABLE_AGENT_SCOPE_PIN=yes` environment variable set before you use scoped tokens to join Agent roles other than SSH Services (`node`) and Bots. This includes scoped Kubernetes Services and Application Services.

## Currently supported features

The current implementation provides the following functionality. Treat this as the exhaustive list of scoped features; features that are not listed here should be assumed unsupported in scoped mode.

- The `scoped_role` resource for describing scoped permissions.
- The `scoped_role_assignment` resource for assigning scoped roles to users and Bots.
- The `scoped_token` resource for joining Agents at a specific scope.
- Scope pinning, allowing a user to log in at a scope and create a session whose privileges are limited to the target scope.
- Basic scoped SSH access, including joining SSH Services at a scope and assigning scoped SSH access to users.
- Basic scoped Kubernetes access, including joining Kubernetes services at a scope, registering dynamic clusters, and assigning scoped Kubernetes access to users.
- Basic scoped Application access, including joining Application Services at a scope and assigning scoped application access to users.
- Scoped Access Lists that can be managed by scoped administrators and used to grant scoped roles to groups of users.
- Scoped Bots for Machine and Workload Identity (MWI), supporting both identity output and SSH access.
- Scoped Workload Identity resources for issuing SPIFFE credentials to workloads within a scope.

## Basic usage

Scoped and unscoped operations are mutually exclusive. When logged in without a scope, scoped roles do not grant any privileges. When logged in to a scope, only features that have been explicitly updated for scopes are expected to work. The following commands are officially supported in scoped mode:

- `tsh login --scope=<scope> ...`
- `tsh logout`
- `tsh scopes ls` (lists the scopes at which the user has assigned privileges)
- `tsh ls` (lists SSH instances available at the current scope)
- `tsh ssh <login>@<host>` (for SSH instances available at the current scope)
- `tsh kube ls` (lists Kubernetes clusters available at the current scope)
- `tsh kube login <cluster>` (for Kubernetes clusters available at the current scope)
- `tsh apps ls` (lists applications available at the current scope)
- `tsh apps login <app>` (for applications available at the current scope)
- `tctl get|create|edit|rm <resource>` (for scoped resources)
- `tctl scoped tokens add|rm|ls ...` (for managing scoped tokens)
- `tctl scopes status` (overview of scoped privilege usage in the cluster)

---

NOTE

The admin action MFA prompt does not currently apply to scoped resources. Scoped commands that do not prompt for MFA today may begin to do so in future releases.

---

## Setting up a scoped admin

A new Teleport cluster has no scoped roles defined. The default `editor` role includes the ability to create and assign scoped roles, so an unscoped administrator can bootstrap the first scoped admin.

While logged in as a user with the `editor` role, create a scoped admin role that can manage scoped permissions, SSH into instances, connect to Kubernetes clusters, and access applications within `/examples` (replace `ubuntu` with your desired OS login):

```
$ tctl create <<EOF
kind: scoped_role
metadata:
  name: example-admin
scope: /examples
spec:
  assignable_scopes:
    - /examples
  kube:
    groups: [cluster-admin]
    labels:
      - name: '*'
        values: ['*']
    resources:
      - kind: '*'
        namespace: '*'
        api_group: '*'
        name: '*'
        verbs: ['*']
  ssh:
    logins: [ubuntu]
    labels:
      - name: '*'
        values: ['*']
    permit_x11_forwarding: true
    forward_agent: true
    file_copy: true
    port_forwarding:
      local:
        enabled: true
      remote:
        enabled: true
  app:
    labels:
      - name: '*'
        values: ['*']
  rules:
    - resources: [scoped_role, scoped_role_assignment, bot, bot_instance, access_list]
      verbs: [create, list, read, update, delete]
    - resources: [scoped_token, kube_cluster]
      verbs: [create, list, read, secrets, update, delete]
version: v1
EOF
```

Next, create a scoped role assignment to grant `example-admin` to the user who will become the scoped admin (replace `alice` with your user):

```
$ tctl create <<EOF
kind: scoped_role_assignment
sub_kind: dynamic
scope: /examples
spec:
  user: alice
  assignments:
    - role: /examples::example-admin
      scope: /examples/basic
version: v1
EOF
```

Although the `example-admin` role is defined at `/examples`, it is assigned to `alice` only at the more specific scope `/examples/basic`. Using scope hierarchy this way ensures `alice` cannot reach across to other scopes under `/examples` and provides a guardrail against `alice` accidentally editing her own admin role and locking herself out.

## Joining a scoped SSH Service

Log the scoped admin into the desired scope:

```
$ tsh login --user=alice --scope=/examples/basic --proxy=teleport.example.com
```

Create a scoped token that can be used to join an SSH Service at the desired scope:

```
$ tctl scoped tokens add --scope=/examples/basic --assign-scope=/examples/basic --ttl=8h --type=node
```

A scoped token has two distinct scopes:

- `--scope` sets the scope that the token resource itself lives in. This is the administrative scope of the token, and determines who can manage it. A scoped admin can only create tokens within their own scope or a descendant scope.
- `--assign-scope` sets the scope that will be assigned to any resource (such as an SSH Service) that joins the cluster using this token. The assigned scope must be equal to or a descendant of the token's `--scope`.

In this example, both values are `/examples/basic` because the scoped admin is creating a token within their scope and using it to provision an SSH Service instance in the same scope. To provision an SSH Service into a more specific scope, use a more specific `--assign-scope`. For example, with `--scope=/examples/basic --assign-scope=/examples/basic/west`, the token is owned at `/examples/basic` but the joined SSH Service will live at `/examples/basic/west`.

Follow the printed instructions to join an SSH Service using the generated token. Once the instance has joined, the scoped admin can list it:

```
$ tsh ls
Scope           Node Name    Address Labels
--------------- ------------ ------- -----------
/examples/basic example-node Tunnel  foo=bar
```

Finally, SSH into the SSH Service instance using the scoped admin user:

```
$ tsh ssh ubuntu@example-node
```

## Joining a scoped Kubernetes Service

Kubernetes Clusters discovered by the Teleport Discovery Service are not supported by scopes at this time.

Log the scoped admin into the desired scope:

```
$ tsh login --user=alice --scope=/examples/basic --proxy=teleport.example.com
```

Create a scoped token that can be used to join a Kubernetes Service at the desired scope:

```
$ tctl scoped tokens add --scope=/examples/basic --assign-scope=/examples/basic --ttl=8h --type=kube
```

A scoped token has two distinct scopes:

- `--scope` sets the scope that the token resource itself lives in. This is the administrative scope of the token, and determines who can manage it. A scoped admin can only create tokens within their own scope or a descendant scope.
- `--assign-scope` sets the scope that will be assigned to any resource (such as a Kubernetes Service) that joins the cluster using this token. The assigned scope must be equal to or a descendant of the token's `--scope`.

In this example, both values are `/examples/basic` because the scoped admin is creating a token within their scope and using it to provision a Kubernetes Service instance in the same scope. To provision a Kubernetes Service into a more specific scope, use a more specific `--assign-scope`. For example, with `--scope=/examples/basic --assign-scope=/examples/basic/west`, the token is owned at `/examples/basic` but the joined Kubernetes Service will live at `/examples/basic/west`.

Follow the printed instructions to join a Kubernetes cluster using helm and the generated token. Once the instance has joined, the scoped admin can list it:

```
$ tsh kube ls
Kube Cluster Name Labels Scope         Selected
----------------- ------ ------------- --------
example-cluster          /example/west

```

Login to the Kubernetes cluster using the scoped admin user:

```
$ tsh kube login example-cluster
```

This generates the kubeconfig necessary to access the cluster using scoped credentials. You can either use `kubectl` directly by specifying the correct context or use the `tsh kubectl` sub-command to interact with the selected cluster.

## Joining a scoped Application Service

Only statically configured applications served by Application Services support joining with scopes. Dynamic application registration, including those discovered by the Teleport Discovery Service, does not yet support scopes.

Log the scoped admin into the desired scope:

```
$ tsh login --user=alice --scope=/examples/basic --proxy=teleport.example.com
```

Create a scoped token that can be used to join an Application Service at the desired scope:

```
$ tctl scoped tokens add --scope=/examples/basic --assign-scope=/examples/basic --ttl=8h --type=app
```

Copy the printed invite token to `/tmp/scoped-app-token` on the Application Service host, then create the following `/etc/teleport.yaml`:

```
version: v3
teleport:
  join_params:
    token_name: "/tmp/scoped-app-token"
    method: token
  proxy_server: "teleport.example.com:443"
auth_service:
  enabled: false
proxy_service:
  enabled: false
ssh_service:
  enabled: false
app_service:
  enabled: true
  apps:
  - name: scoped-grafana
    uri: "http://localhost:3000"
    labels:
      env: "staging"
      tier: "mfa"

```

Start Teleport with this configuration:

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

Once the instance has joined, the scoped admin can list it:

```
$ tsh apps ls
Application                     Description Type Public Address                        Labels
------------------------------- ----------- ---- ------------------------------------- --------------------
/examples/basic::scoped-grafana             HTTP lv7pgada3xgarg2rvk7gm4xgmx7nto6j.t... env=staging,tier=mfa
```

Note that the scope is prefixed, followed by `::`, and then the application name. Scoped applications require the user to refer to each application by their scope-qualified name when interacting with `tsh`.

The public address is also always derived for a scoped application. Users will not be able to define their own public address in order to disallow cross-scope access.

Log in to the application:

```
$ tsh apps login /examples/basic::scoped-grafana
Logged into app /examples/basic::scoped-grafana. Example curl command:

curl \
  --cert "/Users/teleport-user/.tsh/keys/teleport.example.com/teleport-app/teleport.example.com/@lv7pgada3xgarg2rvk7gm4xgmx7nto6j.crt" \
  --key "/Users/teleport-user/.tsh/keys/teleport.example.com/teleport-app/teleport.example.com/@lv7pgada3xgarg2rvk7gm4xgmx7nto6j.key" \
  https://lv7pgada3xgarg2rvk7gm4xgmx7nto6j.teleport.example.com
```

### Adding immutable labels via a token

A scoped token can carry a set of immutable labels that are automatically applied to any SSH instance that joins with the token, and which cannot be overridden by the joining instance.

```
$ tctl scoped tokens add --scope=/examples/basic --assign-scope=/examples/basic --ttl=8h --type=node --ssh-labels=foo=bar,baz=qux

The invite token: /examples/basic::019fb4b9-969d-7f36-b910-84e3e6449756:ZGQ4ZjQ0MTVlNjQ3MmJjZDEzOThhMmU2MzYyNTFjNmY
This token will expire in 480 minutes.

Run this on the new node to join the cluster:

> teleport start \
   --roles=node \
   --token=/examples/basic::019fb4b9-969d-7f36-b910-84e3e6449756:ZGQ4ZjQ0MTVlNjQ3MmJjZDEzOThhMmU2MzYyNTFjNmY \
   --auth-server=proxy.example.com:443
```

Inspect the token to confirm the immutable labels:

```
$ tctl get scoped_token /examples/basic::019fb4b9-969d-7f36-b910-84e3e6449756
kind: scoped_token
metadata:
  expires: "2026-03-14T04:51:29.109545Z"
  name: 019fb4b9-969d-7f36-b910-84e3e6449756
scope: /examples/basic
spec:
  assigned_scope: /examples/basic
  immutable_labels:
    ssh:
      baz: qux
      foo: bar
  join_method: token
  roles:
  - Node
  usage_mode: unlimited
status: {}
version: v1
```

Once the SSH Service instance joins, the immutable labels are merged with any labels set on the instance itself and cannot be overridden:

```
$ tctl get node /examples/basic::edff1d38-bbcb-4a21-b38d-ac43a2e20f85
kind: node
metadata:
  labels:
    env: test
    fruit: pear
  name: edff1d38-bbcb-4a21-b38d-ac43a2e20f85
scope: /examples/basic
spec:
  hostname: example
  immutable_labels:
    baz: qux
    foo: bar
  use_tunnel: true
  version: 18.7.1
version: v2

$ tsh ls
Scope           Node Name Address Labels
--------------- --------- ------- -----------
/examples/basic example   Tunnel  baz=qux,foo=bar,fruit=pear,env=test
```

### Single-use tokens

Scoped tokens can be restricted so that they may only be used to join a single instance. Set `--mode=single_use` when creating the token:

```
$ tctl scoped tokens add --scope=/examples/basic --assign-scope=/examples/basic --ttl=8h --type=node --mode=single_use

The invite token: /examples/basic::019fb4c0-5b15-7c9a-bf91-66f14d8a2b2e:NjNmOTI0YjRmOWI2YTNlMDc1YTU4Y2M4M2Q0ZTgzZWM
This token will expire in 480 minutes.
```

Use the token as normal to join an instance. After joining completes, the token's status reflects that it has been consumed:

```
$ tctl get scoped_token /examples/basic::019fb4c0-5b15-7c9a-bf91-66f14d8a2b2e
kind: scoped_token
metadata:
  expires: "2026-03-14T05:11:26.341373Z"
  name: 019fb4c0-5b15-7c9a-bf91-66f14d8a2b2e
scope: /examples/basic
spec:
  assigned_scope: /examples/basic
  join_method: token
  roles:
  - Node
  usage_mode: single_use
status:
  secret: {}
  usage:
    single_use:
      reusable_until: "2026-03-13T21:44:05.695268Z"
      used_at: "2026-03-13T21:14:05.695268Z"
      used_by_fingerprint: +I0OyNhoiP5BSvA8kIE+QLYOZYHQ7ngDh0/MgTRXncc=
version: v1
```

Subsequent join attempts using the token fail:

```
ERROR REPORT:
Original Error: *interceptors.RemoteError scoped token usage exhausted

```

## Adding scoped users as a scoped admin

Log the scoped admin into the desired scope:

```
$ tsh login --user=alice --scope=/examples/basic --proxy=teleport.example.com
```

Create a scoped role that grants SSH access to SSH Services at the scope (replace `ubuntu` with your desired OS login):

```
$ tctl create <<EOF
kind: scoped_role
metadata:
  name: example-user
scope: /examples/basic
spec:
  assignable_scopes:
  - /examples/basic
  ssh:
    logins: [ubuntu]
    labels:
      - name: '*'
        values: ['*']
    permit_x11_forwarding: true
    forward_agent: true
    file_copy: true
    port_forwarding:
      local:
        enabled: true
      remote:
        enabled: true
version: v1
EOF
```

Assign the role to the intended user (replace `bob` with your user):

```
$ tctl create <<EOF
kind: scoped_role_assignment
sub_kind: dynamic
scope: /examples/basic
spec:
  user: bob
  assignments:
    - role: /examples/basic::example-user
      scope: /examples/basic
version: v1
EOF
```

Bob can now log in to the scope and SSH into SSH Services within it:

```
$ tsh login --user=bob --scope=/examples/basic --proxy=teleport.example.com
$ tsh ls
Scope           Node Name    Address Labels
--------------- ------------ ------- -----------
/examples/basic example-node Tunnel  foo=bar
$ tsh ssh ubuntu@example-node
```

## Granting scoped roles with Access Lists

Access Lists can grant scoped roles to groups of users without requiring an individual scoped role assignment per user.

Log the scoped admin into the desired scope:

```
$ tsh login --user=alice --scope=/examples/basic --proxy=teleport.example.com
```

Create a scoped role that can grant SSH access in `/examples/basic`:

```
$ tctl create -f <<EOF
kind: scoped_role
metadata:
  name: example-access
scope: /examples/basic
spec:
  assignable_scopes:
    - /examples/basic
  ssh:
    logins: [ubuntu]
    labels:
      - name: '*'
        values: ['*']
    permit_x11_forwarding: true
    forward_agent: true
    file_copy: true
    port_forwarding:
      local:
        enabled: true
      remote:
        enabled: true
version: v1
EOF
```

Create an Access List that grants this scoped role to its members:

```
$ tctl create <<EOF
version: v1
kind: access_list
metadata:
  name: example-access-list
scope: /examples/basic
spec:
  title: "Example scoped Access List"
  description: "Grants scoped access to SSH Services in /examples/basic"
  owners:
    - name: alice
      membership_kind: MEMBERSHIP_KIND_USER
  grants:
    scoped_roles:
      - role: /examples/basic::example-access
        scope: /examples/basic
EOF
```

Add members to the Access List:

```
$ tctl acl users add /examples/basic::example-access-list bob
successfully added user bob to access list /examples/basic::example-access-list
$ tctl acl users add /examples/basic::example-access-list charlie
successfully added user charlie to access list /examples/basic::example-access-list
```

Access Lists that grant scoped roles always result in a "materialized" scoped role assignment for each of their members and owners. A "materialized" scoped role assignment is one that is automatically created and kept up to date for each user that is granted a scoped role by an Access List. Each materialized scoped role assignment assigns all of the scoped roles granted by the Access List to the user. Confirm a scoped role assignment has been materialized for each member:

```
$ tctl get scoped_role_assignments --format text
SubKind      ID                                                          Assignee      Assigns
------------ ----------------------------------------------------------- ------------- --------------------------------------------------
materialized /examples/basic::acl-4ezohtYllrk-gbWjn0hj_npCNwhujMurruDLRg user: charlie /examples/basic::example-access -> /examples/basic
materialized /examples/basic::acl-XMyMVGoNZopPQ9WxuZ5RH6GM5FPHVwLSTFUsBg user: bob     /examples/basic::example-access -> /examples/basic
```

Access Lists and members can also be managed via the Web UI under **Identity Governance > Access Lists**. Currently, only unscoped Access Lists can be created or managed in the web UI, but unscoped Access Lists can still grant scoped roles as long as the role is defined in the root scope `/`.

Nested Access Lists are fully supported, meaning Access Lists can have members that are other Access Lists, and members of the child list receive all scoped role grants from the parent list. Scoped roles can also be granted to Access List owners using the `owner_grants` field. See the [Access Lists reference](https://goteleport.com/docs/reference/access-controls/access-lists.md) for details.

### Scoped Access List rules

To maintain hierarchical scoped privilege isolation, the following rules apply to scoped Access Lists:

1. An Access List can only grant scoped roles defined in a scope that is equal to or an ancestor of the list's scope.
2. A scoped role grant can only grant the scoped role at a scope that is equal to or a descendent of the list's scope.
3. An Access List can only be added as a member of another Access List if the member list's scope is equal to or an ancestor of the parent list's scope.
4. An Access List can only be added as an owner of another Access List if the owner list's scope is equal to or an ancestor of the owned list's scope.
5. A scoped Access List (or an unscoped list that grants any scoped roles) may not contain member requirements or owner requirements.

Any attempt to create an Access List violating these rules will be rejected by the Auth Service with an error message.

For an example Access List with scope `/examples/basic`:

1. It can only grant scoped roles defined in `/examples/basic` or `/examples` or `/`.
2. It can only grant scoped roles at `/examples/basic` or `/examples/basic/**`.
3. It can only have member Access Lists that are unscoped or have scope `/examples/basic` or `/examples` or `/`.
4. It can only have owner Access Lists that are unscoped or have scope `/examples/basic` or `/examples` or `/`.
5. It may not contain any member or owner requirements.

## Infrastructure as Code

Scoped Roles, Scoped Role Assignments, Scoped Tokens, Kubernetes clusters, Access Lists, Access List Members, Bots, and Workload Identities can be managed with Teleport's IaC tooling.

The Teleport Terraform provider supports these resources:

- [`teleport_access_list`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/access_list.md)
- [`teleport_access_list_member`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/access_list_member.md)
- [`teleport_bot`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/bot.md)
- [`teleport_kube_cluster`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/kube_cluster.md)
- [`teleport_scoped_role`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/scoped_role.md)
- [`teleport_scoped_role_assignment`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/scoped_role_assignment.md)
- [`teleport_scoped_token`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/scoped_token.md)
- [`teleport_workload_identity`](https://goteleport.com/docs/reference/infrastructure-as-code/terraform-provider/resources/workload_identity.md)

The Kubernetes Operator supports these custom resources:

- [`TeleportBotV1`](https://goteleport.com/docs/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-botsv1.md)
- [`TeleportScopedRoleV1`](https://goteleport.com/docs/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-scopedrolesv1.md)
- [`TeleportScopedRoleAssignmentV1`](https://goteleport.com/docs/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-scopedroleassignmentsv1.md)
- [`TeleportScopedTokenV1`](https://goteleport.com/docs/reference/infrastructure-as-code/operator-resources/resources-teleport-dev-scopedtokensv1.md)

When using the Kubernetes Operator, the Helm values used to install the operator must set `TELEPORT_UNSTABLE_SCOPES=yes` in the operator's environment:

```
extraEnv:
  - name: TELEPORT_UNSTABLE_SCOPES
    value: "yes"

```

## Scoped Machine and Workload Identity

The standard `bot` resource supports the `scope` field. When set, the Bot is considered a scoped Bot. Scoped Bots can be created, read, updated, and deleted by scope admins through scoped roles and scoped role assignments.

The relationship between a scoped Bot and its associated resources is constrained as follows:

- Scoped Bots produce identities pinned to the scope they exist in. They can only access resources within that scope or descendant scopes, not in ancestor or orthogonal scopes.
- Scoped Bots may only be granted privileges within their own scope or descendant scopes.
- Unscoped roles cannot be assigned to a scoped Bot.
- A scoped Bot must authenticate using a scoped token. Scoped Bots cannot use unscoped join tokens.
- The scoped token used by a scoped Bot must exist in the same scope as the Bot or in an ancestor scope.

To authenticate as a scoped Bot, `tbot` must be running in scoped mode, controlled by either the `scoped: true` configuration value or the `--scoped` CLI flag.

### Scoped MWI example

The following example shows the scoped resources required to run a Bot in scoped mode. It assumes you already have an unscoped administrator who can bootstrap the first scoped admin, and a scoped role named `/staging::staging-ssh-access` that grants the Bot its intended SSH privileges.

Grant a scope administrator the ability to manage scoped Bots and scoped tokens:

```
kind: scoped_role
version: v1
metadata:
  name: staging-scope-mwi-admin
scope: /staging
spec:
  assignable_scopes:
    - /staging
  rules:
    - resources:
        - scoped_role
        - scoped_role_assignment
        - bot
        - bot_instance
      verbs:
        - list
        - read
        - create
        - update
        - delete
    - resources:
        - scoped_token
      verbs:
        - list
        - read
        - secrets
        - create
        - update
        - delete
---
kind: scoped_role_assignment
version: v1
metadata:
  name: 8a3f1c2d-9e47-4b6a-a1d0-5c8e7f3b2a92
scope: /staging
sub_kind: dynamic
spec:
  user: my-scope-admin
  assignments:
    - role: /staging::staging-scope-mwi-admin
      scope: /staging

```

The scope administrator can now create a scoped Bot. Beyond a name and a scope, no further configuration is required:

```
kind: bot
version: v1
metadata:
  name: my-scoped-bot
scope: /staging
spec: {}

```

Grant the Bot privileges through a scoped role assignment, identifying the Bot by its scope-qualified name in `spec.bot`:

```
kind: scoped_role_assignment
version: v1
metadata:
  name: 6b72b4dc-655e-4b3b-bae3-515378a296ae
scope: /staging
sub_kind: dynamic
spec:
  bot: /staging::my-scoped-bot
  assignments:
    - role: /staging::staging-ssh-access
      scope: /staging

```

Create a scoped token that the Bot will use to authenticate. The example below uses the `bound_keypair` join method, but other join methods are also supported:

```
kind: scoped_token
version: v1
metadata:
  name: my-scoped-bot
scope: /staging
spec:
  roles: [Bot]
  join_method: bound_keypair
  usage_mode: bot
  bot: /staging::my-scoped-bot
  bound_keypair: {}

```

Configure `tbot` to run in scoped mode by setting `scoped: true` in the configuration file (or by passing `--scoped` on the command line):

```
version: v2
proxy_server: example.teleport.sh:443
onboarding:
  join_method: bound_keypair
  token: /staging::my-scoped-bot
  bound_keypair:
    registration_secret: <secret fetched from "tctl get scoped_token /staging::my-scoped-bot --with-secrets">
scoped: true
storage:
  type: directory
  path: /var/lib/teleport/bot
services:
  - type: identity
    destination:
      type: directory
      path: /opt/machine-id

```

Once `tbot` is running, the credentials it produces can be used to access resources within the scope using `tsh` or `tctl` with the identity file, or with `ssh` using the OpenSSH configuration files generated by `tbot`.

### Scoped SPIFFE

The standard `workload_identity` resource supports the `scope` field. When set, the Workload Identity is considered scoped. Scoped Workload Identities can be created, read, updated, and deleted by scope admins through scoped roles and scoped role assignments, and can be used by scoped Bots to issue SPIFFE credentials to workloads. A scoped Workload Identity cannot be defined in the root scope `/`. See the [Workload Identity resource reference](https://goteleport.com/docs/reference/machine-workload-identity/workload-identity/workload-identity-resource.md) for the available fields.

#### SPIFFE ID structure

A scoped Workload Identity must specify a SPIFFE ID that encodes the scope in which it is defined. The path of the SPIFFE ID consists of three sections:

- The scope section: segments that match the scope in which the Workload Identity is defined.
- The separator segment: `/_/`.
- The administratively-defined section: one or more segments freely chosen by the administrator.

For example, a Workload Identity defined in `/staging` may specify the SPIFFE ID `/staging/_/payments/api`. Workloads are then issued SVIDs with the following SPIFFE ID:

```
spiffe://example.teleport.sh/staging/_/payments/api

```

The following rules apply to the SPIFFE ID of a scoped Workload Identity:

- The scope section must exactly match the scope in which the Workload Identity is defined. It cannot be an ancestor or descendant of that scope. This means the scope responsible for a SPIFFE ID can always be determined from the ID alone, and admins of one scope cannot issue SPIFFE IDs that appear to belong to another scope.
- The scope section must be immediately followed by the separator segment.
- The administratively-defined section must contain at least one segment, and must not itself contain the separator segment, so that a scoped SPIFFE ID contains exactly one separator.
- Templating within the SPIFFE ID is supported. These rules are enforced against the rendered SPIFFE ID at issuance time, so template attributes cannot be used to escape the scope.

#### Granting issuance to a scoped Bot

Issuance is controlled by the `workload_identity` section of a scoped role, which selects the Workload Identities that the role permits issuance with by label. Issuance additionally requires the `read` verb (and, when issuing by label selector, `list`) for the `workload_identity` resource in the role's `rules`.

#### Configuring `tbot` in scoped mode

In scoped mode, `tbot` supports the `workload-identity-x509`, `workload-identity-jwt`, and `workload-identity-api` services. When selecting a scoped Workload Identity by name, use its scope-qualified name, for example `/staging::staging-payments-api`.

### Scoped SPIFFE example

The following example builds on the scoped MWI example above, granting the scoped Bot `/staging::my-scoped-bot` the ability to issue SPIFFE SVIDs. It assumes the scope administrator's role also grants write access to the `workload_identity` resource.

As the scope administrator, create a scoped Workload Identity:

```
kind: workload_identity
version: v1
metadata:
  name: staging-payments-api
  labels:
    app: payments
scope: /staging
spec:
  spiffe:
    id: /staging/_/payments/api

```

Create a scoped role permitting issuance with Workload Identities labeled `app: payments`, and assign it to the Bot:

```
kind: scoped_role
version: v1
metadata:
  name: staging-spiffe-issuer
scope: /staging
spec:
  assignable_scopes:
    - /staging
  workload_identity:
    labels:
      - name: app
        values: [payments]
  rules:
    - resources: [workload_identity]
      verbs: [list, read]
---
kind: scoped_role_assignment
version: v1
metadata:
  name: 9c41d3be-72af-4d9e-8e5a-1f3b6c0d47e1
scope: /staging
sub_kind: dynamic
spec:
  bot: /staging::my-scoped-bot
  assignments:
    - role: /staging::staging-spiffe-issuer
      scope: /staging

```

Add a Workload Identity service to the `tbot` configuration from the previous example, selecting the Workload Identity by its scope-qualified name:

```
services:
  - type: workload-identity-api
    listen: unix:///opt/machine-id/workload.sock
    selector:
      name: /staging::staging-payments-api

```

Workloads connecting to the SPIFFE Workload API socket are issued SVIDs with the following SPIFFE ID:

```
spiffe://example.teleport.sh/staging/_/payments/api

```

## Kubernetes resource RBAC

When defining scoped roles, the `resources` list provided in `scoped_role.spec.kube` must be defined in order for the role to grant access to a Kubernetes cluster. When the intent is to fully rely on Kubernetes' builtin RBAC rules, a wildcard entry must be defined:

```
kind: scoped_role
metadata:
  name: kube-access
scope: /examples
spec:
  assignable_scopes:
    - /examples
  kube:
    groups: [cluster-admin]
    labels:
      - name: '*'
        values: ['*']
    resources:
      - kind: '*'
        namespace: '*'
        api_group: '*'
        name: '*'
        verbs: ['*']
version: v1

```

This is notably opposite to the behavior of unscoped Teleport roles which provide wildcard access by default when `kubernetes_resources` are left undefined. Scoped roles aim to be explicit and deny by default which is incompatible with the unscoped behavior.

## Dynamic Kubernetes cluster registration

With dynamic Kubernetes cluster registration, you can manage the Kubernetes clusters connected to your Teleport cluster without needing to modify the configuration file of an individual Kubernetes Service instance.

Dynamic Kubernetes cluster registration is useful when you have deployed multiple Kubernetes Service instances or need to regularly reconfigure access to Kubernetes clusters in your infrastructure.

In this guide, you will set up dynamic Kubernetes cluster registration, then create, list, update, and delete Kubernetes clusters via `tctl`.

## How it works

The Teleport Kubernetes Service proxies traffic from Teleport users to a Kubernetes API server so you can take advantage of passwordless authentication, role-based access controls, audit logging, and other Teleport features in order to manage access to Kubernetes.

In this step, you will install the Teleport Kubernetes Service on a Linux host and configure it to access any Kubernetes cluster you register with your Teleport cluster.

## Prerequisites

- A running Teleport 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/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/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
    ```

* A Linux host where you will install the Teleport Kubernetes Service.

  ---

  TIP

  Our `teleport-kube-agent` Helm chart does not support dynamic Kubernetes cluster registration.

  ---

* A Kubernetes cluster to join to your Teleport cluster. You must have permissions to create namespaces, secrets, service accounts, cluster roles, and cluster role bindings in the cluster.

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

## Step 1/3. Set up the Teleport Kubernetes Service

This step shows you how to install the Teleport Kubernetes Service on a Linux server.

### Get a join token

Establish trust between your Teleport cluster and your new Kubernetes Service instance by creating a scoped join token:

```
$ tctl scoped tokens add --type=kube --scope=/examples/kube --assign-scope=/examples/kube --ttl=1h --format=text
/examples/kube::abcd123-insecure-do-not-use-this
```

Copy the token and keep it somewhere safe so you can use it when running the Teleport Kubernetes Service.

### Install the Teleport Kubernetes Service

Install the Teleport Kubernetes Service on your Linux host:

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

### Configure the Teleport Kubernetes Service

On the host where you will run the Teleport Kubernetes Service, run the following command to create a base configuration for your Teleport instance, assigning example.teleport.sh:443 to the host and port of your Teleport Proxy Service or Teleport Cloud tenant and join-token to the join token we created earlier:

```
$ sudo teleport configure \
--proxy=example.teleport.sh:443 \
--roles=kube \
--token=join-token \
-o file
```

Edit your configuration file at `/etc/teleport.yaml` to include the following:

```
kubernetes_service:
  enabled: true
  resources:
  - labels:
      "*": "*"

```

This configuration enables your Kubernetes Service instance to connect to any Kubernetes clusters you register with your Teleport cluster within the same scope. This is because the `resources[0].labels` field includes the wildcard pattern (`"*": "*"`), which allows this Kubernetes Service instance to connect to Kubernetes cluster resources with any label key or value. The Kubernetes Service instance's certificate will encode the scope assigned by its join token and be used to ensure only same-scope Kubernetes clusters are visible and registered.

Selectively watching Kubernetes clusters

You can configure a Kubernetes Service instance to watch for a subset of Kubernetes clusters by including specific label keys and values instead of wildcard characters:

```
resources:
- labels:
    "env": "prod"
    "region": "us-east-2"
- labels:
    "env": "test"
    "region": "us-west-1"

```

For the Kubernetes Service to register a cluster, *any* of the items in `resources` must match the cluster's labels. For an item in `resources` to match, *all* of the `labels` entries within that item must match the cluster's labels.

For example, a cluster with the labels `env:prod` and `region:us-west-1` would not match the configuration above, since it only matches the `env:prod` label in the first `resources` item and the `region:us-west-1` label in the second `resources` item.

However, a cluster with `env:test` and `region:us-west-1` would match, since it matches both labels given in the second `resources` item.

When you create dynamic Kubernetes cluster resources later in this guide, you can assign them labels to ensure that only specific Kubernetes Service instances will watch for them.

### Run the Teleport Kubernetes Service

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

**Package Manager**

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

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

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

**TAR Archive**

Configure the Teleport Kubernetes Service to start automatically when the host boots up by creating a systemd service for it. On the host where you will run the Teleport Kubernetes Service, 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 the Teleport Kubernetes Service with `systemctl status teleport` and view its logs with `journalctl -fu teleport`.

**No systemd**

On the host where you will run the Teleport Kubernetes Service, start Teleport:

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

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

## Step 2/3. Authorize your user

To enable dynamic Kubernetes cluster registration in Teleport, you will need to authorize your user to access the Kubernetes clusters you want to register with Teleport. We will configure this access in this step, both in Teleport and on your Kubernetes cluster.

### Allow access to your Kubernetes cluster

Ensure that you are in the correct Kubernetes context for the cluster you would like to enable access to.

Retrieve all available contexts:

```
$ kubectl config get-contexts
```

Switch to your context, replacing `CONTEXT_NAME` with the name of your chosen context:

```
$ kubectl config use-context CONTEXT_NAME
Switched to context CONTEXT_NAME
```

The scoped role you will create in the next section allows your Teleport user access to Kubernetes as the `viewers` group. Configure the `viewers` group in your Kubernetes cluster to have the built-in `view` ClusterRole. When your Teleport user accesses the cluster, the Teleport Kubernetes Service impersonates the `viewers` group and proxies the requests.

Create a file called `viewers-bind.yaml` with the following contents:

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: viewers-crb
subjects:
- kind: Group
  # Bind the group "viewers", corresponding to the group we assign in the
  # scoped role below.
  name: viewers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  # "view" is a default ClusterRole that grants read-only access to resources.
  # See: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles
  name: view
  apiGroup: rbac.authorization.k8s.io

```

Apply the `ClusterRoleBinding` with `kubectl`:

```
$ kubectl apply -f viewers-bind.yaml
```

### Authorize your user to manage Kubernetes clusters

Teleport tracks scoped Kubernetes clusters in your infrastructure via dynamic `kube_cluster` resources. To manage access to Kubernetes clusters with Teleport, your user will need permissions to manage these resources.

Create a scoped role definition called `kube-manager.yaml` with the following content:

```
kind: scoped_role
metadata:
  name: kube-manager
scope: /examples
spec:
  assignable_scopes:
    - /examples/kube
  kube:
    groups: [viewers]
    labels:
      - name: '*'
        values: ['*']
    resources:
      - kind: '*'
        namespace: '*'
        api_group: '*'
        name: '*'
        verbs: ['*']
  rules:
    - resources: [kube_cluster]
      verbs:
        - create
        - list
        - read
        - secrets
        - update
        - delete
version: v1

```

Create the scoped role:

```
$ tctl create -f kube-manager.yaml
```

Assign the role to your user:

```
$ tctl create <<EOF
kind: scoped_role_assignment
sub_kind: dynamic
scope: /examples
spec:
  user: user
  assignments:
    - role: /examples::kube-manager
      scope: /examples/kube
version: v1
EOF
```

Log in to the `/examples/kube` scope so the scoped role assignment is active for the following `tctl` commands:

```
$ tsh login --user=user --scope=/examples/kube --proxy=example.teleport.sh:443
```

## Step 3/3. Manage dynamic Kubernetes cluster resources

Now that your Teleport user has permissions to manage scoped Kubernetes cluster resources, we will show you how to create, list, update, and delete them.

### Create a kubeconfig

In this section, you will create a Kubernetes `Config` resource, or kubeconfig, that your Teleport cluster will use to authenticate to your Kubernetes cluster.

When you signed into Teleport earlier in this guide, `tsh` may have changed your Kubernetes context to one based on your Teleport cluster, so make sure you update your Kubernetes context to match the cluster you would like to connect to Teleport:

```
$ kubectl config get-contexts
Assign CONTEXT_NAME to your chosen context
$ kubectl config use-context CONTEXT_NAME
```

On your workstation, download Teleport's `get-kubeconfig.sh` script, which you will use to generate the kubeconfig:

```
$ curl -OL \
https://raw.githubusercontent.com/gravitational/teleport/v19.0.0-dev/examples/k8s-auth/get-kubeconfig.sh
```

The script creates a service account for the Teleport Kubernetes Service that can get Kubernetes pods as well as impersonate users, groups, and other service accounts. The Teleport Kubernetes Service uses this service account to manage access to resources in your Kubernetes cluster. The script also ensures that there is a Kubernetes `Secret` in your cluster to store service account credentials.

`get-kubeconfig.sh` creates a namespace called `teleport` for the resources it deploys, though you can choose a different name by assigning the `TELEPORT_NAMESPACE` environment variable in the shell where you run the script.

After creating resources, `get-kubeconfig.sh` writes a new kubeconfig to a file called `kubeconfig` in the directory where you run the script.

Run the `get-kubeconfig.sh` script:

```
$ bash get-kubeconfig.sh
```

The script is successful if you see this message:

```
Done!

```

Ignore the script's instructions to copy the generated kubeconfig file to the Teleport Proxy Service. In the next section, we will show you how to use the kubeconfig file when creating a dynamic `kube_cluster` resource.

### Create a Kubernetes cluster resource

Define a scoped `kube_cluster` resource with the following content in a file called `kube_cluster.yaml`:

```
kind: kube_cluster
version: v3
scope: /examples/kube
metadata:
  name: mycluster
spec:
  kubeconfig: |

```

The `spec.kubeconfig` field in the snippet above begins a multi-line string. Below, you will include the contents of the kubeconfig file as its value.

Since `spec.kubeconfig` must be a base64-encoded string, convert the kubeconfig file to base64, then indent it and add it to the `kube_cluster.yaml` resource definition using the following command:

```
$ printf "    %s\n" "$(cat kubeconfig | base64 | tr -d '\n')" >> kube_cluster.yaml
```

This command also removes any newlines added to the middle of the base64-encoded string before writing it to the `kube_cluster` resource definition.

Add labels to your kube\_cluster

You can add labels to the `kube_cluster` resource, allowing you to manage access to specific clusters from your Teleport roles or Kubernetes Service instances.

Dynamic, scoped Kubernetes clusters only support static labels, which are key/value pairs. This example defines the `env=prod` and `team=dev` labels:

```
kind: kube_cluster
version: v3
scope: /examples/kube
metadata:
  name: mycluster
  labels:
    env: prod
    team: dev
spec:
  kubeconfig: KUBECONFIG

```

To create the `kube_cluster` resource, run the following command:

```
$ tctl create kube_cluster.yaml
kubernetes cluster "/examples/kube::mycluster" has been created
```

### Access your new Kubernetes cluster

Instances of the Teleport Kubernetes Service watch for newly created or updated `kube_cluster` resources within their assigned scope. When you create the `kube_cluster` resource, any Kubernetes Service instances you have configured in that scope to track that cluster's labels will register that cluster and enable access to it via Teleport.

As a result, you should now see the cluster you registered above when you run `tsh kube ls`:

```
$ tsh kube ls
 Kube Cluster Name Labels                      Scope          Selected
 ----------------- --------------------------- -------------- --------
 mycluster         teleport.dev/origin=dynamic /examples/kube *
```

The `teleport.dev/origin=dynamic` label indicates that the cluster was registered dynamically.

You can also log in to the cluster you just registered:

```
$ tsh kube login mycluster
Logged into kubernetes cluster "mycluster". Try 'kubectl version' to test the
connection.
```

### List Kubernetes cluster resources

You can list `kube_cluster` resources with the following command:

```
$ tctl get kube_clusters
```

### Update a Kubernetes cluster resource

To update the `kube_cluster` resource you created earlier, execute the following command to open the resource as it exists on the Auth Service's backend in your text editor:

```
$ tctl edit kube_clusters /examples/kube::mycluster
```

Edit the resource to add a label to your `kube_cluster`:

```
  kind: kube_cluster
  scope: /examples/kube
  metadata:
    id: 9999999999999999999
    labels:
      teleport.dev/origin: dynamic
+     env: test
    name: mycluster
  spec:
    aws: {}
    azure: {}
    kubeconfig: KUBECONFIG
  version: v3

```

Save and close the file in your editor to apply your changes.

You should now see the updated labels:

```
$ tsh kube ls
 Kube Cluster Name Labels                               Scope          Selected
 ----------------- ------------------------------------ -------------- --------
 mycluster         env=test teleport.dev/origin=dynamic /examples/kube *
```

---

WARNING

If the updated `kube_cluster` resource's labels no longer match the ones a Teleport Kubernetes Service instance is configured to watch, the instance will unregister and stop proxying the Kubernetes cluster.

---

### Delete Kubernetes cluster resources

To delete the `kube_cluster` resource you created earlier, run the following command:

```
$ tctl rm kube_clusters /examples/kube::mycluster
kubernetes cluster "/examples/kube::mycluster" has been deleted
```

This will unregister the Kubernetes cluster from Teleport:

```
$ tsh kube ls
Kube Cluster Name Labels Scope Selected
----------------- ------ ----- --------
```
