From ccc639383a70f92e20b5356e945cd350c80f1bac Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 10 Jan 2025 21:15:11 +0000 Subject: [PATCH] Workload ID: Introduce basic rule condition operators (#50940) * Pull condition operators into a one of * Add wrapper structs to support repeated * Adjust for new protos * Fix tflint * Start fixing tests * Add more test cases * More test cases * Fix marshalling of resource * Fix test in lib/services --- .../workloadidentity/v1/resource.pb.go | 492 ++++++++++++++---- .../workloadidentity/v1/resource.proto | 38 +- .../data-sources/workload_identity.mdx | 33 +- .../resources/workload_identity.mdx | 37 +- .../teleport_workload_identity/resource.tf | 4 +- .../fixtures/workload_identity_0_create.tf | 4 +- .../fixtures/workload_identity_1_update.tf | 4 +- .../testlib/workload_identity_test.go | 12 +- .../workloadidentity/v1/resource_terraform.go | 491 ++++++++++++++++- .../machineid/workloadidentityv1/decision.go | 21 +- .../workloadidentityv1/decision_test.go | 285 +++++++++- .../workloadidentityv1_test.go | 30 +- lib/services/workload_identity.go | 12 +- lib/services/workload_identity_test.go | 14 +- tool/tctl/common/collection.go | 2 +- 15 files changed, 1317 insertions(+), 162 deletions(-) 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 1849d7e902173..fa758941db455 100644 --- a/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go +++ b/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go @@ -121,20 +121,209 @@ func (x *WorkloadIdentity) GetSpec() *WorkloadIdentitySpec { return nil } +// The attribute casted to a string must be equal to the value. +type WorkloadIdentityConditionEq struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The value to compare the attribute against. + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorkloadIdentityConditionEq) Reset() { + *x = WorkloadIdentityConditionEq{} + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkloadIdentityConditionEq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkloadIdentityConditionEq) ProtoMessage() {} + +func (x *WorkloadIdentityConditionEq) ProtoReflect() protoreflect.Message { + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[1] + 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 WorkloadIdentityConditionEq.ProtoReflect.Descriptor instead. +func (*WorkloadIdentityConditionEq) Descriptor() ([]byte, []int) { + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{1} +} + +func (x *WorkloadIdentityConditionEq) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +// The attribute casted to a string must not be equal to the value. +type WorkloadIdentityConditionNotEq struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The value to compare the attribute against. + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorkloadIdentityConditionNotEq) Reset() { + *x = WorkloadIdentityConditionNotEq{} + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkloadIdentityConditionNotEq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkloadIdentityConditionNotEq) ProtoMessage() {} + +func (x *WorkloadIdentityConditionNotEq) ProtoReflect() protoreflect.Message { + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[2] + 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 WorkloadIdentityConditionNotEq.ProtoReflect.Descriptor instead. +func (*WorkloadIdentityConditionNotEq) Descriptor() ([]byte, []int) { + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{2} +} + +func (x *WorkloadIdentityConditionNotEq) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +// The attribute casted to a string must be in the list of values. +type WorkloadIdentityConditionIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The list of values to compare the attribute against. + Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorkloadIdentityConditionIn) Reset() { + *x = WorkloadIdentityConditionIn{} + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkloadIdentityConditionIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkloadIdentityConditionIn) ProtoMessage() {} + +func (x *WorkloadIdentityConditionIn) ProtoReflect() protoreflect.Message { + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[3] + 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 WorkloadIdentityConditionIn.ProtoReflect.Descriptor instead. +func (*WorkloadIdentityConditionIn) Descriptor() ([]byte, []int) { + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{3} +} + +func (x *WorkloadIdentityConditionIn) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + +// The attribute casted to a string must not be in the list of values. +type WorkloadIdentityConditionNotIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The list of values to compare the attribute against. + Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorkloadIdentityConditionNotIn) Reset() { + *x = WorkloadIdentityConditionNotIn{} + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorkloadIdentityConditionNotIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkloadIdentityConditionNotIn) ProtoMessage() {} + +func (x *WorkloadIdentityConditionNotIn) ProtoReflect() protoreflect.Message { + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[4] + 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 WorkloadIdentityConditionNotIn.ProtoReflect.Descriptor instead. +func (*WorkloadIdentityConditionNotIn) Descriptor() ([]byte, []int) { + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{4} +} + +func (x *WorkloadIdentityConditionNotIn) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + // The individual conditions that make up a rule. type WorkloadIdentityCondition struct { state protoimpl.MessageState `protogen:"open.v1"` // The name of the attribute to evaluate the condition against. Attribute string `protobuf:"bytes,1,opt,name=attribute,proto3" json:"attribute,omitempty"` - // An exact string that the attribute must match. - Equals string `protobuf:"bytes,2,opt,name=equals,proto3" json:"equals,omitempty"` + // Types that are valid to be assigned to Operator: + // + // *WorkloadIdentityCondition_Eq + // *WorkloadIdentityCondition_NotEq + // *WorkloadIdentityCondition_In + // *WorkloadIdentityCondition_NotIn + Operator isWorkloadIdentityCondition_Operator `protobuf_oneof:"operator"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *WorkloadIdentityCondition) Reset() { *x = WorkloadIdentityCondition{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[1] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -146,7 +335,7 @@ func (x *WorkloadIdentityCondition) String() string { func (*WorkloadIdentityCondition) ProtoMessage() {} func (x *WorkloadIdentityCondition) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[1] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -159,7 +348,7 @@ func (x *WorkloadIdentityCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkloadIdentityCondition.ProtoReflect.Descriptor instead. func (*WorkloadIdentityCondition) Descriptor() ([]byte, []int) { - return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{1} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{5} } func (x *WorkloadIdentityCondition) GetAttribute() string { @@ -169,13 +358,81 @@ func (x *WorkloadIdentityCondition) GetAttribute() string { return "" } -func (x *WorkloadIdentityCondition) GetEquals() string { +func (x *WorkloadIdentityCondition) GetOperator() isWorkloadIdentityCondition_Operator { if x != nil { - return x.Equals + return x.Operator } - return "" + return nil +} + +func (x *WorkloadIdentityCondition) GetEq() *WorkloadIdentityConditionEq { + if x != nil { + if x, ok := x.Operator.(*WorkloadIdentityCondition_Eq); ok { + return x.Eq + } + } + return nil +} + +func (x *WorkloadIdentityCondition) GetNotEq() *WorkloadIdentityConditionNotEq { + if x != nil { + if x, ok := x.Operator.(*WorkloadIdentityCondition_NotEq); ok { + return x.NotEq + } + } + return nil } +func (x *WorkloadIdentityCondition) GetIn() *WorkloadIdentityConditionIn { + if x != nil { + if x, ok := x.Operator.(*WorkloadIdentityCondition_In); ok { + return x.In + } + } + return nil +} + +func (x *WorkloadIdentityCondition) GetNotIn() *WorkloadIdentityConditionNotIn { + if x != nil { + if x, ok := x.Operator.(*WorkloadIdentityCondition_NotIn); ok { + return x.NotIn + } + } + return nil +} + +type isWorkloadIdentityCondition_Operator interface { + isWorkloadIdentityCondition_Operator() +} + +type WorkloadIdentityCondition_Eq struct { + // The attribute casted to a string must be equal to the value. + Eq *WorkloadIdentityConditionEq `protobuf:"bytes,3,opt,name=eq,proto3,oneof"` +} + +type WorkloadIdentityCondition_NotEq struct { + // The attribute casted to a string must not be equal to the value. + NotEq *WorkloadIdentityConditionNotEq `protobuf:"bytes,4,opt,name=not_eq,json=notEq,proto3,oneof"` +} + +type WorkloadIdentityCondition_In struct { + // The attribute casted to a string must be in the list of values. + In *WorkloadIdentityConditionIn `protobuf:"bytes,5,opt,name=in,proto3,oneof"` +} + +type WorkloadIdentityCondition_NotIn struct { + // The attribute casted to a string must not be in the list of values. + NotIn *WorkloadIdentityConditionNotIn `protobuf:"bytes,6,opt,name=not_in,json=notIn,proto3,oneof"` +} + +func (*WorkloadIdentityCondition_Eq) isWorkloadIdentityCondition_Operator() {} + +func (*WorkloadIdentityCondition_NotEq) isWorkloadIdentityCondition_Operator() {} + +func (*WorkloadIdentityCondition_In) isWorkloadIdentityCondition_Operator() {} + +func (*WorkloadIdentityCondition_NotIn) isWorkloadIdentityCondition_Operator() {} + // An individual rule that is evaluated during the issuance of a WorkloadIdentity. type WorkloadIdentityRule struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -187,7 +444,7 @@ type WorkloadIdentityRule struct { func (x *WorkloadIdentityRule) Reset() { *x = WorkloadIdentityRule{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[2] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -199,7 +456,7 @@ func (x *WorkloadIdentityRule) String() string { func (*WorkloadIdentityRule) ProtoMessage() {} func (x *WorkloadIdentityRule) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[2] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -212,7 +469,7 @@ func (x *WorkloadIdentityRule) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkloadIdentityRule.ProtoReflect.Descriptor instead. func (*WorkloadIdentityRule) Descriptor() ([]byte, []int) { - return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{2} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{6} } func (x *WorkloadIdentityRule) GetConditions() []*WorkloadIdentityCondition { @@ -235,7 +492,7 @@ type WorkloadIdentityRules struct { func (x *WorkloadIdentityRules) Reset() { *x = WorkloadIdentityRules{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[3] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -247,7 +504,7 @@ func (x *WorkloadIdentityRules) String() string { func (*WorkloadIdentityRules) ProtoMessage() {} func (x *WorkloadIdentityRules) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[3] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -260,7 +517,7 @@ func (x *WorkloadIdentityRules) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkloadIdentityRules.ProtoReflect.Descriptor instead. func (*WorkloadIdentityRules) Descriptor() ([]byte, []int) { - return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{3} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{7} } func (x *WorkloadIdentityRules) GetAllow() []*WorkloadIdentityRule { @@ -284,7 +541,7 @@ type WorkloadIdentitySPIFFEX509 struct { func (x *WorkloadIdentitySPIFFEX509) Reset() { *x = WorkloadIdentitySPIFFEX509{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[4] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -296,7 +553,7 @@ func (x *WorkloadIdentitySPIFFEX509) String() string { func (*WorkloadIdentitySPIFFEX509) ProtoMessage() {} func (x *WorkloadIdentitySPIFFEX509) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[4] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -309,7 +566,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{4} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{8} } func (x *WorkloadIdentitySPIFFEX509) GetDnsSans() []string { @@ -341,7 +598,7 @@ type WorkloadIdentitySPIFFE struct { func (x *WorkloadIdentitySPIFFE) Reset() { *x = WorkloadIdentitySPIFFE{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[5] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -353,7 +610,7 @@ func (x *WorkloadIdentitySPIFFE) String() string { func (*WorkloadIdentitySPIFFE) ProtoMessage() {} func (x *WorkloadIdentitySPIFFE) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[5] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -366,7 +623,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{5} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{9} } func (x *WorkloadIdentitySPIFFE) GetId() string { @@ -404,7 +661,7 @@ type WorkloadIdentitySpec struct { func (x *WorkloadIdentitySpec) Reset() { *x = WorkloadIdentitySpec{} - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[6] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -416,7 +673,7 @@ func (x *WorkloadIdentitySpec) String() string { func (*WorkloadIdentitySpec) ProtoMessage() {} func (x *WorkloadIdentitySpec) ProtoReflect() protoreflect.Message { - mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[6] + mi := &file_teleport_workloadidentity_v1_resource_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -429,7 +686,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{6} + return file_teleport_workloadidentity_v1_resource_proto_rawDescGZIP(), []int{10} } func (x *WorkloadIdentitySpec) GetRules() *WorkloadIdentityRules { @@ -469,56 +726,91 @@ var file_teleport_workloadidentity_v1_resource_proto_rawDesc = []byte{ 0x20, 0x01, 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, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x51, - 0x0a, 0x19, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x71, 0x75, - 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x71, 0x75, 0x61, 0x6c, - 0x73, 0x22, 0x6f, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x57, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 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, 0x43, 0x6f, 0x6e, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x22, 0x61, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x48, 0x0a, 0x05, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 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, + 0x74, 0x69, 0x74, 0x79, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x33, + 0x0a, 0x1b, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x71, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x36, 0x0a, 0x1e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x4e, 0x6f, 0x74, 0x45, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x35, 0x0a, 0x1b, 0x57, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x22, 0x38, 0x0a, 0x1e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, + 0x6f, 0x74, 0x49, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x9b, 0x03, 0x0a, + 0x19, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x4b, 0x0a, 0x02, 0x65, 0x71, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 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, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x71, 0x48, + 0x00, 0x52, 0x02, 0x65, 0x71, 0x12, 0x55, 0x0a, 0x06, 0x6e, 0x6f, 0x74, 0x5f, 0x65, 0x71, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 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, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, + 0x74, 0x45, 0x71, 0x48, 0x00, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x45, 0x71, 0x12, 0x4b, 0x0a, 0x02, + 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 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, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x48, 0x00, 0x52, 0x02, 0x69, 0x6e, 0x12, 0x55, 0x0a, 0x06, 0x6e, 0x6f, 0x74, + 0x5f, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 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, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x49, 0x6e, 0x48, 0x00, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x49, 0x6e, + 0x42, 0x0a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x4a, 0x04, 0x08, 0x02, + 0x10, 0x03, 0x52, 0x06, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x73, 0x22, 0x6f, 0x0a, 0x14, 0x57, 0x6f, + 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x75, + 0x6c, 0x65, 0x12, 0x57, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 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, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x61, 0x0a, 0x15, 0x57, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x75, 0x6c, 0x65, 0x73, 0x12, 0x48, 0x0a, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, + 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, 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, 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, } var ( @@ -533,30 +825,38 @@ 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, 7) +var file_teleport_workloadidentity_v1_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_teleport_workloadidentity_v1_resource_proto_goTypes = []any{ - (*WorkloadIdentity)(nil), // 0: teleport.workloadidentity.v1.WorkloadIdentity - (*WorkloadIdentityCondition)(nil), // 1: teleport.workloadidentity.v1.WorkloadIdentityCondition - (*WorkloadIdentityRule)(nil), // 2: teleport.workloadidentity.v1.WorkloadIdentityRule - (*WorkloadIdentityRules)(nil), // 3: teleport.workloadidentity.v1.WorkloadIdentityRules - (*WorkloadIdentitySPIFFEX509)(nil), // 4: teleport.workloadidentity.v1.WorkloadIdentitySPIFFEX509 - (*WorkloadIdentitySPIFFE)(nil), // 5: teleport.workloadidentity.v1.WorkloadIdentitySPIFFE - (*WorkloadIdentitySpec)(nil), // 6: teleport.workloadidentity.v1.WorkloadIdentitySpec - (*v1.Metadata)(nil), // 7: teleport.header.v1.Metadata + (*WorkloadIdentity)(nil), // 0: teleport.workloadidentity.v1.WorkloadIdentity + (*WorkloadIdentityConditionEq)(nil), // 1: teleport.workloadidentity.v1.WorkloadIdentityConditionEq + (*WorkloadIdentityConditionNotEq)(nil), // 2: teleport.workloadidentity.v1.WorkloadIdentityConditionNotEq + (*WorkloadIdentityConditionIn)(nil), // 3: teleport.workloadidentity.v1.WorkloadIdentityConditionIn + (*WorkloadIdentityConditionNotIn)(nil), // 4: teleport.workloadidentity.v1.WorkloadIdentityConditionNotIn + (*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 } var file_teleport_workloadidentity_v1_resource_proto_depIdxs = []int32{ - 7, // 0: teleport.workloadidentity.v1.WorkloadIdentity.metadata:type_name -> teleport.header.v1.Metadata - 6, // 1: teleport.workloadidentity.v1.WorkloadIdentity.spec:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySpec - 1, // 2: teleport.workloadidentity.v1.WorkloadIdentityRule.conditions:type_name -> teleport.workloadidentity.v1.WorkloadIdentityCondition - 2, // 3: teleport.workloadidentity.v1.WorkloadIdentityRules.allow:type_name -> teleport.workloadidentity.v1.WorkloadIdentityRule - 4, // 4: teleport.workloadidentity.v1.WorkloadIdentitySPIFFE.x509:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySPIFFEX509 - 3, // 5: teleport.workloadidentity.v1.WorkloadIdentitySpec.rules:type_name -> teleport.workloadidentity.v1.WorkloadIdentityRules - 5, // 6: teleport.workloadidentity.v1.WorkloadIdentitySpec.spiffe:type_name -> teleport.workloadidentity.v1.WorkloadIdentitySPIFFE - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 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 + 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 } func init() { file_teleport_workloadidentity_v1_resource_proto_init() } @@ -564,13 +864,19 @@ func file_teleport_workloadidentity_v1_resource_proto_init() { if File_teleport_workloadidentity_v1_resource_proto != nil { return } + file_teleport_workloadidentity_v1_resource_proto_msgTypes[5].OneofWrappers = []any{ + (*WorkloadIdentityCondition_Eq)(nil), + (*WorkloadIdentityCondition_NotEq)(nil), + (*WorkloadIdentityCondition_In)(nil), + (*WorkloadIdentityCondition_NotIn)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_workloadidentity_v1_resource_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 11, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/workloadidentity/v1/resource.proto b/api/proto/teleport/workloadidentity/v1/resource.proto index b0faf7f94b99e..ad4cc03cf4c24 100644 --- a/api/proto/teleport/workloadidentity/v1/resource.proto +++ b/api/proto/teleport/workloadidentity/v1/resource.proto @@ -38,12 +38,46 @@ message WorkloadIdentity { WorkloadIdentitySpec spec = 5; } +// The attribute casted to a string must be equal to the value. +message WorkloadIdentityConditionEq { + // The value to compare the attribute against. + string value = 1; +} + +// The attribute casted to a string must not be equal to the value. +message WorkloadIdentityConditionNotEq { + // The value to compare the attribute against. + string value = 1; +} + +// The attribute casted to a string must be in the list of values. +message WorkloadIdentityConditionIn { + // The list of values to compare the attribute against. + repeated string values = 1; +} + +// The attribute casted to a string must not be in the list of values. +message WorkloadIdentityConditionNotIn { + // The list of values to compare the attribute against. + repeated string values = 1; +} + // The individual conditions that make up a rule. message WorkloadIdentityCondition { + reserved 2; + reserved "equals"; // The name of the attribute to evaluate the condition against. string attribute = 1; - // An exact string that the attribute must match. - string equals = 2; + oneof operator { + // The attribute casted to a string must be equal to the value. + WorkloadIdentityConditionEq eq = 3; + // The attribute casted to a string must not be equal to the value. + WorkloadIdentityConditionNotEq not_eq = 4; + // The attribute casted to a string must be in the list of values. + WorkloadIdentityConditionIn in = 5; + // The attribute casted to a string must not be in the list of values. + WorkloadIdentityConditionNotIn not_in = 6; + } } // An individual rule that is evaluated during the issuance of a WorkloadIdentity. 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 7c7e1a05a5af0..6a5f12830bf4f 100644 --- a/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx +++ b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx @@ -55,7 +55,38 @@ Optional: Optional: - `attribute` (String) The name of the attribute to evaluate the condition against. -- `equals` (String) An exact string that the attribute must match. +- `eq` (Attributes) The attribute casted to a string must be equal to the value. (see [below for nested schema](#nested-schema-for-specrulesallowconditionseq)) +- `in` (Attributes) The attribute casted to a string must be in the list of values. (see [below for nested schema](#nested-schema-for-specrulesallowconditionsin)) +- `not_eq` (Attributes) The attribute casted to a string must not be equal to the value. (see [below for nested schema](#nested-schema-for-specrulesallowconditionsnot_eq)) +- `not_in` (Attributes) The attribute casted to a string must not be in the list of values. (see [below for nested schema](#nested-schema-for-specrulesallowconditionsnot_in)) + +### Nested Schema for `spec.rules.allow.conditions.eq` + +Optional: + +- `value` (String) The value to compare the attribute against. + + +### Nested Schema for `spec.rules.allow.conditions.in` + +Optional: + +- `values` (List of String) The list of values to compare the attribute against. + + +### Nested Schema for `spec.rules.allow.conditions.not_eq` + +Optional: + +- `value` (String) The value to compare the attribute against. + + +### Nested Schema for `spec.rules.allow.conditions.not_in` + +Optional: + +- `values` (List of String) The list of values to compare the attribute against. + diff --git a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx index fbbeb1306abd8..6238a0d535b03 100644 --- a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx +++ b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx @@ -23,7 +23,9 @@ resource "teleport_workload_identity" "example" { { conditions = [{ attribute = "user.name" - equals = "noah" + eq = { + value = "my-user" + } }] } ] @@ -80,7 +82,38 @@ Optional: Optional: - `attribute` (String) The name of the attribute to evaluate the condition against. -- `equals` (String) An exact string that the attribute must match. +- `eq` (Attributes) The attribute casted to a string must be equal to the value. (see [below for nested schema](#nested-schema-for-specrulesallowconditionseq)) +- `in` (Attributes) The attribute casted to a string must be in the list of values. (see [below for nested schema](#nested-schema-for-specrulesallowconditionsin)) +- `not_eq` (Attributes) The attribute casted to a string must not be equal to the value. (see [below for nested schema](#nested-schema-for-specrulesallowconditionsnot_eq)) +- `not_in` (Attributes) The attribute casted to a string must not be in the list of values. (see [below for nested schema](#nested-schema-for-specrulesallowconditionsnot_in)) + +### Nested Schema for `spec.rules.allow.conditions.eq` + +Optional: + +- `value` (String) The value to compare the attribute against. + + +### Nested Schema for `spec.rules.allow.conditions.in` + +Optional: + +- `values` (List of String) The list of values to compare the attribute against. + + +### Nested Schema for `spec.rules.allow.conditions.not_eq` + +Optional: + +- `value` (String) The value to compare the attribute against. + + +### Nested Schema for `spec.rules.allow.conditions.not_in` + +Optional: + +- `values` (List of String) The list of values to compare the attribute against. + diff --git a/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf b/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf index e48ab1e5d0dd2..34dee932f430f 100644 --- a/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf +++ b/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf @@ -9,7 +9,9 @@ resource "teleport_workload_identity" "example" { { conditions = [{ attribute = "user.name" - equals = "noah" + eq = { + value = "my-user" + } }] } ] diff --git a/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf b/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf index b5d0ebe8aae08..a506ee5773d06 100644 --- a/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf +++ b/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf @@ -9,7 +9,9 @@ resource "teleport_workload_identity" "test" { { conditions = [{ attribute = "user.name" - equals = "foo" + eq = { + value = "foo" + } }] } ] diff --git a/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf b/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf index cced0a4f8ecdd..bb64491258471 100644 --- a/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf +++ b/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf @@ -9,7 +9,9 @@ resource "teleport_workload_identity" "test" { { conditions = [{ attribute = "user.name" - equals = "foo" + eq = { + value = "foo" + } }] } ] diff --git a/integrations/terraform/testlib/workload_identity_test.go b/integrations/terraform/testlib/workload_identity_test.go index 1e6d84cf6feb9..3e6d5a6ca4342 100644 --- a/integrations/terraform/testlib/workload_identity_test.go +++ b/integrations/terraform/testlib/workload_identity_test.go @@ -55,7 +55,7 @@ func (s *TerraformSuiteOSS) TestWorkloadIdentity() { resource.TestCheckResourceAttr(name, "kind", "workload_identity"), resource.TestCheckResourceAttr(name, "spec.spiffe.id", "/test"), resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.attribute", "user.name"), - resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.equals", "foo"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.eq.value", "foo"), ), }, { @@ -68,7 +68,7 @@ func (s *TerraformSuiteOSS) TestWorkloadIdentity() { resource.TestCheckResourceAttr(name, "kind", "workload_identity"), resource.TestCheckResourceAttr(name, "spec.spiffe.id", "/test/updated"), resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.attribute", "user.name"), - resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.equals", "foo"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.eq.value", "foo"), ), }, { @@ -101,7 +101,11 @@ func (s *TerraformSuiteOSS) TestImportWorkloadIdentity() { Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ { Attribute: "user.name", - Equals: "foo", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "foo", + }, + }, }, }, }, @@ -133,7 +137,7 @@ func (s *TerraformSuiteOSS) TestImportWorkloadIdentity() { require.Equal(t, types.KindWorkloadIdentity, state[0].Attributes["kind"]) require.Equal(t, "/test", state[0].Attributes["spec.spiffe.id"]) require.Equal(t, "user.name", state[0].Attributes["spec.rules.allow.0.conditions.0.attribute"]) - require.Equal(t, "foo", state[0].Attributes["spec.rules.allow.0.conditions.0.equals"]) + require.Equal(t, "foo", state[0].Attributes["spec.rules.allow.0.conditions.0.eq.value"]) return nil }, diff --git a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go index 5a76525cde345..27c4412b764de 100644 --- a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go +++ b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go @@ -107,10 +107,41 @@ func GenSchemaWorkloadIdentity(ctx context.Context) (github_com_hashicorp_terraf Optional: true, Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, - "equals": { - Description: "An exact string that the attribute must match.", + "eq": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"value": { + Description: "The value to compare the attribute against.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }}), + Description: "The attribute casted to a string must be equal to the value.", + Optional: true, + }, + "in": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"values": { + Description: "The list of values to compare the attribute against.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }}), + Description: "The attribute casted to a string must be in the list of values.", + Optional: true, + }, + "not_eq": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"value": { + Description: "The value to compare the attribute against.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }}), + Description: "The attribute casted to a string must not be equal to the value.", + Optional: true, + }, + "not_in": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"values": { + Description: "The list of values to compare the attribute against.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }}), + Description: "The attribute casted to a string must not be in the list of values.", Optional: true, - Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, }), Description: "The conditions that must be met for this rule to be considered passed.", @@ -408,6 +439,7 @@ func CopyWorkloadIdentityFromTerraform(_ context.Context, tf github_com_hashicor tf := v t = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition{} obj := t + obj.Operator = nil { a, ok := tf.Attrs["attribute"] if !ok { @@ -426,19 +458,162 @@ func CopyWorkloadIdentityFromTerraform(_ context.Context, tf github_com_hashicor } } { - a, ok := tf.Attrs["equals"] + a, ok := tf.Attrs["eq"] if !ok { - diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals"}) + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq"}) } else { - v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) if !ok { - diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) } else { - var t string if !v.Null && !v.Unknown { - t = string(v.Value) + b := &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityConditionEq{} + obj.Operator = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_Eq{Eq: b} + obj := b + tf := v + { + a, ok := tf.Attrs["value"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq.value"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq.value", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Value = t + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["not_eq"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + if !v.Null && !v.Unknown { + b := &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityConditionNotEq{} + obj.Operator = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_NotEq{NotEq: b} + obj := b + tf := v + { + a, ok := tf.Attrs["value"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq.value"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq.value", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Value = t + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["in"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.in"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.in", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + if !v.Null && !v.Unknown { + b := &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityConditionIn{} + obj.Operator = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_In{In: b} + obj := b + tf := v + { + a, ok := tf.Attrs["values"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.in.values"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.in.values", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Values = make([]string, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.in.values", "github_com_hashicorp_terraform_plugin_framework_types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Values[k] = t + } + } + } + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["not_in"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + if !v.Null && !v.Unknown { + b := &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityConditionNotIn{} + obj.Operator = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_NotIn{NotIn: b} + obj := b + tf := v + { + a, ok := tf.Attrs["values"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in.values"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in.values", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Values = make([]string, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in.values", "github_com_hashicorp_terraform_plugin_framework_types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Values[k] = t + } + } + } + } + } + } } - obj.Equals = t } } } @@ -984,25 +1159,297 @@ func CopyWorkloadIdentityToTerraform(ctx context.Context, obj *github_com_gravit } } { - t, ok := tf.AttrTypes["equals"] + a, ok := tf.AttrTypes["eq"] if !ok { - diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals"}) + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq"}) } else { - v, ok := tf.Attrs["equals"].(github_com_hashicorp_terraform_plugin_framework_types.String) + obj, ok := obj.Operator.(*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_Eq) 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.rules.allow.conditions.equals", err}) + obj = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_Eq{} + } + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["eq"].(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)) + } } - v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if obj.Eq == nil { + v.Null = true + } else { + obj := obj.Eq + tf := &v + { + t, ok := tf.AttrTypes["value"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq.value"}) + } else { + v, ok := tf.Attrs["value"].(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.rules.allow.conditions.eq.value", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.eq.value", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Value) == "" + } + v.Value = string(obj.Value) + v.Unknown = false + tf.Attrs["value"] = v + } + } + } + v.Unknown = false + tf.Attrs["eq"] = v + } + } + } + { + a, ok := tf.AttrTypes["not_eq"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq"}) + } else { + obj, ok := obj.Operator.(*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_NotEq) + if !ok { + obj = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_NotEq{} + } + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["not_eq"].(github_com_hashicorp_terraform_plugin_framework_types.Object) if !ok { - diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + 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.NotEq == nil { + v.Null = true + } else { + obj := obj.NotEq + tf := &v + { + t, ok := tf.AttrTypes["value"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq.value"}) + } else { + v, ok := tf.Attrs["value"].(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.rules.allow.conditions.not_eq.value", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_eq.value", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Value) == "" + } + v.Value = string(obj.Value) + v.Unknown = false + tf.Attrs["value"] = v + } + } } - v.Null = string(obj.Equals) == "" + v.Unknown = false + tf.Attrs["not_eq"] = v + } + } + } + { + a, ok := tf.AttrTypes["in"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.in"}) + } else { + obj, ok := obj.Operator.(*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_In) + if !ok { + obj = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_In{} + } + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.in", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["in"].(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.In == nil { + v.Null = true + } else { + obj := obj.In + tf := &v + { + a, ok := tf.AttrTypes["values"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.in.values"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.in.values", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["values"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) + } + } + if obj.Values != nil { + t := o.ElemType + if len(obj.Values) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) + } + for k, a := range obj.Values { + v, ok := tf.Attrs["values"].(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.rules.allow.conditions.in.values", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.in.values", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(a) == "" + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Values) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["values"] = c + } + } + } + } + v.Unknown = false + tf.Attrs["in"] = v + } + } + } + { + a, ok := tf.AttrTypes["not_in"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in"}) + } else { + obj, ok := obj.Operator.(*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_NotIn) + if !ok { + obj = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition_NotIn{} + } + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["not_in"].(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.NotIn == nil { + v.Null = true + } else { + obj := obj.NotIn + tf := &v + { + a, ok := tf.AttrTypes["values"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in.values"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in.values", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["values"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) + } + } + if obj.Values != nil { + t := o.ElemType + if len(obj.Values) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Values)) + } + for k, a := range obj.Values { + v, ok := tf.Attrs["values"].(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.rules.allow.conditions.not_in.values", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.not_in.values", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(a) == "" + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Values) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["values"] = c + } + } + } + } + v.Unknown = false + tf.Attrs["not_in"] = v } - v.Value = string(obj.Equals) - v.Unknown = false - tf.Attrs["equals"] = v } } } diff --git a/lib/auth/machineid/workloadidentityv1/decision.go b/lib/auth/machineid/workloadidentityv1/decision.go index 4e959efe6ee12..ccbbde2967c90 100644 --- a/lib/auth/machineid/workloadidentityv1/decision.go +++ b/lib/auth/machineid/workloadidentityv1/decision.go @@ -176,8 +176,25 @@ ruleLoop: if err != nil { return trace.Wrap(err) } - if val != condition.Equals { - continue ruleLoop + switch c := condition.Operator.(type) { + case *workloadidentityv1pb.WorkloadIdentityCondition_Eq: + if val != c.Eq.Value { + continue ruleLoop + } + case *workloadidentityv1pb.WorkloadIdentityCondition_NotEq: + if val == c.NotEq.Value { + continue ruleLoop + } + case *workloadidentityv1pb.WorkloadIdentityCondition_In: + if !slices.Contains(c.In.Values, val) { + continue ruleLoop + } + case *workloadidentityv1pb.WorkloadIdentityCondition_NotIn: + if slices.Contains(c.NotIn.Values, val) { + continue ruleLoop + } + default: + return trace.BadParameter("unsupported operator %T", c) } } return nil diff --git a/lib/auth/machineid/workloadidentityv1/decision_test.go b/lib/auth/machineid/workloadidentityv1/decision_test.go index 5d00bf7595669..3d2b9ed4cff95 100644 --- a/lib/auth/machineid/workloadidentityv1/decision_test.go +++ b/lib/auth/machineid/workloadidentityv1/decision_test.go @@ -263,28 +263,285 @@ func Test_evaluateRules(t *testing.T) { User: &workloadidentityv1pb.UserAttrs{ Name: "foo", }, + Workload: &workloadidentityv1pb.WorkloadAttrs{ + Kubernetes: &workloadidentityv1pb.WorkloadAttrsKubernetes{ + PodName: "pod1", + Namespace: "default", + }, + }, + } + + var noMatchRule require.ErrorAssertionFunc = func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.Contains(t, err.Error(), "no matching rule found") } - wi := &workloadidentityv1pb.WorkloadIdentity{ - Kind: types.KindWorkloadIdentity, - Version: types.V1, - Metadata: &headerv1.Metadata{ - Name: "test", + + tests := []struct { + name string + wid *workloadidentityv1pb.WorkloadIdentity + attrs *workloadidentityv1pb.Attrs + requireErr require.ErrorAssertionFunc + }{ + { + name: "no rules: pass", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{}, + }, + }, + attrs: attrs, + requireErr: require.NoError, + }, + { + name: "eq: pass", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + }, + }, + attrs: attrs, + requireErr: require.NoError, + }, + { + name: "eq: fail", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "not-foo", + }, + }, + }, + }, + }, + }, + }, + }, + }, + attrs: attrs, + requireErr: noMatchRule, + }, + { + name: "not_eq: pass", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_NotEq{ + NotEq: &workloadidentityv1pb.WorkloadIdentityConditionNotEq{ + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + }, + }, + attrs: attrs, + requireErr: require.NoError, + }, + { + name: "not_eq: fail", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_NotEq{ + NotEq: &workloadidentityv1pb.WorkloadIdentityConditionNotEq{ + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + }, + }, + attrs: attrs, + requireErr: noMatchRule, + }, + { + name: "in: pass", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_In{ + In: &workloadidentityv1pb.WorkloadIdentityConditionIn{ + Values: []string{"bar", "foo"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + attrs: attrs, + requireErr: require.NoError, }, - Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ - Rules: &workloadidentityv1pb.WorkloadIdentityRules{ - Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ - { - Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + name: "in: fail", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ { - Attribute: "user.name", - Equals: "foo", + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_In{ + In: &workloadidentityv1pb.WorkloadIdentityConditionIn{ + Values: []string{"bar", "fizz"}, + }, + }, + }, + }, }, }, }, }, }, + attrs: attrs, + requireErr: noMatchRule, }, + { + name: "not_in: pass", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_NotIn{ + NotIn: &workloadidentityv1pb.WorkloadIdentityConditionNotIn{ + Values: []string{"bar", "fizz"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + attrs: attrs, + requireErr: require.NoError, + }, + { + name: "in: fail", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_NotIn{ + NotIn: &workloadidentityv1pb.WorkloadIdentityConditionNotIn{ + Values: []string{"bar", "foo"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + attrs: attrs, + requireErr: noMatchRule, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := evaluateRules(tt.wid, tt.attrs) + tt.requireErr(t, err) + }) } - err := evaluateRules(wi, attrs) - require.NoError(t, err) } diff --git a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go index e5f23dc96216c..1ddf63bcf28d1 100644 --- a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go +++ b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go @@ -187,7 +187,11 @@ func TestIssueWorkloadIdentityE2E(t *testing.T) { Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ { Attribute: "join.kubernetes.service_account.namespace", - Equals: "my-namespace", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "my-namespace", + }, + }, }, }, }, @@ -402,11 +406,19 @@ func TestIssueWorkloadIdentity(t *testing.T) { Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ { Attribute: "user.name", - Equals: "dog", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "dog", + }, + }, }, { Attribute: "workload.kubernetes.namespace", - Equals: "default", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "default", + }, + }, }, }, }, @@ -768,7 +780,11 @@ func TestIssueWorkloadIdentities(t *testing.T) { Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ { Attribute: "workload.kubernetes.namespace", - Equals: "default", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "default", + }, + }, }, }, }, @@ -798,7 +814,11 @@ func TestIssueWorkloadIdentities(t *testing.T) { Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ { Attribute: "workload.kubernetes.namespace", - Equals: "default", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "default", + }, + }, }, }, }, diff --git a/lib/services/workload_identity.go b/lib/services/workload_identity.go index 89b87ba0d2473..826ab6540b0e6 100644 --- a/lib/services/workload_identity.go +++ b/lib/services/workload_identity.go @@ -104,16 +104,8 @@ func ValidateWorkloadIdentity(s *workloadidentityv1pb.WorkloadIdentity) error { if condition.Attribute == "" { return trace.BadParameter("spec.rules.allow[%d].conditions[%d].attribute: must be non-empty", i, j) } - // Ensure exactly one operator is set. - operatorsSet := 0 - if condition.Equals != "" { - operatorsSet++ - } - if operatorsSet == 0 || operatorsSet > 1 { - return trace.BadParameter( - "spec.rules.allow[%d].conditions[%d]: exactly one operator must be specified, found %d", - i, j, operatorsSet, - ) + if condition.Operator == nil { + return trace.BadParameter("spec.rules.allow[%d].conditions[%d]: operator must be specified", i, j) } } } diff --git a/lib/services/workload_identity_test.go b/lib/services/workload_identity_test.go index 429612ed48555..27d0e1ec0261b 100644 --- a/lib/services/workload_identity_test.go +++ b/lib/services/workload_identity_test.go @@ -92,7 +92,11 @@ func TestValidateWorkloadIdentity(t *testing.T) { Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ { Attribute: "example", - Equals: "foo", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "foo", + }, + }, }, }, }, @@ -180,7 +184,11 @@ func TestValidateWorkloadIdentity(t *testing.T) { Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ { Attribute: "", - Equals: "foo", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "foo", + }, + }, }, }, }, @@ -218,7 +226,7 @@ func TestValidateWorkloadIdentity(t *testing.T) { }, }, }, - requireErr: errContains("spec.rules.allow[0].conditions[0]: exactly one operator must be specified, found 0"), + requireErr: errContains("spec.rules.allow[0].conditions[0]: operator must be specified"), }, } diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index c1ea21addc2b6..4bf5d1629d0c9 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -1792,7 +1792,7 @@ type workloadIdentityCollection struct { func (c *workloadIdentityCollection) resources() []types.Resource { r := make([]types.Resource, 0, len(c.items)) for _, resource := range c.items { - r = append(r, types.Resource153ToLegacy(resource)) + r = append(r, types.ProtoResource153ToLegacy(resource)) } return r }