From c9d04ffc42584bf2eb4e0c010fd5c382986231d4 Mon Sep 17 00:00:00 2001 From: Michael Wilson Date: Tue, 20 Jun 2023 15:50:00 -0400 Subject: [PATCH] Add reviewer and requester roles. (#28076) Reviewer and requester roles have been added to allow for easy defaults for reviewing and requesting applications and user groups for the Okta service. --- api/proto/teleport/legacy/types/types.proto | 2 + api/types/constants.go | 16 + api/types/role.go | 18 + api/types/role_test.go | 145 ++++++ api/types/types.pb.go | 2 + constants.go | 12 + docs/cspell.json | 1 + .../access-requests/resource-requests.mdx | 66 +-- .../pages/access-controls/getting-started.mdx | 45 +- .../access-controls/guides/dual-authz.mdx | 8 +- .../guides/hardware-key-support.mdx | 22 +- .../access-controls/guides/passwordless.mdx | 16 + .../pages/access-controls/guides/webauthn.mdx | 22 +- docs/pages/access-controls/reference.mdx | 2 + docs/pages/access-controls/sso/github-sso.mdx | 52 ++- .../connect-your-client/introduction.mdx | 46 +- .../pages/database-access/getting-started.mdx | 7 + .../deployments/aws-terraform.mdx | 40 +- .../deploy-a-cluster/helm-deployments/aws.mdx | 12 + .../helm-deployments/custom.mdx | 12 + .../helm-deployments/digitalocean.mdx | 30 ++ .../deploy-a-cluster/helm-deployments/gcp.mdx | 16 +- docs/pages/get-started.mdx | 1 - .../includes/database-access/create-user.mdx | 13 + docs/pages/management/admin/labels.mdx | 19 +- .../management/admin/trustedclusters.mdx | 20 +- docs/pages/management/admin/users.mdx | 7 + docs/pages/reference/cli.mdx | 4 +- docs/pages/server-access/getting-started.mdx | 2 +- docs/pages/server-access/guides/openssh.mdx | 19 +- lib/auth/init.go | 10 +- lib/auth/init_test.go | 16 +- lib/services/presets.go | 208 ++++++++- lib/services/presets_test.go | 423 ++++++++++++++++++ 34 files changed, 1241 insertions(+), 93 deletions(-) create mode 100644 api/types/role_test.go create mode 100644 lib/services/presets_test.go diff --git a/api/proto/teleport/legacy/types/types.proto b/api/proto/teleport/legacy/types/types.proto index 894f07a6ca018..1547ec9185c6b 100644 --- a/api/proto/teleport/legacy/types/types.proto +++ b/api/proto/teleport/legacy/types/types.proto @@ -2724,6 +2724,7 @@ message SessionJoinPolicy { // AccessRequestConditions is a matcher for allow/deny restrictions on // access-requests. +// Please remember to update IsEmpty when updating this message. message AccessRequestConditions { // Roles is the name of roles which will match the request rule. repeated string Roles = 1 [(gogoproto.jsontag) = "roles,omitempty"]; @@ -2768,6 +2769,7 @@ message AccessRequestConditions { // AccessReviewConditions is a matcher for allow/deny restrictions on // access reviews. +// Please remember to update IsEmpty when updating this message. message AccessReviewConditions { // Roles is the name of roles which may be reviewed. repeated string Roles = 1 [(gogoproto.jsontag) = "roles,omitempty"]; diff --git a/api/types/constants.go b/api/types/constants.go index 05635950d2e27..0c03b96b5d8eb 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -568,6 +568,22 @@ const ( // downgraded before being returned to clients on older versions that do not // support one or more features enabled in that resource. TeleportDowngradedLabel = TeleportInternalLabelPrefix + "downgraded" + + // TeleportInternalResourceType indicates the type of internal Teleport resource a resource is. + // Valid values are: + // - system: These resources will be automatically created and overwritten on startup. Users should + // not change these resources. + // - preset: These resources will be created if they don't exist. Updates may be applied to them, + // but user changes to these resources will be preserved. + TeleportInternalResourceType = TeleportInternalLabelPrefix + "resource-type" + + // SystemResource are resources that will be automatically created and overwritten on startup. Users + // should not change these resources. + SystemResource = "system" + + // PresetResource are resources resources will be created if they don't exist. Updates may be applied + // to them, but user changes to these resources will be preserved. + PresetResource = "preset" ) // CloudHostnameTag is the name of the tag in a cloud instance used to override a node's hostname. diff --git a/api/types/role.go b/api/types/role.go index 365c1ca3f761c..f36919e65ad7a 100644 --- a/api/types/role.go +++ b/api/types/role.go @@ -1655,3 +1655,21 @@ var LabelMatcherKinds = []string{ KindWindowsDesktopService, KindUserGroup, } + +// IsEmpty will return true if the condition is empty. +func (a AccessRequestConditions) IsEmpty() bool { + return len(a.Annotations) == 0 && + len(a.ClaimsToRoles) == 0 && + len(a.Roles) == 0 && + len(a.SearchAsRoles) == 0 && + len(a.SuggestedReviewers) == 0 && + len(a.Thresholds) == 0 +} + +// IsEmpty will return true if the condition is empty. +func (a AccessReviewConditions) IsEmpty() bool { + return len(a.ClaimsToRoles) == 0 && + len(a.PreviewAsRoles) == 0 && + len(a.Roles) == 0 && + len(a.Where) == 0 +} diff --git a/api/types/role_test.go b/api/types/role_test.go new file mode 100644 index 0000000000000..5441edfccc9fe --- /dev/null +++ b/api/types/role_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2023 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types/wrappers" +) + +func TestAccessRequestConditionsIsEmpty(t *testing.T) { + tests := []struct { + name string + arc AccessRequestConditions + expected bool + }{ + { + name: "empty", + arc: AccessRequestConditions{}, + expected: true, + }, + { + name: "annotations", + arc: AccessRequestConditions{ + Annotations: wrappers.Traits{ + "test": []string{"test"}, + }, + }, + expected: false, + }, + { + name: "claims to roles", + arc: AccessRequestConditions{ + ClaimsToRoles: []ClaimMapping{ + {}, + }, + }, + expected: false, + }, + { + name: "roles", + arc: AccessRequestConditions{ + Roles: []string{"test"}, + }, + expected: false, + }, + { + name: "search as roles", + arc: AccessRequestConditions{ + SearchAsRoles: []string{"test"}, + }, + expected: false, + }, + { + name: "suggested reviewers", + arc: AccessRequestConditions{ + SuggestedReviewers: []string{"test"}, + }, + expected: false, + }, + { + name: "thresholds", + arc: AccessRequestConditions{ + Thresholds: []AccessReviewThreshold{ + { + Name: "test", + }, + }, + }, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expected, test.arc.IsEmpty()) + }) + } +} + +func TestAccessReviewConditionsIsEmpty(t *testing.T) { + tests := []struct { + name string + arc AccessReviewConditions + expected bool + }{ + { + name: "empty", + arc: AccessReviewConditions{}, + expected: true, + }, + { + name: "claims to roles", + arc: AccessReviewConditions{ + ClaimsToRoles: []ClaimMapping{ + {}, + }, + }, + expected: false, + }, + { + name: "preview as roles", + arc: AccessReviewConditions{ + PreviewAsRoles: []string{"test"}, + }, + expected: false, + }, + { + name: "roles", + arc: AccessReviewConditions{ + Roles: []string{"test"}, + }, + expected: false, + }, + { + name: "where", + arc: AccessReviewConditions{ + Where: "test", + }, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expected, test.arc.IsEmpty()) + }) + } +} diff --git a/api/types/types.pb.go b/api/types/types.pb.go index 1f5719d4b6408..2dee5b7d998fa 100644 --- a/api/types/types.pb.go +++ b/api/types/types.pb.go @@ -6879,6 +6879,7 @@ var xxx_messageInfo_SessionJoinPolicy proto.InternalMessageInfo // AccessRequestConditions is a matcher for allow/deny restrictions on // access-requests. +// Please remember to update IsEmpty when updating this message. type AccessRequestConditions struct { // Roles is the name of roles which will match the request rule. Roles []string `protobuf:"bytes,1,rep,name=Roles,proto3" json:"roles,omitempty"` @@ -6944,6 +6945,7 @@ var xxx_messageInfo_AccessRequestConditions proto.InternalMessageInfo // AccessReviewConditions is a matcher for allow/deny restrictions on // access reviews. +// Please remember to update IsEmpty when updating this message. type AccessReviewConditions struct { // Roles is the name of roles which may be reviewed. Roles []string `protobuf:"bytes,1,rep,name=Roles,proto3" json:"roles,omitempty"` diff --git a/constants.go b/constants.go index 7881d408093ea..25ce36e2b8888 100644 --- a/constants.go +++ b/constants.go @@ -612,6 +612,18 @@ const ( // PresetAuditorRoleName is a name of a preset role that allows // reading cluster events and playing back session records. PresetAuditorRoleName = "auditor" + + // PresetReviewerRoleName is a name of a preset role that allows + // for reviewing access requests. + PresetReviewerRoleName = "reviewer" + + // PresetRequesterRoleName is a name of a preset role that allows + // for requesting access to resources. + PresetRequesterRoleName = "requester" + + // PresetGroupAccessRoleName is a name of a preset role that allows + // access to all user groups. + PresetGroupAccessRoleName = "group-access" ) var PresetRoles = []string{PresetEditorRoleName, PresetAccessRoleName, PresetAuditorRoleName} diff --git a/docs/cspell.json b/docs/cspell.json index 999f679492f13..1b71579507740 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -36,6 +36,7 @@ "CLOUDSDK", "CTAP", "Cgajq", + "dbreviewer", "DBSIZE", "DEBU", "DHDR", diff --git a/docs/pages/access-controls/access-requests/resource-requests.mdx b/docs/pages/access-controls/access-requests/resource-requests.mdx index 92c7bf621324e..4da7e6134330d 100644 --- a/docs/pages/access-controls/access-requests/resource-requests.mdx +++ b/docs/pages/access-controls/access-requests/resource-requests.mdx @@ -39,57 +39,13 @@ It is not recommended to enable Resource Access Requests by setting any upgraded to version 10. -## Step 1/8. Create the requester role +## Step 1/6. Grant the reviewer and requester roles to users -This role allows the requester to search for resources accessible by the -`access` role (all resources by default) and request access to them. - -```yaml -# requester.yaml -kind: role -version: v5 -metadata: - name: requester -spec: - allow: - request: - search_as_roles: - - access -``` - -```code -$ tctl create requester.yaml -``` - -## Step 2/8. Create the reviewer role - -This role allows the reviewer to approve all requests for the `access` role. - -```yaml -# reviewer.yaml -kind: role -version: v5 -metadata: - name: reviewer -spec: - allow: - review_requests: - roles: - - access - preview_as_roles: - - access -``` - -```code -$ tctl create reviewer.yaml -``` - -## Step 3/8. Grant the roles to users - -Grant the `requester` and `reviewer` roles to existing users, or create new -users to test this feature. -Make sure the requester has a valid `login` so that they can view and access SSH -nodes. +Teleport comes with the built-in role `requester`, which allows users to request access +to all resources within Teleport, and `reviewer`, which allows users to review all access +requests within Teleport. To test this feature, grant the built-in `requester` and `reviewer` +roles to existing users, or create new users. Make sure the requester has a valid `login` +so that they can view and access SSH nodes. ```code $ tctl users add alice --roles requester --logins alice @@ -100,7 +56,7 @@ For the rest of the guide we will assume that the `requester` role has been granted to a user named `alice` and the `reviewer` role has been granted to a user named `bob`. -## Step 4/8. Search for resources +## Step 2/6. Search for resources First, log in as `alice`. @@ -146,7 +102,7 @@ To request access to these resources, run --reason ``` -## Step 5/8. Request access to a resource +## Step 3/6. Request access to a resource Copy the command output by `tsh request search` in the previous step, optionally filling in a request reason. @@ -170,7 +126,7 @@ Waiting for request approval... The command will automatically wait until the request is approved. -## Step 6/8. Approve the Access Request +## Step 4/6. Approve the Access Request First, log in as `bob`. @@ -210,7 +166,7 @@ Check out our to notify the right people about new Access Requests. -## Step 7/8. Access the requested resource +## Step 5/6. Access the requested resource `alice`'s `tsh request create` command should resolve now that the request has been approved. @@ -256,7 +212,7 @@ $ tsh ssh alice@iot iot:~ alice$ ``` -## Step 8/8. Resume regular access +## Step 6/6. Resume regular access While logged in with a Resource Access Request, users will be blocked from access to any other resources. This is necessary because their certificate now contains an elevated role, diff --git a/docs/pages/access-controls/getting-started.mdx b/docs/pages/access-controls/getting-started.mdx index 0e308d1cb921e..28aca2be7ab8f 100644 --- a/docs/pages/access-controls/getting-started.mdx +++ b/docs/pages/access-controls/getting-started.mdx @@ -20,19 +20,38 @@ wrap up with creating your own role. ## Step 1/3. Add local users with preset roles -Teleport provides several preset roles: `editor`, `auditor` and `access`. The -`editor` role authorizes users to modify cluster configuration, the `auditor` -role to view audit logs, and `access` role to access cluster resources. +Teleport provides several preset roles: `editor`, `auditor`, and `access`. +- The `editor` role authorizes users to modify cluster configuration. +- The `auditor` role authorizes users to view audit logs. +- The `access` role authorizes users to access cluster resources. + +
+Teleport Enterprise contains two additional preset roles: `reviewer` and `requester`. + +- The `reviewer` role authorizes users to review Access Requests. +- The `requester` role authorizes users to request resources. +
+ + Invite the local user Alice as cluster `editor`: ```code $ tctl users add alice --roles=editor ``` + + +Invite the local user Alice as cluster `editor` and `reviewer`: + +```code +$ tctl users add alice --roles=editor,reviewer +``` + Once Alice signs up, she will be able to edit cluster configuration. You can list users and their roles using `tctl users ls`. + ```code $ tctl users ls @@ -40,15 +59,33 @@ $ tctl users ls # -------------------- -------------- # alice editor ``` + + +```code +$ tctl users ls + +# User Roles +# -------------------- -------------- +# alice editor, reviewer +``` + You can update the user's roles using the `tctl users update` command: + ```code # Once Alice logs back in, she will be able to view audit logs $ tctl users update alice --set-roles=editor,auditor ``` + + +```code +# Once Alice logs back in, she will be able to view audit logs +$ tctl users update alice --set-roles=editor,reviewer,auditor +``` + -Because Alice has two roles, permissions from those roles create a union. She +Because Alice has two or more roles, permissions from those roles create a union. She will be able to act as a system administrator and auditor at the same time. ## Step 2/3. Map SSO users to roles diff --git a/docs/pages/access-controls/guides/dual-authz.mdx b/docs/pages/access-controls/guides/dual-authz.mdx index 59f0bcdabb849..8ce47c6a3fc13 100644 --- a/docs/pages/access-controls/guides/dual-authz.mdx +++ b/docs/pages/access-controls/guides/dual-authz.mdx @@ -117,13 +117,13 @@ Alice and Ivan are reviewers. They can approve requests for assuming role `dbadmin`. Bob is a DevOps engineer and can assume the `dbadmin` role if two members of the `reviewer` role approve the request. -Create the following `dbadmin`, `reviewer` and `devops` roles: +Create the following `dbadmin`, `dbreviewer` and `devops` roles: ```yaml kind: role version: v5 metadata: - name: reviewer + name: dbreviewer spec: allow: review_requests: @@ -157,8 +157,8 @@ The commands below create the local users Bob, Alice, and Ivan. ```code $ tctl users add bob@example.com --roles=devops -$ tctl users add alice@example.com --roles=reviewer -$ tctl users add ivan@example.com --roles=reviewer +$ tctl users add alice@example.com --roles=dbreviewer +$ tctl users add ivan@example.com --roles=dbreviewer ``` ### Create an Access Request diff --git a/docs/pages/access-controls/guides/hardware-key-support.mdx b/docs/pages/access-controls/guides/hardware-key-support.mdx index 8adc155a72e43..17b73a8a1f945 100644 --- a/docs/pages/access-controls/guides/hardware-key-support.mdx +++ b/docs/pages/access-controls/guides/hardware-key-support.mdx @@ -111,7 +111,7 @@ Once hardware key support is enforced, affected users will be required to have t These users will be prompted to connect and touch their YubiKey on log in: - + ```code $ tsh login --user=dev --proxy=proxy.example.com:3080 @@ -129,6 +129,24 @@ $ tsh login --user=dev --proxy=proxy.example.com:3080 + + +```code +$ tsh login --user=dev --proxy=proxy.example.com:3080 +# Enter password for Teleport user dev: +# Tap your YubiKey +# > Profile URL: https://example.com +# Logged in as: dev +# Cluster: example.com +# Roles: access, editor, reviewer +# Logins: bjoerger +# Kubernetes: enabled +# Valid until: 2022-10-11 01:53:44 -0700 PDT [valid for 8h0m0s] +# Extensions: permit-X11-forwarding, permit-agent-forwarding, permit-port-forwarding, permit-pty, private-key-policy +``` + + + ```code @@ -138,7 +156,7 @@ $ tsh login --proxy=mytenant.teleport.sh # > Profile URL: https://example.com # Logged in as: dev # Cluster: example.com -# Roles: access, editor +# Roles: access, editor, reviewer # Logins: bjoerger # Kubernetes: enabled # Valid until: 2022-10-11 01:53:44 -0700 PDT [valid for 8h0m0s] diff --git a/docs/pages/access-controls/guides/passwordless.mdx b/docs/pages/access-controls/guides/passwordless.mdx index b365b4e6c40f7..d484ee6d3e84a 100644 --- a/docs/pages/access-controls/guides/passwordless.mdx +++ b/docs/pages/access-controls/guides/passwordless.mdx @@ -69,6 +69,7 @@ between `tsh`, the Teleport Web UI, and different computers. Authenticate using your passwordless credential: + ```code $ tsh login --proxy=example.com --auth=passwordless # Tap your security key @@ -81,6 +82,21 @@ $ tsh login --proxy=example.com --auth=passwordless # Valid until: 2021-10-04 23:32:29 -0700 PDT [valid for 12h0m0s] # Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty ``` + + +```code +$ tsh login --proxy=example.com --auth=passwordless +# Tap your security key +# > Profile URL: https://example.com +# Logged in as: codingllama +# Cluster: example.com +# Roles: access, editor, reviewer +# Logins: codingllama +# Kubernetes: enabled +# Valid until: 2021-10-04 23:32:29 -0700 PDT [valid for 12h0m0s] +# Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty +``` + A fully passwordless cluster defaults to passwordless logins, making `--auth=passwordless` unnecessary. See the next section to learn how to enable diff --git a/docs/pages/access-controls/guides/webauthn.mdx b/docs/pages/access-controls/guides/webauthn.mdx index aa4e3822d7aa0..44966f99334d6 100644 --- a/docs/pages/access-controls/guides/webauthn.mdx +++ b/docs/pages/access-controls/guides/webauthn.mdx @@ -179,7 +179,7 @@ Reference](../../reference/cli.mdx#tsh-global-flags) page. Once a WebAuthn device is registered, the user will be prompted for it on login: - + ```code $ tsh login --proxy=example.com @@ -197,6 +197,24 @@ $ tsh login --proxy=example.com + + +```code +$ tsh login --proxy=example.com +# Enter password for Teleport user codingllama: +# Tap any security key or enter a code from a OTP device: +# > Profile URL: https://example.com +# Logged in as: codingllama +# Cluster: example.com +# Roles: access, editor, reviewer +# Logins: codingllama +# Kubernetes: enabled +# Valid until: 2021-10-04 23:32:29 -0700 PDT [valid for 12h0m0s] +# Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty +``` + + + ```code @@ -206,7 +224,7 @@ $ tsh login --proxy=mytenant.teleport.sh # > Profile URL: https://mytenant.teleport.sh # Logged in as: codingllama # Cluster: mytenant.teleport.sh -# Roles: access, editor +# Roles: access, editor, reviewer # Logins: codingllama # Kubernetes: enabled # Valid until: 2021-10-04 23:32:29 -0700 PDT [valid for 12h0m0s] diff --git a/docs/pages/access-controls/reference.mdx b/docs/pages/access-controls/reference.mdx index 281480195623b..88dfbdeba5a4c 100644 --- a/docs/pages/access-controls/reference.mdx +++ b/docs/pages/access-controls/reference.mdx @@ -127,6 +127,8 @@ Teleport provides several pre-defined roles out-of-the-box: | `editor` | Allows editing of cluster configuration settings. | | `auditor`| Allows reading cluster events, audit logs, and playing back session records. | | `access`| Allows access to cluster resources. | +| `requester`| Enterprise-only role that allows a user to create Access Requests. | +| `reviewer`| Enterprise-only role that allows review of Access Requests. | ### Role versions diff --git a/docs/pages/access-controls/sso/github-sso.mdx b/docs/pages/access-controls/sso/github-sso.mdx index f3c08fb34065a..870a3ff08433a 100644 --- a/docs/pages/access-controls/sso/github-sso.mdx +++ b/docs/pages/access-controls/sso/github-sso.mdx @@ -68,6 +68,7 @@ $ tctl sso configure github \ The contents of `github.yaml` should resemble the following: + ```yaml kind: github metadata: @@ -88,6 +89,31 @@ spec: team: GITHUB-TEAM version: v3 ``` + + + +```yaml +kind: github +metadata: + name: github +spec: + api_endpoint_url: "" + client_id: + client_secret: + display: GitHub + endpoint_url: "" + redirect_url: https:///v1/webapi/github/callback + teams_to_logins: null + teams_to_roles: + - organization: ORG-NAME + roles: + - access + - editor + - reviewer + team: GITHUB-TEAM +version: v3 +``` +
You can add multiple instances of the `--teams-to-roles` flag or edit the connector @@ -252,14 +278,32 @@ After logging in successfully, you will see the following: You will receive the details of your user session within the CLI: - + ```code > Profile URL: https://tele.example.com:443 Logged in as: jeff Cluster: tele.example.com Roles: access - Logins: jeff, ubuntu, debian, -teleport-internal-join + Logins: jeff, ubuntu, debian + Kubernetes: enabled + Kubernetes users: dev + Kubernetes groups: developer + Valid until: 2023-03-08 17:13:50 -0600 CST [valid for 7h51m0s] + Extensions: permit-port-forwarding, permit-pty, private-key-policy +``` + + + + + + +```code +> Profile URL: https://tele.example.com:443 + Logged in as: jeff + Cluster: tele.example.com + Roles: access, requester + Logins: jeff, ubuntu, debian Kubernetes: enabled Kubernetes users: dev Kubernetes groups: developer @@ -275,8 +319,8 @@ You will receive the details of your user session within the CLI: > Profile URL: https://mytenant.teleport.sh:443 Logged in as: jeff Cluster: mytenant.teleport.sh - Roles: access - Logins: jeff, ubuntu, debian, -teleport-internal-join + Roles: access, requester + Logins: jeff, ubuntu, debian Kubernetes: enabled Kubernetes users: dev Kubernetes groups: developer diff --git a/docs/pages/connect-your-client/introduction.mdx b/docs/pages/connect-your-client/introduction.mdx index 0bb8476056230..a0640106c4be4 100644 --- a/docs/pages/connect-your-client/introduction.mdx +++ b/docs/pages/connect-your-client/introduction.mdx @@ -17,6 +17,7 @@ Teleport, and includes links to more detailed documentation at the end. [downloading](https://goteleport.com/download/) and installing `tsh`, sign in to your Teleport cluster: + ```code @@ -33,6 +34,48 @@ Tap any security key Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty, private-key-policy ``` + + + +```code +$ tsh login --proxy= --auth=github +If browser window does not open automatically, open it by clicking on the link: + http://127.0.0.1:49927/1d80e257-ec61-4ed2-9403-784f8d35b2fe +> Profile URL: https://teleport.example.com:443 + Logged in as: user@example.com + Cluster: example.com + Roles: access + Logins: ubuntu, ec2-user + Kubernetes: enabled + Valid until: 2022-11-01 22:37:05 -0500 CDT [valid for 12h0m0s] + Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty, private-key-policy +``` + +Depending on how Teleport was configured for your network, you may not need +the additional flags `--auth`. Your administrator should provide the details +required for your particular use case. + + + + + + + + +```code +$ tsh login --proxy= --user= +Enter password for Teleport user alice: +Tap any security key +> Profile URL: https://teleport.example.com:443 + Logged in as: alice + Cluster: example.com + Roles: access, requester + Logins: ubuntu, ec2-user + Kubernetes: enabled + Valid until: 2022-11-01 22:37:05 -0500 CDT [valid for 12h0m0s] + Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty, private-key-policy +``` + @@ -43,7 +86,7 @@ If browser window does not open automatically, open it by clicking on the link: > Profile URL: https://teleport.example.com:443 Logged in as: user@example.com Cluster: example.com - Roles: access + Roles: access, requester Logins: ubuntu, ec2-user Kubernetes: enabled Valid until: 2022-11-01 22:37:05 -0500 CDT [valid for 12h0m0s] @@ -56,6 +99,7 @@ required for your particular use case. + ### Teleport Connect diff --git a/docs/pages/database-access/getting-started.mdx b/docs/pages/database-access/getting-started.mdx index 360f822c8ac8f..38d5caf3f1e2c 100644 --- a/docs/pages/database-access/getting-started.mdx +++ b/docs/pages/database-access/getting-started.mdx @@ -167,9 +167,16 @@ EOF Create the Teleport user assigned the `db` role we've just created: + ```code $ tctl users add --roles=access,db alice ``` + + +```code +$ tctl users add --roles=access,requester,db alice +``` + ## Step 4/4. Connect diff --git a/docs/pages/deploy-a-cluster/deployments/aws-terraform.mdx b/docs/pages/deploy-a-cluster/deployments/aws-terraform.mdx index 3a24ee770ca32..31b0b9722ed5b 100644 --- a/docs/pages/deploy-a-cluster/deployments/aws-terraform.mdx +++ b/docs/pages/deploy-a-cluster/deployments/aws-terraform.mdx @@ -487,6 +487,7 @@ $ ssh -i ${TF_VAR_key_name}.pem -o ProxyCommand="ssh -i ${TF_VAR_key_name}.pem - 4 - Use the `tctl` command to create an admin user for Teleport: + ```code # From EC2 host $ sudo tctl users add teleport-admin --roles=editor,access --logins=root @@ -494,8 +495,20 @@ $ sudo tctl users add teleport-admin --roles=editor,access --logins=root # https://teleport.example.com:443/web/newuser/6489ae886babf4232826076279bcb2fb # NOTE: Make sure teleport.example.com:443 points at a Teleport proxy which users can access. -# When the user 'teleport-admin' activates their account, they will be assigned roles [admin] +# When the user 'teleport-admin' activates their account, they will be assigned roles [editor, access] ``` + + +```code +# From EC2 host +$ sudo tctl users add teleport-admin --roles=editor,access,reviewer --logins=root +# Signup token has been created and is valid for 1 hours. Share this URL with the user: +# https://teleport.example.com:443/web/newuser/6489ae886babf4232826076279bcb2fb + +# NOTE: Make sure teleport.example.com:443 points at a Teleport proxy which users can access. +# When the user 'teleport-admin' activates their account, they will be assigned roles [editor, access, reviewer] +``` + 5 - Click the link to launch the Teleport web UI and finish setting up your user. You will need to scan the QR code with an TOTP-compatible app like Google Authenticator or Authy. You will also set a password for the @@ -511,6 +524,7 @@ You can [download the Teleport package containing the `tsh` client from here](ht - the client is the same for both OSS and Enterprise versions of Teleport. + ```code $ tsh login --proxy=${TF_VAR_route53_domain} --user=teleport-admin # Enter password for Teleport user teleport-admin: @@ -532,6 +546,30 @@ $ tsh ls $ tsh ssh root@ip-172-31-11-69-ec2-internal # [root@ip-172-31-11-69 ~]# ``` + + +```code +$ tsh login --proxy=${TF_VAR_route53_domain} --user=teleport-admin +# Enter password for Teleport user teleport-admin: +# Enter your OTP token: +# 567989 +# > Profile URL: https://teleport.example.com:443 +# Logged in as: teleport-admin +# Cluster: example-cluster +# Roles: editor,access,reviewer +# Logins: root +# Valid until: 2020-03-06 22:07:11 -0400 AST [valid for 12h0m0s] +# Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty + +$ tsh ls +# Node Name Address Labels +# ---------------------------- ----------------- ------ +# ip-172-31-11-69-ec2-internal 172.31.11.69:3022 + +$ tsh ssh root@ip-172-31-11-69-ec2-internal +# [root@ip-172-31-11-69 ~]# +``` + ## Restarting/checking Teleport services diff --git a/docs/pages/deploy-a-cluster/helm-deployments/aws.mdx b/docs/pages/deploy-a-cluster/helm-deployments/aws.mdx index 67ff35ebd100f..f1a49dd73b265 100644 --- a/docs/pages/deploy-a-cluster/helm-deployments/aws.mdx +++ b/docs/pages/deploy-a-cluster/helm-deployments/aws.mdx @@ -503,6 +503,7 @@ $ aws route53 get-change --id "${CHANGEID?}" | jq '.ChangeInfo.Status' Create a user to be able to log into Teleport. This needs to be done on the Teleport auth server, so we can run the command using `kubectl`: + ```code $ kubectl --namespace teleport exec deploy/teleport-auth -- tctl users add test --roles=access,editor @@ -511,6 +512,17 @@ https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68 NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access. ``` + + +```code +$ kubectl --namespace teleport exec deploy/teleport-auth -- tctl users add test --roles=access,editor,reviewer + +User "test" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h: +https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68 + +NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access. +``` + Load the user creation link to create a password and set up 2-factor authentication for the Teleport user via the web UI. diff --git a/docs/pages/deploy-a-cluster/helm-deployments/custom.mdx b/docs/pages/deploy-a-cluster/helm-deployments/custom.mdx index 1945f5eb04c67..0ff9691b5e6d1 100644 --- a/docs/pages/deploy-a-cluster/helm-deployments/custom.mdx +++ b/docs/pages/deploy-a-cluster/helm-deployments/custom.mdx @@ -214,6 +214,7 @@ If you're not migrating an existing Teleport cluster, you'll need to create a user to be able to log into Teleport. This needs to be done on the Teleport auth server, so we can run the command using `kubectl`: + ```code $ kubectl --namespace teleport exec deployment/teleport-auth -- tctl users add test --roles=access,editor @@ -222,6 +223,17 @@ https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68 NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access. ``` + + +```code +$ kubectl --namespace teleport exec deployment/teleport-auth -- tctl users add test --roles=access,editor,reviewer + +User "test" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h: +https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68 + +NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access. +``` + If you didn't set up DNS for your hostname earlier, remember to replace diff --git a/docs/pages/deploy-a-cluster/helm-deployments/digitalocean.mdx b/docs/pages/deploy-a-cluster/helm-deployments/digitalocean.mdx index a0174a050aa84..eb1ff67a3e008 100644 --- a/docs/pages/deploy-a-cluster/helm-deployments/digitalocean.mdx +++ b/docs/pages/deploy-a-cluster/helm-deployments/digitalocean.mdx @@ -121,6 +121,7 @@ Once you get the value for the external IP (it may take a few minutes for this f ## Step 3/4. Create and set up Teleport user Now we create a Teleport user by executing the `tctl` command with `kubectl`. + ```code $ kubectl --namespace teleport-cluster exec deployment/teleport-cluster-auth -- tctl users add tadmin --roles=access,editor --logins=ubuntu @@ -129,6 +130,17 @@ https://tele.example.com:443/web/invite/ NOTE: Make sure tele.example.com:443 points at a Teleport proxy which users can access. ``` + + +```code +$ kubectl --namespace teleport-cluster exec deployment/teleport-cluster-auth -- tctl users add tadmin --roles=access,editor,reviewer --logins=ubuntu + +User "tadmin" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h: +https://tele.example.com:443/web/invite/ + +NOTE: Make sure tele.example.com:443 points at a Teleport proxy which users can access. +``` + Copy the link shown after executing the above command and open the link in a web browser to complete the user registration process (the link is `https://tele.example.com:443/web/invite/` in the above case).
@@ -199,6 +211,7 @@ $ export KUBECONFIG=${HOME?}/teleport-kubeconfig.yaml + ```code $ tsh login --proxy=tele.example.com:443 --auth=local --user=tadmin Enter password for Teleport user tadmin: @@ -213,6 +226,23 @@ Enter your OTP token: Valid until: 2021-10-27 06:37:15 +0000 UTC [valid for 12h0m0s] Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty ``` + + +```code +$ tsh login --proxy=tele.example.com:443 --auth=local --user=tadmin +Enter password for Teleport user tadmin: +Enter your OTP token: +540255 +> Profile URL: https://tele.example.com:443 + Logged in as: tadmin + Cluster: tele.example.com + Roles: access, editor, reviewer, member + Logins: ubuntu + Kubernetes: enabled + Valid until: 2021-10-27 06:37:15 +0000 UTC [valid for 12h0m0s] + Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty +``` + ### Select the Kubernetes cluster diff --git a/docs/pages/deploy-a-cluster/helm-deployments/gcp.mdx b/docs/pages/deploy-a-cluster/helm-deployments/gcp.mdx index 5415b356406d1..e825965cc8e5e 100644 --- a/docs/pages/deploy-a-cluster/helm-deployments/gcp.mdx +++ b/docs/pages/deploy-a-cluster/helm-deployments/gcp.mdx @@ -419,14 +419,26 @@ $ gcloud dns record-sets transaction execute --zone="${MYZONE?}" Create a user to be able to log into Teleport. This needs to be done on the Teleport auth server, so we can run the command using `kubectl`: + ```code -$ kubectl --namespace teleport exec deployment/teleport-auth -- tctl users add test --roles=access,editor +$ kubectl --namespace teleport exec deploy/teleport-auth -- tctl users add test --roles=access,editor User "test" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h: https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68 -NOTE: Make sure teleport.example.com:443 points at a Teleport proxy which users can access. +NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access. ``` + + +```code +$ kubectl --namespace teleport exec deploy/teleport-auth -- tctl users add test --roles=access,editor,reviewer + +User "test" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h: +https://teleport.example.com:443/web/invite/91cfbd08bc89122275006e48b516cc68 + +NOTE: Make sure teleport.example.com:443 points at a Teleport proxy that users can access. +``` + Load the user creation link to create a password and set up 2-factor authentication for the Teleport user via the web UI. diff --git a/docs/pages/get-started.mdx b/docs/pages/get-started.mdx index a7f8c2272403e..631f34c94e1cc 100644 --- a/docs/pages/get-started.mdx +++ b/docs/pages/get-started.mdx @@ -174,7 +174,6 @@ $ tsh login --proxy= --user=teleport-admin Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty ``` -
## Step 4/4. Enroll your infrastructure diff --git a/docs/pages/includes/database-access/create-user.mdx b/docs/pages/includes/database-access/create-user.mdx index a790902ad872a..2f18d26ccc600 100644 --- a/docs/pages/includes/database-access/create-user.mdx +++ b/docs/pages/includes/database-access/create-user.mdx @@ -4,6 +4,7 @@ To modify an existing user to provide access to the Database Service, see [Datab + Create a local Teleport user with the built-in `access` role: ```code @@ -13,6 +14,18 @@ $ tctl users add \ --db-names=\* \ alice ``` + + +Create a local Teleport user with the built-in `access` and `requester` roles: + +```code +$ tctl users add \ + --roles=access,requester \ + --db-users=\* \ + --db-names=\* \ + alice +``` + | Flag | Description | |---------------------------|------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/docs/pages/management/admin/labels.mdx b/docs/pages/management/admin/labels.mdx index b54588ebc3a52..ceb1d7c718472 100644 --- a/docs/pages/management/admin/labels.mdx +++ b/docs/pages/management/admin/labels.mdx @@ -181,7 +181,7 @@ your Node. Run the following command to get the current logins for your user: - + ```code $ tsh status @@ -195,6 +195,21 @@ $ tsh status Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty ``` + + + +```code +$ tsh status +> Profile URL: https://teleport.example.com:443 + Logged in as: myuser + Cluster: teleport.example.com + Roles: access, editor, reviewer + Logins: -teleport-nologin-d4bc1dad-ce49-4bbe-925d-a67f8d2d6afe + Kubernetes: enabled + Valid until: 2022-04-27 22:26:50 -0400 EDT [valid for 11h40m0s] + Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty +``` + @@ -203,7 +218,7 @@ tsh status > Profile URL: https://mytenant.teleport.sh:443 Logged in as: myuser Cluster: mytenant.teleport.sh - Roles: access, editor + Roles: access, editor, reviewer Logins: -teleport-nologin-d4bc1dad-ce49-4bbe-925d-a67f8d2d6afe Kubernetes: enabled Valid until: 2022-04-27 22:26:50 -0400 EDT [valid for 11h40m0s] diff --git a/docs/pages/management/admin/trustedclusters.mdx b/docs/pages/management/admin/trustedclusters.mdx index 4b9300b7598b7..fb972184fe823 100644 --- a/docs/pages/management/admin/trustedclusters.mdx +++ b/docs/pages/management/admin/trustedclusters.mdx @@ -240,7 +240,7 @@ You can create a join token using the `tctl` tool. First, log out of all clusters and log in to the root cluster. - + ```code $ tsh logout @@ -255,6 +255,22 @@ $ tsh login --user=myuser --proxy=rootcluster.example.com Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty ``` + + + +```code +$ tsh logout +$ tsh login --user=myuser --proxy=rootcluster.example.com +> Profile URL: https://rootcluster.example.com:443 + Logged in as: myuser + Cluster: rootcluster.example.com + Roles: access, auditor, editor, reviewer + Logins: root + Kubernetes: enabled + Valid until: 2022-04-29 03:07:22 -0400 EDT [valid for 12h0m0s] + Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty +``` + @@ -263,7 +279,7 @@ $ tsh login --user=myuser --proxy=myrootclustertenant.teleport.sh > Profile URL: https://rootcluster.teleport.sh:443 Logged in as: myuser Cluster: rootcluster.teleport.sh - Roles: access, auditor, editor + Roles: access, auditor, editor, reviewer Logins: root Kubernetes: enabled Valid until: 2022-04-29 03:07:22 -0400 EDT [valid for 12h0m0s] diff --git a/docs/pages/management/admin/users.mdx b/docs/pages/management/admin/users.mdx index 2c6f700c1e626..914687c44e67b 100644 --- a/docs/pages/management/admin/users.mdx +++ b/docs/pages/management/admin/users.mdx @@ -37,9 +37,16 @@ Let's look at this table: Let's add a new user to Teleport using the `tctl` tool: + ```code $ tctl users add joe --logins=joe,root --roles=access,editor ``` + + +```code +$ tctl users add joe --logins=joe,root --roles=access,editor,reviewer +``` + Teleport generates an auto-expiring token (with a TTL of one hour) and prints the token URL, which must be used before the TTL expires. diff --git a/docs/pages/reference/cli.mdx b/docs/pages/reference/cli.mdx index 19980b89b9146..e92ac26e37362 100644 --- a/docs/pages/reference/cli.mdx +++ b/docs/pages/reference/cli.mdx @@ -1672,9 +1672,9 @@ These flags are available for all commands `--debug, --config`. Run ```code # Adds teleport user "joe" with mappings to # OS users and {{ internal.logins }} to "joe" and "ubuntu" -$ tctl users add joe --roles=access joe,ubuntu +$ tctl users add joe --roles=access,requester joe,ubuntu # Adds Teleport user "joe" with mappings to the editor role -$ tctl users add joe --roles=editor +$ tctl users add joe --roles=editor,reviewer ``` ### tctl users update diff --git a/docs/pages/server-access/getting-started.mdx b/docs/pages/server-access/getting-started.mdx index 6d620ce62fd04..88336874d577e 100644 --- a/docs/pages/server-access/getting-started.mdx +++ b/docs/pages/server-access/getting-started.mdx @@ -133,7 +133,7 @@ configuration: Run the following command to create a user that can access the Teleport Web UI: ```code -$ sudo tctl users add tele-admin --roles=editor,access --logins=root,ubuntu,ec2-user +$ sudo tctl users add tele-admin --roles=editor,access,reviewer --logins=root,ubuntu,ec2-user ``` This will generate an initial login link where you can create a password and set diff --git a/docs/pages/server-access/guides/openssh.mdx b/docs/pages/server-access/guides/openssh.mdx index 6f0e5bca64f38..b43b6e9ea286a 100644 --- a/docs/pages/server-access/guides/openssh.mdx +++ b/docs/pages/server-access/guides/openssh.mdx @@ -75,7 +75,7 @@ authenticate the `sshd` host using the host certificate you generated earlier. First, make sure you have logged in to your Teleport cluster: - + ```code $ tsh status @@ -89,6 +89,21 @@ $ tsh status Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty ``` + + + +```code +$ tsh status +> Profile URL: https://teleport.example.com:443 + Logged in as: myuser + Cluster: teleport.example.com + Roles: access, auditor, editor, reviewer, host-certifier + Logins: ubuntu, root + Kubernetes: enabled + Valid until: 2022-05-06 22:54:01 -0400 EDT [valid for 11h53m0s] + Extensions: permit-agent-forwarding, permit-port-forwarding, permit-pty +``` + @@ -97,7 +112,7 @@ $ tsh status > Profile URL: https://mytenant.teleport.sh:443 Logged in as: myuser Cluster: mytenant.teleport.sh - Roles: access, auditor, editor, host-certifier + Roles: access, auditor, editor, reviewer, host-certifier Logins: ubuntu, root Kubernetes: enabled Valid until: 2022-05-06 22:54:01 -0400 EDT [valid for 11h53m0s] diff --git a/lib/auth/init.go b/lib/auth/init.go index 11a3d539936c6..2a0de201108d9 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -614,11 +614,19 @@ type PresetRoleManager interface { // createPresets creates preset resources (eg, roles). func createPresets(ctx context.Context, rm PresetRoleManager) error { roles := []types.Role{ + services.NewPresetGroupAccessRole(), services.NewPresetEditorRole(), services.NewPresetAccessRole(), services.NewPresetAuditorRole(), + services.NewPresetReviewerRole(), + services.NewPresetRequesterRole(), } for _, role := range roles { + // If the role is nil, skip because it doesn't apply to this Teleport installation. + if role == nil { + continue + } + err := rm.CreateRole(ctx, role) if err != nil { if !trace.IsAlreadyExists(err) { @@ -630,7 +638,7 @@ func createPresets(ctx context.Context, rm PresetRoleManager) error { return trace.Wrap(err) } - role, err := services.AddDefaultAllowConditions(currentRole) + role, err := services.AddRoleDefaults(currentRole) if trace.IsAlreadyExists(err) { continue } diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index d09f1a446d364..bce6d54b82697 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -43,6 +43,7 @@ import ( "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/lite" + "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/suite" "github.com/gravitational/teleport/lib/sshutils" @@ -613,9 +614,7 @@ func TestPresets(t *testing.T) { require.Equal(t, types.Labels{types.Wildcard: []string{types.Wildcard}}, deniedDatabaseServiceLabels, "keeps the deny label for DatabaseService") }) - t.Run("Does not upsert roles if nothing changes", func(t *testing.T) { - presetRoleCount := 3 - + upsertRoleTest := func(t *testing.T, presetRoleCount int) { roleManager := &mockRoleManager{ roles: make(map[string]types.Role, presetRoleCount), } @@ -659,6 +658,17 @@ func TestPresets(t *testing.T) { require.Equal(t, 1, roleManager.upsertRoleCallsCount, "unexpected call to UpsertRole") require.Equal(t, presetRoleCount, roleManager.getRoleCallsCount, "unexpected number of calls to CreateRole, got %d calls", roleManager.getRoleCallsCount) require.Equal(t, presetRoleCount, roleManager.createRoleCallsCount, "unexpected number of calls to CreateRole, got %d calls", roleManager.createRoleCallsCount) + } + + t.Run("Does not upsert roles if nothing changes", func(t *testing.T) { + upsertRoleTest(t, 3 /* presetRoleCount */) + }) + + t.Run("Does not upsert roles if nothing changes (enterprise)", func(t *testing.T) { + modules.SetTestModules(t, &modules.TestModules{ + TestBuildType: modules.BuildEnterprise, + }) + upsertRoleTest(t, 6 /* presetRoleCount */) }) } diff --git a/lib/services/presets.go b/lib/services/presets.go index 2a43a7407d928..28665b30f3013 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/teleport/api/constants" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/modules" ) // NewPresetEditorRole returns a new pre-defined role for cluster @@ -38,6 +39,9 @@ func NewPresetEditorRole() types.Role { Name: teleport.PresetEditorRoleName, Namespace: apidefaults.Namespace, Description: "Edit cluster configuration", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, }, Spec: types.RoleSpecV6{ Options: types.RoleOptions{ @@ -107,6 +111,9 @@ func NewPresetAccessRole() types.Role { Name: teleport.PresetAccessRoleName, Namespace: apidefaults.Namespace, Description: "Access cluster resources", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, }, Spec: types.RoleSpecV6{ Options: types.RoleOptions{ @@ -170,6 +177,9 @@ func NewPresetAuditorRole() types.Role { Name: teleport.PresetAuditorRoleName, Namespace: apidefaults.Namespace, Description: "Review cluster events and replay sessions", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, }, Spec: types.RoleSpecV6{ Options: types.RoleOptions{ @@ -195,6 +205,112 @@ func NewPresetAuditorRole() types.Role { return role } +// NewPresetReviewerRole returns a new pre-defined role for reviewer. The +// reviewer will be able to review all access requests. +func NewPresetReviewerRole() types.Role { + if modules.GetModules().BuildType() != modules.BuildEnterprise { + return nil + } + + role := &types.RoleV6{ + Kind: types.KindRole, + Version: types.V6, + Metadata: types.Metadata{ + Name: teleport.PresetReviewerRoleName, + Namespace: apidefaults.Namespace, + Description: "Review access requests", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + ReviewRequests: defaultAllowAccessReviewConditions(true)[teleport.PresetReviewerRoleName], + }, + }, + } + return role +} + +// NewPresetRequesterRole returns a new pre-defined role for requester. The +// requester will be able to request all resources. +func NewPresetRequesterRole() types.Role { + if modules.GetModules().BuildType() != modules.BuildEnterprise { + return nil + } + + role := &types.RoleV6{ + Kind: types.KindRole, + Version: types.V6, + Metadata: types.Metadata{ + Name: teleport.PresetRequesterRoleName, + Namespace: apidefaults.Namespace, + Description: "Request all resources", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Request: defaultAllowAccessRequestConditions(true)[teleport.PresetRequesterRoleName], + }, + }, + } + return role +} + +// NewPresetGroupAccessRole returns a new pre-defined role for group access - +// a role used for requesting and reviewing user group access. +func NewPresetGroupAccessRole() types.Role { + if modules.GetModules().BuildType() != modules.BuildEnterprise { + return nil + } + + role := &types.RoleV6{ + Kind: types.KindRole, + Version: types.V6, + Metadata: types.Metadata{ + Name: teleport.PresetGroupAccessRoleName, + Namespace: apidefaults.Namespace, + Description: "Have access to all user groups", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Namespaces: []string{apidefaults.Namespace}, + GroupLabels: types.Labels{ + types.Wildcard: []string{types.Wildcard}, + }, + Rules: []types.Rule{ + types.NewRule(types.KindUserGroup, RO()), + // Please see defaultAllowRules when adding a new rule. + }, + }, + }, + } + return role +} + +// bootstrapRoleMetadataLabels are metadata labels that will be applied to each role. +// These are intended to add labels for older roles that didn't previously have them. +func bootstrapRoleMetadataLabels() map[string]map[string]string { + return map[string]map[string]string{ + teleport.PresetAccessRoleName: { + types.TeleportInternalResourceType: types.PresetResource, + }, + teleport.PresetEditorRoleName: { + types.TeleportInternalResourceType: types.PresetResource, + }, + teleport.PresetAuditorRoleName: { + types.TeleportInternalResourceType: types.PresetResource, + }, + // Group access, reviewer and requester are intentionally not added here as there may be + // existing customer defined roles that have these labels. + } +} + // defaultAllowRules has the Allow rules that should be set as default when // they were not explicitly defined. This is used to update the current cluster // roles when deploying a new resource. It will also update all existing roles @@ -229,7 +345,7 @@ func defaultAllowRules() map[string][]types.Rule { } // defaultAllowLabels has the Allow labels that should be set as default when they were not explicitly defined. -// This is used to update exiting builtin preset roles with new permissions during cluster upgrades. +// This is used to update existing builtin preset roles with new permissions during cluster upgrades. // The following Labels are supported: // - DatabaseServiceLabels (db_service_labels) func defaultAllowLabels() map[string]types.RoleConditions { @@ -241,11 +357,77 @@ func defaultAllowLabels() map[string]types.RoleConditions { } } -// AddDefaultAllowConditions adds default allow Role Conditions to a preset role. -// Only rules/labels whose resources are not already defined (either allowing or denying) are added. -func AddDefaultAllowConditions(role types.Role) (types.Role, error) { +// defaultAllowAccessRequestConditions has the access request conditions that should be set as default when they were +// not explicitly defined. +func defaultAllowAccessRequestConditions(enterprise bool) map[string]*types.AccessRequestConditions { + if enterprise { + return map[string]*types.AccessRequestConditions{ + teleport.PresetRequesterRoleName: { + SearchAsRoles: []string{ + teleport.PresetAccessRoleName, + teleport.PresetGroupAccessRoleName, + }, + }, + } + } + + return map[string]*types.AccessRequestConditions{} +} + +// defaultAllowAccessReviewConditions has the access review conditions that should be set as default when they were +// not explicitly defined. +func defaultAllowAccessReviewConditions(enterprise bool) map[string]*types.AccessReviewConditions { + if enterprise { + return map[string]*types.AccessReviewConditions{ + teleport.PresetReviewerRoleName: { + PreviewAsRoles: []string{ + teleport.PresetAccessRoleName, + teleport.PresetGroupAccessRoleName, + }, + Roles: []string{ + teleport.PresetAccessRoleName, + teleport.PresetGroupAccessRoleName, + }, + }, + } + } + + return map[string]*types.AccessReviewConditions{} +} + +// AddRoleDefaults adds default role attributes to a preset role. +// Only attributes whose resources are not already defined (either allowing or denying) are added. +func AddRoleDefaults(role types.Role) (types.Role, error) { changed := false + // Role labels + defaultRoleLabels, ok := bootstrapRoleMetadataLabels()[role.GetName()] + if ok { + metadata := role.GetMetadata() + + if metadata.Labels == nil { + metadata.Labels = make(map[string]string, len(defaultRoleLabels)) + } + for label, value := range defaultRoleLabels { + if _, ok := metadata.Labels[label]; !ok { + metadata.Labels[label] = value + changed = true + } + } + + if changed { + role.SetMetadata(metadata) + } + } + + // Check if the role has a TeleportInternalResourceType attached. We do this after setting the role metadata + // labels because we set the role metadata labels for roles that have been well established (access, + // editor, auditor) that may not already have this label set, but we don't set it for newer roles + // (group-access, reviewer, requester) that may have customer definitions. + if role.GetMetadata().Labels[types.TeleportInternalResourceType] != types.PresetResource { + return nil, trace.AlreadyExists("not modifying user created role") + } + // Resource Rules defaultRules, ok := defaultAllowRules()[role.GetName()] if ok { @@ -281,6 +463,24 @@ func AddDefaultAllowConditions(role types.Role) (types.Role, error) { } } + enterprise := modules.GetModules().BuildType() == modules.BuildEnterprise + + if role.GetAccessRequestConditions(types.Allow).IsEmpty() { + arc := defaultAllowAccessRequestConditions(enterprise)[role.GetName()] + if arc != nil { + role.SetAccessRequestConditions(types.Allow, *arc) + changed = true + } + } + + if role.GetAccessReviewConditions(types.Allow).IsEmpty() { + arc := defaultAllowAccessReviewConditions(enterprise)[role.GetName()] + if arc != nil { + role.SetAccessReviewConditions(types.Allow, *arc) + changed = true + } + } + if !changed { return nil, trace.AlreadyExists("no change") } diff --git a/lib/services/presets_test.go b/lib/services/presets_test.go new file mode 100644 index 0000000000000..a830c20aef352 --- /dev/null +++ b/lib/services/presets_test.go @@ -0,0 +1,423 @@ +/* +Copyright 2023 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package services + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/constants" + apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/modules" +) + +func TestAddRoleDefaults(t *testing.T) { + noChange := func(t require.TestingT, err error, i ...interface{}) { + require.ErrorIs(t, err, trace.AlreadyExists("no change")) + } + notModifying := func(t require.TestingT, err error, i ...interface{}) { + require.ErrorIs(t, err, trace.AlreadyExists("not modifying user created role")) + } + + tests := []struct { + name string + role types.Role + enterprise bool + reviewNotEmpty bool + accessRequestsNotEmpty bool + + expectedErr require.ErrorAssertionFunc + expected types.Role + }{ + { + name: "nothing added", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + }, + expectedErr: noChange, + expected: nil, + }, + { + name: "editor", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetEditorRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + }, + expectedErr: require.NoError, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetEditorRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: defaultAllowRules()[teleport.PresetEditorRoleName], + }, + }, + }, + }, + { + name: "editor (only missing label)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetEditorRoleName, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: defaultAllowRules()[teleport.PresetEditorRoleName], + }, + }, + }, + expectedErr: require.NoError, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetEditorRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: defaultAllowRules()[teleport.PresetEditorRoleName], + }, + }, + }, + }, + { + name: "access (access review, db labels, identical rules)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAccessRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: defaultAllowRules()[teleport.PresetAccessRoleName], + }, + }, + }, + expectedErr: require.NoError, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAccessRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + DatabaseServiceLabels: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseServiceLabels, + DatabaseRoles: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseRoles, + Rules: defaultAllowRules()[teleport.PresetAccessRoleName], + }, + }, + }, + }, + { + name: "access (only missing label)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAccessRoleName, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + DatabaseServiceLabels: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseServiceLabels, + DatabaseRoles: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseRoles, + Rules: defaultAllowRules()[teleport.PresetAccessRoleName], + }, + }, + }, + expectedErr: require.NoError, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAccessRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + DatabaseServiceLabels: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseServiceLabels, + DatabaseRoles: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseRoles, + Rules: defaultAllowRules()[teleport.PresetAccessRoleName], + }, + }, + }, + }, + { + name: "auditor", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAuditorRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Options: types.RoleOptions{ + CertificateFormat: constants.CertificateFormatStandard, + MaxSessionTTL: types.NewDuration(apidefaults.MaxCertDuration), + RecordSession: &types.RecordSession{ + Desktop: types.NewBoolOption(false), + }, + }, + }, + }, + expectedErr: require.NoError, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAuditorRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Options: types.RoleOptions{ + CertificateFormat: constants.CertificateFormatStandard, + MaxSessionTTL: types.NewDuration(apidefaults.MaxCertDuration), + RecordSession: &types.RecordSession{ + Desktop: types.NewBoolOption(false), + }, + }, + Allow: types.RoleConditions{ + Rules: defaultAllowRules()[teleport.PresetAuditorRoleName], + }, + }, + }, + }, + { + name: "auditor (only missing label)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAuditorRoleName, + }, + Spec: types.RoleSpecV6{ + Options: types.RoleOptions{ + CertificateFormat: constants.CertificateFormatStandard, + MaxSessionTTL: types.NewDuration(apidefaults.MaxCertDuration), + RecordSession: &types.RecordSession{ + Desktop: types.NewBoolOption(false), + }, + }, + Allow: types.RoleConditions{ + Rules: defaultAllowRules()[teleport.PresetAuditorRoleName], + }, + }, + }, + expectedErr: require.NoError, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetAuditorRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Options: types.RoleOptions{ + CertificateFormat: constants.CertificateFormatStandard, + MaxSessionTTL: types.NewDuration(apidefaults.MaxCertDuration), + RecordSession: &types.RecordSession{ + Desktop: types.NewBoolOption(false), + }, + }, + Allow: types.RoleConditions{ + Rules: defaultAllowRules()[teleport.PresetAuditorRoleName], + }, + }, + }, + }, + { + name: "reviewer (not enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetReviewerRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + }, + expectedErr: noChange, + expected: nil, + }, + { + name: "reviewer (enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetReviewerRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + }, + enterprise: true, + expectedErr: require.NoError, + reviewNotEmpty: true, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetReviewerRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + ReviewRequests: defaultAllowAccessReviewConditions(true)[teleport.PresetReviewerRoleName], + }, + }, + }, + }, + { + name: "reviewer (enterprise, created by user)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetReviewerRoleName, + }, + }, + enterprise: true, + expectedErr: notModifying, + expected: nil, + }, + { + name: "reviewer (enterprise, existing review requests)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetReviewerRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + ReviewRequests: &types.AccessReviewConditions{ + Roles: []string{"some-role"}, + }, + }, + }, + }, + enterprise: true, + expectedErr: noChange, + }, + { + name: "requester (not enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + }, + expectedErr: noChange, + expected: nil, + }, + { + name: "requester (enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + }, + enterprise: true, + expectedErr: require.NoError, + accessRequestsNotEmpty: true, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Request: defaultAllowAccessRequestConditions(true)[teleport.PresetRequesterRoleName], + }, + }, + }, + }, + { + name: "requester (enterprise, created by user)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetRequesterRoleName, + }, + }, + enterprise: true, + expectedErr: notModifying, + expected: nil, + }, + { + name: "requester (enterprise, existing requests)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.PresetRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.PresetResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Request: &types.AccessRequestConditions{ + Roles: []string{"some-role"}, + }, + }, + }, + }, + enterprise: true, + expectedErr: noChange, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.enterprise { + modules.SetTestModules(t, &modules.TestModules{ + TestBuildType: modules.BuildEnterprise, + }) + } + + role, err := AddRoleDefaults(test.role) + test.expectedErr(t, err) + + require.Empty(t, cmp.Diff(role, test.expected)) + + if test.expected != nil { + require.Equal(t, test.reviewNotEmpty, !role.GetAccessReviewConditions(types.Allow).IsEmpty()) + require.Equal(t, test.accessRequestsNotEmpty, !role.GetAccessRequestConditions(types.Allow).IsEmpty()) + } + }) + } +}