Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/auth/machineid/machineidv1/bot_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,10 @@ func (bs *BotService) UpdateBot(
opts := role.GetOptions()
opts.MaxSessionTTL = types.Duration(req.Bot.Spec.MaxSessionTtl.AsDuration())
role.SetOptions(opts)
case "metadata.description":
meta := user.GetMetadata()
meta.Description = req.Bot.Metadata.Description
user.SetMetadata(meta)
default:
return nil, trace.BadParameter("update_mask: unsupported path %q", path)
}
Expand Down
34 changes: 22 additions & 12 deletions lib/auth/machineid/machineidv1/machineidv1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "pre-existing",
Name: "pre-existing",
Description: "before",
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{beforeRole.GetName()},
Expand Down Expand Up @@ -708,7 +709,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: preExistingBot.Metadata.Name,
Name: preExistingBot.Metadata.Name,
Description: "after",
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{afterRole.GetName()},
Expand All @@ -728,7 +730,7 @@ func TestUpdateBot(t *testing.T) {
},
},
UpdateMask: &fieldmaskpb.FieldMask{
Paths: []string{"spec.roles", "spec.traits", "spec.max_session_ttl"},
Paths: []string{"spec.roles", "spec.traits", "spec.max_session_ttl", "metadata.description"},
},
},

