Add a guide to writing an Access Request plugin (#22441)

* Add a guide to writing an Access Request plugin

Closes #22227

This guide uses a minimal working example of an Access Request plugin to
demonstrate some of the key libraries that Teleport makes available to
plugin developers. The guide is based on a 270-line plugin that manages
Access Requests using Google Sheets.

* Respond to hugoShaka feedback

* Respond to zmb3 feedback

* Add some minor tweaks

- Update the line of code count in the demo program
- Mention Machine ID

* Respond to alexfornuto PR feedback
This commit is contained in:
Paul Gottschling 2023-03-14 13:35:05 -04:00 committed by GitHub
parent 3d60c64067
commit c553fa7d66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 776 additions and 25 deletions

View file

@ -1181,6 +1181,10 @@
{
"title": "Architecture",
"slug": "/api/architecture/"
},
{
"title": "How to Build an Access Request Plugin",
"slug": "/api/access-plugin/"
}
]
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

View file

@ -0,0 +1,767 @@
---
title: How to Build an Access Request Plugin
description: Manage Access Requests using custom workflows with the Teleport API
---
With Teleport [Access Requests](../access-controls/access-requests.mdx), you can
assign Teleport users to less privileged roles by default and allow them to
temporarily escalate their privileges. Reviewers can grant or deny Access
Requests within your organization's existing communication workflows (e.g.,
Slack, email, and PagerDuty) using [Access Request
plugins](../access-controls/access-request-plugins/index.mdx).
You can use Teleport's API client library to build an Access Request plugin that
integrates with your organization's unique workflows.
In this guide, we will explore a number of Teleport's API client libraries by
showing you how to write a plugin that lets you manage Access Requests via
Google Sheets. The plugin lists new Access Requests in a Google Sheets
spreadsheet, with links to allow or deny each request. You can write the plugin
in 260 lines of Go code.
![The result of the plugin](../../img/api/google-sheets.png)
<Notice type="danger">
The plugin we will build in this guide is intended as a learning tool. **Do not
connect it to your production Teleport cluster.** Use a demo cluster instead.
</Notice>
## Prerequisites
(!docs/pages/includes/commercial-prereqs-tabs.mdx!)
- Go version (=teleport.golang=)+ installed on your workstation. See the [Go
download page](https://go.dev/dl/). You do not need to be familiar with Go to
complete this guide, though Go knowledge is required if you want to build your
own Access Request plugin.
You will need the following in order to set up the demo plugin, which requires
authenticating to the Google Sheets API:
- A Google Cloud project with permissions to create service accounts.
- A Google account that you will use to create a Google Sheets spreadsheet. We
will grant permissions to edit the spreadsheet to the service account used for
the plugin.
<Admonition type="tip">
Even if you do not plan to set up the demo project, you can follow this guide to see
which libraries, types, and functions you can use to develop an Access Request
plugin.
The demo is a minimal working example, and you can see fully fledged plugins in
the
[`gravitational/teleport-plugins`](https://github.com/gravitational/teleport-plugins)
repository on GitHub.
</Admonition>
## Step 1/7. Set up your Go project
The first step is to create a project directory and initialize a Go module for
your project:
```code
$ mkdir teleport-sheets
$ cd teleport-sheets
$ go mod init teleport-sheets
go: creating new go.mod: module teleport-sheets
```
This creates a new file in your project directory, `go.mod`, which Go's package
management system uses to fetch your plugin's dependencies.
## Step 2/7. Set up the Google Sheets API
Access Request plugins typically communicate with two APIs. They receive Access
Request events from the Teleport Auth Service's gRPC API, and use the data to
interact with the API of your chosen messaging or collaboration tool.
In this section, we will enable the Google Sheets API, create a Google Cloud
service account for the plugin, and use the service account to authenticate the
plugin to Google Sheets.
### Enable the Google Sheets API
Enable the Google Sheets API by visiting the following Google Cloud console URL:
https://console.cloud.google.com/apis/enableflow?apiid=sheets.googleapis.com
Ensure that your Google Cloud project is the one you intend to use.
Click **Next** > **Enable**.
### Create a Google Cloud service account for the plugin
Visit the following Google Cloud console URL:
https://console.cloud.google.com/iam-admin/serviceaccounts
Click **Create Service Account**.
For **Service account name**, enter "Teleport Google Sheets Plugin". Google
Cloud will populate the **Service account ID** field for you.
Click **Create and Continue**. When prompted to grant roles to the service
account, click **Continue** again. We will create our service account without
roles. Skip the step to grant users access to the service account, clicking
**Done**.
The console will take you to the **Service accounts** view. Click the name of
the service account you just created, then click the **Keys** tab. Click **Add
Key**, then **Create new key**. Leave the **Key type** as "JSON" and click
**Create**.
Save your Google Cloud credentials file as `credentials.json` in your Go project
directory.
Your plugin will use this JSON file to authenticate to Google Sheets.
### Create a Google Sheets spreadsheet
Visit the following URL and make sure you are authenticated as the correct user:
https://sheets.new
Name your spreadsheet.
Give the plugin access to the spreadsheet by clicking **Share**. In the **Add
people and groups** field, enter
`teleport-google-sheets-plugin@PROJECT_NAME.iam.gserviceaccount.com`, replacing
`PROJECT_NAME` with the name of your project. Make sure that the service account
has "Editor" permissions. Click **Share**, then **Share anyway** when prompted
with a warning.
By authenticating to Google Sheets with the service account you created, the
plugin will have access to modify your spreadsheet.
Next, ensure that the following is true within your spreadsheet:
- There is only one sheet
- The sheet includes the following columns:
|ID|Created|User|Roles|Status|Link|
|---|---|---|---|---|---|
After we write our Access Request plugin, it will populate the spreadsheet with
data automatically.
## Step 3/7. Set up Teleport RBAC
In this section, we will set up Teleport roles that enable creating and
reviewing Access Requests, plus another Teleport role that can generate
credentials for your Access Request plugin to authenticate to Teleport.
### Create a user and role for the plugin
(!docs/pages/includes/plugins/rbac.mdx!)
### Export the access plugin identity
You will use the `tctl auth sign` command to request the credentials that the
`access-plugin` needs to connect to your Teleport cluster.
The following `tctl auth sign` command impersonates the `access-plugin` user,
generates signed credentials, and writes an identity file to the local
directory:
```code
$ tctl auth sign --user=access-plugin --out=auth.pem
```
Teleport's Access Request plugins listen for new and updated Access Requests by
connecting to the Teleport Auth Service's gRPC endpoint over TLS.
The identity file, `auth.pem`, includes both TLS and SSH credentials. Your
Access Request plugin uses the SSH credentials to connect to the Proxy Service,
which establishes a reverse tunnel connection to the Auth Service. The plugin
uses this reverse tunnel, along with your TLS credentials, to connect to the
Auth Service's gRPC endpoint.
You will refer to this file later when configuring the plugin.
### Set up Role Access Requests
In this guide, we will use our plugin to manage Role Access Requests. For this
to work, we will set up Role Access Requests in your cluster.
(!/docs/pages/includes/plugins/editor-request-rbac.mdx!)
## Step 4/7. Write the Access Request plugin
At this point, we have completed the preparatory steps required to run an Access
Request plugin, including setting up your Teleport roles to support Role Access
Requests and getting credentials for the APIs the plugin will authenticate to.
Next, we will write the Access Request plugin.
### Add imports
Create a file called `main.go` in your project directory with the following
content:
```go
package main
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/gravitational/teleport-plugins/lib"
"github.com/gravitational/teleport-plugins/lib/watcherjob"
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/trace"
"google.golang.org/api/option"
sheets "google.golang.org/api/sheets/v4"
"google.golang.org/grpc"
)
```
Here are the packages our Access Request plugin will import from Go's standard
library:
|Package|Description|
|---|---|
|`context`|Includes the `context.Context` type. `context.Context` is an abstraction for controlling long-running routines, such as connections to external services, that might fail or time out. Programs can cancel contexts or assign them timeouts and metadata. |
|`errors`|Working with errors.|
|`fmt`|Formatting data for printing, strings, or errors.|
|`time`|Dealing with time. We will use this to define a timeout for connecting to the Auth Service.|
|`strings`|Manipulating strings.|
The pugin imports the following third-party code:
|Package|Description|
|---|---|
|`github.com/gravitational/teleport-plugins/lib`|Code shared between Teleport plugins, e.g., to run a background job for retrieving new events from the Teleport Auth Service.|
|`github.com/gravitational/teleport-plugins/lib/watcherjob`|A library for writing programs that listen for audit events from the Auth Service.|
|`github.com/gravitational/teleport/api/client`|A library for authenticating to the Auth Service's gRPC API and making requests.|
|`github.com/gravitational/teleport/api/types`|Types used in the Auth Service API, e.g., Access Requests.|
|`github.com/gravitational/trace`|Presenting errors with more useful detail than the standard library provides.|
|`google.golang.org/api/option`|Settings for configuring Google API clients.|
|`google.golang.org/api/sheets/v4`|The Google Sheets API client library, aliased as `sheets` in our program.|
|`google.golang.org/grpc`|The gRPC client and server library.|
### Make some initial declarations
After the `import` block, add the following declarations:
```go
const (
proxyAddr string = ""
initTimeout = time.Duration(30) * time.Second
spreadSheetID string = ""
)
var requestStates = map[types.RequestState]string{
types.RequestState_APPROVED: "APPROVED",
types.RequestState_DENIED: "DENIED",
types.RequestState_PENDING: "PENDING",
types.RequestState_NONE: "NONE",
}
type googleSheetsPlugin struct {
sheetsClient *sheets.SpreadsheetsService
teleportClient *client.Client
}
```
`proxyAddr` indicates the hostname and port of your Teleport Proxy Service or
Teleport Enterprise Cloud tenant. Assign it to the address of your own Proxy
Service, e.g., `mytenant.teleport.sh:443`.
`initTimeout` is the maximum time we will wait to connect to the Auth Service,
in this case, 30 seconds.
Assign `spreadSheetID` to the ID of the spreadsheet you created earlier. To find
the spreadsheet ID, visit your spreadsheet in Google Drive. The ID will be in
the URL path segment called `SPREADSHEET_ID` below:
```text
https://docs.google.com/spreadsheets/d/SPREADHSEET_ID/edit#gid=0
```
Access Requests have one of four states: approved, denied, pending, and none.
Later in the guide, we will need a way to map these states to strings that we
can write to our Google Sheets spreadsheet. We obtain the request states from
Teleport's `types` library and map them to strings in the `requestStates` map.
`googleSheetsPlugin` is a struct type that contains the API clients for the
Teleport Auth Service and Google Sheets. We will explain later why we use a
struct to organize these two clients, instead of, say, two separate variables.
### Prepare row data
Whether creating a new row of the spreadsheet or updating an existing one, we
need a way to extract data from an Access Reqeust in order to provide it to
Google Sheets. Below the `googleSheetsPlugin` type declaration, add the
following functions:
```go
func stringPtr(s string) *string { return &s }
func (g *googleSheetsPlugin) makeRowData(ar types.AccessRequest) *sheets.RowData {
requestState, ok := requestStates[ar.GetState()]
// Could not find a state, but this is still a valid Access Request
if !ok {
requestState = requestStates[types.RequestState_NONE]
}
viewLink := fmt.Sprintf(
`=HYPERLINK("%v", "%v")`,
"https://"+proxyAddr+"/web/requests/"+ar.GetName(),
"View Access Request",
)
return &sheets.RowData{
Values: []*sheets.CellData{
&sheets.CellData{
UserEnteredValue: &sheets.ExtendedValue{
StringValue: stringPtr(ar.GetName()),
},
},
&sheets.CellData{
UserEnteredValue: &sheets.ExtendedValue{
StringValue: stringPtr(ar.GetCreationTime().String()),
},
},
&sheets.CellData{
UserEnteredValue: &sheets.ExtendedValue{
StringValue: stringPtr(ar.GetUser()),
},
},
&sheets.CellData{
UserEnteredValue: &sheets.ExtendedValue{
StringValue: stringPtr(strings.Join(ar.GetRoles(), ",")),
},
},
&sheets.CellData{
UserEnteredValue: &sheets.ExtendedValue{
StringValue: &requestState,
},
},
&sheets.CellData{
UserEnteredValue: &sheets.ExtendedValue{
FormulaValue: &viewLink,
},
},
},
}
}
```
The `sheets.RowData` type makes extensive use of pointers to strings, so we
introduce a utility function called `stringPtr` that returns the pointer to the
provided string. This makes it easier to assign the values of cells in the
`sheets.RowData` using chains of function calls.
`makeRowData` is a method of the `googleSheetsPlugin` type. (The `*` before
`googleSheetsPlugin` indicates that the method receives a *pointer* to a
`googleSheetsPlugin`.) It takes a `types.AccessRequest`, which Teleport's API
library uses to represent the fields within an Access Request.
The Google Sheets client library defines a `sheets.RowData` type that we
include in requests to update a spreadsheet. This function converts a
`types.AccessRequest` into a `*sheets.RowData` (another pointer).
When extracting the data, we use the `types.AccessRequest.GetName()` method to
retrieve the ID of the Access Request as a string we can include in the
spreadsheet.
Users can review an Access Request by visiting a URL within the Teleport Web UI
that corresponds to the request's ID. `makeRowData` assembles a `=HYPERLINK`
formula that we can insert into the spreadsheet as a link to this URL.
Next, we'll add two functions that call `makeRowData`, one to create a new row
and one to update an existing row.
### Create a row
Below `makeRowData`, add the following:
```go
func (g *googleSheetsPlugin) createRow(ar types.AccessRequest) error {
row := g.makeRowData(ar)
req := sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{
{
AppendCells: &sheets.AppendCellsRequest{
Fields: "*",
Rows: []*sheets.RowData{
row,
},
},
},
},
}
resp, err := g.sheetsClient.BatchUpdate(spreadSheetID, &req).Do()
if err != nil {
return trace.Wrap(err)
}
if resp.HTTPStatusCode == 201 || resp.HTTPStatusCode == 200 {
fmt.Println("Successfully created a row")
} else {
fmt.Printf(
"Unexpected response code creating a row: %v\n",
resp.HTTPStatusCode,
)
}
return nil
}
```
`createRow` submits a request to the Google Sheets API to create a new row based
on an incoming Access Request, using the data returned by `makeRowData`. It
returns an error if the attempt to create a row failed.
It assembles a `sheets.BatchUpdateSpreadsheetRequest` and sends it to the Google
Sheets API using `g.sheetsClient.BatchUpdate()`, returning errors encountered
while sending the request.
We log unexpected HTTP status codes without returning an error since these may
be transient server-side issues. A production Access Request plugin would handle
these situations in a more sophisticated way, e.g., storing the request so it
can retry it later.
### Update a row
The code for updating a row is similar to the code for creating a new row. Add
this function below `createRow`:
```go
func (g *googleSheetsPlugin) updateRow(ar types.AccessRequest, rowNum int64) error {
row := g.makeRowData(ar)
req := sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{
{
UpdateCells: &sheets.UpdateCellsRequest{
Fields: "*",
Start: &sheets.GridCoordinate{
RowIndex: rowNum,
},
Rows: []*sheets.RowData{
row,
},
},
},
},
}
resp, err := g.sheetsClient.BatchUpdate(spreadSheetID, &req).Do()
if err != nil {
return trace.Wrap(err)
}
if resp.HTTPStatusCode == 201 || resp.HTTPStatusCode == 200 {
fmt.Println("Successfully updated a row")
} else {
fmt.Printf(
"Unexpected response code updating a row: %v\n",
resp.HTTPStatusCode,
)
}
return nil
}
```
The only difference between `updateRow` and `createRow` is that we send a
`&sheets.UpdateCellsRequest` instead of a `&sheets.AppendCellsRequest`. This
function takes the number of a row within the spreadsheet to update and sends a
request to update that row with information from the provided Access Request.
### Determine where to update the spreadsheet
Below `updateRow`, add this function:
```go
func (g *googleSheetsPlugin) updateSpreadsheet(ar types.AccessRequest) error {
s, err := g.sheetsClient.Get(spreadSheetID).IncludeGridData(true).Do()
if err != nil {
return trace.Wrap(err)
}
if len(s.Sheets) != 1 {
return trace.Wrap(
errors.New("the spreadsheet must have a single sheet"),
)
}
for _, d := range s.Sheets[0].Data {
for i, r := range d.RowData {
if r.Values[0] != nil &&
r.Values[0].UserEnteredValue != nil &&
r.Values[0].UserEnteredValue.StringValue != nil &&
*r.Values[0].UserEnteredValue.StringValue == ar.GetName() {
if err := g.updateRow(ar, int64(i)); err != nil {
return trace.Wrap(err)
}
fmt.Println("Updated a spreadsheet row.")
}
}
}
return nil
}
```
`updateSpreadSheet` takes a `types.AccessRequest`, gets the latest data from
your spreadsheet, determines which row to update, and calls `updateRow`
accordingly. It uses linear search to look up the first column within each row
of the sheet and check whether that column matches the ID of the Access Request.
It then calls `updateRow` with the Access Request and the row's number.
### Handle incoming Access Requests
Next, we will determine whether to call `updateSpreadsheet` or `createRow`
depending on whether an incoming Access Request event is a new Access Request or
an update to an existing one. Add the following function below
`updateSpreadsheet`:
```go
func (g *googleSheetsPlugin) handleEvent(ctx context.Context, event types.Event) error {
if event.Resource == nil {
return nil
}
r := event.Resource.(types.AccessRequest)
if r.GetState() == types.RequestState_PENDING {
return g.createRow(r)
}
return g.updateSpreadsheet(r)
}
```
`handleEvent` checks whether an Access Request is in a pending state, i.e.,
whether the request is new. If so, we call `createRow`. If not, we call
`updateSpreadsheet`.
It is worth noting that the function takes any `types.Event`, which includes
Access Requests but also other audit events, such as the creation of a
certificate. We will explain in the next section why we are using this function
signature for `handleEvent`.
### Run a watcher job
Now that we have a function that can handle incoming Teleport events, we will
add a function that calls our handler function when it receives an event. Add
the following below `handleEvent`:
```go
func (g *googleSheetsPlugin) run() error {
ctx := context.Background()
proc := lib.NewProcess(ctx)
watcherJob := watcherjob.NewJob(
g.teleportClient,
watcherjob.Config{
Watch: types.Watch{Kinds: []types.WatchKind{types.WatchKind{Kind: types.KindAccessRequest}}},
},
g.handleEvent,
)
proc.SpawnCriticalJob(watcherJob)
fmt.Println("Started the watcher job")
<-watcherJob.Done()
fmt.Println("The watcher job is finished")
return nil
}
```
The `run` function starts a new background routine using `lib.NewProcess(ctx)`.
This returns an implementation of the `lib.Process` interface, which Access
Request plugins use to run jobs independently of the main program. This function
takes a Go *context*, an abstraction that Go libraries often use to control
long-running routines.
Next, `run` calls `watcherjob.NewJob`. A watcher job is an implementation of a
`lib.ServiceJob`, a task for the `lib.Process` to execute. In this case, the
`lib.ServiceJob` listens for new events from the Teleport Auth Service and
executes code in response.
In `watcherjob.NewJob`, we have configured the watcher job to use a Teleport
client (which we will create in the next section), as well as to watch only for
Access Request events.
Finally, by passing the `g.handleEvent` method to `watcherjob.NewJob`, we
instruct the watcher job to call `g.handleEvent` whenever it receives an Access
Request. The function we pass to `watcherjob.NewJob` must have the following
signature, which we implement in the `handleEvent` method:
```go
func (ctx context.Context, event types.Event) error
```
This is also why we define `handleEvent` as a method of `googleSheetsPlugin`,
since it means that we can include data about our API clients in this function
while adhering to the expected function signature (and without declaring more
global variables).
Finally, we begin the watcher job within our `lib.Process` using
`proc.SpawnCriticalJob`, and wait for the job to terminate. We do this by
receiving from a Go *channel*, which is a way for multiple concurrent routines
to communicate with one another.
### Initialize the API clients
Now we have all the code we need to use the Teleport and Google Sheets API
clients to listen for Access Request events and use them to maintain a
spreadsheet. The final step is to start our program by initializing the API
clients.
Add the following below the `run` function we defined in the last section:
```go
func main() {
ctx := context.Background()
svc, err := sheets.NewService(ctx, option.WithCredentialsFile("credentials.json"))
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(ctx, initTimeout)
defer cancel()
creds := client.LoadIdentityFile("auth.pem")
teleport, err := client.New(ctx, client.Config{
Addrs: []string{proxyAddr},
Credentials: []client.Credentials{creds},
DialOpts: []grpc.DialOption{
grpc.WithReturnConnectionError(),
},
})
if err != nil {
panic(err)
}
gs := googleSheetsPlugin{
sheetsClient: sheets.NewSpreadsheetsService(svc),
teleportClient: teleport,
}
if err := gs.run(); err != nil {
panic(err)
}
}
```
The `main` function, the entrypoint to our program, initializes a
`googleSheetsPlugin` and uses it run the plugin.
The function creates a Google Sheets API client by loading the credentials file
you downloaded earlier at the relative path `credentials.json`.
`client` is Teleport's library for setting up an API client. Our plugin does so
by calling `client.LoadIdentityFile` to obtain a `client.Credentials`. It then
uses the `client.Credentials` to call `client.New`, which connects to the
Teleport Proxy Service specified in the `Addrs` field using the provided
identity file.
In this example, we are passing the `grpc.WithReturnConnectionError()` function
call to `client.New`, which instructs the gRPC client to return more detailed
connection errors.
<Admonition type="warning">
This program does not validate your credentials or Teleport cluster address.
Make sure that:
- The identity file you exported earlier does not have an expired TTL
- The value you supplied for the `proxyAddr` constant includes both the host
**and** the web port of your Teleport Proxy Service, e.g.,
`mytenant.teleport.sh:443`
</Admonition>
## Step 7/7. Test your plugin
Now that you have written your plugin, run it to forward Access Requests from
your Teleport cluster to Google Sheets. Execute the following commands from
within your project directory:
```code
$ go get
$ go run main.go
```
Now that the plugin is running, create an Access Request:
(!docs/pages/includes/plugins/create-request.mdx!)
You should see the new Access Request in your spreadsheet with the `PENDING`
state.
In your spreadsheet, click "View Access Request" next to your new request. Sign
into the Teleport Web UI as your original user. When you submit your review,
e.g., deny the request, the new status will appear within the spreadsheet.
<Notice type="danger">
Access Request plugins must not enable reviewing Access Requests via the plugin,
and must always refer a reviewer to the Teleport Web UI to complete the review.
Otherwise, an unauthorized party could spoof traffic to the plugin and escalate
privileges.
</Notice>
## Next steps
In this guide, we showed you how to set up an Access Request plugin using
Teleport's API client libraries. To go beyond the minimal plugin we demonstrate
in this guide, you can use the Teleport API to set up more sophisticated
workflows that take full advantage of your communication and project management
tools.
### Use the common plugin base
The
[`common.BaseApp`](https://pkg.go.dev/github.com/gravitational/teleport-plugins/access/common#BaseApp)
type makes it easier to write an Access Request plugin based on a messaging
application like Discord or Slack, helping to ensure that your plugin implements
a full set of features.
### Manage state
While the plugin we developed in this guide is stateless, updating Access
Request information by searching all rows of a spreadsheet, real-world Access
Request plugins typically need to manage state. You can use the
[`plugindata`](https://pkg.go.dev/github.com/gravitational/teleport-plugins/lib/plugindata)
package to make it easier for your Access Request plugin to do this.
### Consult the examples
Explore the
[`gravitational/teleport-plugins`](https://github.com/gravitational/teleport-plugins)
repository on GitHub for examples of plugins developed at Teleport. You can see
how these plugins use the packages we discuss in this guide, as well as how they
add more complete functionality like configuraiton validation and state
management.
### Provision the plugin with short-lived credentials
In this example, we used the `tctl auth sign` command to fetch credentials for
the plugin. For production usage, we recommend provisioning short-lived
credentials via Machine ID, which reduces the risk of these credentials becoming
stolen. View our [Machine ID documentation](../machine-id/introduction.mdx) to
learn more.

View file

@ -22,3 +22,6 @@ Here is what you can do with the Go Client:
Create an API client in 3 minutes with the [Getting Started
Guide](./getting-started.mdx).
Follow our [Access Request Plugin Guide](./access-plugin.mdx) for a tour of
Teleport's API libraries and develop a minimal working example.

View file

@ -36,32 +36,9 @@ role 'editor-requester' has been created
```
Allow yourself to review requests by users with the `editor-requester` role by
assigning yourself the `editor-reviewer` role. First, retrieve your user
definition:
assigning yourself the `editor-reviewer` role.
```code
$ TELEPORT_USER=$(tsh status --format=json | jq -r .active.username)
$ tctl get user/${TELEPORT_USER?} > user.yaml
```
Edit `user.yaml` to add the `editor-reviewer` role:
```diff
spec:
roles:
- access
- editor
+ - editor-reviewer
```
Update your user definition:
```code
$ tctl create -f user.yaml
```
Log out of Teleport and log in again. You will now have the ability to review
requests for the `editor` role.
(!docs/pages/includes/add-role-to-user.mdx role="editor-reviewer"!)
Create a user called `myuser` who has the `editor-requester` role. This user
cannot edit your cluster configuration unless they request the `editor` role: