diff --git a/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go b/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go index be64a262a9353..3fcba9f65074d 100644 --- a/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go +++ b/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go @@ -527,6 +527,74 @@ func (x *WorkloadIdentityRules) GetAllow() []*WorkloadIdentityRule { return nil } +// Template for an X509 Distinguished Name (DN). +// Each field is optional, and, if provided, supports templating using attributes. +type X509DistinguishedNameTemplate struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Common Name (CN) - 2.5.4.3 + // If empty, the RDN will be omitted from the DN. + CommonName string `protobuf:"bytes,1,opt,name=common_name,json=commonName,proto3" json:"common_name,omitempty"` + // Organization (O) - 2.5.4.10 + // If empty, the RDN will be omitted from the DN. + Organization string `protobuf:"bytes,2,opt,name=organization,proto3" json:"organization,omitempty"` + // Organizational Unit (OU) - 2.5.4.11 + // If empty, the RDN will be omitted from the DN. + OrganizationalUnit string `protobuf:"bytes,3,opt,name=organizational_unit,json=organizationalUnit,proto3" json:"organizational_unit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *X509DistinguishedNameTemplate) Reset() { + *x = X509DistinguishedNameTemplate{} + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *X509DistinguishedNameTemplate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*X509DistinguishedNameTemplate) ProtoMessage() {} + +func (x *X509DistinguishedNameTemplate) ProtoReflect() protoreflect.Message { + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[8] + 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 X509DistinguishedNameTemplate.ProtoReflect.Descriptor instead. +func (*X509DistinguishedNameTemplate) Descriptor() ([]byte, []int) { + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{8} +} + +func (x *X509DistinguishedNameTemplate) GetCommonName() string { + if x != nil { + return x.CommonName + } + return "" +} + +func (x *X509DistinguishedNameTemplate) GetOrganization() string { + if x != nil { + return x.Organization + } + return "" +} + +func (x *X509DistinguishedNameTemplate) GetOrganizationalUnit() string { + if x != nil { + return x.OrganizationalUnit + } + return "" +} + // Configuration specific to the issuance of X509-SVIDs. type WorkloadIdentitySPIFFEX509 struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -534,14 +602,22 @@ type WorkloadIdentitySPIFFEX509 struct { // X509-SVID issued using this WorkloadIdentity. // // Each entry in this list supports templating using attributes. - DnsSans []string `protobuf:"bytes,1,rep,name=dns_sans,json=dnsSans,proto3" json:"dns_sans,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + DnsSans []string `protobuf:"bytes,1,rep,name=dns_sans,json=dnsSans,proto3" json:"dns_sans,omitempty"` + // Used to configure the Subject Distinguished Name (DN) of the X509-SVID. + // + // In most circumstances, it is recommended to prefer relying on the SPIFFE ID + // encoded in the URI SAN. However, the Subject DN may be needed to support + // legacy systems designed for X509 and not SPIFFE/WIMSE. + // + // If not provided, the X509-SVID will be issued with an empty Subject DN. + SubjectTemplate *X509DistinguishedNameTemplate `protobuf:"bytes,2,opt,name=subject_template,json=subjectTemplate,proto3" json:"subject_template,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *WorkloadIdentitySPIFFEX509) Reset() { *x = WorkloadIdentitySPIFFEX509{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[8] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -553,7 +629,7 @@ func (x *WorkloadIdentitySPIFFEX509) String() string { func (*WorkloadIdentitySPIFFEX509) ProtoMessage() {} func (x *WorkloadIdentitySPIFFEX509) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[8] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -566,7 +642,7 @@ func (x *WorkloadIdentitySPIFFEX509) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkloadIdentitySPIFFEX509.ProtoReflect.Descriptor instead. func (*WorkloadIdentitySPIFFEX509) Descriptor() ([]byte, []int) { - return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{8} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{9} } func (x *WorkloadIdentitySPIFFEX509) GetDnsSans() []string { @@ -576,6 +652,13 @@ func (x *WorkloadIdentitySPIFFEX509) GetDnsSans() []string { return nil } +func (x *WorkloadIdentitySPIFFEX509) GetSubjectTemplate() *X509DistinguishedNameTemplate { + if x != nil { + return x.SubjectTemplate + } + return nil +} + // Configuration pertaining to the issuance of SPIFFE-compatible workload // identity credentials. type WorkloadIdentitySPIFFE struct { @@ -598,7 +681,7 @@ type WorkloadIdentitySPIFFE struct { func (x *WorkloadIdentitySPIFFE) Reset() { *x = WorkloadIdentitySPIFFE{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[9] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -610,7 +693,7 @@ func (x *WorkloadIdentitySPIFFE) String() string { func (*WorkloadIdentitySPIFFE) ProtoMessage() {} func (x *WorkloadIdentitySPIFFE) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[9] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -623,7 +706,7 @@ func (x *WorkloadIdentitySPIFFE) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkloadIdentitySPIFFE.ProtoReflect.Descriptor instead. func (*WorkloadIdentitySPIFFE) Descriptor() ([]byte, []int) { - return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{9} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{10} } func (x *WorkloadIdentitySPIFFE) GetId() string { @@ -661,7 +744,7 @@ type WorkloadIdentitySpec struct { func (x *WorkloadIdentitySpec) Reset() { *x = WorkloadIdentitySpec{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[10] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -673,7 +756,7 @@ func (x *WorkloadIdentitySpec) String() string { func (*WorkloadIdentitySpec) ProtoMessage() {} func (x *WorkloadIdentitySpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[10] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -686,7 +769,7 @@ func (x *WorkloadIdentitySpec) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkloadIdentitySpec.ProtoReflect.Descriptor instead. func (*WorkloadIdentitySpec) Descriptor() ([]byte, []int) { - return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{10} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{11} } func (x *WorkloadIdentitySpec) GetRules() *WorkloadIdentityRules { @@ -779,38 +862,54 @@ var file_teleport_workloadidentity_v1_resource_proto_rawDesc = []byte{ 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x22, 0x37, - 0x0a, 0x1a, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x53, 0x50, 0x49, 0x46, 0x46, 0x45, 0x58, 0x35, 0x30, 0x39, 0x12, 0x19, 0x0a, 0x08, - 0x64, 0x6e, 0x73, 0x5f, 0x73, 0x61, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, - 0x64, 0x6e, 0x73, 0x53, 0x61, 0x6e, 0x73, 0x22, 0x8a, 0x01, 0x0a, 0x16, 0x57, 0x6f, 0x72, 0x6b, - 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x50, 0x49, 0x46, - 0x46, 0x45, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x68, 0x69, 0x6e, 0x74, 0x12, 0x4c, 0x0a, 0x04, 0x78, 0x35, 0x30, 0x39, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x53, 0x50, 0x49, 0x46, 0x46, 0x45, 0x58, 0x35, 0x30, 0x39, 0x52, 0x04, - 0x78, 0x35, 0x30, 0x39, 0x22, 0xaf, 0x01, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, - 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x12, 0x49, 0x0a, - 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74, - 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, - 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, - 0x73, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x70, 0x69, 0x66, - 0x66, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x50, 0x49, 0x46, 0x46, 0x45, 0x52, 0x06, - 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x42, 0x64, 0x5a, 0x62, 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, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, - 0x61, 0x64, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x22, 0x95, + 0x01, 0x0a, 0x1d, 0x58, 0x35, 0x30, 0x39, 0x44, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x75, 0x69, + 0x73, 0x68, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x13, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x12, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x55, 0x6e, 0x69, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x1a, 0x57, 0x6f, 0x72, 0x6b, 0x6c, + 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x50, 0x49, 0x46, 0x46, + 0x45, 0x58, 0x35, 0x30, 0x39, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, 0x73, 0x61, 0x6e, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x53, 0x61, 0x6e, 0x73, + 0x12, 0x66, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x58, 0x35, 0x30, 0x39, 0x44, 0x69, + 0x73, 0x74, 0x69, 0x6e, 0x67, 0x75, 0x69, 0x73, 0x68, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x16, 0x57, 0x6f, 0x72, + 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x50, 0x49, + 0x46, 0x46, 0x45, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x68, 0x69, 0x6e, 0x74, 0x12, 0x4c, 0x0a, 0x04, 0x78, 0x35, 0x30, 0x39, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x50, 0x49, 0x46, 0x46, 0x45, 0x58, 0x35, 0x30, 0x39, 0x52, + 0x04, 0x78, 0x35, 0x30, 0x39, 0x22, 0xaf, 0x01, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, + 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x12, 0x49, + 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, + 0x64, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, + 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, + 0x65, 0x73, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x06, 0x73, 0x70, 0x69, + 0x66, 0x66, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, + 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x50, 0x49, 0x46, 0x46, 0x45, 0x52, + 0x06, 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x42, 0x64, 0x5a, 0x62, 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, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x77, 0x6f, 0x72, 0x6b, 0x6c, + 0x6f, 0x61, 0x64, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -825,7 +924,7 @@ func file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP() []byte { return file_teleport_workloadidentity_v1_resource_proto_rawDescData } -var file_teleport_workloadidentity_v1_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_teleport_workloadidentity_v1_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_teleport_workloadidentity_v1_resource_proto_goTypes = []any{ (*WorkloadIdentity)(nil), // 0: teleport.workloadidentity.v1.WorkloadIdentity (*WorkloadIdentityConditionEq)(nil), // 1: teleport.workloadidentity.v1.WorkloadIdentityConditionEq @@ -835,28 +934,30 @@ var file_teleport_workloadidentity_v1_resource_proto_goTypes = []any{ (*WorkloadIdentityCondition)(nil), // 5: teleport.workloadidentity.v1.WorkloadIdentityCondition (*WorkloadIdentityRule)(nil), // 6: teleport.workloadidentity.v1.WorkloadIdentityRule (*WorkloadIdentityRules)(nil), // 7: teleport.workloadidentity.v1.WorkloadIdentityRules - (*WorkloadIdentitySPIFFEX509)(nil), // 8: teleport.workloadidentity.v1.WorkloadIdentitySPIFFEX509 - (*WorkloadIdentitySPIFFE)(nil), // 9: teleport.workloadidentity.v1.WorkloadIdentitySPIFFE - (*WorkloadIdentitySpec)(nil), // 10: teleport.workloadidentity.v1.WorkloadIdentitySpec - (*v1.Metadata)(nil), // 11: teleport.header.v1.Metadata + (*X509DistinguishedNameTemplate)(nil), // 8: teleport.workloadidentity.v1.X509DistinguishedNameTemplate + (*WorkloadIdentitySPIFFEX509)(nil), // 9: teleport.workloadidentity.v1.WorkloadIdentitySPIFFEX509 + (*WorkloadIdentitySPIFFE)(nil), // 10: teleport.workloadidentity.v1.WorkloadIdentitySPIFFE + (*WorkloadIdentitySpec)(nil), // 11: teleport.workloadidentity.v1.WorkloadIdentitySpec + (*v1.Metadata)(nil), // 12: teleport.header.v1.Metadata } var file_teleport_workloadidentity_v1_resource_proto_depIdxs = []int32{ - 11, // 0: teleport.workloadidentity.v1.WorkloadIdentity.metadata:type_name -> teleport.header.v1.Metadata - 10, // 1: teleport.workloadidentity.v1.WorkloadIdentity.spec:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySpec + 12, // 0: teleport.workloadidentity.v1.WorkloadIdentity.metadata:type_name -> teleport.header.v1.Metadata + 11, // 1: teleport.workloadidentity.v1.WorkloadIdentity.spec:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySpec 1, // 2: teleport.workloadidentity.v1.WorkloadIdentityCondition.eq:type_name -> teleport.workloadidentity.v1.WorkloadIdentityConditionEq 2, // 3: teleport.workloadidentity.v1.WorkloadIdentityCondition.not_eq:type_name -> teleport.workloadidentity.v1.WorkloadIdentityConditionNotEq 3, // 4: teleport.workloadidentity.v1.WorkloadIdentityCondition.in:type_name -> teleport.workloadidentity.v1.WorkloadIdentityConditionIn 4, // 5: teleport.workloadidentity.v1.WorkloadIdentityCondition.not_in:type_name -> teleport.workloadidentity.v1.WorkloadIdentityConditionNotIn 5, // 6: teleport.workloadidentity.v1.WorkloadIdentityRule.conditions:type_name -> teleport.workloadidentity.v1.WorkloadIdentityCondition 6, // 7: teleport.workloadidentity.v1.WorkloadIdentityRules.allow:type_name -> teleport.workloadidentity.v1.WorkloadIdentityRule - 8, // 8: teleport.workloadidentity.v1.WorkloadIdentitySPIFFE.x509:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySPIFFEX509 - 7, // 9: teleport.workloadidentity.v1.WorkloadIdentitySpec.rules:type_name -> teleport.workloadidentity.v1.WorkloadIdentityRules - 9, // 10: teleport.workloadidentity.v1.WorkloadIdentitySpec.spiffe:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySPIFFE - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 8, // 8: teleport.workloadidentity.v1.WorkloadIdentitySPIFFEX509.subject_template:type_name -> teleport.workloadidentity.v1.X509DistinguishedNameTemplate + 9, // 9: teleport.workloadidentity.v1.WorkloadIdentitySPIFFE.x509:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySPIFFEX509 + 7, // 10: teleport.workloadidentity.v1.WorkloadIdentitySpec.rules:type_name -> teleport.workloadidentity.v1.WorkloadIdentityRules + 10, // 11: teleport.workloadidentity.v1.WorkloadIdentitySpec.spiffe:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySPIFFE + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_teleport_workloadidentity_v1_resource_proto_init() } @@ -876,7 +977,7 @@ func file_teleport_workloadidentity_v1_resource_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_workloadidentity_v1_resource_proto_rawDesc, NumEnums: 0, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/workloadidentity/v1/resource.proto b/api/proto/teleport/workloadidentity/v1/resource.proto index ad4cc03cf4c24..c1b663ea816ed 100644 --- a/api/proto/teleport/workloadidentity/v1/resource.proto +++ b/api/proto/teleport/workloadidentity/v1/resource.proto @@ -94,6 +94,20 @@ message WorkloadIdentityRules { repeated WorkloadIdentityRule allow = 1; } +// Template for an X509 Distinguished Name (DN). +// Each field is optional, and, if provided, supports templating using attributes. +message X509DistinguishedNameTemplate { + // Common Name (CN) - 2.5.4.3 + // If empty, the RDN will be omitted from the DN. + string common_name = 1; + // Organization (O) - 2.5.4.10 + // If empty, the RDN will be omitted from the DN. + string organization = 2; + // Organizational Unit (OU) - 2.5.4.11 + // If empty, the RDN will be omitted from the DN. + string organizational_unit = 3; +} + // Configuration specific to the issuance of X509-SVIDs. message WorkloadIdentitySPIFFEX509 { // The DNS Subject Alternative Names (SANs) that should be included in an @@ -101,6 +115,15 @@ message WorkloadIdentitySPIFFEX509 { // // Each entry in this list supports templating using attributes. repeated string dns_sans = 1; + + // Used to configure the Subject Distinguished Name (DN) of the X509-SVID. + // + // In most circumstances, it is recommended to prefer relying on the SPIFFE ID + // encoded in the URI SAN. However, the Subject DN may be needed to support + // legacy systems designed for X509 and not SPIFFE/WIMSE. + // + // If not provided, the X509-SVID will be issued with an empty Subject DN. + X509DistinguishedNameTemplate subject_template = 2; } // Configuration pertaining to the issuance of SPIFFE-compatible workload diff --git a/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx index 6a5f12830bf4f..4c7b71a51bfcb 100644 --- a/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx +++ b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx @@ -104,4 +104,13 @@ Optional: Optional: - `dns_sans` (List of String) The DNS Subject Alternative Names (SANs) that should be included in an X509-SVID issued using this WorkloadIdentity. Each entry in this list supports templating using attributes. +- `subject_template` (Attributes) Used to configure the Subject Distinguished Name (DN) of the X509-SVID. In most circumstances, it is recommended to prefer relying on the SPIFFE ID encoded in the URI SAN. However, the Subject DN may be needed to support legacy systems designed for X509 and not SPIFFE/WIMSE. If not provided, the X509-SVID will be issued with an empty Subject DN. (see [below for nested schema](#nested-schema-for-specspiffex509subject_template)) + +### Nested Schema for `spec.spiffe.x509.subject_template` + +Optional: + +- `common_name` (String) Common Name (CN) - 2.5.4.3 If empty, the RDN will be omitted from the DN. +- `organization` (String) Organization (O) - 2.5.4.10 If empty, the RDN will be omitted from the DN. +- `organizational_unit` (String) Organizational Unit (OU) - 2.5.4.11 If empty, the RDN will be omitted from the DN. diff --git a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx index 6238a0d535b03..b91d39d393f3b 100644 --- a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx +++ b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx @@ -131,4 +131,13 @@ Optional: Optional: - `dns_sans` (List of String) The DNS Subject Alternative Names (SANs) that should be included in an X509-SVID issued using this WorkloadIdentity. Each entry in this list supports templating using attributes. +- `subject_template` (Attributes) Used to configure the Subject Distinguished Name (DN) of the X509-SVID. In most circumstances, it is recommended to prefer relying on the SPIFFE ID encoded in the URI SAN. However, the Subject DN may be needed to support legacy systems designed for X509 and not SPIFFE/WIMSE. If not provided, the X509-SVID will be issued with an empty Subject DN. (see [below for nested schema](#nested-schema-for-specspiffex509subject_template)) + +### Nested Schema for `spec.spiffe.x509.subject_template` + +Optional: + +- `common_name` (String) Common Name (CN) - 2.5.4.3 If empty, the RDN will be omitted from the DN. +- `organization` (String) Organization (O) - 2.5.4.10 If empty, the RDN will be omitted from the DN. +- `organizational_unit` (String) Organizational Unit (OU) - 2.5.4.11 If empty, the RDN will be omitted from the DN. diff --git a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go index 27c4412b764de..74432d2075d1e 100644 --- a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go +++ b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go @@ -166,11 +166,34 @@ func GenSchemaWorkloadIdentity(ctx context.Context) (github_com_hashicorp_terraf Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, "x509": { - Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"dns_sans": { - Description: "The DNS Subject Alternative Names (SANs) that should be included in an X509-SVID issued using this WorkloadIdentity. Each entry in this list supports templating using attributes.", - Optional: true, - Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, - }}), + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "dns_sans": { + Description: "The DNS Subject Alternative Names (SANs) that should be included in an X509-SVID issued using this WorkloadIdentity. Each entry in this list supports templating using attributes.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }, + "subject_template": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "common_name": { + Description: "Common Name (CN) - 2.5.4.3 If empty, the RDN will be omitted from the DN.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "organization": { + Description: "Organization (O) - 2.5.4.10 If empty, the RDN will be omitted from the DN.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "organizational_unit": { + Description: "Organizational Unit (OU) - 2.5.4.11 If empty, the RDN will be omitted from the DN.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Used to configure the Subject Distinguished Name (DN) of the X509-SVID. In most circumstances, it is recommended to prefer relying on the SPIFFE ID encoded in the URI SAN. However, the Subject DN may be needed to support legacy systems designed for X509 and not SPIFFE/WIMSE. If not provided, the X509-SVID will be issued with an empty Subject DN.", + Optional: true, + }, + }), Description: "Configuration specific to X509-SVIDs.", Optional: true, }, @@ -726,6 +749,75 @@ func CopyWorkloadIdentityFromTerraform(_ context.Context, tf github_com_hashicor } } } + { + a, ok := tf.Attrs["subject_template"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.SubjectTemplate = nil + if !v.Null && !v.Unknown { + tf := v + obj.SubjectTemplate = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.X509DistinguishedNameTemplate{} + obj := obj.SubjectTemplate + { + a, ok := tf.Attrs["common_name"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.common_name"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.common_name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.CommonName = t + } + } + } + { + a, ok := tf.Attrs["organization"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organization"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organization", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Organization = t + } + } + } + { + a, ok := tf.Attrs["organizational_unit"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organizational_unit"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organizational_unit", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.OrganizationalUnit = t + } + } + } + } + } + } + } } } } @@ -1633,6 +1725,104 @@ func CopyWorkloadIdentityToTerraform(ctx context.Context, obj *github_com_gravit } } } + { + a, ok := tf.AttrTypes["subject_template"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["subject_template"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.SubjectTemplate == nil { + v.Null = true + } else { + obj := obj.SubjectTemplate + tf := &v + { + t, ok := tf.AttrTypes["common_name"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.common_name"}) + } else { + v, ok := tf.Attrs["common_name"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.spiffe.x509.subject_template.common_name", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.common_name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.CommonName) == "" + } + v.Value = string(obj.CommonName) + v.Unknown = false + tf.Attrs["common_name"] = v + } + } + { + t, ok := tf.AttrTypes["organization"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organization"}) + } else { + v, ok := tf.Attrs["organization"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.spiffe.x509.subject_template.organization", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organization", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Organization) == "" + } + v.Value = string(obj.Organization) + v.Unknown = false + tf.Attrs["organization"] = v + } + } + { + t, ok := tf.AttrTypes["organizational_unit"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organizational_unit"}) + } else { + v, ok := tf.Attrs["organizational_unit"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.spiffe.x509.subject_template.organizational_unit", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.x509.subject_template.organizational_unit", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.OrganizationalUnit) == "" + } + v.Value = string(obj.OrganizationalUnit) + v.Unknown = false + tf.Attrs["organizational_unit"] = v + } + } + } + v.Unknown = false + tf.Attrs["subject_template"] = v + } + } + } } v.Unknown = false tf.Attrs["x509"] = v diff --git a/lib/auth/machineid/workloadidentityv1/decision.go b/lib/auth/machineid/workloadidentityv1/decision.go index ccbbde2967c90..98b3221730450 100644 --- a/lib/auth/machineid/workloadidentityv1/decision.go +++ b/lib/auth/machineid/workloadidentityv1/decision.go @@ -83,6 +83,41 @@ func decide( d.templatedWorkloadIdentity.Spec.Spiffe.X509.DnsSans[i] = templated } + st := wi.GetSpec().GetSpiffe().GetX509().GetSubjectTemplate() + if st != nil { + dst := d.templatedWorkloadIdentity.Spec.Spiffe.X509.SubjectTemplate + + templated, err = templateString(st.CommonName, attrs) + if err != nil { + d.reason = trace.Wrap( + err, + "templating spec.spiffe.x509.subject_template.common_name", + ) + return d + } + dst.CommonName = templated + + templated, err = templateString(st.Organization, attrs) + if err != nil { + d.reason = trace.Wrap( + err, + "templating spec.spiffe.x509.subject_template.organization", + ) + return d + } + dst.Organization = templated + + templated, err = templateString(st.OrganizationalUnit, attrs) + if err != nil { + d.reason = trace.Wrap( + err, + "templating spec.spiffe.x509.subject_template.organizational_unit", + ) + return d + } + dst.OrganizationalUnit = templated + } + // Yay - made it to the end! d.shouldIssue = true return d @@ -141,6 +176,10 @@ func getFieldStringValue(attrs *workloadidentityv1pb.Attrs, attr string) (string // TODO(noah): In a coming PR, this will be replaced by evaluating the values // within the handlebars as expressions. func templateString(in string, attrs *workloadidentityv1pb.Attrs) (string, error) { + if len(in) == 0 { + return in, nil + } + re := regexp.MustCompile(`\{\{([^{}]+?)\}\}`) matches := re.FindAllStringSubmatch(in, -1) diff --git a/lib/auth/machineid/workloadidentityv1/issuer_service.go b/lib/auth/machineid/workloadidentityv1/issuer_service.go index 73694e0de54e8..66422268c8fcd 100644 --- a/lib/auth/machineid/workloadidentityv1/issuer_service.go +++ b/lib/auth/machineid/workloadidentityv1/issuer_service.go @@ -349,8 +349,9 @@ func x509Template( notAfter time.Time, spiffeID spiffeid.ID, dnsSANs []string, + subjectTemplate *workloadidentityv1pb.X509DistinguishedNameTemplate, ) *x509.Certificate { - return &x509.Certificate{ + c := &x509.Certificate{ SerialNumber: serialNumber, NotBefore: notBefore, NotAfter: notAfter, @@ -379,6 +380,19 @@ func x509Template( URIs: []*url.URL{spiffeID.URL()}, DNSNames: dnsSANs, } + if subjectTemplate != nil { + c.Subject.CommonName = subjectTemplate.CommonName + if subjectTemplate.Organization != "" { + c.Subject.Organization = []string{ + subjectTemplate.Organization, + } + } + if subjectTemplate.OrganizationalUnit != "" { + c.Subject.OrganizationalUnit = []string{subjectTemplate.OrganizationalUnit} + } + } + + return c } func (s *IssuanceService) getX509CA( @@ -484,6 +498,7 @@ func (s *IssuanceService) issueX509SVID( notAfter, spiffeID, wid.GetSpec().GetSpiffe().GetX509().GetDnsSans(), + wid.GetSpec().GetSpiffe().GetX509().GetSubjectTemplate(), ), ca.Cert, pubKey, diff --git a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go index 167ee3f33e1f2..280425f71c14b 100644 --- a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go +++ b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go @@ -21,6 +21,7 @@ import ( "crypto" "crypto/tls" "crypto/x509" + "crypto/x509/pkix" "errors" "fmt" "net" @@ -433,6 +434,27 @@ func TestIssueWorkloadIdentity(t *testing.T) { }) require.NoError(t, err) + subjectTemplate, err := tp.srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "subject-template", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/foo", + X509: &workloadidentityv1pb.WorkloadIdentitySPIFFEX509{ + SubjectTemplate: &workloadidentityv1pb.X509DistinguishedNameTemplate{ + CommonName: "{{user.name}}", + Organization: "{{user.name}} Inc", + OrganizationalUnit: "Team {{user.name}}", + }, + }, + }, + }, + }) + require.NoError(t, err) + workloadAttrs := func(f func(attrs *workloadidentityv1pb.WorkloadAttrs)) *workloadidentityv1pb.WorkloadAttrs { attrs := &workloadidentityv1pb.WorkloadAttrs{ Kubernetes: &workloadidentityv1pb.WorkloadAttrsKubernetes{ @@ -615,6 +637,8 @@ func TestIssueWorkloadIdentity(t *testing.T) { // 4.4: When included, fields id-kp-serverAuth and id-kp-clientAuth MUST be set. require.Contains(t, cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) require.Contains(t, cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) + // Expect blank subject field. + require.Equal(t, pkix.Name{}, cert.Subject) // Check cert signature is valid _, err = cert.Verify(x509.VerifyOptions{ @@ -657,6 +681,119 @@ func TestIssueWorkloadIdentity(t *testing.T) { )) }, }, + { + name: "x509 svid - subject templating", + client: wilcardAccessClient, + req: &workloadidentityv1pb.IssueWorkloadIdentityRequest{ + Name: subjectTemplate.GetMetadata().GetName(), + Credential: &workloadidentityv1pb.IssueWorkloadIdentityRequest_X509SvidParams{ + X509SvidParams: &workloadidentityv1pb.X509SVIDParams{ + PublicKey: workloadKeyPubBytes, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: require.NoError, + assert: func(t *testing.T, res *workloadidentityv1pb.IssueWorkloadIdentityResponse) { + cred := res.Credential + require.NotNil(t, res.Credential) + + wantSPIFFEID := "spiffe://localhost/foo" + wantTTL := time.Hour + require.Empty(t, cmp.Diff( + cred, + &workloadidentityv1pb.Credential{ + Ttl: durationpb.New(wantTTL), + SpiffeId: wantSPIFFEID, + WorkloadIdentityName: subjectTemplate.GetMetadata().GetName(), + WorkloadIdentityRevision: subjectTemplate.GetMetadata().GetRevision(), + }, + protocmp.Transform(), + protocmp.IgnoreFields( + &workloadidentityv1pb.Credential{}, + "expires_at", + ), + protocmp.IgnoreOneofs( + &workloadidentityv1pb.Credential{}, + "credential", + ), + )) + // Check expiry makes sense + require.WithinDuration(t, tp.clock.Now().Add(wantTTL), cred.GetExpiresAt().AsTime(), time.Second) + + // Check the X509 + cert, err := x509.ParseCertificate(cred.GetX509Svid().GetCert()) + require.NoError(t, err) + // Check included public key matches + require.Equal(t, workloadKey.Public(), cert.PublicKey) + // Check cert expiry + require.WithinDuration(t, tp.clock.Now().Add(wantTTL), cert.NotAfter, time.Second) + // Check cert nbf + require.WithinDuration(t, tp.clock.Now().Add(-1*time.Minute), cert.NotBefore, time.Second) + // Check cert TTL + require.Equal(t, cert.NotAfter.Sub(cert.NotBefore), wantTTL+time.Minute) + + // Check against SPIFFE SPEC + // References are to https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md + // 2: An X.509 SVID MUST contain exactly one URI SAN, and by extension, exactly one SPIFFE ID + require.Len(t, cert.URIs, 1) + require.Equal(t, wantSPIFFEID, cert.URIs[0].String()) + // 4.1: leaf certificates MUST set the cA field to false. + require.False(t, cert.IsCA) + require.Greater(t, cert.KeyUsage&x509.KeyUsageDigitalSignature, 0) + // 4.3: They MAY set keyEncipherment and/or keyAgreement + require.Greater(t, cert.KeyUsage&x509.KeyUsageKeyEncipherment, 0) + require.Greater(t, cert.KeyUsage&x509.KeyUsageKeyAgreement, 0) + // 4.3: Leaf SVIDs MUST NOT set keyCertSign or cRLSign + require.EqualValues(t, 0, cert.KeyUsage&x509.KeyUsageCertSign) + require.EqualValues(t, 0, cert.KeyUsage&x509.KeyUsageCRLSign) + // 4.4: When included, fields id-kp-serverAuth and id-kp-clientAuth MUST be set. + require.Contains(t, cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) + require.Contains(t, cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) + + // Check subject has been templated + require.Empty(t, cmp.Diff(pkix.Name{ + CommonName: "dog", + Organization: []string{"dog Inc"}, + OrganizationalUnit: []string{"Team dog"}, + }, cert.Subject, cmpopts.IgnoreFields(pkix.Name{}, "Names"))) + + // Check cert signature is valid + _, err = cert.Verify(x509.VerifyOptions{ + Roots: tp.spiffeX509CAPool, + CurrentTime: tp.srv.Auth().GetClock().Now(), + }) + require.NoError(t, err) + + // Check audit log event + evt, ok := tp.eventRecorder.LastEvent().(*events.SPIFFESVIDIssued) + require.True(t, ok) + require.NotEmpty(t, evt.ConnectionMetadata.RemoteAddr) + require.Equal(t, cred.GetX509Svid().GetSerialNumber(), evt.SerialNumber) + require.Empty(t, cmp.Diff( + evt, + &events.SPIFFESVIDIssued{ + Metadata: events.Metadata{ + Type: libevents.SPIFFESVIDIssuedEvent, + Code: libevents.SPIFFESVIDIssuedSuccessCode, + }, + UserMetadata: events.UserMetadata{ + User: wildcardAccess.GetName(), + UserKind: events.UserKind_USER_KIND_HUMAN, + }, + SPIFFEID: "spiffe://localhost/foo", + SVIDType: "x509", + WorkloadIdentity: subjectTemplate.GetMetadata().GetName(), + WorkloadIdentityRevision: subjectTemplate.GetMetadata().GetRevision(), + }, + cmpopts.IgnoreFields( + events.SPIFFESVIDIssued{}, + "ConnectionMetadata", + "SerialNumber", + ), + )) + }, + }, { name: "unauthorized by rules", client: wilcardAccessClient,