diff --git a/docs/config.json b/docs/config.json index 123d39ccd71..4e5d5e7b30f 100644 --- a/docs/config.json +++ b/docs/config.json @@ -1181,6 +1181,10 @@ { "title": "Architecture", "slug": "/api/architecture/" + }, + { + "title": "How to Build an Access Request Plugin", + "slug": "/api/access-plugin/" } ] }, diff --git a/docs/img/api/google-sheets.png b/docs/img/api/google-sheets.png new file mode 100644 index 00000000000..7b36b4e086b Binary files /dev/null and b/docs/img/api/google-sheets.png differ diff --git a/docs/pages/api/access-plugin.mdx b/docs/pages/api/access-plugin.mdx new file mode 100644 index 00000000000..45aec2731ac --- /dev/null +++ b/docs/pages/api/access-plugin.mdx @@ -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) + + + +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. + + + +## 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. + + + +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. + + + +## 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. + + + +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` + + + +## 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. + + + +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. + + + +## 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. + diff --git a/docs/pages/api/introduction.mdx b/docs/pages/api/introduction.mdx index 5e412048687..a4e06c4da00 100644 --- a/docs/pages/api/introduction.mdx +++ b/docs/pages/api/introduction.mdx @@ -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. diff --git a/docs/pages/includes/plugins/editor-request-rbac.mdx b/docs/pages/includes/plugins/editor-request-rbac.mdx index 00266568823..a4ee41b8de9 100644 --- a/docs/pages/includes/plugins/editor-request-rbac.mdx +++ b/docs/pages/includes/plugins/editor-request-rbac.mdx @@ -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: