diff --git a/api/client/client.go b/api/client/client.go index 8e692aee2f681..8ffb7dcabc6bd 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -49,6 +49,8 @@ import ( "github.com/gravitational/teleport/api/breaker" "github.com/gravitational/teleport/api/client/accesslist" "github.com/gravitational/teleport/api/client/accessmonitoringrules" + "github.com/gravitational/teleport/api/client/crownjewel" + crownjewelapi "github.com/gravitational/teleport/api/client/crownjewel" "github.com/gravitational/teleport/api/client/discoveryconfig" "github.com/gravitational/teleport/api/client/externalauditstorage" kubewaitingcontainerclient "github.com/gravitational/teleport/api/client/kubewaitingcontainer" @@ -64,6 +66,7 @@ import ( accessmonitoringrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" @@ -4784,8 +4787,16 @@ func (c *Client) DiscoveryConfigClient() *discoveryconfig.Client { return discoveryconfig.NewClient(discoveryconfigv1.NewDiscoveryConfigServiceClient(c.conn)) } +// CrownJewelServiceClient returns a CrownJewel client. +// Clients connecting to older Teleport versions, still get a CrownJewel client +// when calling this method, but all RPCs will return "not implemented" errors +// (as per the default gRPC behavior). +func (c *Client) CrownJewelServiceClient() *crownjewelapi.Client { + return crownjewel.NewClient(crownjewelv1.NewCrownJewelServiceClient(c.conn)) +} + // UserLoginStateClient returns a user login state client. -// Clients connecting to older Teleport versions, still get a user login state client +// Clients connecting to older Teleport versions, still get a user login state client // when calling this method, but all RPCs will return "not implemented" errors // (as per the default gRPC behavior). func (c *Client) UserLoginStateClient() *userloginstate.Client { diff --git a/api/client/crownjewel/crownjewel.go b/api/client/crownjewel/crownjewel.go new file mode 100644 index 0000000000000..710b5f1a66936 --- /dev/null +++ b/api/client/crownjewel/crownjewel.go @@ -0,0 +1,106 @@ +// Copyright 2024 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 crownjewel + +import ( + "context" + + "github.com/gravitational/trace" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" +) + +// Client is a client for the Crown Jewel API. +type Client struct { + grpcClient crownjewelv1.CrownJewelServiceClient +} + +// NewClient creates a new Discovery Config client. +func NewClient(grpcClient crownjewelv1.CrownJewelServiceClient) *Client { + return &Client{ + grpcClient: grpcClient, + } +} + +// ListCrownJewels returns a list of Crown Jewels. +func (c *Client) ListCrownJewels(ctx context.Context, pageSize int64, nextToken string) ([]*crownjewelv1.CrownJewel, string, error) { + resp, err := c.grpcClient.ListCrownJewels(ctx, &crownjewelv1.ListCrownJewelsRequest{ + PageSize: pageSize, + PageToken: nextToken, + }) + if err != nil { + return nil, "", trace.Wrap(err) + } + + return resp.CrownJewels, resp.NextPageToken, nil +} + +// CreateCrownJewel creates a new Crown Jewel. +func (c *Client) CreateCrownJewel(ctx context.Context, req *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) { + rsp, err := c.grpcClient.CreateCrownJewel(ctx, &crownjewelv1.CreateCrownJewelRequest{ + CrownJewels: req, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return rsp, nil +} + +// GetCrownJewel returns a Crown Jewel by name. +func (c *Client) GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.CrownJewel, error) { + rsp, err := c.grpcClient.GetCrownJewel(ctx, &crownjewelv1.GetCrownJewelRequest{ + Name: name, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return rsp, nil +} + +// UpdateCrownJewel updates an existing Crown Jewel. +func (c *Client) UpdateCrownJewel(ctx context.Context, req *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) { + rsp, err := c.grpcClient.UpdateCrownJewel(ctx, &crownjewelv1.UpdateCrownJewelRequest{ + CrownJewels: req, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return rsp, nil +} + +// UpsertCrownJewel upserts a Crown Jewel. +func (c *Client) UpsertCrownJewel(ctx context.Context, req *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) { + rsp, err := c.grpcClient.UpsertCrownJewel(ctx, &crownjewelv1.UpsertCrownJewelRequest{ + CrownJewels: req, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return rsp, nil +} + +// DeleteCrownJewel deletes a Crown Jewel. +func (c *Client) DeleteCrownJewel(ctx context.Context, name string) error { + _, err := c.grpcClient.DeleteCrownJewel(ctx, &crownjewelv1.DeleteCrownJewelRequest{ + Name: name, + }) + return trace.Wrap(err) +} + +// DeleteAllCrownJewels deletes all Crown Jewels. +// Not implemented. Added to satisfy the interface. +func (c *Client) DeleteAllCrownJewels(_ context.Context) error { + return trace.NotImplemented("DeleteAllCrownJewels is not implemented") +} diff --git a/api/client/events.go b/api/client/events.go index b3e8c28d61cb9..db57cd81a90e8 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -19,6 +19,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" accessmonitoringrulesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1" "github.com/gravitational/teleport/api/types" @@ -70,6 +71,10 @@ func EventToGRPC(in types.Event) (*proto.Event, error) { out.Resource = &proto.Event_AccessMonitoringRule{ AccessMonitoringRule: r, } + case *crownjewelv1.CrownJewel: + out.Resource = &proto.Event_CrownJewel{ + CrownJewel: r, + } } case *types.ResourceHeader: out.Resource = &proto.Event_ResourceHeader{ @@ -487,6 +492,9 @@ func EventFromGRPC(in *proto.Event) (*types.Event, error) { } else if r := in.GetAccessMonitoringRule(); r != nil { out.Resource = types.Resource153ToLegacy(r) return &out, nil + } else if r := in.GetCrownJewel(); r != nil { + out.Resource = types.Resource153ToLegacy(r) + return &out, nil } else { return nil, trace.BadParameter("received unsupported resource %T", in.Resource) } diff --git a/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service.pb.go b/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service.pb.go index 2f53ac3a959c9..338c16b88838d 100644 --- a/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service.pb.go +++ b/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service.pb.go @@ -83,6 +83,55 @@ func (x *CreateCrownJewelRequest) GetCrownJewels() *CrownJewel { return nil } +// GetCrownJewelRequest is a request to get a CrownJewel by name. +type GetCrownJewelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is the name of the CrownJewel to get. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetCrownJewelRequest) Reset() { + *x = GetCrownJewelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCrownJewelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCrownJewelRequest) ProtoMessage() {} + +func (x *GetCrownJewelRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCrownJewelRequest.ProtoReflect.Descriptor instead. +func (*GetCrownJewelRequest) Descriptor() ([]byte, []int) { + return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{1} +} + +func (x *GetCrownJewelRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + // ListCrownJewelsRequest is a request to get a list of CrownJewels. type ListCrownJewelsRequest struct { state protoimpl.MessageState @@ -99,7 +148,7 @@ type ListCrownJewelsRequest struct { func (x *ListCrownJewelsRequest) Reset() { *x = ListCrownJewelsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[1] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -112,7 +161,7 @@ func (x *ListCrownJewelsRequest) String() string { func (*ListCrownJewelsRequest) ProtoMessage() {} func (x *ListCrownJewelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[1] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -125,7 +174,7 @@ func (x *ListCrownJewelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCrownJewelsRequest.ProtoReflect.Descriptor instead. func (*ListCrownJewelsRequest) Descriptor() ([]byte, []int) { - return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{1} + return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{2} } func (x *ListCrownJewelsRequest) GetPageSize() int64 { @@ -157,7 +206,7 @@ type ListCrownJewelsResponse struct { func (x *ListCrownJewelsResponse) Reset() { *x = ListCrownJewelsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[2] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -170,7 +219,7 @@ func (x *ListCrownJewelsResponse) String() string { func (*ListCrownJewelsResponse) ProtoMessage() {} func (x *ListCrownJewelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[2] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -183,7 +232,7 @@ func (x *ListCrownJewelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListCrownJewelsResponse.ProtoReflect.Descriptor instead. func (*ListCrownJewelsResponse) Descriptor() ([]byte, []int) { - return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{2} + return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{3} } func (x *ListCrownJewelsResponse) GetCrownJewels() []*CrownJewel { @@ -212,7 +261,7 @@ type UpdateCrownJewelRequest struct { func (x *UpdateCrownJewelRequest) Reset() { *x = UpdateCrownJewelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[3] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -225,7 +274,7 @@ func (x *UpdateCrownJewelRequest) String() string { func (*UpdateCrownJewelRequest) ProtoMessage() {} func (x *UpdateCrownJewelRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[3] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -238,7 +287,7 @@ func (x *UpdateCrownJewelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateCrownJewelRequest.ProtoReflect.Descriptor instead. func (*UpdateCrownJewelRequest) Descriptor() ([]byte, []int) { - return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{3} + return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{4} } func (x *UpdateCrownJewelRequest) GetCrownJewels() *CrownJewel { @@ -248,33 +297,32 @@ func (x *UpdateCrownJewelRequest) GetCrownJewels() *CrownJewel { return nil } -// DeleteCrownJewelRequest is a request to delete a CrownJewel. -type DeleteCrownJewelRequest struct { +// UpsertCrownJewelRequest is a request to upsert a CrownJewel. +type UpsertCrownJewelRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Name is the name of the CrownJewel to delete. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + CrownJewels *CrownJewel `protobuf:"bytes,1,opt,name=crown_jewels,json=crownJewels,proto3" json:"crown_jewels,omitempty"` } -func (x *DeleteCrownJewelRequest) Reset() { - *x = DeleteCrownJewelRequest{} +func (x *UpsertCrownJewelRequest) Reset() { + *x = UpsertCrownJewelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[4] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *DeleteCrownJewelRequest) String() string { +func (x *UpsertCrownJewelRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteCrownJewelRequest) ProtoMessage() {} +func (*UpsertCrownJewelRequest) ProtoMessage() {} -func (x *DeleteCrownJewelRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[4] +func (x *UpsertCrownJewelRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -285,42 +333,45 @@ func (x *DeleteCrownJewelRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteCrownJewelRequest.ProtoReflect.Descriptor instead. -func (*DeleteCrownJewelRequest) Descriptor() ([]byte, []int) { - return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{4} +// Deprecated: Use UpsertCrownJewelRequest.ProtoReflect.Descriptor instead. +func (*UpsertCrownJewelRequest) Descriptor() ([]byte, []int) { + return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{5} } -func (x *DeleteCrownJewelRequest) GetName() string { +func (x *UpsertCrownJewelRequest) GetCrownJewels() *CrownJewel { if x != nil { - return x.Name + return x.CrownJewels } - return "" + return nil } -// DeleteAllCrownJewelsRequest is a request to delete all CrownJewels. -type DeleteAllCrownJewelsRequest struct { +// DeleteCrownJewelRequest is a request to delete a CrownJewel. +type DeleteCrownJewelRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + // Name is the name of the CrownJewel to delete. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } -func (x *DeleteAllCrownJewelsRequest) Reset() { - *x = DeleteAllCrownJewelsRequest{} +func (x *DeleteCrownJewelRequest) Reset() { + *x = DeleteCrownJewelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[5] + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *DeleteAllCrownJewelsRequest) String() string { +func (x *DeleteCrownJewelRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*DeleteAllCrownJewelsRequest) ProtoMessage() {} +func (*DeleteCrownJewelRequest) ProtoMessage() {} -func (x *DeleteAllCrownJewelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[5] +func (x *DeleteCrownJewelRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -331,9 +382,16 @@ func (x *DeleteAllCrownJewelsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use DeleteAllCrownJewelsRequest.ProtoReflect.Descriptor instead. -func (*DeleteAllCrownJewelsRequest) Descriptor() ([]byte, []int) { - return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{5} +// Deprecated: Use DeleteCrownJewelRequest.ProtoReflect.Descriptor instead. +func (*DeleteCrownJewelRequest) Descriptor() ([]byte, []int) { + return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP(), []int{6} +} + +func (x *DeleteCrownJewelRequest) GetName() string { + if x != nil { + return x.Name + } + return "" } var File_teleport_crownjewel_v1_crownjewel_service_proto protoreflect.FileDescriptor @@ -354,65 +412,85 @@ var file_teleport_crownjewel_v1_crownjewel_service_proto_rawDesc = []byte{ 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, - 0x73, 0x22, 0x54, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, - 0x77, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, - 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x88, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x5f, 0x6a, 0x65, 0x77, - 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x0b, 0x63, - 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, - 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0x60, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, - 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a, - 0x0c, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x5f, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, - 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, - 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, - 0x77, 0x65, 0x6c, 0x73, 0x22, 0x2d, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, - 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x22, 0x1d, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, - 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x32, 0xb6, 0x03, 0x0a, 0x11, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, - 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x2f, 0x2e, 0x74, + 0x73, 0x22, 0x2a, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, + 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x54, 0x0a, + 0x16, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x88, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, + 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x45, 0x0a, 0x0c, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x5f, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x6f, 0x77, 0x6e, + 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x60, + 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, + 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a, 0x0c, 0x63, 0x72, 0x6f, + 0x77, 0x6e, 0x5f, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, + 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, + 0x77, 0x65, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, + 0x22, 0x60, 0x0a, 0x17, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, + 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a, 0x0c, 0x63, + 0x72, 0x6f, 0x77, 0x6e, 0x5f, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, + 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, + 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, + 0x6c, 0x73, 0x22, 0x2d, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, + 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x32, 0x82, 0x05, 0x0a, 0x11, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x2f, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, + 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, + 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, + 0x12, 0x61, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, + 0x6c, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, + 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, + 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, + 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, + 0x77, 0x65, 0x6c, 0x12, 0x72, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, + 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x12, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x2f, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, + 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, - 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, - 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, + 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, + 0x12, 0x67, 0x0a, 0x10, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, + 0x65, 0x77, 0x65, 0x6c, 0x12, 0x2f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, + 0x73, 0x65, 0x72, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x5b, 0x0a, 0x10, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x2f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, - 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, - 0x6c, 0x12, 0x72, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, - 0x77, 0x65, 0x6c, 0x73, 0x12, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, - 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x2f, 0x2e, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, - 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, - 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x5b, - 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, - 0x65, 0x6c, 0x12, 0x2f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, - 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x58, 0x5a, 0x56, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, - 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x63, 0x72, 0x6f, 0x77, 0x6e, - 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, - 0x77, 0x65, 0x6c, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x6f, + 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x58, 0x5a, 0x56, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, + 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x76, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -427,34 +505,40 @@ func file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescGZIP() []byte { return file_teleport_crownjewel_v1_crownjewel_service_proto_rawDescData } -var file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_teleport_crownjewel_v1_crownjewel_service_proto_goTypes = []interface{}{ - (*CreateCrownJewelRequest)(nil), // 0: teleport.crownjewel.v1.CreateCrownJewelRequest - (*ListCrownJewelsRequest)(nil), // 1: teleport.crownjewel.v1.ListCrownJewelsRequest - (*ListCrownJewelsResponse)(nil), // 2: teleport.crownjewel.v1.ListCrownJewelsResponse - (*UpdateCrownJewelRequest)(nil), // 3: teleport.crownjewel.v1.UpdateCrownJewelRequest - (*DeleteCrownJewelRequest)(nil), // 4: teleport.crownjewel.v1.DeleteCrownJewelRequest - (*DeleteAllCrownJewelsRequest)(nil), // 5: teleport.crownjewel.v1.DeleteAllCrownJewelsRequest - (*CrownJewel)(nil), // 6: teleport.crownjewel.v1.CrownJewel - (*emptypb.Empty)(nil), // 7: google.protobuf.Empty + (*CreateCrownJewelRequest)(nil), // 0: teleport.crownjewel.v1.CreateCrownJewelRequest + (*GetCrownJewelRequest)(nil), // 1: teleport.crownjewel.v1.GetCrownJewelRequest + (*ListCrownJewelsRequest)(nil), // 2: teleport.crownjewel.v1.ListCrownJewelsRequest + (*ListCrownJewelsResponse)(nil), // 3: teleport.crownjewel.v1.ListCrownJewelsResponse + (*UpdateCrownJewelRequest)(nil), // 4: teleport.crownjewel.v1.UpdateCrownJewelRequest + (*UpsertCrownJewelRequest)(nil), // 5: teleport.crownjewel.v1.UpsertCrownJewelRequest + (*DeleteCrownJewelRequest)(nil), // 6: teleport.crownjewel.v1.DeleteCrownJewelRequest + (*CrownJewel)(nil), // 7: teleport.crownjewel.v1.CrownJewel + (*emptypb.Empty)(nil), // 8: google.protobuf.Empty } var file_teleport_crownjewel_v1_crownjewel_service_proto_depIdxs = []int32{ - 6, // 0: teleport.crownjewel.v1.CreateCrownJewelRequest.crown_jewels:type_name -> teleport.crownjewel.v1.CrownJewel - 6, // 1: teleport.crownjewel.v1.ListCrownJewelsResponse.crown_jewels:type_name -> teleport.crownjewel.v1.CrownJewel - 6, // 2: teleport.crownjewel.v1.UpdateCrownJewelRequest.crown_jewels:type_name -> teleport.crownjewel.v1.CrownJewel - 0, // 3: teleport.crownjewel.v1.CrownJewelService.CreateCrownJewel:input_type -> teleport.crownjewel.v1.CreateCrownJewelRequest - 1, // 4: teleport.crownjewel.v1.CrownJewelService.ListCrownJewels:input_type -> teleport.crownjewel.v1.ListCrownJewelsRequest - 3, // 5: teleport.crownjewel.v1.CrownJewelService.UpdateCrownJewel:input_type -> teleport.crownjewel.v1.UpdateCrownJewelRequest - 4, // 6: teleport.crownjewel.v1.CrownJewelService.DeleteCrownJewel:input_type -> teleport.crownjewel.v1.DeleteCrownJewelRequest - 6, // 7: teleport.crownjewel.v1.CrownJewelService.CreateCrownJewel:output_type -> teleport.crownjewel.v1.CrownJewel - 2, // 8: teleport.crownjewel.v1.CrownJewelService.ListCrownJewels:output_type -> teleport.crownjewel.v1.ListCrownJewelsResponse - 6, // 9: teleport.crownjewel.v1.CrownJewelService.UpdateCrownJewel:output_type -> teleport.crownjewel.v1.CrownJewel - 7, // 10: teleport.crownjewel.v1.CrownJewelService.DeleteCrownJewel:output_type -> google.protobuf.Empty - 7, // [7:11] is the sub-list for method output_type - 3, // [3:7] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 7, // 0: teleport.crownjewel.v1.CreateCrownJewelRequest.crown_jewels:type_name -> teleport.crownjewel.v1.CrownJewel + 7, // 1: teleport.crownjewel.v1.ListCrownJewelsResponse.crown_jewels:type_name -> teleport.crownjewel.v1.CrownJewel + 7, // 2: teleport.crownjewel.v1.UpdateCrownJewelRequest.crown_jewels:type_name -> teleport.crownjewel.v1.CrownJewel + 7, // 3: teleport.crownjewel.v1.UpsertCrownJewelRequest.crown_jewels:type_name -> teleport.crownjewel.v1.CrownJewel + 0, // 4: teleport.crownjewel.v1.CrownJewelService.CreateCrownJewel:input_type -> teleport.crownjewel.v1.CreateCrownJewelRequest + 1, // 5: teleport.crownjewel.v1.CrownJewelService.GetCrownJewel:input_type -> teleport.crownjewel.v1.GetCrownJewelRequest + 2, // 6: teleport.crownjewel.v1.CrownJewelService.ListCrownJewels:input_type -> teleport.crownjewel.v1.ListCrownJewelsRequest + 4, // 7: teleport.crownjewel.v1.CrownJewelService.UpdateCrownJewel:input_type -> teleport.crownjewel.v1.UpdateCrownJewelRequest + 5, // 8: teleport.crownjewel.v1.CrownJewelService.UpsertCrownJewel:input_type -> teleport.crownjewel.v1.UpsertCrownJewelRequest + 6, // 9: teleport.crownjewel.v1.CrownJewelService.DeleteCrownJewel:input_type -> teleport.crownjewel.v1.DeleteCrownJewelRequest + 7, // 10: teleport.crownjewel.v1.CrownJewelService.CreateCrownJewel:output_type -> teleport.crownjewel.v1.CrownJewel + 7, // 11: teleport.crownjewel.v1.CrownJewelService.GetCrownJewel:output_type -> teleport.crownjewel.v1.CrownJewel + 3, // 12: teleport.crownjewel.v1.CrownJewelService.ListCrownJewels:output_type -> teleport.crownjewel.v1.ListCrownJewelsResponse + 7, // 13: teleport.crownjewel.v1.CrownJewelService.UpdateCrownJewel:output_type -> teleport.crownjewel.v1.CrownJewel + 7, // 14: teleport.crownjewel.v1.CrownJewelService.UpsertCrownJewel:output_type -> teleport.crownjewel.v1.CrownJewel + 8, // 15: teleport.crownjewel.v1.CrownJewelService.DeleteCrownJewel:output_type -> google.protobuf.Empty + 10, // [10:16] is the sub-list for method output_type + 4, // [4:10] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_teleport_crownjewel_v1_crownjewel_service_proto_init() } @@ -477,7 +561,7 @@ func file_teleport_crownjewel_v1_crownjewel_service_proto_init() { } } file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListCrownJewelsRequest); i { + switch v := v.(*GetCrownJewelRequest); i { case 0: return &v.state case 1: @@ -489,7 +573,7 @@ func file_teleport_crownjewel_v1_crownjewel_service_proto_init() { } } file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListCrownJewelsResponse); i { + switch v := v.(*ListCrownJewelsRequest); i { case 0: return &v.state case 1: @@ -501,7 +585,7 @@ func file_teleport_crownjewel_v1_crownjewel_service_proto_init() { } } file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateCrownJewelRequest); i { + switch v := v.(*ListCrownJewelsResponse); i { case 0: return &v.state case 1: @@ -513,7 +597,7 @@ func file_teleport_crownjewel_v1_crownjewel_service_proto_init() { } } file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteCrownJewelRequest); i { + switch v := v.(*UpdateCrownJewelRequest); i { case 0: return &v.state case 1: @@ -525,7 +609,19 @@ func file_teleport_crownjewel_v1_crownjewel_service_proto_init() { } } file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteAllCrownJewelsRequest); i { + switch v := v.(*UpsertCrownJewelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_crownjewel_v1_crownjewel_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCrownJewelRequest); i { case 0: return &v.state case 1: @@ -543,7 +639,7 @@ func file_teleport_crownjewel_v1_crownjewel_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_crownjewel_v1_crownjewel_service_proto_rawDesc, NumEnums: 0, - NumMessages: 6, + NumMessages: 7, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service_grpc.pb.go b/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service_grpc.pb.go index 9571c35e239b9..50bf14c9e9c28 100644 --- a/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/crownjewel/v1/crownjewel_service_grpc.pb.go @@ -35,8 +35,10 @@ const _ = grpc.SupportPackageIsVersion7 const ( CrownJewelService_CreateCrownJewel_FullMethodName = "/teleport.crownjewel.v1.CrownJewelService/CreateCrownJewel" + CrownJewelService_GetCrownJewel_FullMethodName = "/teleport.crownjewel.v1.CrownJewelService/GetCrownJewel" CrownJewelService_ListCrownJewels_FullMethodName = "/teleport.crownjewel.v1.CrownJewelService/ListCrownJewels" CrownJewelService_UpdateCrownJewel_FullMethodName = "/teleport.crownjewel.v1.CrownJewelService/UpdateCrownJewel" + CrownJewelService_UpsertCrownJewel_FullMethodName = "/teleport.crownjewel.v1.CrownJewelService/UpsertCrownJewel" CrownJewelService_DeleteCrownJewel_FullMethodName = "/teleport.crownjewel.v1.CrownJewelService/DeleteCrownJewel" ) @@ -46,10 +48,14 @@ const ( type CrownJewelServiceClient interface { // CreateCrownJewel creates a new CrownJewel. CreateCrownJewel(ctx context.Context, in *CreateCrownJewelRequest, opts ...grpc.CallOption) (*CrownJewel, error) + // GetCrownJewel gets a CrownJewel by name. + GetCrownJewel(ctx context.Context, in *GetCrownJewelRequest, opts ...grpc.CallOption) (*CrownJewel, error) // ListCrownJewels returns a list of CrownJewels. It supports pagination. ListCrownJewels(ctx context.Context, in *ListCrownJewelsRequest, opts ...grpc.CallOption) (*ListCrownJewelsResponse, error) // UpdateCrownJewel updates an existing CrownJewel. UpdateCrownJewel(ctx context.Context, in *UpdateCrownJewelRequest, opts ...grpc.CallOption) (*CrownJewel, error) + // UpsertCrownJewel upserts a CrownJewel. + UpsertCrownJewel(ctx context.Context, in *UpsertCrownJewelRequest, opts ...grpc.CallOption) (*CrownJewel, error) // DeleteCrownJewel deletes a CrownJewel. DeleteCrownJewel(ctx context.Context, in *DeleteCrownJewelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } @@ -71,6 +77,15 @@ func (c *crownJewelServiceClient) CreateCrownJewel(ctx context.Context, in *Crea return out, nil } +func (c *crownJewelServiceClient) GetCrownJewel(ctx context.Context, in *GetCrownJewelRequest, opts ...grpc.CallOption) (*CrownJewel, error) { + out := new(CrownJewel) + err := c.cc.Invoke(ctx, CrownJewelService_GetCrownJewel_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *crownJewelServiceClient) ListCrownJewels(ctx context.Context, in *ListCrownJewelsRequest, opts ...grpc.CallOption) (*ListCrownJewelsResponse, error) { out := new(ListCrownJewelsResponse) err := c.cc.Invoke(ctx, CrownJewelService_ListCrownJewels_FullMethodName, in, out, opts...) @@ -89,6 +104,15 @@ func (c *crownJewelServiceClient) UpdateCrownJewel(ctx context.Context, in *Upda return out, nil } +func (c *crownJewelServiceClient) UpsertCrownJewel(ctx context.Context, in *UpsertCrownJewelRequest, opts ...grpc.CallOption) (*CrownJewel, error) { + out := new(CrownJewel) + err := c.cc.Invoke(ctx, CrownJewelService_UpsertCrownJewel_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *crownJewelServiceClient) DeleteCrownJewel(ctx context.Context, in *DeleteCrownJewelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, CrownJewelService_DeleteCrownJewel_FullMethodName, in, out, opts...) @@ -104,10 +128,14 @@ func (c *crownJewelServiceClient) DeleteCrownJewel(ctx context.Context, in *Dele type CrownJewelServiceServer interface { // CreateCrownJewel creates a new CrownJewel. CreateCrownJewel(context.Context, *CreateCrownJewelRequest) (*CrownJewel, error) + // GetCrownJewel gets a CrownJewel by name. + GetCrownJewel(context.Context, *GetCrownJewelRequest) (*CrownJewel, error) // ListCrownJewels returns a list of CrownJewels. It supports pagination. ListCrownJewels(context.Context, *ListCrownJewelsRequest) (*ListCrownJewelsResponse, error) // UpdateCrownJewel updates an existing CrownJewel. UpdateCrownJewel(context.Context, *UpdateCrownJewelRequest) (*CrownJewel, error) + // UpsertCrownJewel upserts a CrownJewel. + UpsertCrownJewel(context.Context, *UpsertCrownJewelRequest) (*CrownJewel, error) // DeleteCrownJewel deletes a CrownJewel. DeleteCrownJewel(context.Context, *DeleteCrownJewelRequest) (*emptypb.Empty, error) mustEmbedUnimplementedCrownJewelServiceServer() @@ -120,12 +148,18 @@ type UnimplementedCrownJewelServiceServer struct { func (UnimplementedCrownJewelServiceServer) CreateCrownJewel(context.Context, *CreateCrownJewelRequest) (*CrownJewel, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateCrownJewel not implemented") } +func (UnimplementedCrownJewelServiceServer) GetCrownJewel(context.Context, *GetCrownJewelRequest) (*CrownJewel, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCrownJewel not implemented") +} func (UnimplementedCrownJewelServiceServer) ListCrownJewels(context.Context, *ListCrownJewelsRequest) (*ListCrownJewelsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListCrownJewels not implemented") } func (UnimplementedCrownJewelServiceServer) UpdateCrownJewel(context.Context, *UpdateCrownJewelRequest) (*CrownJewel, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateCrownJewel not implemented") } +func (UnimplementedCrownJewelServiceServer) UpsertCrownJewel(context.Context, *UpsertCrownJewelRequest) (*CrownJewel, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpsertCrownJewel not implemented") +} func (UnimplementedCrownJewelServiceServer) DeleteCrownJewel(context.Context, *DeleteCrownJewelRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteCrownJewel not implemented") } @@ -160,6 +194,24 @@ func _CrownJewelService_CreateCrownJewel_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _CrownJewelService_GetCrownJewel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCrownJewelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CrownJewelServiceServer).GetCrownJewel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CrownJewelService_GetCrownJewel_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CrownJewelServiceServer).GetCrownJewel(ctx, req.(*GetCrownJewelRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _CrownJewelService_ListCrownJewels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListCrownJewelsRequest) if err := dec(in); err != nil { @@ -196,6 +248,24 @@ func _CrownJewelService_UpdateCrownJewel_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _CrownJewelService_UpsertCrownJewel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpsertCrownJewelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CrownJewelServiceServer).UpsertCrownJewel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CrownJewelService_UpsertCrownJewel_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CrownJewelServiceServer).UpsertCrownJewel(ctx, req.(*UpsertCrownJewelRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _CrownJewelService_DeleteCrownJewel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteCrownJewelRequest) if err := dec(in); err != nil { @@ -225,6 +295,10 @@ var CrownJewelService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreateCrownJewel", Handler: _CrownJewelService_CreateCrownJewel_Handler, }, + { + MethodName: "GetCrownJewel", + Handler: _CrownJewelService_GetCrownJewel_Handler, + }, { MethodName: "ListCrownJewels", Handler: _CrownJewelService_ListCrownJewels_Handler, @@ -233,6 +307,10 @@ var CrownJewelService_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateCrownJewel", Handler: _CrownJewelService_UpdateCrownJewel_Handler, }, + { + MethodName: "UpsertCrownJewel", + Handler: _CrownJewelService_UpsertCrownJewel_Handler, + }, { MethodName: "DeleteCrownJewel", Handler: _CrownJewelService_DeleteCrownJewel_Handler, diff --git a/api/proto/teleport/crownjewel/v1/crownjewel_service.proto b/api/proto/teleport/crownjewel/v1/crownjewel_service.proto index 23fd32763dd04..9593437089556 100644 --- a/api/proto/teleport/crownjewel/v1/crownjewel_service.proto +++ b/api/proto/teleport/crownjewel/v1/crownjewel_service.proto @@ -25,10 +25,14 @@ option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport service CrownJewelService { // CreateCrownJewel creates a new CrownJewel. rpc CreateCrownJewel(CreateCrownJewelRequest) returns (CrownJewel); + // GetCrownJewel gets a CrownJewel by name. + rpc GetCrownJewel(GetCrownJewelRequest) returns (CrownJewel); // ListCrownJewels returns a list of CrownJewels. It supports pagination. rpc ListCrownJewels(ListCrownJewelsRequest) returns (ListCrownJewelsResponse); // UpdateCrownJewel updates an existing CrownJewel. rpc UpdateCrownJewel(UpdateCrownJewelRequest) returns (CrownJewel); + // UpsertCrownJewel upserts a CrownJewel. + rpc UpsertCrownJewel(UpsertCrownJewelRequest) returns (CrownJewel); // DeleteCrownJewel deletes a CrownJewel. rpc DeleteCrownJewel(DeleteCrownJewelRequest) returns (google.protobuf.Empty); } @@ -38,6 +42,12 @@ message CreateCrownJewelRequest { teleport.crownjewel.v1.CrownJewel crown_jewels = 1; } +// GetCrownJewelRequest is a request to get a CrownJewel by name. +message GetCrownJewelRequest { + // Name is the name of the CrownJewel to get. + string name = 1; +} + // ListCrownJewelsRequest is a request to get a list of CrownJewels. message ListCrownJewelsRequest { // page_size is the maximum number of items to return. @@ -61,11 +71,13 @@ message UpdateCrownJewelRequest { teleport.crownjewel.v1.CrownJewel crown_jewels = 1; } +// UpsertCrownJewelRequest is a request to upsert a CrownJewel. +message UpsertCrownJewelRequest { + teleport.crownjewel.v1.CrownJewel crown_jewels = 1; +} + // DeleteCrownJewelRequest is a request to delete a CrownJewel. message DeleteCrownJewelRequest { // Name is the name of the CrownJewel to delete. string name = 1; } - -// DeleteAllCrownJewelsRequest is a request to delete all CrownJewels. -message DeleteAllCrownJewelsRequest {} diff --git a/api/types/constants.go b/api/types/constants.go index 9a7d2bd5d18fa..8064bd67dbf7f 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -193,7 +193,8 @@ const ( // KindKubeServer is an kubernetes server resource. KindKubeServer = "kube_server" - + // KindCrownJewel is a crown jewel resource + KindCrownJewel = "crown_jewel" // KindKubernetesCluster is a Kubernetes cluster. KindKubernetesCluster = "kube_cluster" diff --git a/lib/auth/accesspoint/accesspoint.go b/lib/auth/accesspoint/accesspoint.go index 94f2756b967b6..ef75e6a0af172 100644 --- a/lib/auth/accesspoint/accesspoint.go +++ b/lib/auth/accesspoint/accesspoint.go @@ -133,6 +133,7 @@ func NewAccessCache(cfg AccessCacheConfig) (*cache.Cache, error) { Restrictions: cfg.Services, Apps: cfg.Services, Kubernetes: cfg.Services, + CrownJewels: cfg.Services.CrownJewelClient(), DatabaseServices: cfg.Services, Databases: cfg.Services, AppSession: cfg.Services, diff --git a/lib/auth/api.go b/lib/auth/api.go index b83b2ae934ae0..c26eaa5d2381d 100644 --- a/lib/auth/api.go +++ b/lib/auth/api.go @@ -28,6 +28,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" accessmonitoringrules "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" @@ -1157,10 +1158,16 @@ type Cache interface { // ListAccessListReviews will list access list reviews for a particular access list. ListAccessListReviews(ctx context.Context, accessList string, pageSize int, pageToken string) (reviews []*accesslist.Review, nextToken string, err error) + // ListCrownJewels returns a paginated list of crown jewels. + ListCrownJewels(ctx context.Context, pageSize int64, nextToken string) ([]*crownjewelv1.CrownJewel, string, error) + + // GetCrownJewel returns the specified crown jewel. + GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.CrownJewel, error) + // IntegrationsGetter defines read/list methods for integrations. services.IntegrationsGetter - // NotificationsGetter defines list methods for notifications. + // NotificationGetter defines list methods for notifications. services.NotificationGetter // ListAccessMonitoringRules returns a paginated list of access monitoring rules. diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 687423db756bc..1af9755224dc6 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -243,6 +243,13 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { return nil, trace.Wrap(err) } } + + if cfg.CrownJewels == nil { + cfg.CrownJewels, err = local.NewCrownJewelsService(cfg.Backend) + if err != nil { + return nil, trace.Wrap(err) + } + } if cfg.ConnectionsDiagnostic == nil { cfg.ConnectionsDiagnostic = local.NewConnectionsDiagnosticService(cfg.Backend) } @@ -416,6 +423,7 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { KubeWaitingContainer: cfg.KubeWaitingContainers, Notifications: cfg.Notifications, AccessMonitoringRules: cfg.AccessMonitoringRules, + CrownJewels: cfg.CrownJewels, } as := Server{ @@ -577,6 +585,7 @@ type Services struct { services.SecReports services.KubeWaitingContainer services.AccessMonitoringRules + services.CrownJewels } // SecReportsClient returns the security reports client. @@ -628,6 +637,11 @@ func (r *Services) DiscoveryConfigClient() services.DiscoveryConfigs { return r } +// CrownJewelClient returns the CrownJewels client. +func (r *Services) CrownJewelClient() services.CrownJewels { + return r +} + // UserLoginStateClient returns the user login state client. func (r *Services) UserLoginStateClient() services.UserLoginStates { return r diff --git a/lib/auth/clt.go b/lib/auth/clt.go index 9fca11e471835..0a3b62a1d690d 100644 --- a/lib/auth/clt.go +++ b/lib/auth/clt.go @@ -28,6 +28,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/client/crownjewel" "github.com/gravitational/teleport/api/client/externalauditstorage" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/client/scim" @@ -558,6 +559,11 @@ func (c *Client) DiscoveryConfigClient() services.DiscoveryConfigWithStatusUpdat return c.APIClient.DiscoveryConfigClient() } +// CrownJewelsClient returns a client for managing Crown Jewel resources. +func (c *Client) CrownJewelsClient() services.CrownJewels { + return c.APIClient.CrownJewelServiceClient() +} + // DeleteStaticTokens deletes static tokens func (c *Client) DeleteStaticTokens() error { return trace.NotImplemented(notImplementedMessage) @@ -1093,7 +1099,7 @@ type ClientI interface { SecReportsClient() *secreport.Client // BotServiceClient returns a client for security reports. - // Clients connecting to older Teleport versions, still get a bot service client + // Clients connecting to older Teleport versions, still get a bot service client // when calling this method, but all RPCs will return "not implemented" errors // (as per the default gRPC behavior). BotServiceClient() machineidv1pb.BotServiceClient @@ -1110,6 +1116,9 @@ type ClientI interface { // (as per the default gRPC behavior). DiscoveryConfigClient() services.DiscoveryConfigWithStatusUpdater + // CrownJewelServiceClient returns a Crown Jewel service client. + CrownJewelServiceClient() *crownjewel.Client + // ResourceUsageClient returns a resource usage service client. // Clients connecting to non-Enterprise clusters, or older Teleport versions, // still get a client when calling this method, but all RPCs will return diff --git a/lib/auth/crownjewel/crownjewelv1/service.go b/lib/auth/crownjewel/crownjewelv1/service.go new file mode 100644 index 0000000000000..3b251c9b16b0d --- /dev/null +++ b/lib/auth/crownjewel/crownjewelv1/service.go @@ -0,0 +1,234 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package crownjewelv1 + +import ( + "context" + + "github.com/gravitational/trace" + "google.golang.org/protobuf/types/known/emptypb" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth/crownjewel" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/services" +) + +// ServiceConfig holds configuration options for the CrownJewel gRPC service. +type ServiceConfig struct { + // Authorizer is the authorizer to use. + Authorizer authz.Authorizer + + // Backend is the backend for storing CrownJewel. + Backend services.CrownJewels + + // Reader is the cache for storing CrownJewel. + Reader Reader +} + +// CheckAndSetDefaults checks the ServiceConfig fields and returns an error if +// a required param is not provided. +// Authorizer, Cache and Backend are required params +func (s *ServiceConfig) CheckAndSetDefaults() error { + if s.Authorizer == nil { + return trace.BadParameter("authorizer is required") + } + if s.Backend == nil { + return trace.BadParameter("backend is required") + } + if s.Reader == nil { + return trace.BadParameter("cache is required") + } + + return nil +} + +type Reader interface { + ListCrownJewels(ctx context.Context, pageSize int64, nextToken string) ([]*crownjewelv1.CrownJewel, string, error) + GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.CrownJewel, error) +} + +// Service implements the teleport.CrownJewel.v1.CrownJewelService RPC service. +type Service struct { + crownjewelv1.UnimplementedCrownJewelServiceServer + + authorizer authz.Authorizer + backend services.CrownJewels + reader Reader +} + +// NewService returns a new CrownJewel gRPC service. +func NewService(cfg ServiceConfig) (*Service, error) { + if err := cfg.CheckAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + + return &Service{ + authorizer: cfg.Authorizer, + backend: cfg.Backend, + reader: cfg.Reader, + }, nil +} + +// CreateCrownJewel creates crown jewel resource. +func (s *Service) CreateCrownJewel(ctx context.Context, req *crownjewelv1.CreateCrownJewelRequest) (*crownjewelv1.CrownJewel, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindCrownJewel, types.VerbCreate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + if err := crownjewel.ValidateCrownJewel(req.CrownJewels); err != nil { + return nil, trace.Wrap(err) + } + + rsp, err := s.backend.CreateCrownJewel(ctx, req.CrownJewels) + if err != nil { + return nil, trace.Wrap(err) + } + + return rsp, nil +} + +// ListCrownJewels returns a list of crown jewels. +func (s *Service) ListCrownJewels(ctx context.Context, req *crownjewelv1.ListCrownJewelsRequest) (*crownjewelv1.ListCrownJewelsResponse, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindCrownJewel, types.VerbRead, types.VerbList); err != nil { + return nil, trace.Wrap(err) + } + + rsp, nextToken, err := s.reader.ListCrownJewels(ctx, req.PageSize, req.PageToken) + if err != nil { + return nil, trace.Wrap(err) + } + + return &crownjewelv1.ListCrownJewelsResponse{ + CrownJewels: rsp, + NextPageToken: nextToken, + }, nil +} + +// GetCrownJewel returns crown jewel resource. +func (s *Service) GetCrownJewel(ctx context.Context, req *crownjewelv1.GetCrownJewelRequest) (*crownjewelv1.CrownJewel, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindCrownJewel, types.VerbRead); err != nil { + return nil, trace.Wrap(err) + } + + rsp, err := s.reader.GetCrownJewel(ctx, req.GetName()) + if err != nil { + return nil, trace.Wrap(err) + } + + return rsp, nil + +} + +// UpdateCrownJewel updates crown jewel resource. +func (s *Service) UpdateCrownJewel(ctx context.Context, req *crownjewelv1.UpdateCrownJewelRequest) (*crownjewelv1.CrownJewel, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindCrownJewel, types.VerbUpdate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + if err := crownjewel.ValidateCrownJewel(req.CrownJewels); err != nil { + return nil, trace.Wrap(err) + } + + rsp, err := s.backend.UpdateCrownJewel(ctx, req.CrownJewels) + if err != nil { + return nil, trace.Wrap(err) + } + + return rsp, nil +} + +// UpsertCrownJewel upserts crown jewel resource. +func (s *Service) UpsertCrownJewel(ctx context.Context, req *crownjewelv1.UpsertCrownJewelRequest) (*crownjewelv1.CrownJewel, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindCrownJewel, types.VerbUpdate, types.VerbCreate); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + if err := crownjewel.ValidateCrownJewel(req.CrownJewels); err != nil { + return nil, trace.Wrap(err) + } + + rsp, err := s.backend.UpsertCrownJewel(ctx, req.CrownJewels) + if err != nil { + return nil, trace.Wrap(err) + } + + return rsp, nil + +} + +// DeleteCrownJewel deletes crown jewel resource. +func (s *Service) DeleteCrownJewel(ctx context.Context, req *crownjewelv1.DeleteCrownJewelRequest) (*emptypb.Empty, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.CheckAccessToKind(types.KindCrownJewel, types.VerbDelete); err != nil { + return nil, trace.Wrap(err) + } + + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + if err := s.backend.DeleteCrownJewel(ctx, req.GetName()); err != nil { + return nil, trace.Wrap(err) + } + + return &emptypb.Empty{}, nil +} diff --git a/lib/auth/crownjewel/crownjewelv1/service_test.go b/lib/auth/crownjewel/crownjewelv1/service_test.go new file mode 100644 index 0000000000000..d9c450746fd82 --- /dev/null +++ b/lib/auth/crownjewel/crownjewelv1/service_test.go @@ -0,0 +1,219 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package crownjewelv1 + +import ( + "context" + "fmt" + "slices" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/backend/memory" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/local" + "github.com/gravitational/teleport/lib/utils" +) + +func TestServiceAccess(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + allowedVerbs []string + allowedStates []authz.AdminActionAuthState + }{ + { + name: "CreateCrownJewel", + allowedStates: []authz.AdminActionAuthState{authz.AdminActionAuthNotRequired, authz.AdminActionAuthMFAVerified}, + allowedVerbs: []string{types.VerbCreate}, + }, + { + name: "UpdateCrownJewel", + allowedStates: []authz.AdminActionAuthState{authz.AdminActionAuthNotRequired, authz.AdminActionAuthMFAVerified}, + allowedVerbs: []string{types.VerbUpdate}, + }, + { + name: "DeleteCrownJewel", + allowedStates: []authz.AdminActionAuthState{authz.AdminActionAuthNotRequired, authz.AdminActionAuthMFAVerified}, + allowedVerbs: []string{types.VerbDelete}, + }, + { + name: "UpsertCrownJewel", + allowedStates: []authz.AdminActionAuthState{authz.AdminActionAuthNotRequired, authz.AdminActionAuthMFAVerified}, + allowedVerbs: []string{types.VerbCreate, types.VerbUpdate}, + }, + { + name: "ListCrownJewels", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthUnauthorized, authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbRead, types.VerbList}, + }, + { + name: "GetCrownJewel", + allowedStates: []authz.AdminActionAuthState{ + authz.AdminActionAuthUnauthorized, authz.AdminActionAuthNotRequired, + authz.AdminActionAuthMFAVerified, authz.AdminActionAuthMFAVerifiedWithReuse, + }, + allowedVerbs: []string{types.VerbRead}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + // test the method with allowed admin states, each one separately. + t.Run("allowed admin states", func(t *testing.T) { + for _, state := range tt.allowedStates { + t.Run(stateToString(state), func(t *testing.T) { + for _, verbs := range utils.Combinations(tt.allowedVerbs) { + t.Run(fmt.Sprintf("verbs=%v", verbs), func(t *testing.T) { + service := newService(t, state, fakeChecker{allowedVerbs: verbs}) + err := callMethod(t, service, tt.name) + // expect access denied except with full set of verbs. + if len(verbs) == len(tt.allowedVerbs) { + require.False(t, trace.IsAccessDenied(err)) + } else { + require.True(t, trace.IsAccessDenied(err), "expected access denied for verbs %v, got err=%v", verbs, err) + } + }) + } + }) + } + }) + + // test the method with disallowed admin states; expect failures. + t.Run("disallowed admin states", func(t *testing.T) { + disallowedStates := otherAdminStates(tt.allowedStates) + for _, state := range disallowedStates { + t.Run(stateToString(state), func(t *testing.T) { + // it is enough to test against tt.allowedVerbs, + // this is the only different data point compared to the test cases above. + service := newService(t, state, fakeChecker{allowedVerbs: tt.allowedVerbs}) + err := callMethod(t, service, tt.name) + require.True(t, trace.IsAccessDenied(err)) + }) + } + }) + }) + } + + // verify that all declared methods have matching test cases + t.Run("verify coverage", func(t *testing.T) { + for _, method := range crownjewelv1.CrownJewelService_ServiceDesc.Methods { + t.Run(method.MethodName, func(t *testing.T) { + match := false + for _, testCase := range testCases { + match = match || testCase.name == method.MethodName + } + require.True(t, match, "method %v without coverage, no matching tests", method.MethodName) + }) + } + }) +} + +var allAdminStates = map[authz.AdminActionAuthState]string{ + authz.AdminActionAuthUnauthorized: "Unauthorized", + authz.AdminActionAuthNotRequired: "NotRequired", + authz.AdminActionAuthMFAVerified: "MFAVerified", + authz.AdminActionAuthMFAVerifiedWithReuse: "MFAVerifiedWithReuse", +} + +func stateToString(state authz.AdminActionAuthState) string { + str, ok := allAdminStates[state] + if !ok { + return fmt.Sprintf("unknown(%v)", state) + } + return str +} + +// otherAdminStates returns all admin states except for those passed in +func otherAdminStates(states []authz.AdminActionAuthState) []authz.AdminActionAuthState { + var out []authz.AdminActionAuthState + for state := range allAdminStates { + found := slices.Index(states, state) != -1 + if !found { + out = append(out, state) + } + } + return out +} + +// callMethod calls a method with given name in the CrownJewel service +func callMethod(t *testing.T, service *Service, method string) error { + for _, desc := range crownjewelv1.CrownJewelService_ServiceDesc.Methods { + if desc.MethodName == method { + _, err := desc.Handler(service, context.Background(), func(_ any) error { return nil }, nil) + return err + } + } + require.FailNow(t, "method %v not found", method) + panic("this line should never be reached: FailNow() should interrupt the test") +} + +type fakeChecker struct { + allowedVerbs []string + services.AccessChecker +} + +func (f fakeChecker) CheckAccessToRule(_ services.RuleContext, _ string, resource string, verb string) error { + if resource == types.KindCrownJewel { + for slices.Contains(f.allowedVerbs, verb) { + return nil + } + } + + return trace.AccessDenied("access denied to rule=%v/verb=%v", resource, verb) +} + +func newService(t *testing.T, authState authz.AdminActionAuthState, checker services.AccessChecker) *Service { + t.Helper() + + b, err := memory.New(memory.Config{}) + require.NoError(t, err) + + backendService, err := local.NewCrownJewelsService(b) + require.NoError(t, err) + + authorizer := authz.AuthorizerFunc(func(ctx context.Context) (*authz.Context, error) { + user, err := types.NewUser("llama") + if err != nil { + return nil, err + } + return &authz.Context{ + User: user, + Checker: checker, + AdminActionAuthState: authState, + }, nil + }) + + service, err := NewService(ServiceConfig{ + Authorizer: authorizer, + Backend: backendService, + Reader: backendService, + }) + require.NoError(t, err) + return service +} diff --git a/lib/auth/crownjewel/object.go b/lib/auth/crownjewel/object.go new file mode 100644 index 0000000000000..b4075b26f8405 --- /dev/null +++ b/lib/auth/crownjewel/object.go @@ -0,0 +1,101 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package crownjewel + +import ( + "github.com/gravitational/trace" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" +) + +// NewCrownJewel creates a new CrownJewel object. +// It validates the object before returning it. +func NewCrownJewel(name string, spec *crownjewelv1.CrownJewelSpec) (*crownjewelv1.CrownJewel, error) { + cj := &crownjewelv1.CrownJewel{ + Metadata: &headerv1.Metadata{ + Name: name, + }, + Spec: spec, + } + + if err := ValidateCrownJewel(cj); err != nil { + return nil, trace.Wrap(err) + } + + return cj, nil +} + +// ValidateCrownJewel validates the CrownJewel object without modifying it. +// Required fields: +// - Metadata.Name +// - Spec.TeleportMatchers or Spec.AwsMatchers +// - Matcher.Name or Matcher.Labels +func ValidateCrownJewel(jewel *crownjewelv1.CrownJewel) error { + switch { + case jewel == nil: + return trace.BadParameter("crown jewel is nil") + case jewel.Metadata == nil: + return trace.BadParameter("crown jewel metadata is nil") + case jewel.Metadata.Name == "": + return trace.BadParameter("crown jewel name is empty") + case jewel.Spec == nil: + return trace.BadParameter("crown jewel spec is nil") + case len(jewel.Spec.TeleportMatchers) == 0 && len(jewel.Spec.AwsMatchers) == 0: + return trace.BadParameter("crown jewel must have at least one matcher") + } + + if len(jewel.Spec.TeleportMatchers) > 0 { + for _, matcher := range jewel.Spec.TeleportMatchers { + if len(matcher.GetKinds()) == 0 { + return trace.BadParameter("teleport matcher kinds must be set") + } + + if matcher.Name == "" && len(matcher.GetLabels()) == 0 { + return trace.BadParameter("teleport matcher name or labels must be set") + } + + for _, label := range matcher.GetLabels() { + if label.Name == "" || len(label.Values) == 0 { + return trace.BadParameter("teleport matcher label name or value is empty") + } + } + } + } + + if len(jewel.Spec.AwsMatchers) > 0 { + for _, matcher := range jewel.Spec.AwsMatchers { + if len(matcher.GetTypes()) == 0 { + return trace.BadParameter("aws matcher type must be set") + } + + if matcher.GetArn() == "" && len(matcher.GetTags()) == 0 { + return trace.BadParameter("aws matcher arn or tags must be set") + } + + for _, label := range matcher.GetTags() { + if label.Key == "" || len(label.Values) == 0 { + return trace.BadParameter("aws matcher tag key or value is empty") + } + } + } + } + + return nil +} diff --git a/lib/auth/crownjewel/object_test.go b/lib/auth/crownjewel/object_test.go new file mode 100644 index 0000000000000..b985b078f4d17 --- /dev/null +++ b/lib/auth/crownjewel/object_test.go @@ -0,0 +1,224 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package crownjewel_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/wrapperspb" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + labelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/label/v1" + "github.com/gravitational/teleport/lib/auth/crownjewel" +) + +func TestValidateCrownJewel(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + jewel *crownjewelv1.CrownJewel + wantErr require.ErrorAssertionFunc + }{ + { + name: "NilCrownJewel", + jewel: nil, + wantErr: require.Error, + }, + { + name: "ValidCrownJewel", + jewel: &crownjewelv1.CrownJewel{ + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"kind1"}, + Name: "name1", + Labels: []*labelv1.Label{ + { + Name: "label1", + Values: []string{"value1"}, + }, + }, + }, + }, + AwsMatchers: []*crownjewelv1.AWSMatcher{ + { + Types: []string{"type1"}, + Arn: "arn1", + Tags: []*crownjewelv1.AWSTag{ + { + Key: "key1", + Values: []*wrapperspb.StringValue{wrapperspb.String("value1")}, + }, + }, + }, + }, + }, + }, + wantErr: require.NoError, + }, + { + name: "MissingMatchers", + jewel: &crownjewelv1.CrownJewel{ + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &crownjewelv1.CrownJewelSpec{}, + }, + wantErr: require.Error, + }, + { + name: "MissingMetadata", + jewel: &crownjewelv1.CrownJewel{ + Spec: &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"kind1"}, + Name: "name1", + Labels: []*labelv1.Label{ + { + Name: "label1", + Values: []string{"value1"}, + }, + }, + }, + }, + }, + }, + wantErr: require.Error, + }, + { + name: "EmptyName", + jewel: &crownjewelv1.CrownJewel{ + Metadata: &headerv1.Metadata{ + Name: "", + }, + Spec: &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"kind1"}, + Name: "name1", + Labels: []*labelv1.Label{ + { + Name: "label1", + Values: []string{"value1"}, + }, + }, + }, + }, + AwsMatchers: []*crownjewelv1.AWSMatcher{ + { + Types: []string{"type1"}, + Arn: "arn1", + Tags: []*crownjewelv1.AWSTag{ + { + Key: "key1", + Values: []*wrapperspb.StringValue{wrapperspb.String("value1")}, + }, + }, + }, + }, + }, + }, + wantErr: require.Error, + }, + { + name: "EmptyTeleportMatcherKinds", + jewel: &crownjewelv1.CrownJewel{ + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{}, + Name: "name1", + Labels: []*labelv1.Label{ + { + Name: "label1", + Values: []string{"value1"}, + }, + }, + }, + }, + AwsMatchers: []*crownjewelv1.AWSMatcher{ + { + Types: []string{"type1"}, + Arn: "arn1", + Tags: []*crownjewelv1.AWSTag{ + { + Key: "key1", + Values: []*wrapperspb.StringValue{wrapperspb.String("value1")}, + }, + }, + }, + }, + }, + }, + wantErr: require.Error, + }, + { + name: "EmptyAWSMatcherKinds", + jewel: &crownjewelv1.CrownJewel{ + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"type2"}, + Name: "name1", + Labels: []*labelv1.Label{ + { + Name: "label1", + Values: []string{"value1"}, + }, + }, + }, + }, + AwsMatchers: []*crownjewelv1.AWSMatcher{ + { + Types: []string{}, + Arn: "arn1", + Tags: []*crownjewelv1.AWSTag{ + { + Key: "key1", + Values: []*wrapperspb.StringValue{wrapperspb.String("value1")}, + }, + }, + }, + }, + }, + }, + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := crownjewel.ValidateCrownJewel(tt.jewel) + tt.wantErr(t, err) + }) + } +} diff --git a/lib/auth/dbobject/dbobjectv1/service_test.go b/lib/auth/dbobject/dbobjectv1/service_test.go index a84af1aa699e3..49a737e1b1986 100644 --- a/lib/auth/dbobject/dbobjectv1/service_test.go +++ b/lib/auth/dbobject/dbobjectv1/service_test.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport/lib/backend/memory" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/local" + "github.com/gravitational/teleport/lib/utils" ) var allAdminStates = map[authz.AdminActionAuthState]string{ @@ -72,29 +73,6 @@ func callMethod(t *testing.T, service *DatabaseObjectService, method string) err panic("this line should never be reached: FailNow() should interrupt the test") } -// allCombinations yields all unique subslices of the input slice. -func allCombinations(verbs []string) [][]string { - var result [][]string - length := len(verbs) - - for i := 0; i < (1 << length); i++ { - subslice := make([]string, 0) - for j := 0; j < length; j++ { - if i&(1<. + */ + +package services + +import ( + "context" + + "github.com/gravitational/trace" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" +) + +// CrownJewels is the interface for managing crown jewel resources. +type CrownJewels interface { + // ListCrownJewels returns the crown jewel resources. + ListCrownJewels(ctx context.Context, pageSize int64, nextToken string) ([]*crownjewelv1.CrownJewel, string, error) + // GetCrownJewel returns the crown jewel resource by name. + GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.CrownJewel, error) + // CreateCrownJewel creates a new crown jewel resource. + CreateCrownJewel(context.Context, *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) + // UpdateCrownJewel updates the crown jewel resource. + UpdateCrownJewel(context.Context, *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) + // UpsertCrownJewel creates or updates the crown jewel resource. + UpsertCrownJewel(context.Context, *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) + // DeleteCrownJewel deletes the crown jewel resource by name. + DeleteCrownJewel(context.Context, string) error + // DeleteAllCrownJewels deletes all crown jewel resources. + DeleteAllCrownJewels(context.Context) error +} + +// MarshalCrownJewel marshals the CrownJewel object into a JSON byte array. +func MarshalCrownJewel(object *crownjewelv1.CrownJewel, opts ...MarshalOption) ([]byte, error) { + cfg, err := CollectOptions(opts) + if err != nil { + return nil, trace.Wrap(err) + } + if !cfg.PreserveResourceID { + object = proto.Clone(object).(*crownjewelv1.CrownJewel) + object.Metadata.Revision = "" + } + data, err := protojson.Marshal(object) + if err != nil { + return nil, trace.Wrap(err) + } + return data, nil +} + +// UnmarshalCrownJewel unmarshals the CrownJewel object from a JSON byte array. +func UnmarshalCrownJewel(data []byte, opts ...MarshalOption) (*crownjewelv1.CrownJewel, error) { + if len(data) == 0 { + return nil, trace.BadParameter("missing crown jewel data") + } + cfg, err := CollectOptions(opts) + if err != nil { + return nil, trace.Wrap(err) + } + var obj crownjewelv1.CrownJewel + if err := protojson.Unmarshal(data, &obj); err != nil { + return nil, trace.BadParameter(err.Error()) + } + if cfg.Revision != "" { + obj.Metadata.Revision = cfg.Revision + } + if !cfg.Expires.IsZero() { + obj.Metadata.Expires = timestamppb.New(cfg.Expires) + } + return &obj, nil +} diff --git a/lib/services/crown_jewels_test.go b/lib/services/crown_jewels_test.go new file mode 100644 index 0000000000000..62f579ea11f47 --- /dev/null +++ b/lib/services/crown_jewels_test.go @@ -0,0 +1,125 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package services + +import ( + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/wrapperspb" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + labelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/label/v1" + "github.com/gravitational/teleport/lib/utils" +) + +func TestMarshalCrownJewelRoundTrip(t *testing.T) { + t.Parallel() + + spec := &crownjewelv1.CrownJewelSpec{} + obj := &crownjewelv1.CrownJewel{ + Metadata: &headerv1.Metadata{ + Name: "dummy-crown-jewel", + }, + Spec: spec, + } + + out, err := MarshalCrownJewel(obj) + require.NoError(t, err) + newObj, err := UnmarshalCrownJewel(out) + require.NoError(t, err) + require.True(t, proto.Equal(obj, newObj), "messages are not equal") +} + +func TestUnmarshalCrownJewel(t *testing.T) { + t.Parallel() + + data, err := utils.ToJSON([]byte(correctCrownJewelYAML)) + require.NoError(t, err) + + expected := &crownjewelv1.CrownJewel{ + Version: "v1", + Kind: "crown_jewel", + Metadata: &headerv1.Metadata{ + Name: "example-crown-jewel", + Labels: map[string]string{ + "env": "example", + }, + }, + Spec: &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"node"}, + Labels: []*labelv1.Label{ + { + Name: "abc", + Values: []string{"xyz"}, + }, + }, + }, + }, + AwsMatchers: []*crownjewelv1.AWSMatcher{ + { + Types: []string{"ec2"}, + Regions: []string{"us-west-1"}, + Tags: []*crownjewelv1.AWSTag{ + { + Key: "env", + Values: []*wrapperspb.StringValue{ + wrapperspb.String("prod"), + }, + }, + }, + }, + }, + }, + } + + obj, err := UnmarshalCrownJewel(data) + require.NoError(t, err) + require.True(t, proto.Equal(expected, obj), "CrownJewel objects are not equal") +} + +const correctCrownJewelYAML = ` +version: v1 +kind: crown_jewel +metadata: + labels: + env: example + name: example-crown-jewel +spec: + aws_matchers: + - regions: + - us-west-1 + tags: + - key: env + values: + - prod + types: + - ec2 + teleport_matchers: + - kinds: + - node + labels: + - name: abc + values: + - xyz +` diff --git a/lib/services/discoveryconfig.go b/lib/services/discoveryconfig.go index 5236c29a6b90a..bef4892ad0d0c 100644 --- a/lib/services/discoveryconfig.go +++ b/lib/services/discoveryconfig.go @@ -61,7 +61,7 @@ type DiscoveryConfigsGetter interface { GetDiscoveryConfig(ctx context.Context, name string) (*discoveryconfig.DiscoveryConfig, error) } -// MarshalDiscoveryConfig marshals the DiscoveryCOnfig resource to JSON. +// MarshalDiscoveryConfig marshals the DiscoveryConfig resource to JSON. func MarshalDiscoveryConfig(discoveryConfig *discoveryconfig.DiscoveryConfig, opts ...MarshalOption) ([]byte, error) { if err := discoveryConfig.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) diff --git a/lib/services/local/crown_jewels.go b/lib/services/local/crown_jewels.go new file mode 100644 index 0000000000000..68be1a0285a38 --- /dev/null +++ b/lib/services/local/crown_jewels.go @@ -0,0 +1,85 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package local + +import ( + "context" + + "github.com/gravitational/trace" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/local/generic" +) + +type CrownJewelsService struct { + service *generic.ServiceWrapper[*crownjewelv1.CrownJewel] +} + +const crownJewelsKey = "crown_jewels" + +// NewCrownJewelsService creates a new CrownJewelsService. +func NewCrownJewelsService(backend backend.Backend) (*CrownJewelsService, error) { + service, err := generic.NewServiceWrapper(backend, + types.KindCrownJewel, + crownJewelsKey, + services.MarshalCrownJewel, + services.UnmarshalCrownJewel) + if err != nil { + return nil, trace.Wrap(err) + } + return &CrownJewelsService{service: service}, nil +} + +func (s *CrownJewelsService) ListCrownJewels(ctx context.Context, pagesize int64, lastKey string) ([]*crownjewelv1.CrownJewel, string, error) { + r, nextToken, err := s.service.ListResources(ctx, int(pagesize), lastKey) + return r, nextToken, trace.Wrap(err) +} + +func (s *CrownJewelsService) GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.CrownJewel, error) { + r, err := s.service.GetResource(ctx, name) + return r, trace.Wrap(err) +} + +func (s *CrownJewelsService) CreateCrownJewel(ctx context.Context, crownJewel *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) { + r, err := s.service.CreateResource(ctx, crownJewel) + return r, trace.Wrap(err) +} + +func (s *CrownJewelsService) UpdateCrownJewel(ctx context.Context, crownJewel *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) { + r, err := s.service.ConditionalUpdateResource(ctx, crownJewel) + return r, trace.Wrap(err) +} + +func (s *CrownJewelsService) UpsertCrownJewel(ctx context.Context, crownJewel *crownjewelv1.CrownJewel) (*crownjewelv1.CrownJewel, error) { + r, err := s.service.UpsertResource(ctx, crownJewel) + return r, trace.Wrap(err) +} + +func (s *CrownJewelsService) DeleteCrownJewel(ctx context.Context, name string) error { + err := s.service.DeleteResource(ctx, name) + return trace.Wrap(err) +} + +func (s *CrownJewelsService) DeleteAllCrownJewels(ctx context.Context) error { + err := s.service.DeleteAllResources(ctx) + return trace.Wrap(err) +} diff --git a/lib/services/local/crown_jewels_test.go b/lib/services/local/crown_jewels_test.go new file mode 100644 index 0000000000000..e90602789c6f9 --- /dev/null +++ b/lib/services/local/crown_jewels_test.go @@ -0,0 +1,305 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package local_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/jonboulle/clockwork" + "github.com/mailgun/holster/v3/clock" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github.com/gravitational/teleport/lib/auth/crownjewel" + "github.com/gravitational/teleport/lib/backend/memory" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/local" +) + +func TestCreateCrownJewel(t *testing.T) { + t.Parallel() + + ctx := context.Background() + service := getService(t) + + obj, err := crownjewel.NewCrownJewel("obj", &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"ssh"}, + Name: "test", + }, + }, + }) + require.NoError(t, err) + + // first attempt should succeed + objOut, err := service.CreateCrownJewel(ctx, obj) + require.NoError(t, err) + require.Equal(t, obj, objOut) + + // second attempt should fail, object already exists + _, err = service.CreateCrownJewel(ctx, obj) + require.Error(t, err) +} + +func TestUpsertCrownJewel(t *testing.T) { + t.Parallel() + + ctx := context.Background() + service := getService(t) + + obj, err := crownjewel.NewCrownJewel("obj", &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"ssh"}, + Name: "test", + }, + }, + }) + require.NoError(t, err) + + // the first attempt should succeed + objOut, err := service.UpsertCrownJewel(ctx, obj) + require.NoError(t, err) + require.Equal(t, obj, objOut) + + // the second attempt should also succeed + objOut, err = service.UpsertCrownJewel(ctx, obj) + require.NoError(t, err) + require.Equal(t, obj, objOut) +} + +func TestGetCrownJewel(t *testing.T) { + t.Parallel() + + ctx := context.Background() + service := getService(t) + prepopulate(t, service, 1) + + tests := []struct { + name string + key string + wantErr bool + wantObj *crownjewelv1.CrownJewel + }{ + { + name: "object does not exist", + key: "dummy", + wantErr: true, + wantObj: nil, + }, + { + name: "success", + key: getObject(t, 0).GetMetadata().GetName(), + wantErr: false, + wantObj: getObject(t, 0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Fetch a specific object. + obj, err := service.GetCrownJewel(ctx, tt.key) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + cmpOpts := []cmp.Option{ + protocmp.IgnoreFields(&headerv1.Metadata{}, "id", "revision"), + protocmp.Transform(), + } + require.Equal(t, "", cmp.Diff(tt.wantObj, obj, cmpOpts...)) + }) + } +} + +func TestUpdateCrownJewel(t *testing.T) { + t.Parallel() + + ctx := context.Background() + service := getService(t) + prepopulate(t, service, 1) + + expiry := timestamppb.New(clock.Now().Add(30 * time.Minute)) + + // Fetch the object from the backend so the revision is populated. + obj, err := service.GetCrownJewel(ctx, getObject(t, 0).GetMetadata().GetName()) + require.NoError(t, err) + // update the expiry time + obj.Metadata.Expires = expiry + + objUpdated, err := service.UpdateCrownJewel(ctx, obj) + require.NoError(t, err) + require.Equal(t, expiry, objUpdated.Metadata.Expires) + + objFresh, err := service.GetCrownJewel(ctx, obj.Metadata.Name) + require.NoError(t, err) + require.Equal(t, expiry, objFresh.Metadata.Expires) +} + +func TestUpdateCrownJewelMissingRevision(t *testing.T) { + t.Parallel() + + ctx := context.Background() + service := getService(t) + prepopulate(t, service, 1) + + expiry := timestamppb.New(clock.Now().Add(30 * time.Minute)) + + obj := getObject(t, 0) + obj.Metadata.Expires = expiry + + // Update should be rejected as the revision is missing. + _, err := service.UpdateCrownJewel(ctx, obj) + require.Error(t, err) +} + +func TestDeleteCrownJewel(t *testing.T) { + t.Parallel() + + ctx := context.Background() + service := getService(t) + prepopulate(t, service, 1) + + tests := []struct { + name string + key string + wantErr bool + }{ + { + name: "object does not exist", + key: "dummy", + wantErr: true, + }, + { + name: "success", + key: getObject(t, 0).GetMetadata().GetName(), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Fetch a specific object. + err := service.DeleteCrownJewel(ctx, tt.key) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestListCrownJewel(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + counts := []int{0, 1, 5, 10} + for _, count := range counts { + t.Run(fmt.Sprintf("count=%v", count), func(t *testing.T) { + service := getService(t) + prepopulate(t, service, count) + + t.Run("one page", func(t *testing.T) { + // Fetch all objects. + elements, nextToken, err := service.ListCrownJewels(ctx, 200, "") + require.NoError(t, err) + require.Empty(t, nextToken) + require.Len(t, elements, count) + + for i := 0; i < count; i++ { + cmpOpts := []cmp.Option{ + protocmp.IgnoreFields(&headerv1.Metadata{}, "id", "revision"), + protocmp.Transform(), + } + require.Equal(t, "", cmp.Diff(getObject(t, i), elements[i], cmpOpts...)) + } + }) + + t.Run("paginated", func(t *testing.T) { + // Fetch a paginated list of objects + elements := make([]*crownjewelv1.CrownJewel, 0) + nextToken := "" + for { + out, token, err := service.ListCrownJewels(ctx, 2, nextToken) + require.NoError(t, err) + nextToken = token + + elements = append(elements, out...) + if nextToken == "" { + break + } + } + + for i := 0; i < count; i++ { + cmpOpts := []cmp.Option{ + protocmp.IgnoreFields(&headerv1.Metadata{}, "id", "revision"), + protocmp.Transform(), + } + require.Equal(t, "", cmp.Diff(getObject(t, i), elements[i], cmpOpts...)) + } + }) + }) + } +} + +func getService(t *testing.T) services.CrownJewels { + backend, err := memory.New(memory.Config{ + Context: context.Background(), + Clock: clockwork.NewFakeClock(), + }) + require.NoError(t, err) + + service, err := local.NewCrownJewelsService(backend) + require.NoError(t, err) + return service +} + +func getObject(t *testing.T, index int) *crownjewelv1.CrownJewel { + name := fmt.Sprintf("obj%v", index) + obj, err := crownjewel.NewCrownJewel(name, &crownjewelv1.CrownJewelSpec{ + TeleportMatchers: []*crownjewelv1.TeleportMatcher{ + { + Kinds: []string{"ssh"}, + Name: "test", + }, + }, + }) + require.NoError(t, err) + + return obj +} + +func prepopulate(t *testing.T, service services.CrownJewels, count int) { + for i := 0; i < count; i++ { + _, err := service.CreateCrownJewel(context.Background(), getObject(t, i)) + require.NoError(t, err) + } +} diff --git a/lib/services/local/events.go b/lib/services/local/events.go index b602b02979a6a..9d0f04ef4c68a 100644 --- a/lib/services/local/events.go +++ b/lib/services/local/events.go @@ -158,6 +158,8 @@ func (e *EventsService) NewWatcher(ctx context.Context, watch types.Watch) (type parser = newInstallerParser() case types.KindKubernetesCluster: parser = newKubeClusterParser() + case types.KindCrownJewel: + parser = newCrownJewelParser() case types.KindPlugin: parser = newPluginParser(kind.LoadSecrets) case types.KindSAMLIdPServiceProvider: @@ -1223,6 +1225,35 @@ func (p *kubeClusterParser) parse(event backend.Event) (types.Resource, error) { } } +func newCrownJewelParser() *crownJewelParser { + return &crownJewelParser{ + baseParser: newBaseParser(backend.Key(crownJewelsKey)), + } +} + +type crownJewelParser struct { + baseParser +} + +func (p *crownJewelParser) parse(event backend.Event) (types.Resource, error) { + switch event.Type { + case types.OpDelete: + return resourceHeader(event, types.KindCrownJewel, types.V1, 0) + case types.OpPut: + r, err := services.UnmarshalCrownJewel(event.Item.Value, + services.WithResourceID(event.Item.ID), + services.WithExpires(event.Item.Expires), + services.WithRevision(event.Item.Revision), + ) + if err != nil { + return nil, trace.Wrap(err) + } + return types.Resource153ToLegacy(r), nil + default: + return nil, trace.BadParameter("event %v is not supported", event.Type) + } +} + func newAppParser() *appParser { return &appParser{ baseParser: newBaseParser(backend.Key(appPrefix)), diff --git a/lib/services/presets.go b/lib/services/presets.go index cafdfc596ef1f..54042ce713838 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -130,6 +130,7 @@ func NewPresetEditorRole() types.Role { types.NewRule(types.KindUser, RW()), types.NewRule(types.KindRole, RW()), types.NewRule(types.KindBot, RW()), + types.NewRule(types.KindCrownJewel, RW()), types.NewRule(types.KindDatabaseObjectImportRule, RW()), types.NewRule(types.KindOIDC, RW()), types.NewRule(types.KindSAML, RW()), diff --git a/lib/services/resource.go b/lib/services/resource.go index 17df1e582d4cc..90e7c2e32b591 100644 --- a/lib/services/resource.go +++ b/lib/services/resource.go @@ -236,6 +236,8 @@ func ParseShortcut(in string) (string, error) { return types.KindAccessMonitoringRule, nil case types.KindDatabaseObject, "database_object": return types.KindDatabaseObject, nil + case types.KindCrownJewel, "crown_jewels": + return types.KindCrownJewel, nil } return "", trace.BadParameter("unsupported resource: %q - resources should be expressed as 'type/name', for example 'connector/github'", in) } diff --git a/lib/services/services.go b/lib/services/services.go index 729c41a42b5df..ce2e67fcf758f 100644 --- a/lib/services/services.go +++ b/lib/services/services.go @@ -56,6 +56,7 @@ type Services interface { UserLoginStateClient() UserLoginStates DiscoveryConfigClient() DiscoveryConfigs SecReportsClient() *secreport.Client + CrownJewelClient() CrownJewels } // RotationGetter returns the rotation state. diff --git a/lib/services/useracl.go b/lib/services/useracl.go index 11a32219b1b1b..7443af2520ed1 100644 --- a/lib/services/useracl.go +++ b/lib/services/useracl.go @@ -108,6 +108,8 @@ type UserACL struct { Bots ResourceAccess `json:"bots"` // AccessMonitoringRule defines access to manage access monitoring rule resources. AccessMonitoringRule ResourceAccess `json:"accessMonitoringRule"` + // CrownJewel defines access to manage CrownJewel resources. + CrownJewel ResourceAccess `json:"crownJewel"` } func hasAccess(roleSet RoleSet, ctx *Context, kind string, verbs ...string) bool { @@ -196,6 +198,7 @@ func NewUserACL(user types.User, userRoles RoleSet, features proto.Features, des accessListAccess := newAccess(userRoles, ctx, types.KindAccessList) externalAuditStorage := newAccess(userRoles, ctx, types.KindExternalAuditStorage) bots := newAccess(userRoles, ctx, types.KindBot) + crownJewelAccess := newAccess(userRoles, ctx, types.KindCrownJewel) var auditQuery ResourceAccess var securityReports ResourceAccess @@ -243,5 +246,6 @@ func NewUserACL(user types.User, userRoles RoleSet, features proto.Features, des AccessGraph: accessGraphAccess, Bots: bots, AccessMonitoringRule: accessMonitoringRules, + CrownJewel: crownJewelAccess, } } diff --git a/lib/utils/algorithms.go b/lib/utils/algorithms.go new file mode 100644 index 0000000000000..944a38b04dd78 --- /dev/null +++ b/lib/utils/algorithms.go @@ -0,0 +1,37 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package utils + +// Combinations yields all unique sub-slices of the input slice. +func Combinations(verbs []string) [][]string { + var result [][]string + length := len(verbs) + + for i := 0; i < (1 << length); i++ { + subslice := make([]string, 0) + for j := 0; j < length; j++ { + if i&(1<. + */ + +package utils + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAllCombinations(t *testing.T) { + require.Len(t, Combinations([]string{"a"}), 2) + require.Len(t, Combinations([]string{"a", "b", "c"}), 8) + require.Len(t, Combinations(make([]string, 5)), 32) +} diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index 7327c1469df20..d2ed16e0ab6f6 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/constants" + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" @@ -916,6 +917,39 @@ func (c *kubeServerCollection) writeJSON(w io.Writer) error { return utils.WriteJSONArray(w, c.servers) } +type crownJewelCollection struct { + items []*crownjewelv1.CrownJewel +} + +func (c *crownJewelCollection) resources() []types.Resource { + r := make([]types.Resource, 0, len(c.items)) + for _, resource := range c.items { + r = append(r, types.Resource153ToLegacy(resource)) + } + return r +} + +// writeText formats the crown jewels into a table and writes them into w. +// If verbose is disabled, labels column can be truncated to fit into the console. +func (c *crownJewelCollection) writeText(w io.Writer, verbose bool) error { + var rows [][]string + for _, item := range c.items { + labels := common.FormatLabels(item.GetMetadata().GetLabels(), verbose) + rows = append(rows, []string{item.Metadata.GetName(), item.GetSpec().String(), labels}) + } + headers := []string{"Name", "Spec", "Labels"} + var t asciitable.Table + if verbose { + t = asciitable.MakeTable(headers, rows...) + } else { + t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels") + } + // stable sort by name. + t.SortRowsBy([]int{0}, true) + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} + type kubeClusterCollection struct { clusters []types.KubeCluster } diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 0a4e2027e1cd7..963ab8d20c417 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -42,6 +42,7 @@ import ( apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" + crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" @@ -156,6 +157,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec types.KindDatabaseObjectImportRule: rc.createDatabaseObjectImportRule, types.KindDatabaseObject: rc.createDatabaseObject, types.KindAccessMonitoringRule: rc.createAccessMonitoringRule, + types.KindCrownJewel: rc.createCrownJewel, } rc.UpdateHandlers = map[ResourceKind]ResourceCreateHandler{ types.KindUser: rc.updateUser, @@ -167,6 +169,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec types.KindClusterAuthPreference: rc.updateAuthPreference, types.KindSessionRecordingConfig: rc.updateSessionRecordingConfig, types.KindAccessMonitoringRule: rc.updateAccessMonitoringRule, + types.KindCrownJewel: rc.updateCrownJewel, } rc.config = config @@ -927,6 +930,40 @@ func (rc *ResourceCommand) createKubeCluster(ctx context.Context, client *auth.C return nil } +func (rc *ResourceCommand) createCrownJewel(ctx context.Context, client *auth.Client, raw services.UnknownResource) error { + crownJewel, err := services.UnmarshalCrownJewel(raw.Raw) + if err != nil { + return trace.Wrap(err) + } + + c := client.CrownJewelsClient() + if rc.force { + if _, err := c.UpsertCrownJewel(ctx, crownJewel); err != nil { + return trace.Wrap(err) + } + fmt.Printf("crown jewel %q has been updated\n", crownJewel.GetMetadata().GetName()) + } else { + if _, err := c.CreateCrownJewel(ctx, crownJewel); err != nil { + return trace.Wrap(err) + } + fmt.Printf("crown jewel %q has been created\n", crownJewel.GetMetadata().GetName()) + } + + return nil +} + +func (rc *ResourceCommand) updateCrownJewel(ctx context.Context, client *auth.Client, resource services.UnknownResource) error { + in, err := services.UnmarshalCrownJewel(resource.Raw) + if err != nil { + return trace.Wrap(err) + } + if _, err := client.CrownJewelsClient().UpdateCrownJewel(ctx, in); err != nil { + return trace.Wrap(err) + } + fmt.Printf("crown jewel %q has been updated\n", in.GetMetadata().GetName()) + return nil +} + func (rc *ResourceCommand) createDatabase(ctx context.Context, client *auth.Client, raw services.UnknownResource) error { database, err := services.UnmarshalDatabase(raw.Raw) if err != nil { @@ -1537,6 +1574,11 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *auth.Client) (err return trace.Wrap(err) } fmt.Printf("%s %q has been deleted\n", resDesc, name) + case types.KindCrownJewel: + if err := client.CrownJewelsClient().DeleteCrownJewel(ctx, rc.ref.Name); err != nil { + return trace.Wrap(err) + } + fmt.Printf("crown_jewel %q has been deleted\n", rc.ref.Name) case types.KindWindowsDesktopService: if err = client.DeleteWindowsDesktopService(ctx, rc.ref.Name); err != nil { return trace.Wrap(err) @@ -2239,6 +2281,24 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *auth.Clien return nil, trace.NotFound("kubernetes cluster %q not found", rc.ref.Name) } return &kubeClusterCollection{clusters: clusters}, nil + case types.KindCrownJewel: + cjClient := client.CrownJewelsClient() + var rules []*crownjewelv1.CrownJewel + nextToken := "" + for { + resp, token, err := cjClient.ListCrownJewels(ctx, 0 /* default size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + + rules = append(rules, resp...) + + if token == "" { + break + } + nextToken = token + } + return &crownJewelCollection{items: rules}, nil case types.KindWindowsDesktopService: services, err := client.GetWindowsDesktopServices(ctx) if err != nil {