From ea37165292893b330ebc792588b7a14d19e80d64 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Sun, 24 Nov 2024 20:43:52 -0500 Subject: [PATCH 1/5] GitHub Proxy part 3: gen github user cert and export CA --- api/client/client.go | 5 + .../integration/v1/integration_service.pb.go | 445 +++++++++++++++--- .../v1/integration_service_grpc.pb.go | 94 +++- .../integration/v1/integration_service.proto | 42 ++ lib/auth/authclient/clt.go | 4 + .../integration/integrationv1/credentials.go | 72 +++ .../integrationv1/credentials_test.go | 125 +++++ lib/auth/integration/integrationv1/github.go | 103 ++++ .../integration/integrationv1/github_test.go | 82 ++++ .../integration/integrationv1/service_test.go | 52 +- lib/client/ca_export.go | 76 +++ lib/client/ca_export_test.go | 52 +- lib/sshutils/fingerprint.go | 17 + lib/sshutils/fingerprint_test.go | 64 +++ tool/tctl/common/auth_command.go | 4 + 15 files changed, 1125 insertions(+), 112 deletions(-) create mode 100644 lib/auth/integration/integrationv1/credentials_test.go create mode 100644 lib/auth/integration/integrationv1/github.go create mode 100644 lib/auth/integration/integrationv1/github_test.go create mode 100644 lib/sshutils/fingerprint_test.go diff --git a/api/client/client.go b/api/client/client.go index a9b8c665de599..20f53625ce9cb 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -5190,3 +5190,8 @@ func (c *Client) IdentityCenterClient() identitycenterv1.IdentityCenterServiceCl func (c *Client) ProvisioningServiceClient() provisioningv1.ProvisioningServiceClient { return provisioningv1.NewProvisioningServiceClient(c.conn) } + +// IntegrationsClient returns integrations client. +func (c *Client) IntegrationsClient() integrationpb.IntegrationServiceClient { + return c.integrationsClient() +} diff --git a/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go b/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go index 4bf4813a11236..0d12bd10e55ee 100644 --- a/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go +++ b/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go @@ -24,6 +24,7 @@ import ( types "github.com/gravitational/teleport/api/types" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" @@ -494,6 +495,233 @@ func (x *GenerateAWSOIDCTokenResponse) GetToken() string { return "" } +// GenerateGitHubUserCertRequest is a request to sign a client certificate used by +// GitHub integration to authenticate with GitHub enterprise. +type GenerateGitHubUserCertRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Integration is the name of the integration; + Integration string `protobuf:"bytes,1,opt,name=integration,proto3" json:"integration,omitempty"` + // PublicKey is the public key to be signed. + PublicKey []byte `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + // UserId is the GitHub user id. + UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + // KeyId is the certficate ID, usually the Teleport username. + KeyId string `protobuf:"bytes,4,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` + // Ttl is the duration the certificate will be valid for. + Ttl *durationpb.Duration `protobuf:"bytes,5,opt,name=ttl,proto3" json:"ttl,omitempty"` +} + +func (x *GenerateGitHubUserCertRequest) Reset() { + *x = GenerateGitHubUserCertRequest{} + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenerateGitHubUserCertRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateGitHubUserCertRequest) ProtoMessage() {} + +func (x *GenerateGitHubUserCertRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateGitHubUserCertRequest.ProtoReflect.Descriptor instead. +func (*GenerateGitHubUserCertRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{9} +} + +func (x *GenerateGitHubUserCertRequest) GetIntegration() string { + if x != nil { + return x.Integration + } + return "" +} + +func (x *GenerateGitHubUserCertRequest) GetPublicKey() []byte { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *GenerateGitHubUserCertRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *GenerateGitHubUserCertRequest) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + +func (x *GenerateGitHubUserCertRequest) GetTtl() *durationpb.Duration { + if x != nil { + return x.Ttl + } + return nil +} + +// GenerateGitHubUserCertResponse contains a signed certificate. +type GenerateGitHubUserCertResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // AuthorizedKey is the signed certificate. + AuthorizedKey []byte `protobuf:"bytes,1,opt,name=authorized_key,json=authorizedKey,proto3" json:"authorized_key,omitempty"` +} + +func (x *GenerateGitHubUserCertResponse) Reset() { + *x = GenerateGitHubUserCertResponse{} + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenerateGitHubUserCertResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateGitHubUserCertResponse) ProtoMessage() {} + +func (x *GenerateGitHubUserCertResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateGitHubUserCertResponse.ProtoReflect.Descriptor instead. +func (*GenerateGitHubUserCertResponse) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{10} +} + +func (x *GenerateGitHubUserCertResponse) GetAuthorizedKey() []byte { + if x != nil { + return x.AuthorizedKey + } + return nil +} + +// ExportIntegrationCertAuthoritiesRequest is the request to export cert +// authorities for an integration. +type ExportIntegrationCertAuthoritiesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Integration is the name of the integration; + Integration string `protobuf:"bytes,1,opt,name=integration,proto3" json:"integration,omitempty"` +} + +func (x *ExportIntegrationCertAuthoritiesRequest) Reset() { + *x = ExportIntegrationCertAuthoritiesRequest{} + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExportIntegrationCertAuthoritiesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportIntegrationCertAuthoritiesRequest) ProtoMessage() {} + +func (x *ExportIntegrationCertAuthoritiesRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportIntegrationCertAuthoritiesRequest.ProtoReflect.Descriptor instead. +func (*ExportIntegrationCertAuthoritiesRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{11} +} + +func (x *ExportIntegrationCertAuthoritiesRequest) GetIntegration() string { + if x != nil { + return x.Integration + } + return "" +} + +// ExportIntegrationCertAuthoritiesResponse is the response to +// ExportIntegrationCertAuthorities. +type ExportIntegrationCertAuthoritiesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // CertAuthorities are the CA key sets used to sign any new certificates. + CertAuthorities *types.CAKeySet `protobuf:"bytes,1,opt,name=cert_authorities,json=certAuthorities,proto3" json:"cert_authorities,omitempty"` +} + +func (x *ExportIntegrationCertAuthoritiesResponse) Reset() { + *x = ExportIntegrationCertAuthoritiesResponse{} + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExportIntegrationCertAuthoritiesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportIntegrationCertAuthoritiesResponse) ProtoMessage() {} + +func (x *ExportIntegrationCertAuthoritiesResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportIntegrationCertAuthoritiesResponse.ProtoReflect.Descriptor instead. +func (*ExportIntegrationCertAuthoritiesResponse) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{12} +} + +func (x *ExportIntegrationCertAuthoritiesResponse) GetCertAuthorities() *types.CAKeySet { + if x != nil { + return x.CertAuthorities + } + return nil +} + var File_teleport_integration_v1_integration_service_proto protoreflect.FileDescriptor var file_teleport_integration_v1_integration_service_proto_rawDesc = []byte{ @@ -501,7 +729,9 @@ var file_teleport_integration_v1_integration_service_proto_rawDesc = []byte{ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, - 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x67, 0x6f, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x1e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, @@ -547,60 +777,107 @@ var file_teleport_integration_v1_integration_service_proto_rawDesc = []byte{ 0x1c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x57, 0x53, 0x4f, 0x49, 0x44, 0x43, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x32, 0xef, 0x05, 0x0a, 0x12, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x10, 0x4c, 0x69, - 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, + 0x6b, 0x65, 0x6e, 0x22, 0xbd, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, + 0x74, 0x74, 0x6c, 0x22, 0x47, 0x0a, 0x1e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, + 0x69, 0x74, 0x48, 0x75, 0x62, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x22, 0x4b, 0x0a, 0x27, + 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x66, 0x0a, 0x28, 0x45, 0x78, 0x70, + 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x65, + 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x41, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, + 0x52, 0x0f, 0x63, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, + 0x73, 0x32, 0xa5, 0x08, 0x0a, 0x12, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x74, - 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, - 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, - 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5c, 0x0a, 0x11, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, + 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5c, 0x0a, 0x11, 0x55, 0x70, 0x64, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5c, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5e, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5c, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 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, 0x12, 0x66, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x35, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 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, 0x12, - 0x83, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x57, 0x53, 0x4f, - 0x49, 0x44, 0x43, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x57, 0x53, 0x4f, 0x49, - 0x44, 0x43, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, + 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5e, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, + 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 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, 0x12, 0x66, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, + 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, + 0x6c, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 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, 0x12, 0x83, 0x01, + 0x0a, 0x14, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x57, 0x53, 0x4f, 0x49, 0x44, + 0x43, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, 0x57, 0x53, 0x4f, 0x49, 0x44, 0x43, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x41, + 0x57, 0x53, 0x4f, 0x49, 0x44, 0x43, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x12, 0x36, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x41, 0x57, 0x53, 0x4f, 0x49, 0x44, 0x43, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5a, 0x5a, 0x58, 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, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x76, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x55, + 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0xa7, 0x01, 0x0a, 0x20, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x69, 0x65, 0x73, 0x12, 0x40, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x45, + 0x78, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x41, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5a, 0x5a, 0x58, 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, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -615,43 +892,55 @@ func file_teleport_integration_v1_integration_service_proto_rawDescGZIP() []byte return file_teleport_integration_v1_integration_service_proto_rawDescData } -var file_teleport_integration_v1_integration_service_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_teleport_integration_v1_integration_service_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_teleport_integration_v1_integration_service_proto_goTypes = []any{ - (*ListIntegrationsRequest)(nil), // 0: teleport.integration.v1.ListIntegrationsRequest - (*ListIntegrationsResponse)(nil), // 1: teleport.integration.v1.ListIntegrationsResponse - (*GetIntegrationRequest)(nil), // 2: teleport.integration.v1.GetIntegrationRequest - (*CreateIntegrationRequest)(nil), // 3: teleport.integration.v1.CreateIntegrationRequest - (*UpdateIntegrationRequest)(nil), // 4: teleport.integration.v1.UpdateIntegrationRequest - (*DeleteIntegrationRequest)(nil), // 5: teleport.integration.v1.DeleteIntegrationRequest - (*DeleteAllIntegrationsRequest)(nil), // 6: teleport.integration.v1.DeleteAllIntegrationsRequest - (*GenerateAWSOIDCTokenRequest)(nil), // 7: teleport.integration.v1.GenerateAWSOIDCTokenRequest - (*GenerateAWSOIDCTokenResponse)(nil), // 8: teleport.integration.v1.GenerateAWSOIDCTokenResponse - (*types.IntegrationV1)(nil), // 9: types.IntegrationV1 - (*emptypb.Empty)(nil), // 10: google.protobuf.Empty + (*ListIntegrationsRequest)(nil), // 0: teleport.integration.v1.ListIntegrationsRequest + (*ListIntegrationsResponse)(nil), // 1: teleport.integration.v1.ListIntegrationsResponse + (*GetIntegrationRequest)(nil), // 2: teleport.integration.v1.GetIntegrationRequest + (*CreateIntegrationRequest)(nil), // 3: teleport.integration.v1.CreateIntegrationRequest + (*UpdateIntegrationRequest)(nil), // 4: teleport.integration.v1.UpdateIntegrationRequest + (*DeleteIntegrationRequest)(nil), // 5: teleport.integration.v1.DeleteIntegrationRequest + (*DeleteAllIntegrationsRequest)(nil), // 6: teleport.integration.v1.DeleteAllIntegrationsRequest + (*GenerateAWSOIDCTokenRequest)(nil), // 7: teleport.integration.v1.GenerateAWSOIDCTokenRequest + (*GenerateAWSOIDCTokenResponse)(nil), // 8: teleport.integration.v1.GenerateAWSOIDCTokenResponse + (*GenerateGitHubUserCertRequest)(nil), // 9: teleport.integration.v1.GenerateGitHubUserCertRequest + (*GenerateGitHubUserCertResponse)(nil), // 10: teleport.integration.v1.GenerateGitHubUserCertResponse + (*ExportIntegrationCertAuthoritiesRequest)(nil), // 11: teleport.integration.v1.ExportIntegrationCertAuthoritiesRequest + (*ExportIntegrationCertAuthoritiesResponse)(nil), // 12: teleport.integration.v1.ExportIntegrationCertAuthoritiesResponse + (*types.IntegrationV1)(nil), // 13: types.IntegrationV1 + (*durationpb.Duration)(nil), // 14: google.protobuf.Duration + (*types.CAKeySet)(nil), // 15: types.CAKeySet + (*emptypb.Empty)(nil), // 16: google.protobuf.Empty } var file_teleport_integration_v1_integration_service_proto_depIdxs = []int32{ - 9, // 0: teleport.integration.v1.ListIntegrationsResponse.integrations:type_name -> types.IntegrationV1 - 9, // 1: teleport.integration.v1.CreateIntegrationRequest.integration:type_name -> types.IntegrationV1 - 9, // 2: teleport.integration.v1.UpdateIntegrationRequest.integration:type_name -> types.IntegrationV1 - 0, // 3: teleport.integration.v1.IntegrationService.ListIntegrations:input_type -> teleport.integration.v1.ListIntegrationsRequest - 2, // 4: teleport.integration.v1.IntegrationService.GetIntegration:input_type -> teleport.integration.v1.GetIntegrationRequest - 3, // 5: teleport.integration.v1.IntegrationService.CreateIntegration:input_type -> teleport.integration.v1.CreateIntegrationRequest - 4, // 6: teleport.integration.v1.IntegrationService.UpdateIntegration:input_type -> teleport.integration.v1.UpdateIntegrationRequest - 5, // 7: teleport.integration.v1.IntegrationService.DeleteIntegration:input_type -> teleport.integration.v1.DeleteIntegrationRequest - 6, // 8: teleport.integration.v1.IntegrationService.DeleteAllIntegrations:input_type -> teleport.integration.v1.DeleteAllIntegrationsRequest - 7, // 9: teleport.integration.v1.IntegrationService.GenerateAWSOIDCToken:input_type -> teleport.integration.v1.GenerateAWSOIDCTokenRequest - 1, // 10: teleport.integration.v1.IntegrationService.ListIntegrations:output_type -> teleport.integration.v1.ListIntegrationsResponse - 9, // 11: teleport.integration.v1.IntegrationService.GetIntegration:output_type -> types.IntegrationV1 - 9, // 12: teleport.integration.v1.IntegrationService.CreateIntegration:output_type -> types.IntegrationV1 - 9, // 13: teleport.integration.v1.IntegrationService.UpdateIntegration:output_type -> types.IntegrationV1 - 10, // 14: teleport.integration.v1.IntegrationService.DeleteIntegration:output_type -> google.protobuf.Empty - 10, // 15: teleport.integration.v1.IntegrationService.DeleteAllIntegrations:output_type -> google.protobuf.Empty - 8, // 16: teleport.integration.v1.IntegrationService.GenerateAWSOIDCToken:output_type -> teleport.integration.v1.GenerateAWSOIDCTokenResponse - 10, // [10:17] is the sub-list for method output_type - 3, // [3:10] 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 + 13, // 0: teleport.integration.v1.ListIntegrationsResponse.integrations:type_name -> types.IntegrationV1 + 13, // 1: teleport.integration.v1.CreateIntegrationRequest.integration:type_name -> types.IntegrationV1 + 13, // 2: teleport.integration.v1.UpdateIntegrationRequest.integration:type_name -> types.IntegrationV1 + 14, // 3: teleport.integration.v1.GenerateGitHubUserCertRequest.ttl:type_name -> google.protobuf.Duration + 15, // 4: teleport.integration.v1.ExportIntegrationCertAuthoritiesResponse.cert_authorities:type_name -> types.CAKeySet + 0, // 5: teleport.integration.v1.IntegrationService.ListIntegrations:input_type -> teleport.integration.v1.ListIntegrationsRequest + 2, // 6: teleport.integration.v1.IntegrationService.GetIntegration:input_type -> teleport.integration.v1.GetIntegrationRequest + 3, // 7: teleport.integration.v1.IntegrationService.CreateIntegration:input_type -> teleport.integration.v1.CreateIntegrationRequest + 4, // 8: teleport.integration.v1.IntegrationService.UpdateIntegration:input_type -> teleport.integration.v1.UpdateIntegrationRequest + 5, // 9: teleport.integration.v1.IntegrationService.DeleteIntegration:input_type -> teleport.integration.v1.DeleteIntegrationRequest + 6, // 10: teleport.integration.v1.IntegrationService.DeleteAllIntegrations:input_type -> teleport.integration.v1.DeleteAllIntegrationsRequest + 7, // 11: teleport.integration.v1.IntegrationService.GenerateAWSOIDCToken:input_type -> teleport.integration.v1.GenerateAWSOIDCTokenRequest + 9, // 12: teleport.integration.v1.IntegrationService.GenerateGitHubUserCert:input_type -> teleport.integration.v1.GenerateGitHubUserCertRequest + 11, // 13: teleport.integration.v1.IntegrationService.ExportIntegrationCertAuthorities:input_type -> teleport.integration.v1.ExportIntegrationCertAuthoritiesRequest + 1, // 14: teleport.integration.v1.IntegrationService.ListIntegrations:output_type -> teleport.integration.v1.ListIntegrationsResponse + 13, // 15: teleport.integration.v1.IntegrationService.GetIntegration:output_type -> types.IntegrationV1 + 13, // 16: teleport.integration.v1.IntegrationService.CreateIntegration:output_type -> types.IntegrationV1 + 13, // 17: teleport.integration.v1.IntegrationService.UpdateIntegration:output_type -> types.IntegrationV1 + 16, // 18: teleport.integration.v1.IntegrationService.DeleteIntegration:output_type -> google.protobuf.Empty + 16, // 19: teleport.integration.v1.IntegrationService.DeleteAllIntegrations:output_type -> google.protobuf.Empty + 8, // 20: teleport.integration.v1.IntegrationService.GenerateAWSOIDCToken:output_type -> teleport.integration.v1.GenerateAWSOIDCTokenResponse + 10, // 21: teleport.integration.v1.IntegrationService.GenerateGitHubUserCert:output_type -> teleport.integration.v1.GenerateGitHubUserCertResponse + 12, // 22: teleport.integration.v1.IntegrationService.ExportIntegrationCertAuthorities:output_type -> teleport.integration.v1.ExportIntegrationCertAuthoritiesResponse + 14, // [14:23] is the sub-list for method output_type + 5, // [5:14] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_teleport_integration_v1_integration_service_proto_init() } @@ -665,7 +954,7 @@ func file_teleport_integration_v1_integration_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_integration_v1_integration_service_proto_rawDesc, NumEnums: 0, - NumMessages: 9, + NumMessages: 13, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/integration/v1/integration_service_grpc.pb.go b/api/gen/proto/go/teleport/integration/v1/integration_service_grpc.pb.go index f88c39e9d6154..e003922829236 100644 --- a/api/gen/proto/go/teleport/integration/v1/integration_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/integration/v1/integration_service_grpc.pb.go @@ -35,13 +35,15 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - IntegrationService_ListIntegrations_FullMethodName = "/teleport.integration.v1.IntegrationService/ListIntegrations" - IntegrationService_GetIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/GetIntegration" - IntegrationService_CreateIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/CreateIntegration" - IntegrationService_UpdateIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/UpdateIntegration" - IntegrationService_DeleteIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/DeleteIntegration" - IntegrationService_DeleteAllIntegrations_FullMethodName = "/teleport.integration.v1.IntegrationService/DeleteAllIntegrations" - IntegrationService_GenerateAWSOIDCToken_FullMethodName = "/teleport.integration.v1.IntegrationService/GenerateAWSOIDCToken" + IntegrationService_ListIntegrations_FullMethodName = "/teleport.integration.v1.IntegrationService/ListIntegrations" + IntegrationService_GetIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/GetIntegration" + IntegrationService_CreateIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/CreateIntegration" + IntegrationService_UpdateIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/UpdateIntegration" + IntegrationService_DeleteIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/DeleteIntegration" + IntegrationService_DeleteAllIntegrations_FullMethodName = "/teleport.integration.v1.IntegrationService/DeleteAllIntegrations" + IntegrationService_GenerateAWSOIDCToken_FullMethodName = "/teleport.integration.v1.IntegrationService/GenerateAWSOIDCToken" + IntegrationService_GenerateGitHubUserCert_FullMethodName = "/teleport.integration.v1.IntegrationService/GenerateGitHubUserCert" + IntegrationService_ExportIntegrationCertAuthorities_FullMethodName = "/teleport.integration.v1.IntegrationService/ExportIntegrationCertAuthorities" ) // IntegrationServiceClient is the client API for IntegrationService service. @@ -65,6 +67,10 @@ type IntegrationServiceClient interface { DeleteAllIntegrations(ctx context.Context, in *DeleteAllIntegrationsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // GenerateAWSOIDCToken generates a token to be used when executing an AWS OIDC Integration action. GenerateAWSOIDCToken(ctx context.Context, in *GenerateAWSOIDCTokenRequest, opts ...grpc.CallOption) (*GenerateAWSOIDCTokenResponse, error) + // GenerateGitHubUserCert signs a SSH certificate for GitHub integration. + GenerateGitHubUserCert(ctx context.Context, in *GenerateGitHubUserCertRequest, opts ...grpc.CallOption) (*GenerateGitHubUserCertResponse, error) + // ExportIntegrationCertAuthorities exports cert authorities for an integration. + ExportIntegrationCertAuthorities(ctx context.Context, in *ExportIntegrationCertAuthoritiesRequest, opts ...grpc.CallOption) (*ExportIntegrationCertAuthoritiesResponse, error) } type integrationServiceClient struct { @@ -145,6 +151,26 @@ func (c *integrationServiceClient) GenerateAWSOIDCToken(ctx context.Context, in return out, nil } +func (c *integrationServiceClient) GenerateGitHubUserCert(ctx context.Context, in *GenerateGitHubUserCertRequest, opts ...grpc.CallOption) (*GenerateGitHubUserCertResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GenerateGitHubUserCertResponse) + err := c.cc.Invoke(ctx, IntegrationService_GenerateGitHubUserCert_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *integrationServiceClient) ExportIntegrationCertAuthorities(ctx context.Context, in *ExportIntegrationCertAuthoritiesRequest, opts ...grpc.CallOption) (*ExportIntegrationCertAuthoritiesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExportIntegrationCertAuthoritiesResponse) + err := c.cc.Invoke(ctx, IntegrationService_ExportIntegrationCertAuthorities_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // IntegrationServiceServer is the server API for IntegrationService service. // All implementations must embed UnimplementedIntegrationServiceServer // for forward compatibility. @@ -166,6 +192,10 @@ type IntegrationServiceServer interface { DeleteAllIntegrations(context.Context, *DeleteAllIntegrationsRequest) (*emptypb.Empty, error) // GenerateAWSOIDCToken generates a token to be used when executing an AWS OIDC Integration action. GenerateAWSOIDCToken(context.Context, *GenerateAWSOIDCTokenRequest) (*GenerateAWSOIDCTokenResponse, error) + // GenerateGitHubUserCert signs a SSH certificate for GitHub integration. + GenerateGitHubUserCert(context.Context, *GenerateGitHubUserCertRequest) (*GenerateGitHubUserCertResponse, error) + // ExportIntegrationCertAuthorities exports cert authorities for an integration. + ExportIntegrationCertAuthorities(context.Context, *ExportIntegrationCertAuthoritiesRequest) (*ExportIntegrationCertAuthoritiesResponse, error) mustEmbedUnimplementedIntegrationServiceServer() } @@ -197,6 +227,12 @@ func (UnimplementedIntegrationServiceServer) DeleteAllIntegrations(context.Conte func (UnimplementedIntegrationServiceServer) GenerateAWSOIDCToken(context.Context, *GenerateAWSOIDCTokenRequest) (*GenerateAWSOIDCTokenResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GenerateAWSOIDCToken not implemented") } +func (UnimplementedIntegrationServiceServer) GenerateGitHubUserCert(context.Context, *GenerateGitHubUserCertRequest) (*GenerateGitHubUserCertResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GenerateGitHubUserCert not implemented") +} +func (UnimplementedIntegrationServiceServer) ExportIntegrationCertAuthorities(context.Context, *ExportIntegrationCertAuthoritiesRequest) (*ExportIntegrationCertAuthoritiesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExportIntegrationCertAuthorities not implemented") +} func (UnimplementedIntegrationServiceServer) mustEmbedUnimplementedIntegrationServiceServer() {} func (UnimplementedIntegrationServiceServer) testEmbeddedByValue() {} @@ -344,6 +380,42 @@ func _IntegrationService_GenerateAWSOIDCToken_Handler(srv interface{}, ctx conte return interceptor(ctx, in, info, handler) } +func _IntegrationService_GenerateGitHubUserCert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenerateGitHubUserCertRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).GenerateGitHubUserCert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_GenerateGitHubUserCert_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).GenerateGitHubUserCert(ctx, req.(*GenerateGitHubUserCertRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _IntegrationService_ExportIntegrationCertAuthorities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExportIntegrationCertAuthoritiesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).ExportIntegrationCertAuthorities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_ExportIntegrationCertAuthorities_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).ExportIntegrationCertAuthorities(ctx, req.(*ExportIntegrationCertAuthoritiesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // IntegrationService_ServiceDesc is the grpc.ServiceDesc for IntegrationService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -379,6 +451,14 @@ var IntegrationService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GenerateAWSOIDCToken", Handler: _IntegrationService_GenerateAWSOIDCToken_Handler, }, + { + MethodName: "GenerateGitHubUserCert", + Handler: _IntegrationService_GenerateGitHubUserCert_Handler, + }, + { + MethodName: "ExportIntegrationCertAuthorities", + Handler: _IntegrationService_ExportIntegrationCertAuthorities_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "teleport/integration/v1/integration_service.proto", diff --git a/api/proto/teleport/integration/v1/integration_service.proto b/api/proto/teleport/integration/v1/integration_service.proto index 6306a204a79d7..6ba566276cb2d 100644 --- a/api/proto/teleport/integration/v1/integration_service.proto +++ b/api/proto/teleport/integration/v1/integration_service.proto @@ -16,6 +16,7 @@ syntax = "proto3"; package teleport.integration.v1; +import "google/protobuf/duration.proto"; import "google/protobuf/empty.proto"; import "teleport/legacy/types/types.proto"; @@ -44,6 +45,12 @@ service IntegrationService { // GenerateAWSOIDCToken generates a token to be used when executing an AWS OIDC Integration action. rpc GenerateAWSOIDCToken(GenerateAWSOIDCTokenRequest) returns (GenerateAWSOIDCTokenResponse); + + // GenerateGitHubUserCert signs a SSH certificate for GitHub integration. + rpc GenerateGitHubUserCert(GenerateGitHubUserCertRequest) returns (GenerateGitHubUserCertResponse); + + // ExportIntegrationCertAuthorities exports cert authorities for an integration. + rpc ExportIntegrationCertAuthorities(ExportIntegrationCertAuthoritiesRequest) returns (ExportIntegrationCertAuthoritiesResponse); } // ListIntegrationsRequest is a request for a paginated list of Integrations. @@ -111,3 +118,38 @@ message GenerateAWSOIDCTokenResponse { // Token is the signed JWT ready to be used string token = 1; } + +// GenerateGitHubUserCertRequest is a request to sign a client certificate used by +// GitHub integration to authenticate with GitHub enterprise. +message GenerateGitHubUserCertRequest { + // Integration is the name of the integration; + string integration = 1; + // PublicKey is the public key to be signed. + bytes public_key = 2; + // UserId is the GitHub user id. + string user_id = 3; + // KeyId is the certficate ID, usually the Teleport username. + string key_id = 4; + // Ttl is the duration the certificate will be valid for. + google.protobuf.Duration ttl = 5; +} + +// GenerateGitHubUserCertResponse contains a signed certificate. +message GenerateGitHubUserCertResponse { + // AuthorizedKey is the signed certificate. + bytes authorized_key = 1; +} + +// ExportIntegrationCertAuthoritiesRequest is the request to export cert +// authorities for an integration. +message ExportIntegrationCertAuthoritiesRequest { + // Integration is the name of the integration; + string integration = 1; +} + +// ExportIntegrationCertAuthoritiesResponse is the response to +// ExportIntegrationCertAuthorities. +message ExportIntegrationCertAuthoritiesResponse { + // CertAuthorities are the CA key sets used to sign any new certificates. + types.CAKeySet cert_authorities = 1; +} diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go index 8c818c9015d80..823eebd1232df 100644 --- a/lib/auth/authclient/clt.go +++ b/lib/auth/authclient/clt.go @@ -44,6 +44,7 @@ import ( dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1" + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1" machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" @@ -1890,4 +1891,7 @@ type ClientI interface { // ProvisioningServiceClient returns provisioning service client. ProvisioningServiceClient() provisioningv1.ProvisioningServiceClient + + // IntegrationsClient returns integrations client. + IntegrationsClient() integrationpb.IntegrationServiceClient } diff --git a/lib/auth/integration/integrationv1/credentials.go b/lib/auth/integration/integrationv1/credentials.go index 7ea4935461b42..52d8fac68f2cb 100644 --- a/lib/auth/integration/integrationv1/credentials.go +++ b/lib/auth/integration/integrationv1/credentials.go @@ -25,6 +25,7 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/cryptosuites" ) @@ -45,6 +46,34 @@ const ( purposeGitHubOAuth = "github-oauth" ) +// ExportIntegrationCertAuthorities exports cert authorities for an integration. +func (s *Service) ExportIntegrationCertAuthorities(ctx context.Context, in *integrationpb.ExportIntegrationCertAuthoritiesRequest) (*integrationpb.ExportIntegrationCertAuthoritiesResponse, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindIntegration, types.VerbRead); err != nil { + return nil, trace.Wrap(err) + } + ig, err := s.cache.GetIntegration(ctx, in.Integration) + if err != nil { + return nil, trace.Wrap(err) + } + + // Currently only public keys are exported. + switch ig.GetSubKind() { + case types.IntegrationSubKindGitHub: + caKeySet, err := s.getGitHubCertAuthorities(ctx, ig) + if err != nil { + return nil, trace.Wrap(err) + } + caKeySetWithoutSecerts := caKeySet.WithoutSecrets() + return &integrationpb.ExportIntegrationCertAuthoritiesResponse{CertAuthorities: &caKeySetWithoutSecerts}, nil + default: + return nil, trace.BadParameter("unsupported for integration subkind %v", ig.GetSubKind()) + } +} + func newStaticCredentialsRef(uuid string) *types.PluginStaticCredentialsRef { return &types.PluginStaticCredentialsRef{ Labels: map[string]string{ @@ -226,3 +255,46 @@ func (s *Service) removeStaticCredentials(ctx context.Context, ig types.Integrat } return trace.NewAggregate(errors...) } + +func (s *Service) getStaticCredentialsWithPurpose(ctx context.Context, ig types.Integration, purpose string) (types.PluginStaticCredentials, error) { + if ig.GetCredentials() == nil || ig.GetCredentials().GetStaticCredentialsRef() == nil { + return nil, trace.BadParameter("missing credentials ref") + } + labels := ig.GetCredentials().GetStaticCredentialsRef().Labels + if len(labels) == 0 { + return nil, trace.BadParameter("missing labels from credentials ref") + } + labels[labelStaticCredentialsPurpose] = purpose + + // TODO(greedy52) use cache + creds, err := s.backend.GetPluginStaticCredentialsByLabels(ctx, labels) + if err != nil { + return nil, trace.Wrap(err) + } + switch len(creds) { + case 0: + return nil, trace.NotFound("%v credentials not found", purpose) + case 1: + return creds[0], nil + default: + return nil, trace.CompareFailed("expecting one plugin static credentials but got %v", len(creds)) + } +} + +func (s *Service) getGitHubCertAuthorities(ctx context.Context, ig types.Integration) (*types.CAKeySet, error) { + if ig.GetSubKind() != types.IntegrationSubKindGitHub { + return nil, trace.BadParameter("integration is not a GitHub integration") + } + creds, err := s.getStaticCredentialsWithPurpose(ctx, ig, purposeGitHubSSHCA) + if err != nil { + return nil, trace.Wrap(err) + } + + cas := creds.GetSSHCertAuthorities() + if len(cas) == 0 { + return nil, trace.BadParameter("missing SSH cert authorities from plugin static credentials") + } + return &types.CAKeySet{ + SSH: cas, + }, nil +} diff --git a/lib/auth/integration/integrationv1/credentials_test.go b/lib/auth/integration/integrationv1/credentials_test.go new file mode 100644 index 0000000000000..1fc22d7d93e62 --- /dev/null +++ b/lib/auth/integration/integrationv1/credentials_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 integrationv1 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/trace" +) + +func TestExportIntegrationCertAuthorities(t *testing.T) { + t.Parallel() + + ca := newCertAuthority(t, types.HostCA, "test-cluster") + ctx, localClient, resourceSvc := initSvc(t, ca, ca.GetClusterName(), "127.0.0.1.nip.io") + + githubIntegration, err := newGitHubIntegration("github-my-org", "id", "secret") + require.NoError(t, err) + + oidcIntegration, err := types.NewIntegrationAWSOIDC( + types.Metadata{Name: "aws-oidc"}, + &types.AWSOIDCIntegrationSpecV1{ + RoleARN: "arn:aws:iam::123456789012:role/OpsTeam", + }, + ) + require.NoError(t, err) + + adminCtx := authz.ContextWithUser(ctx, authz.BuiltinRole{ + Role: types.RoleAdmin, + Username: string(types.RoleAdmin), + }) + + _, err = resourceSvc.CreateIntegration(adminCtx, &integrationpb.CreateIntegrationRequest{Integration: githubIntegration}) + require.NoError(t, err) + _, err = resourceSvc.CreateIntegration(adminCtx, &integrationpb.CreateIntegrationRequest{Integration: oidcIntegration}) + require.NoError(t, err) + + tests := []struct { + name string + integration string + identity context.Context + check func(*testing.T, *integrationpb.ExportIntegrationCertAuthoritiesResponse, error) + }{ + { + name: "success", + integration: githubIntegration.GetName(), + identity: adminCtx, + check: func(t *testing.T, resp *integrationpb.ExportIntegrationCertAuthoritiesResponse, err error) { + t.Helper() + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.CertAuthorities) + require.Len(t, resp.CertAuthorities.SSH, 1) + require.NotNil(t, resp.CertAuthorities.SSH[0]) + assert.NotEmpty(t, resp.CertAuthorities.SSH[0].PublicKey) + assert.Empty(t, resp.CertAuthorities.SSH[0].PrivateKey) + }, + }, + { + name: "not found", + integration: "not-found", + identity: adminCtx, + check: func(t *testing.T, resp *integrationpb.ExportIntegrationCertAuthoritiesResponse, err error) { + t.Helper() + require.Nil(t, resp) + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) + }, + }, + { + name: "not allowed", + integration: githubIntegration.GetName(), + identity: authorizerForDummyUser(t, ctx, types.RoleSpecV6{}, localClient), + check: func(t *testing.T, resp *integrationpb.ExportIntegrationCertAuthoritiesResponse, err error) { + t.Helper() + require.Nil(t, resp) + require.Error(t, err) + require.True(t, trace.IsAccessDenied(err)) + }, + }, + { + name: "not supported", + integration: oidcIntegration.GetName(), + identity: adminCtx, + check: func(t *testing.T, resp *integrationpb.ExportIntegrationCertAuthoritiesResponse, err error) { + t.Helper() + require.Nil(t, resp) + require.Error(t, err) + require.True(t, trace.IsBadParameter(err)) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resp, err := resourceSvc.ExportIntegrationCertAuthorities(test.identity, &integrationpb.ExportIntegrationCertAuthoritiesRequest{ + Integration: test.integration, + }) + test.check(t, resp, err) + }) + } +} diff --git a/lib/auth/integration/integrationv1/github.go b/lib/auth/integration/integrationv1/github.go new file mode 100644 index 0000000000000..00506cf83b1fa --- /dev/null +++ b/lib/auth/integration/integrationv1/github.go @@ -0,0 +1,103 @@ +/* + * 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 integrationv1 + +import ( + "context" + "crypto/rand" + "time" + + "github.com/gravitational/trace" + "golang.org/x/crypto/ssh" + + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/authz" +) + +// GenerateGitHubUserCert signs a SSH certificate for GitHub integration. +func (s *Service) GenerateGitHubUserCert(ctx context.Context, in *integrationpb.GenerateGitHubUserCertRequest) (*integrationpb.GenerateGitHubUserCertResponse, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if !authz.HasBuiltinRole(*authCtx, string(types.RoleProxy)) { + return nil, trace.AccessDenied("GenerateGitHubUserCert is only available to proxy services") + } + + cert, err := s.prepareGitHubCert(ctx, in) + if err != nil { + return nil, trace.Wrap(err) + } + caSigner, err := s.getGitHubSigner(ctx, in.Integration) + if err != nil { + return nil, trace.Wrap(err) + } + if err := cert.SignCert(rand.Reader, caSigner); err != nil { + return nil, trace.Wrap(err) + } + return &integrationpb.GenerateGitHubUserCertResponse{ + AuthorizedKey: ssh.MarshalAuthorizedKey(cert), + }, nil +} + +func (s *Service) prepareGitHubCert(ctx context.Context, in *integrationpb.GenerateGitHubUserCertRequest) (*ssh.Certificate, error) { + if in.UserId == "" { + return nil, trace.BadParameter("missing UserId for GenerateGitHubUserCert") + } + if in.KeyId == "" { + return nil, trace.BadParameter("missing KeyId for GenerateGitHubUserCert") + } + key, _, _, _, err := ssh.ParseAuthorizedKey(in.PublicKey) + if err != nil { + return nil, trace.Wrap(err) + } + // Sign with user ID set in id@github.com extension. + // https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-git-access-to-your-organizations-repositories/about-ssh-certificate-authorities + now := time.Now() + cert := &ssh.Certificate{ + Key: key, + CertType: ssh.UserCert, + KeyId: in.KeyId, + ValidAfter: uint64(now.Add(-time.Minute).Unix()), + ValidBefore: uint64(now.Add(in.Ttl.AsDuration()).Unix()), + Permissions: ssh.Permissions{ + Extensions: map[string]string{ + "id@github.com": in.UserId, + }, + }, + } + return cert, nil +} + +func (s *Service) getGitHubSigner(ctx context.Context, integration string) (ssh.Signer, error) { + ig, err := s.cache.GetIntegration(ctx, integration) + if err != nil { + return nil, trace.Wrap(err) + } + caKeySet, err := s.getGitHubCertAuthorities(ctx, ig) + if err != nil { + return nil, trace.Wrap(err) + } + caSigner, err := s.keyStoreManager.GetSSHSignerFromKeySet(ctx, *caKeySet) + if err != nil { + return nil, trace.Wrap(err) + } + return caSigner, nil +} diff --git a/lib/auth/integration/integrationv1/github_test.go b/lib/auth/integration/integrationv1/github_test.go new file mode 100644 index 0000000000000..44fccf55ccb4a --- /dev/null +++ b/lib/auth/integration/integrationv1/github_test.go @@ -0,0 +1,82 @@ +/* + * 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 integrationv1 + +import ( + "testing" + "time" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" + "google.golang.org/protobuf/types/known/durationpb" + + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/cryptosuites" +) + +func TestGenerateGitHubUserCert(t *testing.T) { + t.Parallel() + + ca := newCertAuthority(t, types.HostCA, "test-cluster") + ctx, _, resourceSvc := initSvc(t, ca, ca.GetClusterName(), "127.0.0.1.nip.io") + + githubIntegration, err := newGitHubIntegration("github-my-org", "id", "secret") + require.NoError(t, err) + + adminCtx := authz.ContextWithUser(ctx, authz.BuiltinRole{ + Role: types.RoleAdmin, + Username: string(types.RoleAdmin), + }) + _, err = resourceSvc.CreateIntegration(adminCtx, &integrationpb.CreateIntegrationRequest{Integration: githubIntegration}) + require.NoError(t, err) + + key, err := cryptosuites.GeneratePrivateKeyWithAlgorithm(cryptosuites.Ed25519) + require.NoError(t, err) + + req := &integrationpb.GenerateGitHubUserCertRequest{ + Integration: "github-my-org", + PublicKey: key.MarshalSSHPublicKey(), + UserId: "1122334455", + KeyId: "alice", + Ttl: durationpb.New(time.Minute), + } + + // Admin users cannot generate certs. + _, err = resourceSvc.GenerateGitHubUserCert(adminCtx, req) + require.True(t, trace.IsAccessDenied(err)) + + // Call as Proxy. + proxyCtx := authz.ContextWithUser(ctx, authz.BuiltinRole{ + Role: types.RoleProxy, + Username: string(types.RoleProxy), + }) + resp, err := resourceSvc.GenerateGitHubUserCert(proxyCtx, req) + require.NoError(t, err) + authorizedKey, _, _, _, err := ssh.ParseAuthorizedKey(resp.AuthorizedKey) + require.NoError(t, err) + sshCert, ok := authorizedKey.(*ssh.Certificate) + require.True(t, ok) + + assert.Equal(t, "alice", sshCert.KeyId) + assert.Equal(t, map[string]string{"id@github.com": "1122334455"}, sshCert.Permissions.Extensions) +} diff --git a/lib/auth/integration/integrationv1/service_test.go b/lib/auth/integration/integrationv1/service_test.go index 6bf73b4897157..3a72d93637c7c 100644 --- a/lib/auth/integration/integrationv1/service_test.go +++ b/lib/auth/integration/integrationv1/service_test.go @@ -63,32 +63,6 @@ func TestIntegrationCRUD(t *testing.T) { return ig } - newGitHubIntegration := func(name, id, secret string) (*types.IntegrationV1, error) { - ig, err := types.NewIntegrationGitHub( - types.Metadata{ - Name: name, - }, - &types.GitHubIntegrationSpecV1{ - Organization: "my-org", - }, - ) - if err != nil { - return nil, trace.Wrap(err) - } - - if secret != "" { - ig.SetCredentials(&types.PluginCredentialsV1{ - Credentials: &types.PluginCredentialsV1_IdSecret{ - IdSecret: &types.PluginIdSecretCredential{ - Id: id, - Secret: secret, - }, - }, - }) - } - return ig, nil - } - tt := []struct { Name string Role types.RoleSpecV6 @@ -769,6 +743,32 @@ func newPlugin(t *testing.T, integrationName string) *types.PluginV1 { } } +func newGitHubIntegration(name, id, secret string) (*types.IntegrationV1, error) { + ig, err := types.NewIntegrationGitHub( + types.Metadata{ + Name: name, + }, + &types.GitHubIntegrationSpecV1{ + Organization: "my-org", + }, + ) + if err != nil { + return nil, trace.Wrap(err) + } + + if secret != "" { + ig.SetCredentials(&types.PluginCredentialsV1{ + Credentials: &types.PluginCredentialsV1_IdSecret{ + IdSecret: &types.PluginIdSecretCredential{ + Id: id, + Secret: secret, + }, + }, + }) + } + return ig, nil +} + func mustFindGitHubCredentials(t *testing.T, localClient Backend, igName, wantId, wantSecret string) { t.Helper() diff --git a/lib/client/ca_export.go b/lib/client/ca_export.go index d8f648819e4b8..6a1aefddb058d 100644 --- a/lib/client/ca_export.go +++ b/lib/client/ca_export.go @@ -22,12 +22,15 @@ import ( "context" "encoding/pem" "errors" + "fmt" + "log/slog" "strings" "time" "github.com/gravitational/trace" apidefaults "github.com/gravitational/teleport/api/defaults" + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/mfa" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/authclient" @@ -49,6 +52,23 @@ type ExportAuthoritiesRequest struct { AuthType string ExportAuthorityFingerprint string UseCompatVersion bool + Integration string +} + +func (r *ExportAuthoritiesRequest) shouldExportIntegration(ctx context.Context) (bool, error) { + switch r.AuthType { + case "github": + if r.Integration == "" { + return false, trace.BadParameter("integration name must be provided for %q CAs", r.AuthType) + } + return true, nil + default: + if r.Integration != "" { + r.Integration = "" + slog.DebugContext(ctx, "Integration name is ignored for non-integration CAs") + } + return false, nil + } } // ExportAuthorities returns the list of authorities in OpenSSH compatible formats as a string. @@ -76,12 +96,22 @@ type ExportAuthoritiesRequest struct { // > @cert-authority *.cluster-a ssh-rsa AAA... type=host // URL encoding is used to pass the CA type and allowed logins into the comment field. func ExportAuthorities(ctx context.Context, client authclient.ClientI, req ExportAuthoritiesRequest) (string, error) { + if isIntegration, err := req.shouldExportIntegration(ctx); err != nil { + return "", trace.Wrap(err) + } else if isIntegration { + return exportAuthForIntegration(ctx, client, req) + } return exportAuth(ctx, client, req, false /* exportSecrets */) } // ExportAuthoritiesSecrets exports the Authority Certificate secrets (private keys). // See ExportAuthorities for more information. func ExportAuthoritiesSecrets(ctx context.Context, client authclient.ClientI, req ExportAuthoritiesRequest) (string, error) { + if isIntegration, err := req.shouldExportIntegration(ctx); err != nil { + return "", trace.Wrap(err) + } else if isIntegration { + return "", trace.NotImplemented("export with secrets is not supported for %q CAs", req.AuthType) + } return exportAuth(ctx, client, req, true /* exportSecrets */) } @@ -344,3 +374,49 @@ func hostCAFormat(ca types.CertAuthority, keyBytes []byte, client authclient.Cli }, }) } + +func exportAuthForIntegration(ctx context.Context, client authclient.ClientI, req ExportAuthoritiesRequest) (string, error) { + switch req.AuthType { + case "github": + keySet, err := fetchIntegrationCAKeySet(ctx, client, req.Integration) + if err != nil { + return "", trace.Wrap(err) + } + ret, err := exportGitHubCAs(keySet, req) + if err != nil { + return "", trace.Wrap(err) + } + return ret, nil + + default: + return "", trace.BadParameter("unknown integration CA type %q", req.AuthType) + } +} + +func fetchIntegrationCAKeySet(ctx context.Context, client authclient.ClientI, integration string) (*types.CAKeySet, error) { + resp, err := client.IntegrationsClient().ExportIntegrationCertAuthorities(ctx, &integrationpb.ExportIntegrationCertAuthoritiesRequest{ + Integration: integration, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp.CertAuthorities, nil +} + +func exportGitHubCAs(keySet *types.CAKeySet, req ExportAuthoritiesRequest) (string, error) { + ret := strings.Builder{} + for _, key := range keySet.SSH { + if req.ExportAuthorityFingerprint != "" { + if fingerprint, err := sshutils.AuthorizedKeyFingerprint(key.PublicKey); err != nil { + return "", trace.Wrap(err) + } else if !sshutils.EqualFingerprints(req.ExportAuthorityFingerprint, fingerprint) { + continue + } + } + + // GitHub only needs the keys like "ssh-rsa xxx" so print them without + // cert-authority for easier copy-and-paste. + ret.WriteString(fmt.Sprintf("%s integration=%s\n", strings.TrimSpace(string(key.PublicKey)), req.Integration)) + } + return ret.String(), nil +} diff --git a/lib/client/ca_export_test.go b/lib/client/ca_export_test.go index 32082427ee523..cf7ff693716fe 100644 --- a/lib/client/ca_export_test.go +++ b/lib/client/ca_export_test.go @@ -26,18 +26,22 @@ import ( "testing" "github.com/stretchr/testify/require" + "google.golang.org/grpc" "github.com/gravitational/teleport/api/client/proto" + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/mfa" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/authclient" + "github.com/gravitational/teleport/lib/fixtures" ) type mockAuthClient struct { authclient.ClientI - server *auth.Server + server *auth.Server + integrationsClient mockIntegrationsClient } func (m *mockAuthClient) GetDomainName(ctx context.Context) (string, error) { @@ -57,6 +61,21 @@ func (m *mockAuthClient) PerformMFACeremony(ctx context.Context, challengeReques return nil, &mfa.ErrMFANotRequired } +func (m *mockAuthClient) IntegrationsClient() integrationpb.IntegrationServiceClient { + return &m.integrationsClient +} + +type mockIntegrationsClient struct { + integrationpb.IntegrationServiceClient + caKeySet *types.CAKeySet +} + +func (m *mockIntegrationsClient) ExportIntegrationCertAuthorities(ctx context.Context, in *integrationpb.ExportIntegrationCertAuthoritiesRequest, opts ...grpc.CallOption) (*integrationpb.ExportIntegrationCertAuthoritiesResponse, error) { + return &integrationpb.ExportIntegrationCertAuthoritiesResponse{ + CertAuthorities: m.caKeySet, + }, nil +} + func TestExportAuthorities(t *testing.T) { ctx := context.Background() const localClusterName = "localcluster" @@ -100,6 +119,10 @@ func TestExportAuthorities(t *testing.T) { require.NotNil(t, privKey, "x509.ParsePKCS8PrivateKey returned a nil key") } + validateGitHubCAFunc := func(t *testing.T, s string) { + require.Contains(t, s, fixtures.SSHCAPublicKey) + } + for _, exportSecrets := range []bool{false, true} { for _, tt := range []struct { name string @@ -247,10 +270,33 @@ func TestExportAuthorities(t *testing.T) { assertNoSecrets: validateTLSCertificateDERFunc, assertSecrets: validateRSAPrivateKeyDERFunc, }, + { + name: "github missing integration", + req: ExportAuthoritiesRequest{ + AuthType: "github", + }, + errorCheck: require.Error, + }, + { + name: "github", + req: ExportAuthoritiesRequest{ + AuthType: "github", + Integration: "my-github", + }, + errorCheck: require.NoError, + assertNoSecrets: validateGitHubCAFunc, + }, } { t.Run(fmt.Sprintf("%s_exportSecrets_%v", tt.name, exportSecrets), func(t *testing.T) { mockedClient := &mockAuthClient{ server: testAuth.AuthServer, + integrationsClient: mockIntegrationsClient{ + caKeySet: &types.CAKeySet{ + SSH: []*types.SSHKeyPair{{ + PublicKey: []byte(fixtures.SSHCAPublicKey), + }}, + }, + }, } var ( err error @@ -264,6 +310,10 @@ func TestExportAuthorities(t *testing.T) { checkFunc = tt.assertSecrets } + if checkFunc == nil { + t.Skip("assert func not provided") + } + exported, err = exportFunc(ctx, mockedClient, tt.req) tt.errorCheck(t, err) diff --git a/lib/sshutils/fingerprint.go b/lib/sshutils/fingerprint.go index bae36ae07857e..010dc6bb7b783 100644 --- a/lib/sshutils/fingerprint.go +++ b/lib/sshutils/fingerprint.go @@ -19,6 +19,8 @@ package sshutils import ( + "strings" + "github.com/gravitational/trace" "golang.org/x/crypto/ssh" ) @@ -47,3 +49,18 @@ func PrivateKeyFingerprint(keyBytes []byte) (string, error) { } return Fingerprint(signer.PublicKey()), nil } + +// fingerprintPrefix is the fingerprint prefix added by ssh.FingerprintSHA256. +const fingerprintPrefix = "SHA256:" + +func maybeAddPrefix(fingerprint string) string { + if !strings.HasPrefix(fingerprint, fingerprintPrefix) { + return fingerprintPrefix + fingerprint + } + return fingerprint +} + +// EqualFingerprints checks if two finger prints are equal. +func EqualFingerprints(a, b string) bool { + return strings.EqualFold(maybeAddPrefix(a), maybeAddPrefix(b)) +} diff --git a/lib/sshutils/fingerprint_test.go b/lib/sshutils/fingerprint_test.go new file mode 100644 index 0000000000000..a17878236008a --- /dev/null +++ b/lib/sshutils/fingerprint_test.go @@ -0,0 +1,64 @@ +/* + * 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 sshutils + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEqualFingerprints(t *testing.T) { + tests := []struct { + name string + a string + b string + check require.BoolAssertionFunc + }{ + { + name: "equal", + a: "SHA256:fingerprint", + b: "SHA256:fingerprint", + check: require.True, + }, + { + name: "not equal", + a: "SHA256:fingerprint", + b: "SHA256:fingerprint2", + check: require.False, + }, + { + name: "equal without prefix", + a: "SHA256:fingerprint", + b: "fingerprint", + check: require.True, + }, + { + name: "equal fold", + a: "FINGERPRINT", + b: "SHA256:fingerprint", + check: require.True, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.check(t, EqualFingerprints(test.a, test.b)) + }) + } +} diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 141a38e8fe46d..f46934965c9b6 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -87,6 +87,7 @@ type AuthCommand struct { caType string streamTarfile bool identityWriter identityfile.ConfigWriter + integration string rotateGracePeriod time.Duration rotateType string @@ -116,6 +117,7 @@ func (a *AuthCommand) Initialize(app *kingpin.Application, config *servicecfg.Co a.authExport.Flag("type", fmt.Sprintf("export certificate type (%v)", strings.Join(allowedCertificateTypes, ", "))). EnumVar(&a.authType, allowedCertificateTypes...) + a.authExport.Flag("integration", "Name of the integration. Only applies to \"github\" CAs.").StringVar(&a.integration) a.authGenerate = auth.Command("gen", "Generate a new SSH keypair.").Hidden() a.authGenerate.Flag("pub-key", "path to the public key").Required().StringVar(&a.genPubPath) @@ -206,6 +208,7 @@ var allowedCertificateTypes = []string{ "db-client-der", "openssh", "saml-idp", + "github", } // allowedCRLCertificateTypes list of certificate authorities types that can @@ -233,6 +236,7 @@ func (a *AuthCommand) ExportAuthorities(ctx context.Context, clt *authclient.Cli AuthType: a.authType, ExportAuthorityFingerprint: a.exportAuthorityFingerprint, UseCompatVersion: a.compatVersion == "1.0", + Integration: a.integration, }, ) if err != nil { From 43a40e97406851780dd7bfda147b3e46901b7bd1 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Tue, 3 Dec 2024 09:35:20 -0500 Subject: [PATCH 2/5] address pr comment --- .../go/teleport/integration/v1/integration_service.pb.go | 2 +- api/proto/teleport/integration/v1/integration_service.proto | 2 +- lib/auth/integration/integrationv1/credentials_test.go | 5 +++-- lib/auth/integration/integrationv1/github.go | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go b/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go index 0d12bd10e55ee..caebbb68f43fd 100644 --- a/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go +++ b/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go @@ -508,7 +508,7 @@ type GenerateGitHubUserCertRequest struct { PublicKey []byte `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // UserId is the GitHub user id. UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - // KeyId is the certficate ID, usually the Teleport username. + // KeyId is the certificate ID, usually the Teleport username. KeyId string `protobuf:"bytes,4,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` // Ttl is the duration the certificate will be valid for. Ttl *durationpb.Duration `protobuf:"bytes,5,opt,name=ttl,proto3" json:"ttl,omitempty"` diff --git a/api/proto/teleport/integration/v1/integration_service.proto b/api/proto/teleport/integration/v1/integration_service.proto index 6ba566276cb2d..0528f521f684e 100644 --- a/api/proto/teleport/integration/v1/integration_service.proto +++ b/api/proto/teleport/integration/v1/integration_service.proto @@ -128,7 +128,7 @@ message GenerateGitHubUserCertRequest { bytes public_key = 2; // UserId is the GitHub user id. string user_id = 3; - // KeyId is the certficate ID, usually the Teleport username. + // KeyId is the certificate ID, usually the Teleport username. string key_id = 4; // Ttl is the duration the certificate will be valid for. google.protobuf.Duration ttl = 5; diff --git a/lib/auth/integration/integrationv1/credentials_test.go b/lib/auth/integration/integrationv1/credentials_test.go index 1fc22d7d93e62..fe90c4705ea4f 100644 --- a/lib/auth/integration/integrationv1/credentials_test.go +++ b/lib/auth/integration/integrationv1/credentials_test.go @@ -25,17 +25,18 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/gravitational/trace" + integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/authz" - "github.com/gravitational/trace" ) func TestExportIntegrationCertAuthorities(t *testing.T) { t.Parallel() ca := newCertAuthority(t, types.HostCA, "test-cluster") - ctx, localClient, resourceSvc := initSvc(t, ca, ca.GetClusterName(), "127.0.0.1.nip.io") + ctx, localClient, resourceSvc := initSvc(t, ca, ca.GetClusterName(), "127.0.0.1") githubIntegration, err := newGitHubIntegration("github-my-org", "id", "secret") require.NoError(t, err) diff --git a/lib/auth/integration/integrationv1/github.go b/lib/auth/integration/integrationv1/github.go index 00506cf83b1fa..4283132b2d543 100644 --- a/lib/auth/integration/integrationv1/github.go +++ b/lib/auth/integration/integrationv1/github.go @@ -41,7 +41,7 @@ func (s *Service) GenerateGitHubUserCert(ctx context.Context, in *integrationpb. return nil, trace.AccessDenied("GenerateGitHubUserCert is only available to proxy services") } - cert, err := s.prepareGitHubCert(ctx, in) + cert, err := s.prepareGitHubCert(in) if err != nil { return nil, trace.Wrap(err) } @@ -57,7 +57,7 @@ func (s *Service) GenerateGitHubUserCert(ctx context.Context, in *integrationpb. }, nil } -func (s *Service) prepareGitHubCert(ctx context.Context, in *integrationpb.GenerateGitHubUserCertRequest) (*ssh.Certificate, error) { +func (s *Service) prepareGitHubCert(in *integrationpb.GenerateGitHubUserCertRequest) (*ssh.Certificate, error) { if in.UserId == "" { return nil, trace.BadParameter("missing UserId for GenerateGitHubUserCert") } @@ -70,7 +70,7 @@ func (s *Service) prepareGitHubCert(ctx context.Context, in *integrationpb.Gener } // Sign with user ID set in id@github.com extension. // https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-git-access-to-your-organizations-repositories/about-ssh-certificate-authorities - now := time.Now() + now := s.clock.Now() cert := &ssh.Certificate{ Key: key, CertType: ssh.UserCert, From d361b7363c34edbdc072d734100deb6334dd5d81 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Tue, 3 Dec 2024 10:59:14 -0500 Subject: [PATCH 3/5] minor refactor --- .../integration/credentials/credentials.go | 107 +++++++++++++++ .../credentials/credentials_test.go | 129 ++++++++++++++++++ .../integration/integrationv1/credentials.go | 70 ++-------- .../integration/integrationv1/service_test.go | 7 +- 4 files changed, 250 insertions(+), 63 deletions(-) create mode 100644 lib/auth/integration/credentials/credentials.go create mode 100644 lib/auth/integration/credentials/credentials_test.go diff --git a/lib/auth/integration/credentials/credentials.go b/lib/auth/integration/credentials/credentials.go new file mode 100644 index 0000000000000..2c16a39568395 --- /dev/null +++ b/lib/auth/integration/credentials/credentials.go @@ -0,0 +1,107 @@ +/* + * 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 credentials + +import ( + "context" + "maps" + + "github.com/google/uuid" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" +) + +// Package credentials defines constants and provides helper functions for +// integration credentials. + +const ( + // LabelStaticCredentialsIntegration is the label used to store the + // UUID ref in the static credentials. + LabelStaticCredentialsIntegration = types.TeleportInternalLabelPrefix + types.KindIntegration + // LabelStaticCredentialsPurpose is the label used to store the purpose of + // the static credentials. + LabelStaticCredentialsPurpose = "purpose" + + // PurposeGitHubSSHCA is the label value that indicates the static + // credentials contains the GitHub SSH CA. + PurposeGitHubSSHCA = "github-sshca" + // PurposeGitHubOAuth is the label value that indicates the static + // credentials contains the GitHub OAuth ID and secret. + PurposeGitHubOAuth = "github-oauth" +) + +// NewRef creates a new PluginStaticCredentialsRef that is saved along with the +// integration resource in the backend. The actual credentials are saved as +// PlugStaticCredentials and can only be retrieved by the ref. +func NewRef() *types.PluginStaticCredentialsRef { + return NewRefWithUUID(uuid.NewString()) +} + +// NewRefWithUUID creates a PluginStaticCredentialsRef with provided UUID. +func NewRefWithUUID(uuid string) *types.PluginStaticCredentialsRef { + return &types.PluginStaticCredentialsRef{ + Labels: map[string]string{ + LabelStaticCredentialsIntegration: uuid, + }, + } +} + +// CopyRefLabels copies the labels from the Ref to the actual credentials so the +// credentials can be retrieved using the same labels. +func CopyRefLabels(cred types.PluginStaticCredentials, ref *types.PluginStaticCredentialsRef) { + labels := cred.GetStaticLabels() + if labels == nil { + labels = make(map[string]string) + } + maps.Copy(labels, ref.Labels) + + cred.SetStaticLabels(labels) +} + +// ByLabelsGetter defines an interface to retrieve credentials by labels. +type ByLabelsGetter interface { + // GetPluginStaticCredentialsByLabels will get a list of plugin static credentials resource by matching labels. + GetPluginStaticCredentialsByLabels(ctx context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error) +} + +// GetByPurpose retrieves a credentials based on the provided purpose. +func GetByPurpose(ctx context.Context, ref *types.PluginStaticCredentialsRef, purpose string, getter ByLabelsGetter) (types.PluginStaticCredentials, error) { + if ref == nil { + return nil, trace.BadParameter("missing credentials ref") + } + labels := ref.Labels + if len(labels) == 0 { + return nil, trace.BadParameter("missing labels from credentials ref") + } + labels[LabelStaticCredentialsPurpose] = purpose + + creds, err := getter.GetPluginStaticCredentialsByLabels(ctx, labels) + if err != nil { + return nil, trace.Wrap(err) + } + switch len(creds) { + case 0: + return nil, trace.NotFound("%v credentials not found", purpose) + case 1: + return creds[0], nil + default: + return nil, trace.CompareFailed("expecting one plugin static credentials but got %v", len(creds)) + } +} diff --git a/lib/auth/integration/credentials/credentials_test.go b/lib/auth/integration/credentials/credentials_test.go new file mode 100644 index 0000000000000..64be2f2dc30b8 --- /dev/null +++ b/lib/auth/integration/credentials/credentials_test.go @@ -0,0 +1,129 @@ +/* + * 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 credentials + +import ( + "context" + "fmt" + "maps" + "testing" + + "github.com/google/uuid" + "github.com/gravitational/trace" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +type mockByLabelsGetter struct { + mock.Mock +} + +func (m *mockByLabelsGetter) GetPluginStaticCredentialsByLabels(_ context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error) { + args := m.Called(labels) + creds, ok := args.Get(0).([]types.PluginStaticCredentials) + if ok { + return creds, args.Error(1) + } + return nil, args.Error(1) +} + +func mustMakeCred(t *testing.T, labels map[string]string) types.PluginStaticCredentials { + t.Helper() + cred, err := types.NewPluginStaticCredentials( + types.Metadata{ + Name: uuid.NewString(), + Labels: labels, + }, + types.PluginStaticCredentialsSpecV1{ + Credentials: &types.PluginStaticCredentialsSpecV1_APIToken{ + APIToken: "token", + }, + }, + ) + require.NoError(t, err) + return cred +} + +func TestGetByPurpose(t *testing.T) { + ref := NewRef() + + wantPurpose := "test-found" + notFoundPurpose := "test-not-found" + backendIssuePurpose := "test-backend-issue" + + wantLabels := map[string]string{LabelStaticCredentialsPurpose: wantPurpose} + maps.Copy(wantLabels, ref.Labels) + wantCred := mustMakeCred(t, wantLabels) + + notFoundLabels := map[string]string{LabelStaticCredentialsPurpose: notFoundPurpose} + maps.Copy(notFoundLabels, ref.Labels) + + m := &mockByLabelsGetter{} + m.On("GetPluginStaticCredentialsByLabels", wantLabels).Return([]types.PluginStaticCredentials{wantCred}, nil) + m.On("GetPluginStaticCredentialsByLabels", notFoundLabels).Return([]types.PluginStaticCredentials{}, nil) + m.On("GetPluginStaticCredentialsByLabels", mock.Anything).Return(nil, trace.ConnectionProblem(fmt.Errorf("backend error"), "backend error")) + + tests := []struct { + name string + ref *types.PluginStaticCredentialsRef + purpose string + wantError func(error) bool + wantCred types.PluginStaticCredentials + }{ + { + name: "nil ref", + ref: nil, + purpose: wantPurpose, + wantError: trace.IsBadParameter, + }, + { + name: "success", + ref: ref, + purpose: wantPurpose, + wantCred: wantCred, + }, + { + name: "no creds found", + ref: ref, + purpose: notFoundPurpose, + wantError: trace.IsNotFound, + }, + { + name: "backend issue", + ref: ref, + purpose: backendIssuePurpose, + wantError: trace.IsConnectionProblem, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cred, err := GetByPurpose(context.Background(), test.ref, test.purpose, m) + if test.wantError != nil { + require.True(t, test.wantError(err)) + return + } + + require.NoError(t, err) + require.Equal(t, test.wantCred, cred) + }) + } +} diff --git a/lib/auth/integration/integrationv1/credentials.go b/lib/auth/integration/integrationv1/credentials.go index 52d8fac68f2cb..a8236d88a3b3b 100644 --- a/lib/auth/integration/integrationv1/credentials.go +++ b/lib/auth/integration/integrationv1/credentials.go @@ -20,32 +20,16 @@ package integrationv1 import ( "context" - "maps" "github.com/google/uuid" "github.com/gravitational/trace" integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth/integration/credentials" "github.com/gravitational/teleport/lib/cryptosuites" ) -const ( - // labelStaticCredentialsIntegration is the label used to store the - // UUID ref in the static credentials. - labelStaticCredentialsIntegration = types.TeleportInternalLabelPrefix + types.KindIntegration - // labelStaticCredentialsPurpose is the label used to store the purpose of - // the static credentials. - labelStaticCredentialsPurpose = "purpose" - - // purposeGitHubSSHCA is the label value that indicates the static - // credentials contains the GitHub SSH CA. - purposeGitHubSSHCA = "github-sshca" - // purposeGitHubOAuth is the label value that indicates the static - // credentials contains the GitHub OAuth ID and secret. - purposeGitHubOAuth = "github-oauth" -) - // ExportIntegrationCertAuthorities exports cert authorities for an integration. func (s *Service) ExportIntegrationCertAuthorities(ctx context.Context, in *integrationpb.ExportIntegrationCertAuthoritiesRequest) (*integrationpb.ExportIntegrationCertAuthoritiesResponse, error) { authCtx, err := s.authorizer.Authorize(ctx) @@ -74,24 +58,6 @@ func (s *Service) ExportIntegrationCertAuthorities(ctx context.Context, in *inte } } -func newStaticCredentialsRef(uuid string) *types.PluginStaticCredentialsRef { - return &types.PluginStaticCredentialsRef{ - Labels: map[string]string{ - labelStaticCredentialsIntegration: uuid, - }, - } -} - -func copyRefLabels(cred types.PluginStaticCredentials, ref *types.PluginStaticCredentialsRef) { - labels := cred.GetStaticLabels() - if labels == nil { - labels = make(map[string]string) - } - maps.Copy(labels, ref.Labels) - - cred.SetStaticLabels(labels) -} - func buildGitHubOAuthCredentials(ig types.Integration) (*types.PluginStaticCredentialsV1, error) { if ig.GetCredentials() == nil || ig.GetCredentials().GetIdSecret() == nil { return nil, trace.BadParameter("GitHub integration requires OAuth ID and secret for credentials") @@ -102,7 +68,7 @@ func buildGitHubOAuthCredentials(ig types.Integration) (*types.PluginStaticCrede Metadata: types.Metadata{ Name: uuid.NewString(), Labels: map[string]string{ - labelStaticCredentialsPurpose: purposeGitHubOAuth, + credentials.LabelStaticCredentialsPurpose: credentials.PurposeGitHubOAuth, }, }, }, @@ -127,7 +93,7 @@ func (s *Service) newGitHubSSHCA(ctx context.Context) (*types.PluginStaticCreden Metadata: types.Metadata{ Name: uuid.NewString(), Labels: map[string]string{ - labelStaticCredentialsPurpose: purposeGitHubSSHCA, + credentials.LabelStaticCredentialsPurpose: credentials.PurposeGitHubSSHCA, }, }, }, @@ -161,11 +127,11 @@ func (s *Service) createGitHubCredentials(ctx context.Context, ig types.Integrat } func (s *Service) createStaticCredentials(ctx context.Context, ig types.Integration, creds ...types.PluginStaticCredentials) error { - ref := newStaticCredentialsRef(uuid.NewString()) + ref := credentials.NewRef() for _, cred := range creds { s.logger.DebugContext(ctx, "Creating static credentials", "integration", ig.GetName(), "labels", cred.GetStaticLabels()) - copyRefLabels(cred, ref) + credentials.CopyRefLabels(cred, ref) if err := s.backend.CreatePluginStaticCredentials(ctx, cred); err != nil { return trace.Wrap(err) } @@ -214,7 +180,7 @@ func (s *Service) updateStaticCredentials(ctx context.Context, ig types.Integrat s.logger.DebugContext(ctx, "Updating static credentials", "integration", ig.GetName(), "labels", cred.GetStaticLabels()) // Use same labels to find existing credentials. - copyRefLabels(cred, ref) + credentials.CopyRefLabels(cred, ref) oldCreds, err := s.backend.GetPluginStaticCredentialsByLabels(ctx, cred.GetStaticLabels()) if err != nil { return trace.Wrap(err) @@ -257,35 +223,19 @@ func (s *Service) removeStaticCredentials(ctx context.Context, ig types.Integrat } func (s *Service) getStaticCredentialsWithPurpose(ctx context.Context, ig types.Integration, purpose string) (types.PluginStaticCredentials, error) { - if ig.GetCredentials() == nil || ig.GetCredentials().GetStaticCredentialsRef() == nil { - return nil, trace.BadParameter("missing credentials ref") - } - labels := ig.GetCredentials().GetStaticCredentialsRef().Labels - if len(labels) == 0 { - return nil, trace.BadParameter("missing labels from credentials ref") + if ig.GetCredentials() == nil { + return nil, trace.BadParameter("missing credentials") } - labels[labelStaticCredentialsPurpose] = purpose // TODO(greedy52) use cache - creds, err := s.backend.GetPluginStaticCredentialsByLabels(ctx, labels) - if err != nil { - return nil, trace.Wrap(err) - } - switch len(creds) { - case 0: - return nil, trace.NotFound("%v credentials not found", purpose) - case 1: - return creds[0], nil - default: - return nil, trace.CompareFailed("expecting one plugin static credentials but got %v", len(creds)) - } + return credentials.GetByPurpose(ctx, ig.GetCredentials().GetStaticCredentialsRef(), purpose, s.backend) } func (s *Service) getGitHubCertAuthorities(ctx context.Context, ig types.Integration) (*types.CAKeySet, error) { if ig.GetSubKind() != types.IntegrationSubKindGitHub { return nil, trace.BadParameter("integration is not a GitHub integration") } - creds, err := s.getStaticCredentialsWithPurpose(ctx, ig, purposeGitHubSSHCA) + creds, err := s.getStaticCredentialsWithPurpose(ctx, ig, credentials.PurposeGitHubSSHCA) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/auth/integration/integrationv1/service_test.go b/lib/auth/integration/integrationv1/service_test.go index 3a72d93637c7c..60b8d9d8b28f4 100644 --- a/lib/auth/integration/integrationv1/service_test.go +++ b/lib/auth/integration/integrationv1/service_test.go @@ -30,6 +30,7 @@ import ( integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/externalauditstorage" + "github.com/gravitational/teleport/lib/auth/integration/credentials" "github.com/gravitational/teleport/lib/auth/keystore" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/authz" @@ -420,7 +421,7 @@ func TestIntegrationCRUD(t *testing.T) { refUUID := uuid.NewString() ig.SetCredentials(&types.PluginCredentialsV1{ Credentials: &types.PluginCredentialsV1_StaticCredentialsRef{ - StaticCredentialsRef: newStaticCredentialsRef(refUUID), + StaticCredentialsRef: credentials.NewRefWithUUID(refUUID), }, }) @@ -433,8 +434,8 @@ func TestIntegrationCRUD(t *testing.T) { Metadata: types.Metadata{ Name: igName, Labels: map[string]string{ - labelStaticCredentialsIntegration: refUUID, - labelStaticCredentialsPurpose: "test", + credentials.LabelStaticCredentialsIntegration: refUUID, + credentials.LabelStaticCredentialsPurpose: "test", }, }, }, From 3e4dd8910e986144c23dfd29f80566fc7414f84d Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Tue, 3 Dec 2024 11:16:54 -0500 Subject: [PATCH 4/5] use cache --- .../credentials/credentials_test.go | 68 +++++++++++-------- .../integration/integrationv1/credentials.go | 3 +- .../integrationv1/credentials_test.go | 3 +- lib/auth/integration/integrationv1/service.go | 3 + .../integration/integrationv1/service_test.go | 4 +- 5 files changed, 47 insertions(+), 34 deletions(-) diff --git a/lib/auth/integration/credentials/credentials_test.go b/lib/auth/integration/credentials/credentials_test.go index 64be2f2dc30b8..03cc55c345d6e 100644 --- a/lib/auth/integration/credentials/credentials_test.go +++ b/lib/auth/integration/credentials/credentials_test.go @@ -64,59 +64,69 @@ func mustMakeCred(t *testing.T, labels map[string]string) types.PluginStaticCred func TestGetByPurpose(t *testing.T) { ref := NewRef() - - wantPurpose := "test-found" - notFoundPurpose := "test-not-found" - backendIssuePurpose := "test-backend-issue" - - wantLabels := map[string]string{LabelStaticCredentialsPurpose: wantPurpose} - maps.Copy(wantLabels, ref.Labels) - wantCred := mustMakeCred(t, wantLabels) - - notFoundLabels := map[string]string{LabelStaticCredentialsPurpose: notFoundPurpose} - maps.Copy(notFoundLabels, ref.Labels) - - m := &mockByLabelsGetter{} - m.On("GetPluginStaticCredentialsByLabels", wantLabels).Return([]types.PluginStaticCredentials{wantCred}, nil) - m.On("GetPluginStaticCredentialsByLabels", notFoundLabels).Return([]types.PluginStaticCredentials{}, nil) - m.On("GetPluginStaticCredentialsByLabels", mock.Anything).Return(nil, trace.ConnectionProblem(fmt.Errorf("backend error"), "backend error")) + purpose := "test-found" + labels := map[string]string{LabelStaticCredentialsPurpose: purpose} + maps.Copy(labels, ref.Labels) + cred := mustMakeCred(t, labels) tests := []struct { name string ref *types.PluginStaticCredentialsRef - purpose string + setupMock func(m *mockByLabelsGetter) wantError func(error) bool wantCred types.PluginStaticCredentials }{ { name: "nil ref", ref: nil, - purpose: wantPurpose, wantError: trace.IsBadParameter, }, { - name: "success", - ref: ref, - purpose: wantPurpose, - wantCred: wantCred, + name: "success", + ref: ref, + setupMock: func(m *mockByLabelsGetter) { + m.On("GetPluginStaticCredentialsByLabels", labels). + Return([]types.PluginStaticCredentials{cred}, nil) + }, + wantCred: cred, }, { - name: "no creds found", - ref: ref, - purpose: notFoundPurpose, + name: "no creds found", + ref: ref, + setupMock: func(m *mockByLabelsGetter) { + m.On("GetPluginStaticCredentialsByLabels", labels). + Return([]types.PluginStaticCredentials{}, nil) + }, wantError: trace.IsNotFound, }, { - name: "backend issue", - ref: ref, - purpose: backendIssuePurpose, + name: "too mandy creds found", + ref: ref, + setupMock: func(m *mockByLabelsGetter) { + m.On("GetPluginStaticCredentialsByLabels", labels). + Return([]types.PluginStaticCredentials{cred, mustMakeCred(t, labels)}, nil) + }, + wantError: trace.IsCompareFailed, + }, + { + name: "backend issue", + ref: ref, + setupMock: func(m *mockByLabelsGetter) { + m.On("GetPluginStaticCredentialsByLabels", labels). + Return(nil, trace.ConnectionProblem(fmt.Errorf("backend"), "problem")) + }, wantError: trace.IsConnectionProblem, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - cred, err := GetByPurpose(context.Background(), test.ref, test.purpose, m) + m := &mockByLabelsGetter{} + if test.setupMock != nil { + test.setupMock(m) + } + + cred, err := GetByPurpose(context.Background(), test.ref, purpose, m) if test.wantError != nil { require.True(t, test.wantError(err)) return diff --git a/lib/auth/integration/integrationv1/credentials.go b/lib/auth/integration/integrationv1/credentials.go index a8236d88a3b3b..4d6cb888d2012 100644 --- a/lib/auth/integration/integrationv1/credentials.go +++ b/lib/auth/integration/integrationv1/credentials.go @@ -227,8 +227,7 @@ func (s *Service) getStaticCredentialsWithPurpose(ctx context.Context, ig types. return nil, trace.BadParameter("missing credentials") } - // TODO(greedy52) use cache - return credentials.GetByPurpose(ctx, ig.GetCredentials().GetStaticCredentialsRef(), purpose, s.backend) + return credentials.GetByPurpose(ctx, ig.GetCredentials().GetStaticCredentialsRef(), purpose, s.cache) } func (s *Service) getGitHubCertAuthorities(ctx context.Context, ig types.Integration) (*types.CAKeySet, error) { diff --git a/lib/auth/integration/integrationv1/credentials_test.go b/lib/auth/integration/integrationv1/credentials_test.go index fe90c4705ea4f..6e44eafe0a6cb 100644 --- a/lib/auth/integration/integrationv1/credentials_test.go +++ b/lib/auth/integration/integrationv1/credentials_test.go @@ -22,11 +22,10 @@ import ( "context" "testing" + "github.com/gravitational/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/gravitational/trace" - integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/authz" diff --git a/lib/auth/integration/integrationv1/service.go b/lib/auth/integration/integrationv1/service.go index 1587ed1135950..2bb00fe4543b1 100644 --- a/lib/auth/integration/integrationv1/service.go +++ b/lib/auth/integration/integrationv1/service.go @@ -53,6 +53,9 @@ type Cache interface { // IntegrationsGetter defines methods to access Integration resources. services.IntegrationsGetter + + // GetPluginStaticCredentialsByLabels will get a list of plugin static credentials resource by matching labels. + GetPluginStaticCredentialsByLabels(ctx context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error) } // KeyStoreManager defines methods to get signers using the server's keystore. diff --git a/lib/auth/integration/integrationv1/service_test.go b/lib/auth/integration/integrationv1/service_test.go index 60b8d9d8b28f4..a8cfc66e4e45e 100644 --- a/lib/auth/integration/integrationv1/service_test.go +++ b/lib/auth/integration/integrationv1/service_test.go @@ -627,7 +627,8 @@ func initSvc(t *testing.T, ca types.CertAuthority, clusterName string, proxyPubl PublicAddrs: []string{proxyPublicAddr}, }}, }, - IntegrationsService: *cacheResourceService, + IntegrationsService: *cacheResourceService, + PluginStaticCredentialsService: localCredService, } resourceSvc, err := NewService(&ServiceConfig{ @@ -664,6 +665,7 @@ type mockCache struct { returnErr error local.IntegrationsService + *local.PluginStaticCredentialsService } func (m *mockCache) GetProxies() ([]types.Server, error) { From 21af65a7d085390de6f26cac5582fbd35e9c2a79 Mon Sep 17 00:00:00 2001 From: STeve Huang Date: Tue, 3 Dec 2024 12:15:40 -0500 Subject: [PATCH 5/5] fix build and cache --- lib/auth/authclient/api.go | 3 +++ lib/cache/plugin_static_credentials.go | 2 +- lib/services/plugin_static_credentials.go | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/auth/authclient/api.go b/lib/auth/authclient/api.go index d2e206fd8dd51..2a9d3095b4137 100644 --- a/lib/auth/authclient/api.go +++ b/lib/auth/authclient/api.go @@ -1242,6 +1242,9 @@ type Cache interface { // ListAccountAssignments fetches a paginated list of IdentityCenter Account Assignments ListAccountAssignments(context.Context, int, *pagination.PageRequestToken) ([]services.IdentityCenterAccountAssignment, pagination.NextPageToken, error) + + // GetPluginStaticCredentialsByLabels will get a list of plugin static credentials resource by matching labels. + GetPluginStaticCredentialsByLabels(ctx context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error) } type NodeWrapper struct { diff --git a/lib/cache/plugin_static_credentials.go b/lib/cache/plugin_static_credentials.go index 6fc38713208ca..a756eb419ce35 100644 --- a/lib/cache/plugin_static_credentials.go +++ b/lib/cache/plugin_static_credentials.go @@ -58,7 +58,7 @@ var _ executor[types.PluginStaticCredentials, pluginStaticCredentialsGetter] = p type pluginStaticCredentialsExecutor struct{} func (pluginStaticCredentialsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.PluginStaticCredentials, error) { - return cache.pluginStaticCredentialsCache.GetAllPluginStaticCredentials(ctx) + return cache.PluginStaticCredentials.GetAllPluginStaticCredentials(ctx) } func (pluginStaticCredentialsExecutor) upsert(ctx context.Context, cache *Cache, resource types.PluginStaticCredentials) error { diff --git a/lib/services/plugin_static_credentials.go b/lib/services/plugin_static_credentials.go index cdb32551ef857..7759df5f933c6 100644 --- a/lib/services/plugin_static_credentials.go +++ b/lib/services/plugin_static_credentials.go @@ -44,6 +44,9 @@ type PluginStaticCredentials interface { // DeletePluginStaticCredentials will delete a plugin static credentials resource. DeletePluginStaticCredentials(ctx context.Context, name string) error + + // GetAllPluginStaticCredentials will get all plugin static credentials. + GetAllPluginStaticCredentials(ctx context.Context) ([]types.PluginStaticCredentials, error) } // MarshalPluginStaticCredentials marshals PluginStaticCredentials resource to JSON.