Expand All @@ -737,7 +739,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: preExistingBot.Metadata.Name,
Name: preExistingBot.Metadata.Name,
Description: "after",
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{afterRole.GetName()},
Expand All @@ -764,8 +767,9 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindUser,
Version: types.V2,
Metadata: types.Metadata{
Name: preExistingBot.Status.UserName,
Namespace: defaults.Namespace,
Name: preExistingBot.Status.UserName,
Description: "after",
Namespace: defaults.Namespace,
Labels: map[string]string{
types.BotLabel: preExistingBot.Metadata.Name,
types.BotGenerationLabel: "1337",
Expand Down Expand Up @@ -821,7 +825,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "valid-bot",
Name: "valid-bot",
Description: preExistingBot.Metadata.Description,
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{beforeRole.GetName()},
Expand Down Expand Up @@ -854,7 +859,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "bernard-lowe",
Name: "bernard-lowe",
Description: "before",
},
Spec: nil,
},
Expand Down Expand Up @@ -895,7 +901,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "",
Name: "",
Description: preExistingBot.Metadata.Description,
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{beforeRole.GetName()},
Expand All @@ -918,7 +925,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "foo",
Name: "foo",
Description: "before",
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{beforeRole.GetName()},
Expand All @@ -939,7 +947,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: "foo",
Name: "foo",
Description: preExistingBot.Metadata.Description,
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{beforeRole.GetName()},
Expand All @@ -962,7 +971,8 @@ func TestUpdateBot(t *testing.T) {
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: preExistingBot.Metadata.Name,
Name: preExistingBot.Metadata.Name,
Description: preExistingBot.Metadata.Description,
},
Spec: &machineidv1pb.BotSpec{
Roles: []string{"foo", "", "bar"},
Expand Down
6 changes: 5 additions & 1 deletion lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1156,9 +1156,13 @@ func (h *Handler) bindDefaultEndpoints() {
// PUT Machine ID bot by name
// TODO(nicholasmarais1158) DELETE IN v20.0.0
// Replaced by `PUT /v2/webapi/sites/:site/machine-id/bot/:name` which allows editing more than just roles.
h.PUT("/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.updateBot))
h.PUT("/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.updateBotV1))
// PUT Machine ID bot by name
// TODO(nicholasmarais1158) DELETE IN v20.0.0
// Replaced by `PUT /v3/webapi/sites/:site/machine-id/bot/:name` which allows editing description.
h.PUT("/v2/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.updateBotV2))
// PUT Machine ID bot by name
h.PUT("/v3/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.updateBotV3))
// Delete Machine ID bot
h.DELETE("/webapi/sites/:site/machine-id/bot/:name", h.WithClusterAuth(h.deleteBot))

Expand Down
121 changes: 64 additions & 57 deletions lib/web/machineid.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package web

import (
"context"
"net/http"
"strconv"
"strings"
Expand Down Expand Up @@ -223,73 +224,86 @@ func (h *Handler) getBot(w http.ResponseWriter, r *http.Request, p httprouter.Pa
// updateBot updates a bot with provided roles. The only supported change via this endpoint today is roles.
// TODO(nicholasmarais1158) DELETE IN v20.0.0 - replaced by updateBotV2
// MUST delete with related code found in `web/packages/teleport/src/services/bot/bot.ts`
func (h *Handler) updateBot(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, cluster reversetunnelclient.Cluster) (any, error) {
var request updateBotRequest
func (h *Handler) updateBotV1(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, cluster reversetunnelclient.Cluster) (any, error) {
var request updateBotRequestV1
if err := httplib.ReadResourceJSON(r, &request); err != nil {
return nil, trace.Wrap(err)
}

botName := p.ByName("name")
if botName == "" {
return nil, trace.BadParameter("empty name")
}
return updateBot(r.Context(), p.ByName("name"), updateBotRequestV3{
Roles: request.Roles,
}, sctx, cluster)
}

clt, err := sctx.GetUserClient(r.Context(), cluster)
if err != nil {
return nil, trace.Wrap(err)
}
type updateBotRequestV1 struct {
Roles []string `json:"roles"`
}

mask, err := fieldmaskpb.New(&machineidv1.Bot{}, "spec.roles")
if err != nil {
// updateBotV2 updates a bot with provided roles, traits and max_session_ttl.
// TODO(nicholasmarais1158) DELETE IN v20.0.0 - replaced by updateBotV3
// MUST delete with related code found in `web/packages/teleport/src/services/bot/bot.ts`
func (h *Handler) updateBotV2(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, cluster reversetunnelclient.Cluster) (any, error) {
var request updateBotRequestV2
if err := httplib.ReadResourceJSON(r, &request); err != nil {
return nil, trace.Wrap(err)
}

updated, err := clt.BotServiceClient().UpdateBot(r.Context(), &machineidv1.UpdateBotRequest{
UpdateMask: mask,
Bot: &machineidv1.Bot{
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: botName,
},
Spec: &machineidv1.BotSpec{
Roles: request.Roles,
},
},
})
if err != nil {
return nil, trace.Wrap(err, "unable to find existing bot")
}
return updateBot(r.Context(), p.ByName("name"), updateBotRequestV3{
Roles: request.Roles,
Traits: request.Traits,
MaxSessionTtl: request.MaxSessionTtl,
}, sctx, cluster)
}

return updated, nil
type updateBotRequestV2 struct {
Roles []string `json:"roles"`
Traits []updateBotRequestTrait `json:"traits"`
MaxSessionTtl string `json:"max_session_ttl"`
}

type updateBotRequest struct {
Roles []string `json:"roles"`
type updateBotRequestTrait struct {
Name string `json:"name"`
Values []string `json:"values"`
}

// updateBotV2 updates a bot with provided roles, traits and max_session_ttl.
func (h *Handler) updateBotV2(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, cluster reversetunnelclient.Cluster) (any, error) {
var request updateBotRequestV2
// updateBot updates a bot with provided roles, traits, max_session_ttl and
// description.
func (h *Handler) updateBotV3(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, cluster reversetunnelclient.Cluster) (any, error) {
Comment thread
nicholasmarais1158 marked this conversation as resolved.
var request updateBotRequestV3
if err := httplib.ReadResourceJSON(r, &request); err != nil {
return nil, trace.Wrap(err)
}

botName := p.ByName("name")
if botName == "" {
return nil, trace.BadParameter("empty name")
}
return updateBot(r.Context(), p.ByName("name"), request, sctx, cluster)
}

clt, err := sctx.GetUserClient(r.Context(), cluster)
type updateBotRequestV3 struct {
Roles []string `json:"roles"`
Traits []updateBotRequestTrait `json:"traits"`
MaxSessionTtl string `json:"max_session_ttl"`
Description *string `json:"description"`
}

// updateBot updates a bot with provided roles, traits, max_session_ttl and
// description.
func updateBot(ctx context.Context, botName string, request updateBotRequestV3, sctx *SessionContext, cluster reversetunnelclient.Cluster) (any, error) {
clt, err := sctx.GetUserClient(ctx, cluster)
if err != nil {
return nil, trace.Wrap(err)
}

if botName == "" {
return nil, trace.BadParameter("empty name")
}

mask, err := fieldmaskpb.New(&machineidv1.Bot{})
if err != nil {
return nil, trace.Wrap(err)
}

metadata := headerv1.Metadata{
Name: botName,
}
spec := machineidv1.BotSpec{}

if request.Roles != nil {
Expand Down Expand Up @@ -323,15 +337,19 @@ func (h *Handler) updateBotV2(w http.ResponseWriter, r *http.Request, p httprout
spec.MaxSessionTtl = durationpb.New(ttl)
}

updated, err := clt.BotServiceClient().UpdateBot(r.Context(), &machineidv1.UpdateBotRequest{
if request.Description != nil {
mask.Append(&machineidv1.Bot{}, "metadata.description")

metadata.Description = *request.Description
}

updated, err := clt.BotServiceClient().UpdateBot(ctx, &machineidv1.UpdateBotRequest{
UpdateMask: mask,
Bot: &machineidv1.Bot{
Kind: types.KindBot,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: botName,
},
Spec: &spec,
Kind: types.KindBot,
Version: types.V1,
Metadata: &metadata,
Spec: &spec,
},
})
if err != nil {
Expand All @@ -341,17 +359,6 @@ func (h *Handler) updateBotV2(w http.ResponseWriter, r *http.Request, p httprout
return updated, nil
}

type updateBotRequestV2 struct {
Roles []string `json:"roles"`
Traits []updateBotRequestTrait `json:"traits"`
MaxSessionTtl string `json:"max_session_ttl"`
}

type updateBotRequestTrait struct {
Name string `json:"name"`
Values []string `json:"values"`
}

// getBotInstance retrieves a bot instance by id
func (h *Handler) getBotInstance(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, cluster reversetunnelclient.Cluster) (any, error) {
botName := p.ByName("name")
Expand Down
67 changes: 66 additions & 1 deletion lib/web/machineid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func TestEditBot(t *testing.T) {
})
require.NoError(t, err)

response, err := pack.clt.PutJSON(ctx, fmt.Sprintf("%s/%s", endpoint, botName), updateBotRequest{
response, err := pack.clt.PutJSON(ctx, fmt.Sprintf("%s/%s", endpoint, botName), updateBotRequestV1{
Roles: []string{"new-new-role"},
})
require.NoError(t, err)
Expand Down Expand Up @@ -545,6 +545,71 @@ func TestEditBotMaxSessionTTL(t *testing.T) {
assert.Equal(t, int64((1*time.Hour+2*time.Minute+3*time.Second)/time.Second), updatedBot.GetSpec().GetMaxSessionTtl().GetSeconds())
}

func TestEditBotDescription(t *testing.T) {
ctx := t.Context()
env := newWebPack(t, 1)
proxy := env.proxies[0]
pack := proxy.authPack(t, "admin", []types.Role{services.NewPresetEditorRole()})
clusterName := env.server.ClusterName()
endpointV1 := pack.clt.Endpoint(
"v1",
"webapi",
"sites",
clusterName,
"machine-id",
"bot",
)
endpointV3 := pack.clt.Endpoint(
"v3",
"webapi",
"sites",
clusterName,
"machine-id",
"bot",
)

// create a bot named `test-bot-edit`
botName := "test-bot-edit"
_, err := pack.clt.PostJSON(ctx, endpointV1, CreateBotRequest{
BotName: botName,
Roles: []string{"test-role"},
Traits: []*machineidv1.Trait{
{
Name: "test-trait-1",
Values: []string{"value-1"},
},
},
})
require.NoError(t, err)

response, err := pack.clt.Get(ctx, fmt.Sprintf("%s/%s", endpointV1, botName), nil)
require.NoError(t, err)

var createdBot machineidv1.Bot
require.NoError(t, json.Unmarshal(response.Bytes(), &createdBot), "invalid response received")
assert.Equal(t, int64(43200), createdBot.GetSpec().GetMaxSessionTtl().GetSeconds())

description := "This is the bot's description."
response, err = pack.clt.PutJSON(ctx, fmt.Sprintf("%s/%s", endpointV3, botName), updateBotRequestV3{
Description: &description,
})
require.NoError(t, err)

var updatedBot machineidv1.Bot
require.NoError(t, json.Unmarshal(response.Bytes(), &updatedBot), "invalid response received")
assert.Equal(t, http.StatusOK, response.Code(), "unexpected status code updating bot")
assert.Equal(t, botName, updatedBot.GetMetadata().GetName())
assert.Equal(t, []string{"test-role"}, updatedBot.GetSpec().GetRoles())
assert.Equal(t, []*machineidv1.Trait{
{
Name: "test-trait-1",
Values: []string{"value-1"},
},
}, updatedBot.GetSpec().Traits)
assert.Equal(t, int64((12*time.Hour)/time.Second), updatedBot.GetSpec().GetMaxSessionTtl().GetSeconds())
assert.Equal(t, description, updatedBot.GetMetadata().GetDescription())
}

func TestListBotInstances(t *testing.T) {
t.Parallel()

Expand Down
Loading
Loading