mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Add IssueTeleportWorkloadIdentity RPC (#66691)
* feat(workloadidentityv1): issue teleport workload identity * refactor(workloadidentityv1): code review suggestions * refactor(workloadidentityv1): code review suggestions * chore(workloadidentityv1): clarify comment * Apply suggestions from code review Co-authored-by: Noah Stride <[email protected]> * chore(workloadidentityv1): code review suggestions and update --------- Co-authored-by: Noah Stride <[email protected]>
This commit is contained in:
co-authored by
Noah Stride
parent
54273f91fe
commit
c5cb5c8cd6
@@ -37,6 +37,55 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// IdentityUsage indicates the generated identity usage.
|
||||
type IdentityUsage int32
|
||||
|
||||
const (
|
||||
// IDENTITY_USAGE_UNSPECIFIED is the `unset` default value. This should
|
||||
// never be used in practice.
|
||||
IdentityUsage_IDENTITY_USAGE_UNSPECIFIED IdentityUsage = 0
|
||||
IdentityUsage_IDENTITY_USAGE_APP_ACCESS IdentityUsage = 1
|
||||
)
|
||||
|
||||
// Enum value maps for IdentityUsage.
|
||||
var (
|
||||
IdentityUsage_name = map[int32]string{
|
||||
0: "IDENTITY_USAGE_UNSPECIFIED",
|
||||
1: "IDENTITY_USAGE_APP_ACCESS",
|
||||
}
|
||||
IdentityUsage_value = map[string]int32{
|
||||
"IDENTITY_USAGE_UNSPECIFIED": 0,
|
||||
"IDENTITY_USAGE_APP_ACCESS": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x IdentityUsage) Enum() *IdentityUsage {
|
||||
p := new(IdentityUsage)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x IdentityUsage) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (IdentityUsage) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_teleport_workloadidentity_v1_issuance_service_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (IdentityUsage) Type() protoreflect.EnumType {
|
||||
return &file_teleport_workloadidentity_v1_issuance_service_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x IdentityUsage) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IdentityUsage.Descriptor instead.
|
||||
func (IdentityUsage) EnumDescriptor() ([]byte, []int) {
|
||||
return file_teleport_workloadidentity_v1_issuance_service_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// The parameters for issuing an X509 SVID.
|
||||
type X509SVIDParams struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@@ -786,6 +835,236 @@ func (x *IssueWorkloadIdentitiesResponse) GetCredentials() []*Credential {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The request for the IssueTeleportWorkloadIdentity RPC.
|
||||
type IssueTeleportWorkloadIdentityRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// The parameters for issuing the credential, varying by credential type.
|
||||
//
|
||||
// Types that are valid to be assigned to Credential:
|
||||
//
|
||||
// *IssueTeleportWorkloadIdentityRequest_X509SvidParams
|
||||
// *IssueTeleportWorkloadIdentityRequest_JwtSvidParams
|
||||
Credential isIssueTeleportWorkloadIdentityRequest_Credential `protobuf_oneof:"credential"`
|
||||
// The TTL that the client is requesting for the resulting credentials.
|
||||
// This may be adjusted by the server and therefore the client MUST check the
|
||||
// returned TTL rather than assuming that the requested TTL was granted.
|
||||
RequestedTtl *durationpb.Duration `protobuf:"bytes,4,opt,name=requested_ttl,json=requestedTtl,proto3" json:"requested_ttl,omitempty"`
|
||||
// Usage indicates what the identity will be used for.
|
||||
//
|
||||
// Each usage carries the information necessary for authorization and
|
||||
// credentials issue.
|
||||
//
|
||||
// Types that are valid to be assigned to Usage:
|
||||
//
|
||||
// *IssueTeleportWorkloadIdentityRequest_AppAccess
|
||||
Usage isIssueTeleportWorkloadIdentityRequest_Usage `protobuf_oneof:"usage"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) Reset() {
|
||||
*x = IssueTeleportWorkloadIdentityRequest{}
|
||||
mi := &file_teleport_workloadidentity_v1_issuance_service_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IssueTeleportWorkloadIdentityRequest) ProtoMessage() {}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_teleport_workloadidentity_v1_issuance_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 IssueTeleportWorkloadIdentityRequest.ProtoReflect.Descriptor instead.
|
||||
func (*IssueTeleportWorkloadIdentityRequest) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_workloadidentity_v1_issuance_service_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) GetCredential() isIssueTeleportWorkloadIdentityRequest_Credential {
|
||||
if x != nil {
|
||||
return x.Credential
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) GetX509SvidParams() *X509SVIDParams {
|
||||
if x != nil {
|
||||
if x, ok := x.Credential.(*IssueTeleportWorkloadIdentityRequest_X509SvidParams); ok {
|
||||
return x.X509SvidParams
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) GetJwtSvidParams() *JWTSVIDParams {
|
||||
if x != nil {
|
||||
if x, ok := x.Credential.(*IssueTeleportWorkloadIdentityRequest_JwtSvidParams); ok {
|
||||
return x.JwtSvidParams
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) GetRequestedTtl() *durationpb.Duration {
|
||||
if x != nil {
|
||||
return x.RequestedTtl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) GetUsage() isIssueTeleportWorkloadIdentityRequest_Usage {
|
||||
if x != nil {
|
||||
return x.Usage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityRequest) GetAppAccess() *AppAccessUsage {
|
||||
if x != nil {
|
||||
if x, ok := x.Usage.(*IssueTeleportWorkloadIdentityRequest_AppAccess); ok {
|
||||
return x.AppAccess
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isIssueTeleportWorkloadIdentityRequest_Credential interface {
|
||||
isIssueTeleportWorkloadIdentityRequest_Credential()
|
||||
}
|
||||
|
||||
type IssueTeleportWorkloadIdentityRequest_X509SvidParams struct {
|
||||
// The parameters for issuing an X509 SVID.
|
||||
X509SvidParams *X509SVIDParams `protobuf:"bytes,1,opt,name=x509_svid_params,json=x509SvidParams,proto3,oneof"`
|
||||
}
|
||||
|
||||
type IssueTeleportWorkloadIdentityRequest_JwtSvidParams struct {
|
||||
// The parameters for issuing a JWT SVID.
|
||||
JwtSvidParams *JWTSVIDParams `protobuf:"bytes,2,opt,name=jwt_svid_params,json=jwtSvidParams,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*IssueTeleportWorkloadIdentityRequest_X509SvidParams) isIssueTeleportWorkloadIdentityRequest_Credential() {
|
||||
}
|
||||
|
||||
func (*IssueTeleportWorkloadIdentityRequest_JwtSvidParams) isIssueTeleportWorkloadIdentityRequest_Credential() {
|
||||
}
|
||||
|
||||
type isIssueTeleportWorkloadIdentityRequest_Usage interface {
|
||||
isIssueTeleportWorkloadIdentityRequest_Usage()
|
||||
}
|
||||
|
||||
type IssueTeleportWorkloadIdentityRequest_AppAccess struct {
|
||||
// Used when the identity will be used for accessing apps.
|
||||
AppAccess *AppAccessUsage `protobuf:"bytes,5,opt,name=app_access,json=appAccess,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*IssueTeleportWorkloadIdentityRequest_AppAccess) isIssueTeleportWorkloadIdentityRequest_Usage() {
|
||||
}
|
||||
|
||||
// The response for the IssueTeleportWorkloadIdentity RPC.
|
||||
type IssueTeleportWorkloadIdentityResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// The issued credential.
|
||||
Credential *Credential `protobuf:"bytes,1,opt,name=credential,proto3" json:"credential,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityResponse) Reset() {
|
||||
*x = IssueTeleportWorkloadIdentityResponse{}
|
||||
mi := &file_teleport_workloadidentity_v1_issuance_service_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IssueTeleportWorkloadIdentityResponse) ProtoMessage() {}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_teleport_workloadidentity_v1_issuance_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 IssueTeleportWorkloadIdentityResponse.ProtoReflect.Descriptor instead.
|
||||
func (*IssueTeleportWorkloadIdentityResponse) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_workloadidentity_v1_issuance_service_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *IssueTeleportWorkloadIdentityResponse) GetCredential() *Credential {
|
||||
if x != nil {
|
||||
return x.Credential
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppAccessUsage used when identity will be used for accessing apps.
|
||||
type AppAccessUsage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// ASN.1 DER user identity certificate that the calling Teleport app
|
||||
// service received from the end user.
|
||||
UserCertificate []byte `protobuf:"bytes,1,opt,name=user_certificate,json=userCertificate,proto3" json:"user_certificate,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AppAccessUsage) Reset() {
|
||||
*x = AppAccessUsage{}
|
||||
mi := &file_teleport_workloadidentity_v1_issuance_service_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AppAccessUsage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AppAccessUsage) ProtoMessage() {}
|
||||
|
||||
func (x *AppAccessUsage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_teleport_workloadidentity_v1_issuance_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 AppAccessUsage.ProtoReflect.Descriptor instead.
|
||||
func (*AppAccessUsage) Descriptor() ([]byte, []int) {
|
||||
return file_teleport_workloadidentity_v1_issuance_service_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *AppAccessUsage) GetUserCertificate() []byte {
|
||||
if x != nil {
|
||||
return x.UserCertificate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_teleport_workloadidentity_v1_issuance_service_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_teleport_workloadidentity_v1_issuance_service_proto_rawDesc = "" +
|
||||
@@ -841,10 +1120,29 @@ const file_teleport_workloadidentity_v1_issuance_service_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"credential\"m\n" +
|
||||
"\x1fIssueWorkloadIdentitiesResponse\x12J\n" +
|
||||
"\vcredentials\x18\x01 \x03(\v2(.teleport.workloadidentity.v1.CredentialR\vcredentials2\xcd\x02\n" +
|
||||
"\vcredentials\x18\x01 \x03(\v2(.teleport.workloadidentity.v1.CredentialR\vcredentials\"\xfd\x02\n" +
|
||||
"$IssueTeleportWorkloadIdentityRequest\x12X\n" +
|
||||
"\x10x509_svid_params\x18\x01 \x01(\v2,.teleport.workloadidentity.v1.X509SVIDParamsH\x00R\x0ex509SvidParams\x12U\n" +
|
||||
"\x0fjwt_svid_params\x18\x02 \x01(\v2+.teleport.workloadidentity.v1.JWTSVIDParamsH\x00R\rjwtSvidParams\x12>\n" +
|
||||
"\rrequested_ttl\x18\x04 \x01(\v2\x19.google.protobuf.DurationR\frequestedTtl\x12M\n" +
|
||||
"\n" +
|
||||
"app_access\x18\x05 \x01(\v2,.teleport.workloadidentity.v1.AppAccessUsageH\x01R\tappAccessB\f\n" +
|
||||
"\n" +
|
||||
"credentialB\a\n" +
|
||||
"\x05usage\"q\n" +
|
||||
"%IssueTeleportWorkloadIdentityResponse\x12H\n" +
|
||||
"\n" +
|
||||
"credential\x18\x01 \x01(\v2(.teleport.workloadidentity.v1.CredentialR\n" +
|
||||
"credential\";\n" +
|
||||
"\x0eAppAccessUsage\x12)\n" +
|
||||
"\x10user_certificate\x18\x01 \x01(\fR\x0fuserCertificate*N\n" +
|
||||
"\rIdentityUsage\x12\x1e\n" +
|
||||
"\x1aIDENTITY_USAGE_UNSPECIFIED\x10\x00\x12\x1d\n" +
|
||||
"\x19IDENTITY_USAGE_APP_ACCESS\x10\x012\xf8\x03\n" +
|
||||
"\x1fWorkloadIdentityIssuanceService\x12\x90\x01\n" +
|
||||
"\x15IssueWorkloadIdentity\x12:.teleport.workloadidentity.v1.IssueWorkloadIdentityRequest\x1a;.teleport.workloadidentity.v1.IssueWorkloadIdentityResponse\x12\x96\x01\n" +
|
||||
"\x17IssueWorkloadIdentities\x12<.teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest\x1a=.teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponseBdZbgithub.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1;workloadidentityv1b\x06proto3"
|
||||
"\x17IssueWorkloadIdentities\x12<.teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest\x1a=.teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponse\x12\xa8\x01\n" +
|
||||
"\x1dIssueTeleportWorkloadIdentity\x12B.teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityRequest\x1aC.teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityResponseBdZbgithub.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1;workloadidentityv1b\x06proto3"
|
||||
|
||||
var (
|
||||
file_teleport_workloadidentity_v1_issuance_service_proto_rawDescOnce sync.Once
|
||||
@@ -858,47 +1156,59 @@ func file_teleport_workloadidentity_v1_issuance_service_proto_rawDescGZIP() []by
|
||||
return file_teleport_workloadidentity_v1_issuance_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_teleport_workloadidentity_v1_issuance_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||
var file_teleport_workloadidentity_v1_issuance_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_teleport_workloadidentity_v1_issuance_service_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
|
||||
var file_teleport_workloadidentity_v1_issuance_service_proto_goTypes = []any{
|
||||
(*X509SVIDParams)(nil), // 0: teleport.workloadidentity.v1.X509SVIDParams
|
||||
(*JWTSVIDParams)(nil), // 1: teleport.workloadidentity.v1.JWTSVIDParams
|
||||
(*X509SVIDCredential)(nil), // 2: teleport.workloadidentity.v1.X509SVIDCredential
|
||||
(*JWTSVIDCredential)(nil), // 3: teleport.workloadidentity.v1.JWTSVIDCredential
|
||||
(*Credential)(nil), // 4: teleport.workloadidentity.v1.Credential
|
||||
(*IssueWorkloadIdentityRequest)(nil), // 5: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest
|
||||
(*IssueWorkloadIdentityResponse)(nil), // 6: teleport.workloadidentity.v1.IssueWorkloadIdentityResponse
|
||||
(*LabelSelector)(nil), // 7: teleport.workloadidentity.v1.LabelSelector
|
||||
(*IssueWorkloadIdentitiesRequest)(nil), // 8: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest
|
||||
(*IssueWorkloadIdentitiesResponse)(nil), // 9: teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponse
|
||||
(*durationpb.Duration)(nil), // 10: google.protobuf.Duration
|
||||
(*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
|
||||
(*WorkloadAttrs)(nil), // 12: teleport.workloadidentity.v1.WorkloadAttrs
|
||||
(IdentityUsage)(0), // 0: teleport.workloadidentity.v1.IdentityUsage
|
||||
(*X509SVIDParams)(nil), // 1: teleport.workloadidentity.v1.X509SVIDParams
|
||||
(*JWTSVIDParams)(nil), // 2: teleport.workloadidentity.v1.JWTSVIDParams
|
||||
(*X509SVIDCredential)(nil), // 3: teleport.workloadidentity.v1.X509SVIDCredential
|
||||
(*JWTSVIDCredential)(nil), // 4: teleport.workloadidentity.v1.JWTSVIDCredential
|
||||
(*Credential)(nil), // 5: teleport.workloadidentity.v1.Credential
|
||||
(*IssueWorkloadIdentityRequest)(nil), // 6: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest
|
||||
(*IssueWorkloadIdentityResponse)(nil), // 7: teleport.workloadidentity.v1.IssueWorkloadIdentityResponse
|
||||
(*LabelSelector)(nil), // 8: teleport.workloadidentity.v1.LabelSelector
|
||||
(*IssueWorkloadIdentitiesRequest)(nil), // 9: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest
|
||||
(*IssueWorkloadIdentitiesResponse)(nil), // 10: teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponse
|
||||
(*IssueTeleportWorkloadIdentityRequest)(nil), // 11: teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityRequest
|
||||
(*IssueTeleportWorkloadIdentityResponse)(nil), // 12: teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityResponse
|
||||
(*AppAccessUsage)(nil), // 13: teleport.workloadidentity.v1.AppAccessUsage
|
||||
(*durationpb.Duration)(nil), // 14: google.protobuf.Duration
|
||||
(*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp
|
||||
(*WorkloadAttrs)(nil), // 16: teleport.workloadidentity.v1.WorkloadAttrs
|
||||
}
|
||||
var file_teleport_workloadidentity_v1_issuance_service_proto_depIdxs = []int32{
|
||||
10, // 0: teleport.workloadidentity.v1.Credential.ttl:type_name -> google.protobuf.Duration
|
||||
11, // 1: teleport.workloadidentity.v1.Credential.expires_at:type_name -> google.protobuf.Timestamp
|
||||
2, // 2: teleport.workloadidentity.v1.Credential.x509_svid:type_name -> teleport.workloadidentity.v1.X509SVIDCredential
|
||||
3, // 3: teleport.workloadidentity.v1.Credential.jwt_svid:type_name -> teleport.workloadidentity.v1.JWTSVIDCredential
|
||||
0, // 4: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.x509_svid_params:type_name -> teleport.workloadidentity.v1.X509SVIDParams
|
||||
1, // 5: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.jwt_svid_params:type_name -> teleport.workloadidentity.v1.JWTSVIDParams
|
||||
12, // 6: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.workload_attrs:type_name -> teleport.workloadidentity.v1.WorkloadAttrs
|
||||
10, // 7: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.requested_ttl:type_name -> google.protobuf.Duration
|
||||
4, // 8: teleport.workloadidentity.v1.IssueWorkloadIdentityResponse.credential:type_name -> teleport.workloadidentity.v1.Credential
|
||||
7, // 9: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.label_selectors:type_name -> teleport.workloadidentity.v1.LabelSelector
|
||||
0, // 10: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.x509_svid_params:type_name -> teleport.workloadidentity.v1.X509SVIDParams
|
||||
1, // 11: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.jwt_svid_params:type_name -> teleport.workloadidentity.v1.JWTSVIDParams
|
||||
12, // 12: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.workload_attrs:type_name -> teleport.workloadidentity.v1.WorkloadAttrs
|
||||
10, // 13: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.requested_ttl:type_name -> google.protobuf.Duration
|
||||
4, // 14: teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponse.credentials:type_name -> teleport.workloadidentity.v1.Credential
|
||||
5, // 15: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentity:input_type -> teleport.workloadidentity.v1.IssueWorkloadIdentityRequest
|
||||
8, // 16: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentities:input_type -> teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest
|
||||
6, // 17: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentity:output_type -> teleport.workloadidentity.v1.IssueWorkloadIdentityResponse
|
||||
9, // 18: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentities:output_type -> teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponse
|
||||
17, // [17:19] is the sub-list for method output_type
|
||||
15, // [15:17] is the sub-list for method input_type
|
||||
15, // [15:15] is the sub-list for extension type_name
|
||||
15, // [15:15] is the sub-list for extension extendee
|
||||
0, // [0:15] is the sub-list for field type_name
|
||||
14, // 0: teleport.workloadidentity.v1.Credential.ttl:type_name -> google.protobuf.Duration
|
||||
15, // 1: teleport.workloadidentity.v1.Credential.expires_at:type_name -> google.protobuf.Timestamp
|
||||
3, // 2: teleport.workloadidentity.v1.Credential.x509_svid:type_name -> teleport.workloadidentity.v1.X509SVIDCredential
|
||||
4, // 3: teleport.workloadidentity.v1.Credential.jwt_svid:type_name -> teleport.workloadidentity.v1.JWTSVIDCredential
|
||||
1, // 4: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.x509_svid_params:type_name -> teleport.workloadidentity.v1.X509SVIDParams
|
||||
2, // 5: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.jwt_svid_params:type_name -> teleport.workloadidentity.v1.JWTSVIDParams
|
||||
16, // 6: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.workload_attrs:type_name -> teleport.workloadidentity.v1.WorkloadAttrs
|
||||
14, // 7: teleport.workloadidentity.v1.IssueWorkloadIdentityRequest.requested_ttl:type_name -> google.protobuf.Duration
|
||||
5, // 8: teleport.workloadidentity.v1.IssueWorkloadIdentityResponse.credential:type_name -> teleport.workloadidentity.v1.Credential
|
||||
8, // 9: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.label_selectors:type_name -> teleport.workloadidentity.v1.LabelSelector
|
||||
1, // 10: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.x509_svid_params:type_name -> teleport.workloadidentity.v1.X509SVIDParams
|
||||
2, // 11: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.jwt_svid_params:type_name -> teleport.workloadidentity.v1.JWTSVIDParams
|
||||
16, // 12: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.workload_attrs:type_name -> teleport.workloadidentity.v1.WorkloadAttrs
|
||||
14, // 13: teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest.requested_ttl:type_name -> google.protobuf.Duration
|
||||
5, // 14: teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponse.credentials:type_name -> teleport.workloadidentity.v1.Credential
|
||||
1, // 15: teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityRequest.x509_svid_params:type_name -> teleport.workloadidentity.v1.X509SVIDParams
|
||||
2, // 16: teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityRequest.jwt_svid_params:type_name -> teleport.workloadidentity.v1.JWTSVIDParams
|
||||
14, // 17: teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityRequest.requested_ttl:type_name -> google.protobuf.Duration
|
||||
13, // 18: teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityRequest.app_access:type_name -> teleport.workloadidentity.v1.AppAccessUsage
|
||||
5, // 19: teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityResponse.credential:type_name -> teleport.workloadidentity.v1.Credential
|
||||
6, // 20: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentity:input_type -> teleport.workloadidentity.v1.IssueWorkloadIdentityRequest
|
||||
9, // 21: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentities:input_type -> teleport.workloadidentity.v1.IssueWorkloadIdentitiesRequest
|
||||
11, // 22: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueTeleportWorkloadIdentity:input_type -> teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityRequest
|
||||
7, // 23: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentity:output_type -> teleport.workloadidentity.v1.IssueWorkloadIdentityResponse
|
||||
10, // 24: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueWorkloadIdentities:output_type -> teleport.workloadidentity.v1.IssueWorkloadIdentitiesResponse
|
||||
12, // 25: teleport.workloadidentity.v1.WorkloadIdentityIssuanceService.IssueTeleportWorkloadIdentity:output_type -> teleport.workloadidentity.v1.IssueTeleportWorkloadIdentityResponse
|
||||
23, // [23:26] is the sub-list for method output_type
|
||||
20, // [20:23] is the sub-list for method input_type
|
||||
20, // [20:20] is the sub-list for extension type_name
|
||||
20, // [20:20] is the sub-list for extension extendee
|
||||
0, // [0:20] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_teleport_workloadidentity_v1_issuance_service_proto_init() }
|
||||
@@ -919,18 +1229,24 @@ func file_teleport_workloadidentity_v1_issuance_service_proto_init() {
|
||||
(*IssueWorkloadIdentitiesRequest_X509SvidParams)(nil),
|
||||
(*IssueWorkloadIdentitiesRequest_JwtSvidParams)(nil),
|
||||
}
|
||||
file_teleport_workloadidentity_v1_issuance_service_proto_msgTypes[10].OneofWrappers = []any{
|
||||
(*IssueTeleportWorkloadIdentityRequest_X509SvidParams)(nil),
|
||||
(*IssueTeleportWorkloadIdentityRequest_JwtSvidParams)(nil),
|
||||
(*IssueTeleportWorkloadIdentityRequest_AppAccess)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_teleport_workloadidentity_v1_issuance_service_proto_rawDesc), len(file_teleport_workloadidentity_v1_issuance_service_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 10,
|
||||
NumEnums: 1,
|
||||
NumMessages: 13,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_teleport_workloadidentity_v1_issuance_service_proto_goTypes,
|
||||
DependencyIndexes: file_teleport_workloadidentity_v1_issuance_service_proto_depIdxs,
|
||||
EnumInfos: file_teleport_workloadidentity_v1_issuance_service_proto_enumTypes,
|
||||
MessageInfos: file_teleport_workloadidentity_v1_issuance_service_proto_msgTypes,
|
||||
}.Build()
|
||||
File_teleport_workloadidentity_v1_issuance_service_proto = out.File
|
||||
|
||||
@@ -33,8 +33,9 @@ import (
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
WorkloadIdentityIssuanceService_IssueWorkloadIdentity_FullMethodName = "/teleport.workloadidentity.v1.WorkloadIdentityIssuanceService/IssueWorkloadIdentity"
|
||||
WorkloadIdentityIssuanceService_IssueWorkloadIdentities_FullMethodName = "/teleport.workloadidentity.v1.WorkloadIdentityIssuanceService/IssueWorkloadIdentities"
|
||||
WorkloadIdentityIssuanceService_IssueWorkloadIdentity_FullMethodName = "/teleport.workloadidentity.v1.WorkloadIdentityIssuanceService/IssueWorkloadIdentity"
|
||||
WorkloadIdentityIssuanceService_IssueWorkloadIdentities_FullMethodName = "/teleport.workloadidentity.v1.WorkloadIdentityIssuanceService/IssueWorkloadIdentities"
|
||||
WorkloadIdentityIssuanceService_IssueTeleportWorkloadIdentity_FullMethodName = "/teleport.workloadidentity.v1.WorkloadIdentityIssuanceService/IssueTeleportWorkloadIdentity"
|
||||
)
|
||||
|
||||
// WorkloadIdentityIssuanceServiceClient is the client API for WorkloadIdentityIssuanceService service.
|
||||
@@ -51,6 +52,10 @@ type WorkloadIdentityIssuanceServiceClient interface {
|
||||
// IssueWorkloadIdentities can issue multiple workload identity credentials
|
||||
// based on label selectors for the WorkloadIdentity resources.
|
||||
IssueWorkloadIdentities(ctx context.Context, in *IssueWorkloadIdentitiesRequest, opts ...grpc.CallOption) (*IssueWorkloadIdentitiesResponse, error)
|
||||
// IssueTeleportWorkloadIdentity issues a workload identity credential for the
|
||||
// requested Teleport usage. This request cannot be performed by users, only
|
||||
// by Teleport services.
|
||||
IssueTeleportWorkloadIdentity(ctx context.Context, in *IssueTeleportWorkloadIdentityRequest, opts ...grpc.CallOption) (*IssueTeleportWorkloadIdentityResponse, error)
|
||||
}
|
||||
|
||||
type workloadIdentityIssuanceServiceClient struct {
|
||||
@@ -81,6 +86,16 @@ func (c *workloadIdentityIssuanceServiceClient) IssueWorkloadIdentities(ctx cont
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *workloadIdentityIssuanceServiceClient) IssueTeleportWorkloadIdentity(ctx context.Context, in *IssueTeleportWorkloadIdentityRequest, opts ...grpc.CallOption) (*IssueTeleportWorkloadIdentityResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(IssueTeleportWorkloadIdentityResponse)
|
||||
err := c.cc.Invoke(ctx, WorkloadIdentityIssuanceService_IssueTeleportWorkloadIdentity_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// WorkloadIdentityIssuanceServiceServer is the server API for WorkloadIdentityIssuanceService service.
|
||||
// All implementations must embed UnimplementedWorkloadIdentityIssuanceServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -95,6 +110,10 @@ type WorkloadIdentityIssuanceServiceServer interface {
|
||||
// IssueWorkloadIdentities can issue multiple workload identity credentials
|
||||
// based on label selectors for the WorkloadIdentity resources.
|
||||
IssueWorkloadIdentities(context.Context, *IssueWorkloadIdentitiesRequest) (*IssueWorkloadIdentitiesResponse, error)
|
||||
// IssueTeleportWorkloadIdentity issues a workload identity credential for the
|
||||
// requested Teleport usage. This request cannot be performed by users, only
|
||||
// by Teleport services.
|
||||
IssueTeleportWorkloadIdentity(context.Context, *IssueTeleportWorkloadIdentityRequest) (*IssueTeleportWorkloadIdentityResponse, error)
|
||||
mustEmbedUnimplementedWorkloadIdentityIssuanceServiceServer()
|
||||
}
|
||||
|
||||
@@ -111,6 +130,9 @@ func (UnimplementedWorkloadIdentityIssuanceServiceServer) IssueWorkloadIdentity(
|
||||
func (UnimplementedWorkloadIdentityIssuanceServiceServer) IssueWorkloadIdentities(context.Context, *IssueWorkloadIdentitiesRequest) (*IssueWorkloadIdentitiesResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method IssueWorkloadIdentities not implemented")
|
||||
}
|
||||
func (UnimplementedWorkloadIdentityIssuanceServiceServer) IssueTeleportWorkloadIdentity(context.Context, *IssueTeleportWorkloadIdentityRequest) (*IssueTeleportWorkloadIdentityResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method IssueTeleportWorkloadIdentity not implemented")
|
||||
}
|
||||
func (UnimplementedWorkloadIdentityIssuanceServiceServer) mustEmbedUnimplementedWorkloadIdentityIssuanceServiceServer() {
|
||||
}
|
||||
func (UnimplementedWorkloadIdentityIssuanceServiceServer) testEmbeddedByValue() {}
|
||||
@@ -169,6 +191,24 @@ func _WorkloadIdentityIssuanceService_IssueWorkloadIdentities_Handler(srv interf
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _WorkloadIdentityIssuanceService_IssueTeleportWorkloadIdentity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(IssueTeleportWorkloadIdentityRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(WorkloadIdentityIssuanceServiceServer).IssueTeleportWorkloadIdentity(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: WorkloadIdentityIssuanceService_IssueTeleportWorkloadIdentity_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WorkloadIdentityIssuanceServiceServer).IssueTeleportWorkloadIdentity(ctx, req.(*IssueTeleportWorkloadIdentityRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// WorkloadIdentityIssuanceService_ServiceDesc is the grpc.ServiceDesc for WorkloadIdentityIssuanceService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -184,6 +224,10 @@ var WorkloadIdentityIssuanceService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "IssueWorkloadIdentities",
|
||||
Handler: _WorkloadIdentityIssuanceService_IssueWorkloadIdentities_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "IssueTeleportWorkloadIdentity",
|
||||
Handler: _WorkloadIdentityIssuanceService_IssueTeleportWorkloadIdentity_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "teleport/workloadidentity/v1/issuance_service.proto",
|
||||
|
||||
@@ -32,6 +32,10 @@ service WorkloadIdentityIssuanceService {
|
||||
// IssueWorkloadIdentities can issue multiple workload identity credentials
|
||||
// based on label selectors for the WorkloadIdentity resources.
|
||||
rpc IssueWorkloadIdentities(IssueWorkloadIdentitiesRequest) returns (IssueWorkloadIdentitiesResponse);
|
||||
// IssueTeleportWorkloadIdentity issues a workload identity credential for the
|
||||
// requested Teleport usage. This request cannot be performed by users, only
|
||||
// by Teleport services.
|
||||
rpc IssueTeleportWorkloadIdentity(IssueTeleportWorkloadIdentityRequest) returns (IssueTeleportWorkloadIdentityResponse);
|
||||
}
|
||||
|
||||
// The parameters for issuing an X509 SVID.
|
||||
@@ -158,3 +162,47 @@ message IssueWorkloadIdentitiesResponse {
|
||||
// The issued credentials.
|
||||
repeated Credential credentials = 1;
|
||||
}
|
||||
|
||||
// IdentityUsage indicates the generated identity usage.
|
||||
enum IdentityUsage {
|
||||
// IDENTITY_USAGE_UNSPECIFIED is the `unset` default value. This should
|
||||
// never be used in practice.
|
||||
IDENTITY_USAGE_UNSPECIFIED = 0;
|
||||
IDENTITY_USAGE_APP_ACCESS = 1;
|
||||
}
|
||||
|
||||
// The request for the IssueTeleportWorkloadIdentity RPC.
|
||||
message IssueTeleportWorkloadIdentityRequest {
|
||||
// The parameters for issuing the credential, varying by credential type.
|
||||
oneof credential {
|
||||
// The parameters for issuing an X509 SVID.
|
||||
X509SVIDParams x509_svid_params = 1;
|
||||
// The parameters for issuing a JWT SVID.
|
||||
JWTSVIDParams jwt_svid_params = 2;
|
||||
}
|
||||
// The TTL that the client is requesting for the resulting credentials.
|
||||
// This may be adjusted by the server and therefore the client MUST check the
|
||||
// returned TTL rather than assuming that the requested TTL was granted.
|
||||
google.protobuf.Duration requested_ttl = 4;
|
||||
// Usage indicates what the identity will be used for.
|
||||
//
|
||||
// Each usage carries the information necessary for authorization and
|
||||
// credentials issue.
|
||||
oneof usage {
|
||||
// Used when the identity will be used for accessing apps.
|
||||
AppAccessUsage app_access = 5;
|
||||
}
|
||||
}
|
||||
|
||||
// The response for the IssueTeleportWorkloadIdentity RPC.
|
||||
message IssueTeleportWorkloadIdentityResponse {
|
||||
// The issued credential.
|
||||
Credential credential = 1;
|
||||
}
|
||||
|
||||
// AppAccessUsage used when identity will be used for accessing apps.
|
||||
message AppAccessUsage {
|
||||
// ASN.1 DER user identity certificate that the calling Teleport app
|
||||
// service received from the end user.
|
||||
bytes user_certificate = 1;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"net/url"
|
||||
@@ -38,11 +39,15 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
apiproto "github.com/gravitational/teleport/api/client/proto"
|
||||
apidefaults "github.com/gravitational/teleport/api/defaults"
|
||||
traitv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1"
|
||||
workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1"
|
||||
"github.com/gravitational/teleport/api/observability/tracing"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
apievents "github.com/gravitational/teleport/api/types/events"
|
||||
"github.com/gravitational/teleport/api/utils/clientutils"
|
||||
apiworkloadidentity "github.com/gravitational/teleport/api/workloadidentity"
|
||||
"github.com/gravitational/teleport/lib/authz"
|
||||
"github.com/gravitational/teleport/lib/events"
|
||||
"github.com/gravitational/teleport/lib/jwt"
|
||||
@@ -88,6 +93,7 @@ type issuerCache interface {
|
||||
GetProxies() ([]types.Server, error)
|
||||
ListProxyServers(context.Context, int, string) ([]types.Server, string, error)
|
||||
GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool) (types.CertAuthority, error)
|
||||
ListResources(ctx context.Context, req apiproto.ListResourcesRequest) (*types.ListResourcesResponse, error)
|
||||
}
|
||||
|
||||
// IssuanceServiceConfig holds configuration options for the IssuanceService.
|
||||
@@ -134,7 +140,6 @@ func NewIssuanceService(cfg *IssuanceServiceConfig) (*IssuanceService, error) {
|
||||
return nil, trace.BadParameter("key store is required")
|
||||
case cfg.OverrideGetter == nil:
|
||||
return nil, trace.BadParameter("override getter is required")
|
||||
|
||||
case cfg.ClusterName == "":
|
||||
return nil, trace.BadParameter("cluster name is required")
|
||||
case cfg.GetSigstorePolicyEvaluator == nil:
|
||||
@@ -239,7 +244,7 @@ func (s *IssuanceService) IssueWorkloadIdentity(
|
||||
var cred *workloadidentityv1pb.Credential
|
||||
switch v := req.GetCredential().(type) {
|
||||
case *workloadidentityv1pb.IssueWorkloadIdentityRequest_X509SvidParams:
|
||||
ca, chain, err := s.getX509CA(ctx, v.X509SvidParams.GetUseIssuerOverrides())
|
||||
ca, chain, err := s.getX509CA(ctx, types.SPIFFECA, v.X509SvidParams.GetUseIssuerOverrides())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "fetching X509 SPIFFE CA")
|
||||
}
|
||||
@@ -349,7 +354,7 @@ func (s *IssuanceService) IssueWorkloadIdentities(
|
||||
creds := make([]*workloadidentityv1pb.Credential, 0, len(shouldIssue))
|
||||
switch v := req.GetCredential().(type) {
|
||||
case *workloadidentityv1pb.IssueWorkloadIdentitiesRequest_X509SvidParams:
|
||||
ca, chain, err := s.getX509CA(ctx, v.X509SvidParams.GetUseIssuerOverrides())
|
||||
ca, chain, err := s.getX509CA(ctx, types.SPIFFECA, v.X509SvidParams.GetUseIssuerOverrides())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "fetching CA to sign X509 SVID")
|
||||
}
|
||||
@@ -411,6 +416,281 @@ func (s *IssuanceService) IssueWorkloadIdentities(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IssueTeleportWorkloadIdentity issues a workload identity credential for the
|
||||
// requested Teleport usage. This request cannot be performed by users, only
|
||||
// by Teleport services.
|
||||
//
|
||||
// Optional future work: define the audit semantics for the workload identity
|
||||
// issued by this RPC. This path currently does not emit per-issuance audit
|
||||
// events because the app-access usage issues short-lived credentials
|
||||
// periodically, up to once every 5 minutes per app session.
|
||||
//
|
||||
// If explicit audit is needed here, prefer a dedicated lower-volume Teleport
|
||||
// workload event over reusing SPIFFESVIDIssued, and review event frequency
|
||||
// before enabling it.
|
||||
func (s *IssuanceService) IssueTeleportWorkloadIdentity(
|
||||
ctx context.Context,
|
||||
req *workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest,
|
||||
) (*workloadidentityv1pb.IssueTeleportWorkloadIdentityResponse, error) {
|
||||
authCtx, err := s.authorizer.Authorize(ctx)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
builtin, ok := authCtx.Identity.(authz.BuiltinRole)
|
||||
if !ok {
|
||||
return nil, trace.AccessDenied("only Teleport services can execute this request")
|
||||
}
|
||||
|
||||
switch usage := req.Usage.(type) {
|
||||
case *workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess:
|
||||
if !authz.HasBuiltinRole(*authCtx, string(types.RoleApp)) {
|
||||
return nil, trace.AccessDenied("only app services can issue workload identity for app access")
|
||||
}
|
||||
switch credParams := req.Credential.(type) {
|
||||
case *workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams:
|
||||
return s.issueAppAccessX509Identity(ctx, builtin.GetServerID(), req, usage.AppAccess, credParams)
|
||||
default:
|
||||
return nil, trace.BadParameter("app access usage only supports issuing x509 credentials")
|
||||
}
|
||||
default:
|
||||
return nil, trace.BadParameter("invalid identity usage")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IssuanceService) issueAppAccessX509Identity(
|
||||
ctx context.Context,
|
||||
hostID string,
|
||||
req *workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest,
|
||||
appUsage *workloadidentityv1pb.AppAccessUsage,
|
||||
credParams *workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams,
|
||||
) (_ *workloadidentityv1pb.IssueTeleportWorkloadIdentityResponse, err error) {
|
||||
ctx, span := tracer.Start(ctx, "IssuanceService/issueAppAccessX509Identity")
|
||||
defer func() { tracing.EndSpan(span, err) }()
|
||||
|
||||
route, userIdentity, err := s.routeToAppFromCert(ctx, appUsage.GetUserCertificate())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// We cannot rely on RouteToApp.Name because:
|
||||
// 1. It might not be available.
|
||||
// 2. The value is not guarateed to be from the correct app, and in some
|
||||
// flows requestors can provide arbitrary values.
|
||||
app, err := s.getApp(ctx, hostID, route)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "unable to locate app")
|
||||
}
|
||||
|
||||
pubKey, err := x509.ParsePKIXPublicKey(credParams.X509SvidParams.PublicKey)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "parsing public key")
|
||||
}
|
||||
|
||||
// AppClient CA doesn't support workload identity CA override.
|
||||
ca, chain, err := s.getX509CA(ctx, types.AppClientCA, false /* useIssuerOverrides */)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "fetching X509 SPIFFE CA")
|
||||
}
|
||||
|
||||
_, notBefore, notAfter, ttl := calculateTTL(
|
||||
ctx,
|
||||
s.logger,
|
||||
s.clock,
|
||||
req.RequestedTtl.AsDuration(),
|
||||
// Cap the cert TTL at the session expiry, plus an alloweance for clock
|
||||
// drift that mirrors [veirfyCertValidityWithSkew]. Without the
|
||||
// allowance session that are within the skew window would yield app
|
||||
// certs with a near-zero (or negative) TTL.
|
||||
s.clock.Until(userIdentity.Expires)+certVerifyClockSkewAllowance,
|
||||
)
|
||||
|
||||
// This intentionally uses cluster name as part of the the SPIFFE trust
|
||||
// domain, matching regular workload identity issuance behavior. Clusters
|
||||
// with names that are not valid SPIFFE trust domain are expected to fail
|
||||
// issuance.
|
||||
td, err := spiffeid.TrustDomainFromString(
|
||||
apiworkloadidentity.NewInternalAppTrustDomain(s.clusterName),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "cluster name cannot be used as SPIFFE ID trust domain")
|
||||
}
|
||||
|
||||
spiffeID, err := spiffeid.FromSegments(td, "app", app.GetName())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "app name contains invalid format and cannot be used as SPIFFE ID")
|
||||
}
|
||||
|
||||
certSerial, err := generateCertSerial()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "generating certificate serial")
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(
|
||||
rand.Reader,
|
||||
x509Template(
|
||||
certSerial,
|
||||
notBefore,
|
||||
notAfter,
|
||||
spiffeID,
|
||||
nil, /* dnsSANs */
|
||||
&workloadidentityv1pb.X509DistinguishedNameTemplate{
|
||||
CommonName: userIdentity.Username,
|
||||
},
|
||||
),
|
||||
ca.Cert,
|
||||
pubKey,
|
||||
ca.Signer,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return &workloadidentityv1pb.IssueTeleportWorkloadIdentityResponse{
|
||||
Credential: &workloadidentityv1pb.Credential{
|
||||
SpiffeId: spiffeID.String(),
|
||||
|
||||
ExpiresAt: timestamppb.New(notAfter),
|
||||
Ttl: durationpb.New(ttl),
|
||||
|
||||
Credential: &workloadidentityv1pb.Credential_X509Svid{
|
||||
X509Svid: &workloadidentityv1pb.X509SVIDCredential{
|
||||
Cert: certBytes,
|
||||
SerialNumber: serialString(certSerial),
|
||||
Chain: chain,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// routeToAppFromCert validates the certificate and extracts the RouteToApp info.
|
||||
//
|
||||
// We intentionally validate only the user certificate signature and expiration,
|
||||
// and app routing metadata here. The app service is expected to call this RPC
|
||||
// only after it has already authenticated the app request and verified that the
|
||||
// referenced AppSession still exists.
|
||||
//
|
||||
// Because it won't look up the AppSession, callers must not use this as the
|
||||
// AppSession validity check.
|
||||
func (s *IssuanceService) routeToAppFromCert(ctx context.Context, rawCert []byte) (tlsca.RouteToApp, *tlsca.Identity, error) {
|
||||
cert, err := x509.ParseCertificate(rawCert)
|
||||
if err != nil {
|
||||
return tlsca.RouteToApp{}, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
ca, err := s.cache.GetCertAuthority(ctx, types.CertAuthID{
|
||||
Type: types.UserCA,
|
||||
DomainName: s.clusterName,
|
||||
}, false)
|
||||
if err != nil {
|
||||
return tlsca.RouteToApp{}, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
for _, key := range ca.GetTrustedTLSKeyPairs() {
|
||||
if ok := roots.AppendCertsFromPEM(key.Cert); !ok {
|
||||
return tlsca.RouteToApp{}, nil, trace.BadParameter("unable to build UserCA pool to validate certificate")
|
||||
}
|
||||
}
|
||||
|
||||
// cert.Verify applies CurrentTime to every certificate in the chain and
|
||||
// does not support separate NotBefore/NotAfter leeway for the leaf
|
||||
// certificate.
|
||||
//
|
||||
// For this reason we perform the certificate verify in two steps:
|
||||
// 1. Verify certificate validity using custom clock-skew policy.
|
||||
// 2. Perform the remaining verification on the cert using a shallow copy
|
||||
// of the certificate that will pass the expiry verification.
|
||||
//
|
||||
// The shallow copy usage still checks the original TBSCertificate bytes,
|
||||
// the only effective difference is the Verify's leaf time check.
|
||||
now := s.clock.Now()
|
||||
if err := verifyCertValidityWithSkew(cert, now); err != nil {
|
||||
return tlsca.RouteToApp{}, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
verifyCert := *cert
|
||||
verifyCert.NotAfter = now.Add(time.Second)
|
||||
verifyCert.NotBefore = now.Add(-time.Second)
|
||||
|
||||
if _, err := verifyCert.Verify(x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
CurrentTime: now,
|
||||
KeyUsages: []x509.ExtKeyUsage{
|
||||
// Extensions added by tlsca.
|
||||
// See https://github.com/gravitational/teleport/blob/master/lib/tlsca/ca.go
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
},
|
||||
}); err != nil {
|
||||
return tlsca.RouteToApp{}, nil, trace.Wrap(err, "requestor provided an invalid certificate")
|
||||
}
|
||||
|
||||
identity, err := tlsca.FromSubject(cert.Subject, cert.NotAfter)
|
||||
if err != nil {
|
||||
return tlsca.RouteToApp{}, nil, trace.Wrap(err, "requestor provided a certificate that doesn't contain a Teleport identity")
|
||||
}
|
||||
|
||||
route, err := identity.GetRouteToApp()
|
||||
if err != nil {
|
||||
return tlsca.RouteToApp{}, nil, trace.Wrap(err, "identity must be from an app access session")
|
||||
}
|
||||
|
||||
// Ensure the required information is available on the identity.
|
||||
//
|
||||
// This acts as a guardrail for any inconsistent or malformed identity and
|
||||
// shouldn't happen. This validation just prevents generating workload
|
||||
// identity that is inconsistent as well.
|
||||
if route.ClusterName != s.clusterName {
|
||||
return tlsca.RouteToApp{}, nil, trace.BadParameter("cannot request workload identity for a different cluster")
|
||||
}
|
||||
|
||||
return route, identity, nil
|
||||
}
|
||||
|
||||
// getApp retrieves the app definition from an AppServer registered by the
|
||||
// requesting host. Requiring the requestor (identified by hostID) to be one
|
||||
// of the app's serving AppServers ensures the app definition we sign comes from
|
||||
// a service authoritative for the app, not from an arbitrary cached entry.
|
||||
//
|
||||
// This is not a strong authorization check: any app service that advertises the
|
||||
// target app can satisfy this lookup, even if it is not actually serving the
|
||||
// specific session referenced by the app session certificate.
|
||||
func (s *IssuanceService) getApp(ctx context.Context, hostID string, route tlsca.RouteToApp) (types.Application, error) {
|
||||
appServersIter := clientutils.Resources(ctx, func(ctx context.Context, limit int, startKey string) ([]types.ResourceWithLabels, string, error) {
|
||||
resp, err := s.cache.ListResources(ctx, apiproto.ListResourcesRequest{
|
||||
Namespace: apidefaults.Namespace,
|
||||
ResourceType: types.KindAppServer,
|
||||
Limit: int32(limit),
|
||||
StartKey: startKey,
|
||||
PredicateExpression: fmt.Sprintf(`resource.spec.public_addr == %q`, route.PublicAddr),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err)
|
||||
}
|
||||
return resp.Resources, resp.NextKey, nil
|
||||
})
|
||||
|
||||
for resource, err := range appServersIter {
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "unabled to complete app servers list")
|
||||
}
|
||||
|
||||
appServer, ok := resource.(types.AppServer)
|
||||
if !ok {
|
||||
return nil, trace.BadParameter("expected types.AppServer, got: %T", resource)
|
||||
}
|
||||
|
||||
// Ensure the requesting app service can serve the app.
|
||||
if appServer.GetHostID() == hostID {
|
||||
return appServer.GetApp(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, trace.NotFound("application at %q not found for app service %q", route.PublicAddr, hostID)
|
||||
}
|
||||
|
||||
func generateCertSerial() (*big.Int, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
return rand.Int(rand.Reader, serialNumberLimit)
|
||||
@@ -470,6 +750,7 @@ func x509Template(
|
||||
|
||||
func (s *IssuanceService) getX509CA(
|
||||
ctx context.Context,
|
||||
caType types.CertAuthType,
|
||||
useIssuerOverrides bool,
|
||||
) (_ *tlsca.CertAuthority, _ [][]byte, err error) {
|
||||
ctx, span := tracer.Start(ctx, "IssuanceService/getX509CA")
|
||||
@@ -477,7 +758,7 @@ func (s *IssuanceService) getX509CA(
|
||||
|
||||
const loadKeysTrue = true
|
||||
ca, err := s.cache.GetCertAuthority(ctx, types.CertAuthID{
|
||||
Type: types.SPIFFECA,
|
||||
Type: caType,
|
||||
DomainName: s.clusterName,
|
||||
}, loadKeysTrue)
|
||||
|
||||
@@ -952,3 +1233,27 @@ func serialString(serial *big.Int) string {
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// certVerifyClockSkewAllowance is the amount of leeway added to the
|
||||
// certificate's expiration status check to allow for clock drift.
|
||||
const certVerifyClockSkewAllowance = 1 * time.Minute
|
||||
|
||||
func verifyCertValidityWithSkew(cert *x509.Certificate, now time.Time) error {
|
||||
if now.Add(certVerifyClockSkewAllowance).Before(cert.NotBefore) {
|
||||
return x509.CertificateInvalidError{
|
||||
Cert: cert,
|
||||
Reason: x509.Expired,
|
||||
Detail: "certificate is not yet valid",
|
||||
}
|
||||
}
|
||||
|
||||
if now.Add(-certVerifyClockSkewAllowance).After(cert.NotAfter) {
|
||||
return x509.CertificateInvalidError{
|
||||
Cert: cert,
|
||||
Reason: x509.Expired,
|
||||
Detail: "certificate has expired",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,11 +19,13 @@ package workloadidentityv1_test
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -42,6 +44,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
@@ -59,6 +62,8 @@ import (
|
||||
"github.com/gravitational/teleport/api/types/events"
|
||||
apiutils "github.com/gravitational/teleport/api/utils"
|
||||
"github.com/gravitational/teleport/api/utils/keys"
|
||||
"github.com/gravitational/teleport/api/utils/tlsutils"
|
||||
apiworkloadidentity "github.com/gravitational/teleport/api/workloadidentity"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/auth/authtest"
|
||||
"github.com/gravitational/teleport/lib/auth/keystore"
|
||||
@@ -116,18 +121,21 @@ func newTestTLSServer(t testing.TB, opts ...authtest.TestTLSServerOption) (*auth
|
||||
type issuanceTestPack struct {
|
||||
srv *authtest.TLSServer
|
||||
eventRecorder *eventstest.MockRecorderEmitter
|
||||
clock clockwork.Clock
|
||||
clock *clockwork.FakeClock
|
||||
sigstorePolicyEvaluator *mockSigstorePolicyEvaluator
|
||||
|
||||
issuer string
|
||||
spiffeX509CAPool *x509.CertPool
|
||||
spiffeJWTSigner crypto.Signer
|
||||
spiffeJWTSignerKID string
|
||||
issuer string
|
||||
spiffeX509CAPool *x509.CertPool
|
||||
spiffeJWTSigner crypto.Signer
|
||||
spiffeJWTSignerKID string
|
||||
appClientX509CAPool *x509.CertPool
|
||||
}
|
||||
|
||||
func newIssuanceTestPack(t *testing.T, ctx context.Context) *issuanceTestPack {
|
||||
srv, eventRecorder := newTestTLSServer(t)
|
||||
clock := srv.Auth().GetClock()
|
||||
fakeClock, ok := clock.(*clockwork.FakeClock)
|
||||
require.True(t, ok, "expected to be a clockwork.FakeClock but got %T", clock)
|
||||
|
||||
// Upsert a fake proxy to ensure we have a public address to use for the
|
||||
// issuer.
|
||||
@@ -157,6 +165,14 @@ func newIssuanceTestPack(t *testing.T, ctx context.Context) *issuanceTestPack {
|
||||
require.NoError(t, err)
|
||||
kid, err := libjwt.KeyID(jwtSigner.Public())
|
||||
require.NoError(t, err)
|
||||
// Fetch X509 AppClient CA for validation of signature later
|
||||
appClientX509CA, err := srv.Auth().GetCertAuthority(ctx, types.CertAuthID{
|
||||
Type: types.AppClientCA,
|
||||
DomainName: srv.ClusterName(),
|
||||
}, false)
|
||||
require.NoError(t, err)
|
||||
appClientX509CAPool, err := services.CertPool(appClientX509CA)
|
||||
require.NoError(t, err)
|
||||
|
||||
sigstorePolicyEvaluator := newMockSigstorePolicyEvaluator(t)
|
||||
srv.Auth().SetSigstorePolicyEvaluator(sigstorePolicyEvaluator)
|
||||
@@ -164,12 +180,13 @@ func newIssuanceTestPack(t *testing.T, ctx context.Context) *issuanceTestPack {
|
||||
return &issuanceTestPack{
|
||||
srv: srv,
|
||||
eventRecorder: eventRecorder,
|
||||
clock: clock,
|
||||
clock: fakeClock,
|
||||
sigstorePolicyEvaluator: sigstorePolicyEvaluator,
|
||||
issuer: wantIssuer,
|
||||
spiffeX509CAPool: spiffeX509CAPool,
|
||||
spiffeJWTSigner: jwtSigner,
|
||||
spiffeJWTSignerKID: kid,
|
||||
appClientX509CAPool: appClientX509CAPool,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1658,6 +1675,348 @@ func TestIssueWorkloadIdentities(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueTeleportWorkloadIdentity(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const appServiceID = "test-server"
|
||||
const appName = "panel"
|
||||
ctx := t.Context()
|
||||
tp := newIssuanceTestPack(t, ctx)
|
||||
clusterName := tp.srv.ClusterName()
|
||||
|
||||
// Register an application.
|
||||
app, err := types.NewAppV3(types.Metadata{
|
||||
Name: appName,
|
||||
}, types.AppSpecV3{
|
||||
URI: "localhost",
|
||||
PublicAddr: appName + "." + clusterName,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
server, err := types.NewAppServerV3FromApp(app, "app", appServiceID)
|
||||
require.NoError(t, err)
|
||||
_, err = tp.srv.Auth().UpsertApplicationServer(t.Context(), server)
|
||||
require.NoError(t, err)
|
||||
|
||||
// This alice is the one accessing the Teleport resource.
|
||||
alice, _, err := authtest.CreateUserAndRole(
|
||||
tp.srv.Auth(),
|
||||
"alice",
|
||||
[]string{},
|
||||
[]types.Rule{
|
||||
types.NewRule(
|
||||
types.KindApp,
|
||||
[]string{types.VerbRead, types.VerbList},
|
||||
),
|
||||
},
|
||||
authtest.WithRoleMutator(func(role types.Role) {
|
||||
role.SetAppLabels(types.Allow, types.Labels{
|
||||
"*": []string{"*"},
|
||||
})
|
||||
}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// To create an expired session, we issue the cert, and the advance the clock.
|
||||
aliceExpiredClt, err := tp.srv.NewClient(authtest.TestUser(alice.GetName()))
|
||||
require.NoError(t, err)
|
||||
aliceExpiredSessionCertBytes, aliceSessionExpiresAt := createAppSessionCertBytes(t, aliceExpiredClt, appName, alice.GetName())
|
||||
// Add a bit more time to "escape" the clock skew condition.
|
||||
tp.clock.Advance(tp.clock.Until(aliceSessionExpiresAt) + 10*time.Minute)
|
||||
|
||||
aliceClt, err := tp.srv.NewClient(authtest.TestUser(alice.GetName()))
|
||||
require.NoError(t, err)
|
||||
aliceSessionCertBytes, aliceSessionExpirity := createAppSessionCertBytes(t, aliceClt, appName, alice.GetName())
|
||||
require.True(t, aliceSessionExpirity.After(tp.clock.Now()))
|
||||
|
||||
appServiceClt, err := tp.srv.NewClient(authtest.TestServerID(types.RoleApp, appServiceID))
|
||||
require.NoError(t, err)
|
||||
// This app service is not serving the requested app.
|
||||
nonServingAppServiceClt, err := tp.srv.NewClient(authtest.TestServerID(types.RoleApp, "random-host-id"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate a keypair to generate x509 SVIDs for.
|
||||
workloadKey, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
|
||||
require.NoError(t, err)
|
||||
workloadKeyPubBytes, err := x509.MarshalPKIXPublicKey(workloadKey.Public())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Those clients are used by the test cases.
|
||||
userClt, err := tp.srv.NewClient(authtest.TestAdmin())
|
||||
require.NoError(t, err)
|
||||
otherServiceClt, err := tp.srv.NewClient(authtest.TestBuiltin(types.RoleDatabase))
|
||||
require.NoError(t, err)
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
clt *authclient.Client
|
||||
req *workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest
|
||||
expectErr require.ErrorAssertionFunc
|
||||
expectResp require.ValueAssertionFunc
|
||||
}{
|
||||
"app service generates workload identity for a session": {
|
||||
clt: appServiceClt,
|
||||
req: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest{
|
||||
Credential: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams{
|
||||
X509SvidParams: &workloadidentityv1pb.X509SVIDParams{
|
||||
PublicKey: workloadKeyPubBytes,
|
||||
},
|
||||
},
|
||||
Usage: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess{
|
||||
AppAccess: &workloadidentityv1pb.AppAccessUsage{
|
||||
UserCertificate: aliceSessionCertBytes,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: require.NoError,
|
||||
expectResp: func(tt require.TestingT, i1 any, i2 ...any) {
|
||||
resp, _ := i1.(*workloadidentityv1pb.IssueTeleportWorkloadIdentityResponse)
|
||||
// Check X509 cert actually included and signed.
|
||||
cert, err := x509.ParseCertificate(resp.Credential.GetX509Svid().GetCert())
|
||||
require.NoError(tt, err)
|
||||
// Check included public key matches
|
||||
require.Equal(tt, workloadKey.Public(), cert.PublicKey)
|
||||
_, err = cert.Verify(x509.VerifyOptions{
|
||||
// Must be signed by AppClient CA
|
||||
Roots: tp.appClientX509CAPool,
|
||||
CurrentTime: tp.srv.Auth().GetClock().Now(),
|
||||
})
|
||||
require.NoError(tt, err)
|
||||
// Check SPIFFE ID contains the trusted domain, and app name.
|
||||
require.Len(tt, cert.URIs, 1, "certificate doesn't contain SPIFFE ID")
|
||||
spiffeID, err := spiffeid.FromString(cert.URIs[0].String())
|
||||
require.NoError(tt, err)
|
||||
require.Len(t, cert.URIs, 1)
|
||||
td, err := spiffeid.TrustDomainFromString(
|
||||
apiworkloadidentity.NewInternalAppTrustDomain(tp.srv.ClusterName()),
|
||||
)
|
||||
require.NoError(tt, err)
|
||||
require.True(tt, spiffeID.MemberOf(td), "expected the returned SPIFFE ID %q to be part of internal trust domain %q", spiffeID, td)
|
||||
},
|
||||
},
|
||||
"other service cannot generate workload identity for app access": {
|
||||
clt: otherServiceClt,
|
||||
req: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest{
|
||||
Credential: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams{
|
||||
X509SvidParams: &workloadidentityv1pb.X509SVIDParams{
|
||||
PublicKey: workloadKeyPubBytes,
|
||||
},
|
||||
},
|
||||
Usage: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess{
|
||||
AppAccess: &workloadidentityv1pb.AppAccessUsage{
|
||||
UserCertificate: aliceSessionCertBytes,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: func(tt require.TestingT, err error, i ...any) {
|
||||
require.ErrorContains(tt, err, "only app services can issue workload identity for app access", i...)
|
||||
},
|
||||
expectResp: require.Nil,
|
||||
},
|
||||
"app service not serving app cannot generate workload identity for requested session": {
|
||||
clt: nonServingAppServiceClt,
|
||||
req: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest{
|
||||
Credential: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams{
|
||||
X509SvidParams: &workloadidentityv1pb.X509SVIDParams{
|
||||
PublicKey: workloadKeyPubBytes,
|
||||
},
|
||||
},
|
||||
Usage: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess{
|
||||
AppAccess: &workloadidentityv1pb.AppAccessUsage{
|
||||
UserCertificate: aliceSessionCertBytes,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: func(tt require.TestingT, err error, i ...any) {
|
||||
require.ErrorContains(tt, err, "unable to locate app", i...)
|
||||
},
|
||||
expectResp: require.Nil,
|
||||
},
|
||||
"app service generates workload identity for an expired session": {
|
||||
clt: appServiceClt,
|
||||
req: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest{
|
||||
Credential: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams{
|
||||
X509SvidParams: &workloadidentityv1pb.X509SVIDParams{
|
||||
PublicKey: workloadKeyPubBytes,
|
||||
},
|
||||
},
|
||||
Usage: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess{
|
||||
AppAccess: &workloadidentityv1pb.AppAccessUsage{
|
||||
UserCertificate: aliceExpiredSessionCertBytes,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: func(tt require.TestingT, err error, i ...any) {
|
||||
require.ErrorContains(tt, err, "certificate has expired", i...)
|
||||
},
|
||||
expectResp: require.Nil,
|
||||
},
|
||||
"users cannot request the RPC": {
|
||||
clt: userClt,
|
||||
req: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest{
|
||||
Credential: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams{
|
||||
X509SvidParams: &workloadidentityv1pb.X509SVIDParams{
|
||||
PublicKey: workloadKeyPubBytes,
|
||||
},
|
||||
},
|
||||
Usage: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess{
|
||||
AppAccess: &workloadidentityv1pb.AppAccessUsage{
|
||||
UserCertificate: aliceSessionCertBytes,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: func(tt require.TestingT, err error, i ...any) {
|
||||
require.ErrorContains(tt, err, "only app services can issue workload identity for app access", i...)
|
||||
},
|
||||
expectResp: require.Nil,
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
c := workloadidentityv1pb.NewWorkloadIdentityIssuanceServiceClient(
|
||||
tc.clt.GetConnection(),
|
||||
)
|
||||
|
||||
res, err := c.IssueTeleportWorkloadIdentity(ctx, tc.req)
|
||||
tc.expectErr(t, err)
|
||||
tc.expectResp(t, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssueTeleportWorkloadIdentityRejectsExpiredUserCA given an app access
|
||||
// workload identity request with an expired UserCA certificate, the server must
|
||||
// reject the request.
|
||||
func TestIssueTeleportWorkloadIdentityRejectsExpiredUserCA(t *testing.T) {
|
||||
const appServiceID = "test-server"
|
||||
const appName = "panel"
|
||||
|
||||
ctx := t.Context()
|
||||
tp := newIssuanceTestPack(t, ctx)
|
||||
clusterName := tp.srv.ClusterName()
|
||||
|
||||
app, err := types.NewAppV3(types.Metadata{
|
||||
Name: appName,
|
||||
}, types.AppSpecV3{
|
||||
URI: "localhost",
|
||||
PublicAddr: appName + "." + clusterName,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
server, err := types.NewAppServerV3FromApp(app, "app", appServiceID)
|
||||
require.NoError(t, err)
|
||||
_, err = tp.srv.Auth().UpsertApplicationServer(t.Context(), server)
|
||||
require.NoError(t, err)
|
||||
|
||||
// This alice is the one accessing the Teleport resource.
|
||||
alice, _, err := authtest.CreateUserAndRole(
|
||||
tp.srv.Auth(),
|
||||
"alice",
|
||||
[]string{},
|
||||
[]types.Rule{
|
||||
types.NewRule(
|
||||
types.KindApp,
|
||||
[]string{types.VerbRead, types.VerbList},
|
||||
),
|
||||
},
|
||||
authtest.WithRoleMutator(func(role types.Role) {
|
||||
role.SetAppLabels(types.Allow, types.Labels{
|
||||
"*": []string{"*"},
|
||||
})
|
||||
}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
aliceClt, err := tp.srv.NewClient(authtest.TestUser(alice.GetName()))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Important: create the app session cert while the User CA is still valid.
|
||||
aliceSessionCertBytes, _ := createAppSessionCertBytes(t, aliceClt, appName, alice.GetName())
|
||||
|
||||
sessionCert, err := x509.ParseCertificate(aliceSessionCertBytes)
|
||||
require.NoError(t, err)
|
||||
require.True(t, tp.clock.Now().After(sessionCert.NotBefore))
|
||||
require.True(t, tp.clock.Now().Before(sessionCert.NotAfter))
|
||||
|
||||
appServiceClt, err := tp.srv.NewClient(authtest.TestServerID(types.RoleApp, appServiceID))
|
||||
require.NoError(t, err)
|
||||
|
||||
workloadKey, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
|
||||
require.NoError(t, err)
|
||||
workloadKeyPubBytes, err := x509.MarshalPKIXPublicKey(workloadKey.Public())
|
||||
require.NoError(t, err)
|
||||
|
||||
c := workloadidentityv1pb.NewWorkloadIdentityIssuanceServiceClient(appServiceClt.GetConnection())
|
||||
|
||||
// Here we ensure the certificate is OK and returns without errors.
|
||||
res, err := c.IssueTeleportWorkloadIdentity(ctx, &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest{
|
||||
Credential: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams{
|
||||
X509SvidParams: &workloadidentityv1pb.X509SVIDParams{
|
||||
PublicKey: workloadKeyPubBytes,
|
||||
},
|
||||
},
|
||||
Usage: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess{
|
||||
AppAccess: &workloadidentityv1pb.AppAccessUsage{
|
||||
UserCertificate: aliceSessionCertBytes,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
|
||||
// Now replace the trusted User CA cert with an expired cert using the same key.
|
||||
userCA, err := tp.srv.Auth().GetCertAuthority(ctx, types.CertAuthID{
|
||||
Type: types.UserCA,
|
||||
DomainName: clusterName,
|
||||
}, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
activeKeys := userCA.GetActiveKeys()
|
||||
userCATLSKey := activeKeys.TLS[0]
|
||||
|
||||
oldUserCACert, err := tlsca.ParseCertificatePEM(userCATLSKey.Cert)
|
||||
require.NoError(t, err)
|
||||
|
||||
userCASigner, err := keys.ParsePrivateKey(userCATLSKey.Key)
|
||||
require.NoError(t, err)
|
||||
|
||||
expiredUserCACert := *oldUserCACert
|
||||
expiredUserCACert.NotBefore = tp.clock.Now().Add(-2 * time.Hour)
|
||||
expiredUserCACert.NotAfter = tp.clock.Now().Add(-time.Minute)
|
||||
|
||||
expiredUserCADER, err := x509.CreateCertificate(
|
||||
rand.Reader,
|
||||
&expiredUserCACert,
|
||||
&expiredUserCACert,
|
||||
userCASigner.Public(),
|
||||
userCASigner,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
userCATLSKey.Cert = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: expiredUserCADER,
|
||||
})
|
||||
activeKeys.TLS[0] = userCATLSKey
|
||||
require.NoError(t, userCA.SetActiveKeys(activeKeys))
|
||||
require.NoError(t, tp.srv.Auth().UpsertCertAuthority(ctx, userCA))
|
||||
|
||||
// This time, the UserCA is expected to be expired, meaning it should fail
|
||||
// the certificate verification.
|
||||
res, err = c.IssueTeleportWorkloadIdentity(ctx, &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest{
|
||||
Credential: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_X509SvidParams{
|
||||
X509SvidParams: &workloadidentityv1pb.X509SVIDParams{
|
||||
PublicKey: workloadKeyPubBytes,
|
||||
},
|
||||
},
|
||||
Usage: &workloadidentityv1pb.IssueTeleportWorkloadIdentityRequest_AppAccess{
|
||||
AppAccess: &workloadidentityv1pb.AppAccessUsage{
|
||||
UserCertificate: aliceSessionCertBytes,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
require.ErrorContains(t, err, "requestor provided an invalid certificate")
|
||||
require.ErrorContains(t, err, "certificate has expired")
|
||||
require.Nil(t, res)
|
||||
}
|
||||
|
||||
func TestResourceService_CreateWorkloadIdentity(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, eventRecorder := newTestTLSServer(t)
|
||||
@@ -3717,3 +4076,23 @@ func TestRevocationService_CRL(t *testing.T) {
|
||||
require.NoError(t, <-rpcErrCh)
|
||||
})
|
||||
}
|
||||
|
||||
// createAppSessionCertBytes creates an app session and returns the session
|
||||
// certificate.
|
||||
func createAppSessionCertBytes(t *testing.T, clt *authclient.Client, appName, username string) ([]byte, time.Time) {
|
||||
t.Helper()
|
||||
|
||||
clusterName, err := clt.GetClusterName(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
aliceSession, err := clt.CreateAppSession(t.Context(), &apiproto.CreateAppSessionRequest{
|
||||
AppName: appName,
|
||||
Username: username,
|
||||
PublicAddr: appName + "." + clusterName.GetClusterName(),
|
||||
ClusterName: clusterName.GetClusterName(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
cert, err := tlsutils.ParseCertificatePEM(aliceSession.GetTLSCert())
|
||||
require.NoError(t, err)
|
||||
return cert.Raw, aliceSession.Expiry()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user