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 {