Skip to content

Commit

Permalink
[v15] bot list and create UI (#38122)
Browse files Browse the repository at this point in the history
* Add endpoint to get bots by name (#37412)

* Add endpoint to get bots by name

* Lint

* Fix test method's name

Co-authored-by: Noah Stride <noah.stride@goteleport.com>

---------

Co-authored-by: Noah Stride <noah.stride@goteleport.com>

* Backend supporting changes for Bot creation flow (#37348)

* Add bot ui labels and github join token

* Use strings.HasPrefix instead of includes

* Add `ssh` to label

* Add bot join token endpoint

* Lint

* Fix comment typo

Co-authored-by: Bartosz Leper <bartosz.leper@goteleport.com>

* Fix comment typo

Co-authored-by: Bartosz Leper <bartosz.leper@goteleport.com>

* Improve webUIFlowLabelKey comment

---------

Co-authored-by: Bartosz Leper <bartosz.leper@goteleport.com>

* add bots UI, disabled (#36845)

- in original data table view

* Add Bot + GitHub Actions SSH UI Flow (#37443)

* add bots UI, disabled

- in original data table view

* Add bot creation UI

* Remove duplicated types

* add bots UI, disabled

- in original data table view

* Fix types and tests

* Use bot join token api endpoint

* Fix tests

* Linting and small fixes

* Fix tests

* Add missing licenses

* Improve styles, error messages, etc

* Remove clusterId from bot routes

* Undo enabling feature

* Remove unused join role bot

* Rename var

* Reuse makeListBot

* Add missing type

* Revert MachineIDIntegrationSection for now

* Lint

* Apply suggestions from code review - fix typos

Co-authored-by: Noah Stride <noah.stride@goteleport.com>

* Remove kubernetes section fromm example yaml

* Remove border color from reftype selector

* Small changes to address code review

* Use setAttempt

* add try/catch block when parsing repo addresses

* Improve tests;remove unecessary fragment

* Use gap in flex. Fix typo

* Lint fix

* Drop "ex" from input placeholders

* Add stories for no perm and bot picker

* Add copy to explain wrkflow name limits

* fix setCurentStep

* Fix invalid host error rendering

* Use PascalCase for error components

* Improve field name validation

* Remove unecessary comments

* Update copy and minor style change

---------

Co-authored-by: Michelle Bergquist <michelle.bergquist@goteleport.com>
Co-authored-by: Noah Stride <noah.stride@goteleport.com>

* add bot edit-role flow (#37828)

* add bot edit-role flow

* only send masked/updated fields

* Add GitHub Actions bot view (#37852)

* add bots UI, disabled

- in original data table view

* Add bot creation UI

* Remove duplicated types

* add bots UI, disabled

- in original data table view

* Fix types and tests

* Use bot join token api endpoint

* Fix tests

* Linting and small fixes

* Fix tests

* Add missing licenses

* Improve styles, error messages, etc

* Remove clusterId from bot routes

* Undo enabling feature

* Remove unused join role bot

* Rename var

* Reuse makeListBot

* Add missing type

* Revert MachineIDIntegrationSection for now

* Lint

* Apply suggestions from code review - fix typos

Co-authored-by: Noah Stride <noah.stride@goteleport.com>

* Remove kubernetes section fromm example yaml

* Remove border color from reftype selector

* Small changes to address code review

* Use setAttempt

* add try/catch block when parsing repo addresses

* Improve tests;remove unecessary fragment

* Use gap in flex. Fix typo

* Lint fix

* Drop "ex" from input placeholders

* Add stories for no perm and bot picker

* Add copy to explain wrkflow name limits

* fix setCurentStep

* Fix invalid host error rendering

* Use PascalCase for error components

* Improve field name validation

* Remove unecessary comments

* Add bot type by label

* Add bot view...

* Show view gh actions yaml only for gh bots

* Use existing pattern for operations

* Add story and missing license

* Fix typos

Co-authored-by: Michelle Bergquist <11967646+michellescripts@users.noreply.github.com>

---------

Co-authored-by: Michelle Bergquist <michelle.bergquist@goteleport.com>
Co-authored-by: Noah Stride <noah.stride@goteleport.com>
Co-authored-by: Michelle Bergquist <11967646+michellescripts@users.noreply.github.com>

* Add buttons to download and copy to clipboard content to `TextEditor` (#37333)

* Add editor buttons

* Set icon size

* Move downloadObject to OSS

* remove unused test-id

* Use values from theme

* Fix buttons positioning

* Convert jsx files to tsx

* Use const instead of var

* Add z-index to buttons

* Add license

* Improvements to Bot resource (additional validation and label propagation) (#38013)

* PRevent creating bots with an empty string role

* Propagate labels to Bot user and vice versa

* Extract slice declaration for nonPropagatedLabels

* Fixed web tests relying on empty string roles

* Use "set" instead of slice for nonPropagatedLabels

* Make testing of empty string handling more thorough

* add role assertions to create

* Appease linter as to want/got order

---------

Co-authored-by: Michelle Bergquist <michelle.bergquist@goteleport.com>

* Add bot permissions and turn on feature (#37943)

* Fix makeBot labels (#38176)

---------

Co-authored-by: matheus <matheus.battirola@goteleport.com>
Co-authored-by: Noah Stride <noah.stride@goteleport.com>
Co-authored-by: Bartosz Leper <bartosz.leper@goteleport.com>
  • Loading branch information
4 people committed Feb 16, 2024
1 parent c579532 commit bd72997
Show file tree
Hide file tree
Showing 66 changed files with 5,439 additions and 13 deletions.
6 changes: 6 additions & 0 deletions api/types/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ type ProvisionToken interface {
GetRoles() SystemRoles
// SetRoles sets teleport roles
SetRoles(SystemRoles)
// SetLabels sets the tokens labels
SetLabels(map[string]string)
// GetAllowRules returns the list of allow rules
GetAllowRules() []*TokenRule
// SetAllowRules sets the allow rules
Expand Down Expand Up @@ -351,6 +353,10 @@ func (p *ProvisionTokenV2) SetRoles(r SystemRoles) {
p.Spec.Roles = r
}

func (p *ProvisionTokenV2) SetLabels(l map[string]string) {
p.Metadata.Labels = l
}

// GetAllowRules returns the list of allow rules
func (p *ProvisionTokenV2) GetAllowRules() []*TokenRule {
return p.Spec.Allow
Expand Down
47 changes: 42 additions & 5 deletions lib/auth/machineid/machineidv1/bot_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package machineidv1
import (
"context"
"fmt"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -464,6 +465,11 @@ func (bs *BotService) UpdateBot(
for _, path := range req.UpdateMask.Paths {
switch {
case path == "spec.roles":
if slices.Contains(req.Bot.Spec.Roles, "") {
return nil, trace.BadParameter(
"spec.roles: must not contain empty strings",
)
}
role.SetImpersonateConditions(types.Allow, types.ImpersonateConditions{
Roles: req.Bot.Spec.Roles,
})
Expand Down Expand Up @@ -623,9 +629,21 @@ func validateBot(b *pb.Bot) error {
if b.Spec == nil {
return trace.BadParameter("spec: must be non-nil")
}
if slices.Contains(b.Spec.Roles, "") {
return trace.BadParameter("spec.roles: must not contain empty strings")
}
return nil
}

// nonPropagatedLabels are labels that are not propagated from the User to the
// Bot when converting a User and Role to a Bot. Typically, these are internal
// labels that are managed by this service and exposing them to the end user
// would allow for misconfiguration.
var nonPropagatedLabels = map[string]struct{}{
types.BotLabel: {},
types.BotGenerationLabel: {},
}

// botFromUserAndRole
//
// Typically, we treat the bot user as the "canonical" source of information
Expand Down Expand Up @@ -653,6 +671,18 @@ func botFromUserAndRole(user types.User, role types.Role) (*pb.Bot, error) {
},
}

// Copy in labels from the user
b.Metadata.Labels = map[string]string{}
for k, v := range user.GetMetadata().Labels {
// We exclude the labels that are implicitly added to the user by the
// bot service.
if _, ok := nonPropagatedLabels[k]; ok {
continue
}
b.Metadata.Labels[k] = v
}

// Copy in traits
for k, v := range user.GetTraits() {
if len(v) == 0 {
continue
Expand Down Expand Up @@ -702,12 +732,19 @@ func botToUserAndRole(bot *pb.Bot, now time.Time, createdBy string) (types.User,
}
user.SetRoles([]string{resourceName})
userMeta := user.GetMetadata()
userMeta.Labels = map[string]string{
types.BotLabel: bot.Metadata.Name,
// We always set this to zero here - but in Upsert, we copy from the
// previous user before writing if necessary.
types.BotGenerationLabel: "0",

// First copy in the labels from the Bot resource
userMeta.Labels = map[string]string{}
for k, v := range bot.Metadata.Labels {
userMeta.Labels[k] = v
}
// Then set these labels over the top - we exclude these when converting
// back.
userMeta.Labels[types.BotLabel] = bot.Metadata.Name
// We always set this to zero here - but in Upsert, we copy from the
// previous user before writing if necessary
userMeta.Labels[types.BotGenerationLabel] = "0"

user.SetMetadata(userMeta)

traits := map[string][]string{}
Expand Down
94 changes: 94 additions & 0 deletions lib/auth/machineid/machineidv1/machineidv1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ func TestCreateBot(t *testing.T) {
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: "success",
Labels: map[string]string{
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{testRole.GetName()},
Expand All @@ -158,6 +162,10 @@ func TestCreateBot(t *testing.T) {
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "success",
Labels: map[string]string{
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{testRole.GetName()},
Expand All @@ -182,6 +190,8 @@ func TestCreateBot(t *testing.T) {
Labels: map[string]string{
types.BotLabel: "success",
types.BotGenerationLabel: "0",
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: types.UserSpecV2{
Expand Down Expand Up @@ -344,6 +354,25 @@ func TestCreateBot(t *testing.T) {
require.True(t, trace.IsBadParameter(err), "error should be bad parameter")
},
},
{
name: "validation - empty role",
user: botCreator.GetName(),
req: &machineidv1pb.CreateBotRequest{
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: "empty-string-role",
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{"foo", "", "bar"},
Traits: []*machineidv1pb.Trait{},
},
},
},
assertError: func(t require.TestingT, err error, i ...interface{}) {
require.ErrorContains(t, err, "spec.roles: must not contain empty strings")
require.True(t, trace.IsBadParameter(err), "error should be bad parameter")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -672,6 +701,28 @@ func TestUpdateBot(t *testing.T) {
require.True(t, trace.IsBadParameter(err), "error should be bad parameter")
},
},
{
name: "validation - empty string role",
user: botUpdaterUser.GetName(),
req: &machineidv1pb.UpdateBotRequest{
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: preExistingBot.Metadata.Name,
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{"foo", "", "bar"},
Traits: []*machineidv1pb.Trait{},
},
},
UpdateMask: &fieldmaskpb.FieldMask{
Paths: []string{"spec.roles"},
},
},
assertError: func(t require.TestingT, err error, i ...interface{}) {
require.ErrorContains(t, err, "spec.roles: must not contain empty strings")
require.True(t, trace.IsBadParameter(err), "error should be bad parameter")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -746,6 +797,10 @@ func TestUpsertBot(t *testing.T) {
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: "pre-existing",
Labels: map[string]string{
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{testRole.GetName()},
Expand Down Expand Up @@ -783,6 +838,10 @@ func TestUpsertBot(t *testing.T) {
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: "new",
Labels: map[string]string{
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{testRole.GetName()},
Expand All @@ -802,6 +861,10 @@ func TestUpsertBot(t *testing.T) {
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "new",
Labels: map[string]string{
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{testRole.GetName()},
Expand All @@ -826,6 +889,8 @@ func TestUpsertBot(t *testing.T) {
Labels: map[string]string{
types.BotLabel: "new",
types.BotGenerationLabel: "0",
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: types.UserSpecV2{
Expand Down Expand Up @@ -882,6 +947,8 @@ func TestUpsertBot(t *testing.T) {
Labels: map[string]string{
types.BotLabel: "pre-existing",
types.BotGenerationLabel: "1337",
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: types.UserSpecV2{
Expand Down Expand Up @@ -973,6 +1040,25 @@ func TestUpsertBot(t *testing.T) {
require.True(t, trace.IsBadParameter(err), "error should be bad parameter")
},
},
{
name: "validation - empty role",
user: botCreator.GetName(),
req: &machineidv1pb.UpsertBotRequest{
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: "empty-string-role",
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{"foo", "", "bar"},
Traits: []*machineidv1pb.Trait{},
},
},
},
assertError: func(t require.TestingT, err error, i ...interface{}) {
require.ErrorContains(t, err, "spec.roles: must not contain empty strings")
require.True(t, trace.IsBadParameter(err), "error should be bad parameter")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -1045,6 +1131,10 @@ func TestGetBot(t *testing.T) {
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: "pre-existing",
Labels: map[string]string{
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{testRole.GetName()},
Expand Down Expand Up @@ -1152,6 +1242,10 @@ func TestListBots(t *testing.T) {
Bot: &machineidv1pb.Bot{
Metadata: &headerv1.Metadata{
Name: "pre-existing",
Labels: map[string]string{
"my-label": "my-value",
"my-other-label": "my-other-value",
},
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{testRole.GetName()},
Expand Down
17 changes: 17 additions & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ const (
// assistantLimiterCapacity is the total capacity of the token bucket for the assistant rate limiter.
// The bucket starts full, prefilled for a week.
assistantLimiterCapacity = assistantTokensPerHour * 24 * 7
// webUIFlowLabelKey is a label that may be added to resources
// created via the web UI, indicating which flow the resource was created on.
// This label is used for enhancing UX in the web app, by showing icons related,
// to the workflow it was added, or providing unique features to those resources.
// Example values:
// - github-actions-ssh: indicates that the resource was added via the Bot GitHub Actions SSH flow
webUIFlowLabelKey = "teleport.internal/ui-flow"
)

// healthCheckAppServerFunc defines a function used to perform a health check
Expand Down Expand Up @@ -956,8 +963,18 @@ func (h *Handler) bindDefaultEndpoints() {
// Channel can contain "/", hence the use of a catch-all parameter
h.GET("/webapi/automaticupgrades/channel/*request", h.WithUnauthenticatedHighLimiter(h.automaticUpgrades))

// GET Machine ID bot by name
h.GET("/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.getBot))
// GET Machine ID bots
h.GET("/webapi/sites/:site/machine-id/bot", h.WithClusterAuth(h.listBots))
// Create Machine ID bots
h.POST("/webapi/sites/:site/machine-id/bot", h.WithClusterAuth(h.createBot))
// Create bot join tokens
h.POST("/webapi/sites/:site/machine-id/token", h.WithClusterAuth(h.createBotJoinToken))
// PUT Machine ID bot by name
h.PUT("/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.updateBot))
// Delete Machine ID bot
h.DELETE("/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.deleteBot))
}

// GetProxyClient returns authenticated auth server client
Expand Down

0 comments on commit bd72997

Please sign in to comment.