Scaling Privileged Access for Modern Infrastructure: Real-World Insights
Apr 25
Virtual
Register Today
Teleport logoTry For Free
Fork me on GitHub

Teleport

Managing Users And Roles With IaC

In this guide, you will see how to create users and grant them roles through infrastructure as code (IaC). Teleports supports three ways to dynamically create resources from code:

  • The Teleport Kubernetes Operator, which allows you to manage Teleport resources from Kubernetes
  • The Teleport Terraform Provider, which allows you to manage Teleport resources via Terraform
  • The tctl CLI, which allows you to manage Teleport resources from your local computer or your CI environment

Prerequisites

To follow this guide, you must have:

  • A running Teleport cluster. If you want to get started with Teleport, sign up for a free trial or set up a demo environment.

  • The tctl admin tool and tsh client tool version >= 15.2.2.

    On Teleport Enterprise, you must use the Enterprise version of tctl, which you can download from your Teleport account workspace. Otherwise, visit Installation for instructions on downloading tctl and tsh for Teleport Community Edition.

A running operator by following either:

You must also set the namespace in which you deployed the operator as this is the namespace where you will deploy the CustomResources:

for operators deployed with the `teleport-cluster` Helm chart

export OPERATOR_NAMESPACE="teleport-cluster"

for standalone operators

export OPERATOR_NAMESPACE="teleport-iac"

A functional Teleport Terraform provider by following the Terraform provider guide.

Step 1/4. Write manifests

In this step, we'll write text files describing the resources we want in Teleport. Those files are called manifests and their syntax will vary based on the IaC tooling you'll use.

Those manifests are typically versioned in a shared revision system like git. This allows you to keep track of all changes, follow standard code review procedures before changing resources in Teleport, and quickly redeploy your Teleport instance if needed.

Write role manifests

We will create 2 roles:

  • manager allows listing users and roles, as well as reviewing audit events and session contents.
  • engineer grants access to dev and staging servers.

Create the following roles.yaml file:

kind: role
version: v7
metadata:
  name: manager
spec:
  allow:
    rules:
      - resources: ['user', 'role']
        verbs: ['list','read']
      - resources: ['session', 'event']
        verbs: ['list', 'read']
---
kind: role
version: v7
metadata:
  name: engineer
spec:
  allow:
    logins: ['root', 'ubuntu', '{{internal.logins}}']
    node_labels:
      'env': ['test', 'staging']

Create the following roles.yaml file:

apiVersion: resources.teleport.dev/v1
kind: TeleportRoleV7
metadata:
  name: manager
spec:
  allow:
    rules:
      - resources: ['user', 'role']
        verbs: ['list','read']
      - resources: ['session', 'event']
        verbs: ['list', 'read']
---
apiVersion: resources.teleport.dev/v1
kind: TeleportRoleV7
metadata:
  name: engineer
spec:
  allow:
    logins: ['root', 'ubuntu', '{{internal.logins}}']
    node_labels:
      'env': ['test', 'staging']
Note

Kubernetes validates all custom resource names to follow RFC 1123, which includes specifications for hostnames. This requires the metadata.name field of Teleport resources controlled by the operator to consist of lowercase alphanumeric characters, - or ., and to start and end with an alphanumeric character.

Create the following roles.tf file:

resource "teleport_role" "manager" {
  version = "v7"
  metadata = {
    name = "manager"
  }

  spec = {
    allow = {
      rules = [
        {
          resources = ["user", "role"]
          verbs     = ["list", "read"]
        },
        {
          resources = ["session", "event"]
          verbs     = ["list", "read"]
        }
      ]
    }
  }
}

resource "teleport_role" "engineer" {
  version = "v7"
  metadata = {
    name = "engineer"
  }

  spec = {
    allow = {
      logins = ["root", "ubuntu", "{{internal.logins}}"]
      node_labels = {
        env = ["test", "staging"]
      }
    }
  }
}

Write user manifests

We will create 2 users:

  • Bob, an engineer with the engineer role.
  • Alice, an engineering manager with both manager and engineer roles.
Note

Users created from manifests are local users, as opposed to users coming from an external SAML/OIDC/GitHub Identity Provider (IdP).

See the user type reference for more details.

Create the file users.yaml with the following content:

kind: user
version: v2
metadata:
  name: alice
spec:
  roles: ['manager', 'engineer']
---
kind: user
version: v2
metadata:
  name: bob
spec:
  roles: ['engineer']

Create the file users.yaml with the following content:

apiVersion: resources.teleport.dev/v2
kind: TeleportUser
metadata:
  name: alice
spec:
  roles: ['manager', 'engineer']
---
apiVersion: resources.teleport.dev/v2
kind: TeleportUser
metadata:
  name: bob
spec:
  roles: ['engineer']
Note

Kubernetes validates all custom resource names to follow RFC 1123, which includes specifications for hostnames. This requires the metadata.name field of Teleport resources controlled by the operator to consist of lowercase alphanumeric characters, - or ., and to start and end with an alphanumeric character.

Create the file users.tf with the following content:

resource "teleport_user" "alice" {
  version = "v2"
  metadata = {
    name        = "alice"
  }

  spec = {
    # referencing to the teleport_role resource name instead of using plain
    # strings tells Terraform that the user depends on the role. Thanks to this,
    # Terraform will create the role first and won't let you remove the role
    # if it is still assigned to a user (which is illegal in Teleport).
    roles = [
      teleport_role.manager.metadata.name,
      teleport_role.engineer.metadata.name,
    ]
  }
}

resource "teleport_user" "bob" {
  version = "v2"
  metadata = {
    name        = "bob"
  }

  spec = {
    roles = [teleport_role.engineer.metadata.name]
  }
}

Step 2/4. Apply all manifests

tctl create -f roles.yaml
role 'manager' has been createdrole 'engineer' has been created
tctl create -f users.yaml
user "alice" has been createduser "bob" has been created
Note

The user resource depends on roles, you must create roles before users as a user with a non-existing role is invalid and might be rejected by Teleport.

kubectl apply -n "$OPERATOR_NAMESPACE" -f roles.yaml
teleportrolev7.resources.teleport.dev/manager createdteleportrolev7.resources.teleport.dev/engineer created
kubectl apply -n "$OPERATOR_NAMESPACE" -f users.yaml
teleportuser.resources.teleport.dev/alice createdteleportuser.resources.teleport.dev/bob created

List the created Kubernetes resources:

kubectl get teleportrolev7 -n "$OPERATOR_NAMESPACE"

NAME AGE

engineer 10m

manager 10m


kubectl get teleportusers -n "$OPERATOR_NAMESPACE"

NAME AGE

alice 10m

bob 10m

terraform plan
[...]Plan: 4 to add, 0 to change, 0 to destroy.
terraform apply
teleport_role.engineer: Creating...teleport_role.manager: Creating...teleport_role.engineer: Creation complete after 0s [id=engineer]teleport_role.manager: Creation complete after 0s [id=manager]teleport_user.bob: Creating...teleport_user.alice: Creating...teleport_user.bob: Creation complete after 0s [id=bob]teleport_user.alice: Creation complete after 0s [id=alice]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Step 3/4. Validate users were created

Now that the IaC tooling has run, we'll validate that the users were properly created and granted the correct roles.

If you have UI access, connect to your Teleport cluster Web UI, open the management panel, and select the "Users" tab.

Two new users alice and bob should be present.

tctl users ls
User Roles----------------------------- -------------------------@teleport-access-approval-bot @teleport-access-approveralice manager,engineerbob engineerbot-operator bot-operator

alternatively you can inspect the users details by doing

tctl get user/alice
kind: usermetadata: id: 1704849160091933780 labels: teleport.dev/origin: kubernetes name: alicespec: created_by: time: "2024-01-10T01:12:40.088581806Z" user: name: bot-operator expires: "0001-01-01T00:00:00Z" roles: - manager - engineer status: is_locked: false lock_expires: "0001-01-01T00:00:00Z" locked_time: "0001-01-01T00:00:00Z" recovery_attempt_lock_expires: "0001-01-01T00:00:00Z"version: v2

At this point, the local users have been created in Teleport. However, we never specified any password or additional authentication factors. You must issue a password reset link for the users to finish their Teleport registration and be able to log in Teleport.

User reset links contain single-use expiring tokens. Because of this, you cannot follow the same declarative approach as for other Teleport resources and generate them via a manifest. You need to create those tokens once after the user creation, and securely send them to the end-user for them to register their password/MFA.

Option 1: Reset via CLI

You can manually reset a user password via tctl by doing:

tctl users reset alice
User "alice" has been reset. Share this URL with the user to complete password reset, link is valid for 8h:https://teleport.example.com:443/web/reset/05b420fdc784597cbbb1d2ba65697cd8
NOTE: Make sure teleport.example.com:443 points at a Teleport proxy which users can access.

Option 2: Automating user reset

If you have a way to securely send reset links to the users, you can build automation to fit your organization's specific needs. For example:

$ tctl users reset alice --format=json | \
    jq '"Sending an email to " + .spec.user +" that contains the link: " + .spec.url'

You must replace the jq command by something that actually sends the link over a secure channel. This channel will depend on your organization. It is usually a direct message or an email.

For Terraform users

You can trigger your custom script on Terraform resource creation with the local-exec provisioner.

resource "teleport_user" "bob" {
  version = v2
  metadata = {
    name = "bob"
  }

  spec = {
    roles = [teleport_role.engineer.metadata.name]
  }
  
  # on user creation, trigger a reset flow and send the link via 
  provisioner "local-exec" {
    command = "tctl users reset alice --format=json | jq '\"Sending an email to \" + .spec.user +\" that contains the link: \" + .spec.url'"
  }
}

Next steps

  • Allow users with the manager role to grant access to production servers to some engineers via Access Lists. Manager will need to justify and review granted access periodically. See the AccessList documentation for a high-level explanation of the feature, and the AccessList IaC guide for a step by step IaC AccessList setup.
  • Allow users with the engineer role to request temporary access to production, and have users with the manager role validate the requests. See the Access Requests documentation
  • You can see all supported fields in the references of the user resource and the role resource.