diff --git a/api/gen/proto/go/teleport/workloadidentity/v1/attrs.pb.go b/api/gen/proto/go/teleport/workloadidentity/v1/attrs.pb.go index a950c51ce67ba..4f15fe8be26f3 100644 --- a/api/gen/proto/go/teleport/workloadidentity/v1/attrs.pb.go +++ b/api/gen/proto/go/teleport/workloadidentity/v1/attrs.pb.go @@ -21,6 +21,7 @@ package workloadidentityv1 import ( + v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -275,6 +276,8 @@ type UserAttrs struct { BotInstanceId string `protobuf:"bytes,4,opt,name=bot_instance_id,json=botInstanceId,proto3" json:"bot_instance_id,omitempty"` // Labels of the user. Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Traits of the user. + Traits []*v1.Trait `protobuf:"bytes,6,rep,name=traits,proto3" json:"traits,omitempty"` } func (x *UserAttrs) Reset() { @@ -342,6 +345,13 @@ func (x *UserAttrs) GetLabels() map[string]string { return nil } +func (x *UserAttrs) GetTraits() []*v1.Trait { + if x != nil { + return x.Traits + } + return nil +} + // The attributes of a principal requesting a workload identity. These // attributes can be leveraged in rules, expressions and templating within the // WorkloadIdentity resource. @@ -418,84 +428,89 @@ var file_teleport_workloadidentity_v1_attrs_proto_rawDesc = []byte{ 0x6f, 0x61, 0x64, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x74, 0x74, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 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, 0x1a, 0x2d, 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, 0x2f, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x74, 0x74, 0x72, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x02, 0x0a, 0x17, 0x57, 0x6f, 0x72, 0x6b, - 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x73, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, - 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, - 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, - 0x08, 0x70, 0x6f, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x70, 0x6f, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x64, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x64, 0x55, 0x69, 0x64, 0x12, 0x59, 0x0a, 0x06, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 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, 0x41, 0x74, 0x74, 0x72, 0x73, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, - 0x73, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x65, 0x0a, 0x11, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, - 0x73, 0x55, 0x6e, 0x69, 0x78, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, - 0x64, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, - 0x70, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x03, 0x67, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, 0xab, 0x01, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, - 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x73, 0x12, 0x43, 0x0a, 0x04, 0x75, 0x6e, 0x69, - 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 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, 0x41, - 0x74, 0x74, 0x72, 0x73, 0x55, 0x6e, 0x69, 0x78, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x78, 0x12, 0x55, - 0x0a, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x35, 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, 0x41, 0x74, 0x74, 0x72, 0x73, 0x4b, - 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x52, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x72, - 0x6e, 0x65, 0x74, 0x65, 0x73, 0x22, 0x81, 0x02, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x41, 0x74, - 0x74, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x62, 0x6f, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x42, 0x6f, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x62, 0x6f, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x62, 0x6f, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x6f, 0x74, - 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x62, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, - 0x64, 0x12, 0x4b, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x77, 0x6f, 0x72, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x1a, 0x1d, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2f, 0x74, 0x72, 0x61, 0x69, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x69, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 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, 0x2f, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x02, 0x0a, 0x17, 0x57, 0x6f, 0x72, 0x6b, 0x6c, + 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x73, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x1c, + 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x70, 0x6f, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x70, 0x6f, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x64, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6f, 0x64, 0x55, 0x69, 0x64, 0x12, 0x59, 0x0a, 0x06, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 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, 0x41, 0x74, 0x74, 0x72, 0x73, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x65, 0x0a, 0x11, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x73, + 0x55, 0x6e, 0x69, 0x78, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, + 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x03, 0x67, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, 0xab, 0x01, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x6c, + 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x73, 0x12, 0x43, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x78, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 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, 0x41, 0x74, + 0x74, 0x72, 0x73, 0x55, 0x6e, 0x69, 0x78, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x78, 0x12, 0x55, 0x0a, + 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x35, 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, 0x55, 0x73, 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, 0x73, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, - 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xca, 0x01, 0x0a, 0x05, 0x41, 0x74, - 0x74, 0x72, 0x73, 0x12, 0x47, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x41, 0x74, 0x74, - 0x72, 0x73, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3b, 0x0a, 0x04, - 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 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, 0x55, 0x73, 0x65, 0x72, 0x41, 0x74, - 0x74, 0x72, 0x73, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x04, 0x6a, 0x6f, 0x69, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 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, 0x4a, 0x6f, 0x69, 0x6e, 0x41, 0x74, 0x74, 0x72, 0x73, - 0x52, 0x04, 0x6a, 0x6f, 0x69, 0x6e, 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, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x74, 0x74, 0x72, 0x73, 0x4b, 0x75, + 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x52, 0x0a, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, + 0x65, 0x74, 0x65, 0x73, 0x22, 0xb3, 0x02, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x41, 0x74, 0x74, + 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x62, 0x6f, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x42, 0x6f, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x62, 0x6f, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x62, 0x6f, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x6f, 0x74, 0x5f, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x62, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x4b, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 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, + 0x55, 0x73, 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, 0x73, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x30, 0x0a, + 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x72, 0x61, 0x69, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x72, 0x61, 0x69, 0x74, 0x52, 0x06, 0x74, 0x72, 0x61, 0x69, 0x74, 0x73, 0x1a, + 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xca, 0x01, 0x0a, 0x05, 0x41, + 0x74, 0x74, 0x72, 0x73, 0x12, 0x47, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 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, 0x41, 0x74, + 0x74, 0x72, 0x73, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3b, 0x0a, + 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 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, 0x55, 0x73, 0x65, 0x72, 0x41, + 0x74, 0x74, 0x72, 0x73, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x04, 0x6a, 0x6f, + 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 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, 0x4a, 0x6f, 0x69, 0x6e, 0x41, 0x74, 0x74, 0x72, + 0x73, 0x52, 0x04, 0x6a, 0x6f, 0x69, 0x6e, 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 ( @@ -519,21 +534,23 @@ var file_teleport_workloadidentity_v1_attrs_proto_goTypes = []any{ (*Attrs)(nil), // 4: teleport.workloadidentity.v1.Attrs nil, // 5: teleport.workloadidentity.v1.WorkloadAttrsKubernetes.LabelsEntry nil, // 6: teleport.workloadidentity.v1.UserAttrs.LabelsEntry - (*JoinAttrs)(nil), // 7: teleport.workloadidentity.v1.JoinAttrs + (*v1.Trait)(nil), // 7: teleport.trait.v1.Trait + (*JoinAttrs)(nil), // 8: teleport.workloadidentity.v1.JoinAttrs } var file_teleport_workloadidentity_v1_attrs_proto_depIdxs = []int32{ 5, // 0: teleport.workloadidentity.v1.WorkloadAttrsKubernetes.labels:type_name -> teleport.workloadidentity.v1.WorkloadAttrsKubernetes.LabelsEntry 1, // 1: teleport.workloadidentity.v1.WorkloadAttrs.unix:type_name -> teleport.workloadidentity.v1.WorkloadAttrsUnix 0, // 2: teleport.workloadidentity.v1.WorkloadAttrs.kubernetes:type_name -> teleport.workloadidentity.v1.WorkloadAttrsKubernetes 6, // 3: teleport.workloadidentity.v1.UserAttrs.labels:type_name -> teleport.workloadidentity.v1.UserAttrs.LabelsEntry - 2, // 4: teleport.workloadidentity.v1.Attrs.workload:type_name -> teleport.workloadidentity.v1.WorkloadAttrs - 3, // 5: teleport.workloadidentity.v1.Attrs.user:type_name -> teleport.workloadidentity.v1.UserAttrs - 7, // 6: teleport.workloadidentity.v1.Attrs.join:type_name -> teleport.workloadidentity.v1.JoinAttrs - 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 + 7, // 4: teleport.workloadidentity.v1.UserAttrs.traits:type_name -> teleport.trait.v1.Trait + 2, // 5: teleport.workloadidentity.v1.Attrs.workload:type_name -> teleport.workloadidentity.v1.WorkloadAttrs + 3, // 6: teleport.workloadidentity.v1.Attrs.user:type_name -> teleport.workloadidentity.v1.UserAttrs + 8, // 7: teleport.workloadidentity.v1.Attrs.join:type_name -> teleport.workloadidentity.v1.JoinAttrs + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_teleport_workloadidentity_v1_attrs_proto_init() } 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 963ef387ef474..e226dcb6aae90 100644 --- a/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go +++ b/api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go @@ -438,7 +438,14 @@ type WorkloadIdentityRule struct { unknownFields protoimpl.UnknownFields // The conditions that must be met for this rule to be considered passed. + // + // Mutually exclusive with expression. Conditions []*WorkloadIdentityCondition `protobuf:"bytes,1,rep,name=conditions,proto3" json:"conditions,omitempty"` + // An expression written in Teleport's predicate language that must evaluate + // to true for this rule to be considered passed. + // + // Mutually exclusive with conditions. + Expression string `protobuf:"bytes,2,opt,name=expression,proto3" json:"expression,omitempty"` } func (x *WorkloadIdentityRule) Reset() { @@ -478,6 +485,13 @@ func (x *WorkloadIdentityRule) GetConditions() []*WorkloadIdentityCondition { return nil } +func (x *WorkloadIdentityRule) GetExpression() string { + if x != nil { + return x.Expression + } + return "" +} + // The rules which are evaluated before the WorkloadIdentity can be issued. type WorkloadIdentityRules struct { state protoimpl.MessageState @@ -853,67 +867,69 @@ var file_teleport_workloadidentity_v1_resource_proto_rawDesc = []byte{ 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, + 0x10, 0x03, 0x52, 0x06, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x73, 0x22, 0x8f, 0x01, 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, 0x12, 0x1e, 0x0a, 0x0a, + 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 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, + 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, 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, 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, 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, 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, + 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 ( diff --git a/api/proto/teleport/workloadidentity/v1/attrs.proto b/api/proto/teleport/workloadidentity/v1/attrs.proto index 1a3b0099bac88..c5ca50f64136a 100644 --- a/api/proto/teleport/workloadidentity/v1/attrs.proto +++ b/api/proto/teleport/workloadidentity/v1/attrs.proto @@ -16,6 +16,7 @@ syntax = "proto3"; package teleport.workloadidentity.v1; +import "teleport/trait/v1/trait.proto"; import "teleport/workloadidentity/v1/join_attrs.proto"; option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1;workloadidentityv1"; @@ -71,6 +72,8 @@ message UserAttrs { string bot_instance_id = 4; // Labels of the user. map labels = 5; + // Traits of the user. + repeated teleport.trait.v1.Trait traits = 6; } // The attributes of a principal requesting a workload identity. These diff --git a/api/proto/teleport/workloadidentity/v1/resource.proto b/api/proto/teleport/workloadidentity/v1/resource.proto index c1b663ea816ed..abf314a5cd4fb 100644 --- a/api/proto/teleport/workloadidentity/v1/resource.proto +++ b/api/proto/teleport/workloadidentity/v1/resource.proto @@ -83,7 +83,15 @@ message WorkloadIdentityCondition { // An individual rule that is evaluated during the issuance of a WorkloadIdentity. message WorkloadIdentityRule { // The conditions that must be met for this rule to be considered passed. + // + // Mutually exclusive with expression. repeated WorkloadIdentityCondition conditions = 1; + + // An expression written in Teleport's predicate language that must evaluate + // to true for this rule to be considered passed. + // + // Mutually exclusive with conditions. + string expression = 2; } // The rules which are evaluated before the WorkloadIdentity can be issued. 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 4c7b71a51bfcb..49c1eb504096e 100644 --- a/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx +++ b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx @@ -48,7 +48,8 @@ Optional: Optional: -- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) +- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. Mutually exclusive with expression. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) +- `expression` (String) An expression written in Teleport's predicate language that must evaluate to true for this rule to be considered passed. Mutually exclusive with conditions. ### Nested Schema for `spec.rules.allow.conditions` diff --git a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx index b91d39d393f3b..663e3211ed1ee 100644 --- a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx +++ b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx @@ -75,7 +75,8 @@ Optional: Optional: -- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) +- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. Mutually exclusive with expression. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) +- `expression` (String) An expression written in Teleport's predicate language that must evaluate to true for this rule to be considered passed. Mutually exclusive with conditions. ### Nested Schema for `spec.rules.allow.conditions` diff --git a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go index 74432d2075d1e..885c773fbdf62 100644 --- a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go +++ b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go @@ -100,53 +100,60 @@ func GenSchemaWorkloadIdentity(ctx context.Context) (github_com_hashicorp_terraf Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ "rules": { Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"allow": { - Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"conditions": { - Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ - "attribute": { - Description: "The name of the attribute to evaluate the condition against.", - Optional: true, - Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, - }, - "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.", + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "conditions": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "attribute": { + Description: "The name of the attribute to evaluate the condition 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.", + }, + "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, - 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.", + }, + "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, - 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.", + }, + "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, - 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, - }, - }), - Description: "The conditions that must be met for this rule to be considered passed.", - 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, + }, + }), + Description: "The conditions that must be met for this rule to be considered passed. Mutually exclusive with expression.", + Optional: true, + }, + "expression": { + Description: "An expression written in Teleport's predicate language that must evaluate to true for this rule to be considered passed. Mutually exclusive with conditions.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), Description: "A list of rules used to determine if a WorkloadIdentity can be issued. If none are provided, it will be considered a pass. If any are provided, then at least one must pass for the rules to be considered passed.", Optional: true, }}), @@ -648,6 +655,23 @@ func CopyWorkloadIdentityFromTerraform(_ context.Context, tf github_com_hashicor } } } + { + a, ok := tf.Attrs["expression"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.expression"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.expression", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Expression = t + } + } + } } obj.Allow[k] = t } @@ -1557,6 +1581,28 @@ func CopyWorkloadIdentityToTerraform(ctx context.Context, obj *github_com_gravit } } } + { + t, ok := tf.AttrTypes["expression"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.expression"}) + } else { + v, ok := tf.Attrs["expression"].(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.expression", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.expression", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Expression) == "" + } + v.Value = string(obj.Expression) + v.Unknown = false + tf.Attrs["expression"] = v + } + } } v.Unknown = false c.Elems[k] = v diff --git a/lib/auth/machineid/workloadidentityv1/decision.go b/lib/auth/machineid/workloadidentityv1/decision.go index 98b3221730450..28da3b47ed9ca 100644 --- a/lib/auth/machineid/workloadidentityv1/decision.go +++ b/lib/auth/machineid/workloadidentityv1/decision.go @@ -18,7 +18,6 @@ package workloadidentityv1 import ( "context" - "regexp" "slices" "strings" @@ -27,6 +26,7 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1/expression" "github.com/gravitational/teleport/lib/utils" ) @@ -52,14 +52,14 @@ func decide( } // Now we can cook up some templating... - templated, err := templateString(wi.GetSpec().GetSpiffe().GetId(), attrs) + templated, err := expression.RenderTemplate(wi.GetSpec().GetSpiffe().GetId(), attrs) if err != nil { d.reason = trace.Wrap(err, "templating spec.spiffe.id") return d } d.templatedWorkloadIdentity.Spec.Spiffe.Id = templated - templated, err = templateString(wi.GetSpec().GetSpiffe().GetHint(), attrs) + templated, err = expression.RenderTemplate(wi.GetSpec().GetSpiffe().GetHint(), attrs) if err != nil { d.reason = trace.Wrap(err, "templating spec.spiffe.hint") return d @@ -67,7 +67,7 @@ func decide( d.templatedWorkloadIdentity.Spec.Spiffe.Hint = templated for i, san := range wi.GetSpec().GetSpiffe().GetX509().GetDnsSans() { - templated, err = templateString(san, attrs) + templated, err = expression.RenderTemplate(san, attrs) if err != nil { d.reason = trace.Wrap(err, "templating spec.spiffe.x509.dns_sans[%d]", i) return d @@ -87,7 +87,7 @@ func decide( if st != nil { dst := d.templatedWorkloadIdentity.Spec.Spiffe.X509.SubjectTemplate - templated, err = templateString(st.CommonName, attrs) + templated, err = expression.RenderTemplate(st.CommonName, attrs) if err != nil { d.reason = trace.Wrap( err, @@ -97,7 +97,7 @@ func decide( } dst.CommonName = templated - templated, err = templateString(st.Organization, attrs) + templated, err = expression.RenderTemplate(st.Organization, attrs) if err != nil { d.reason = trace.Wrap( err, @@ -107,7 +107,7 @@ func decide( } dst.Organization = templated - templated, err = templateString(st.OrganizationalUnit, attrs) + templated, err = expression.RenderTemplate(st.OrganizationalUnit, attrs) if err != nil { d.reason = trace.Wrap( err, @@ -130,8 +130,8 @@ func decide( // The specified attribute must be a string field. If the attribute is not // found, an error is returned. // -// TODO(noah): This function will be replaced by the Teleport predicate language -// in a coming PR. +// TODO: convert rules into predicate expressions, so we can evaluate them +// using the expressions package (and remove this function). func getFieldStringValue(attrs *workloadidentityv1pb.Attrs, attr string) (string, error) { attrParts := strings.Split(attr, ".") message := attrs.ProtoReflect() @@ -167,40 +167,6 @@ func getFieldStringValue(attrs *workloadidentityv1pb.Attrs, attr string) (string return "", nil } -// templateString takes a given input string and replaces any values within -// {{ }} with values from the attribute set. -// -// If the specified value is not found in the attribute set, an error is -// returned. -// -// 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) - - for _, match := range matches { - attrKey := strings.TrimSpace(match[1]) - value, err := getFieldStringValue(attrs, attrKey) - if err != nil { - return "", trace.Wrap(err, "fetching attribute value for %q", attrKey) - } - // We want to have an implicit rule here that if an attribute is - // included in the template, but is not set, we should refuse to issue - // the credential. - if value == "" { - return "", trace.NotFound("attribute %q unset", attrKey) - } - in = strings.Replace(in, match[0], value, 1) - } - - return in, nil -} - func evaluateRules( wi *workloadidentityv1pb.WorkloadIdentity, attrs *workloadidentityv1pb.Attrs, @@ -210,6 +176,18 @@ func evaluateRules( } ruleLoop: for _, rule := range wi.GetSpec().GetRules().GetAllow() { + if rule.GetExpression() != "" { + pass, err := expression.Evaluate(rule.GetExpression(), attrs) + if err != nil { + return err + } + if pass { + return nil + } else { + continue ruleLoop + } + } + for _, condition := range rule.GetConditions() { val, err := getFieldStringValue(attrs, condition.Attribute) if err != nil { diff --git a/lib/auth/machineid/workloadidentityv1/decision_test.go b/lib/auth/machineid/workloadidentityv1/decision_test.go index 3d2b9ed4cff95..e4ebc2cb4d116 100644 --- a/lib/auth/machineid/workloadidentityv1/decision_test.go +++ b/lib/auth/machineid/workloadidentityv1/decision_test.go @@ -76,188 +76,6 @@ func Test_decide(t *testing.T) { } } -func Test_getFieldStringValue(t *testing.T) { - tests := []struct { - name string - in *workloadidentityv1pb.Attrs - attr string - want string - requireErr require.ErrorAssertionFunc - }{ - { - name: "success", - in: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "jeff", - }, - }, - attr: "user.name", - want: "jeff", - requireErr: require.NoError, - }, - { - // This test ensures that the proto name (e.g service_account) is - // used instead of the Go name (e.g serviceAccount). - name: "underscored", - in: &workloadidentityv1pb.Attrs{ - Join: &workloadidentityv1pb.JoinAttrs{ - Kubernetes: &workloadidentityv1pb.JoinAttrsKubernetes{ - ServiceAccount: &workloadidentityv1pb.JoinAttrsKubernetesServiceAccount{ - Namespace: "default", - }, - }, - }, - }, - attr: "join.kubernetes.service_account.namespace", - want: "default", - requireErr: require.NoError, - }, - { - name: "bool", - in: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "jeff", - }, - Workload: &workloadidentityv1pb.WorkloadAttrs{ - Unix: &workloadidentityv1pb.WorkloadAttrsUnix{ - Attested: true, - }, - }, - }, - attr: "workload.unix.attested", - want: "true", - requireErr: require.NoError, - }, - { - name: "int32", - in: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "jeff", - }, - Workload: &workloadidentityv1pb.WorkloadAttrs{ - Unix: &workloadidentityv1pb.WorkloadAttrsUnix{ - Pid: 123, - }, - }, - }, - attr: "workload.unix.pid", - want: "123", - requireErr: require.NoError, - }, - { - name: "uint32", - in: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "jeff", - }, - Workload: &workloadidentityv1pb.WorkloadAttrs{ - Unix: &workloadidentityv1pb.WorkloadAttrsUnix{ - Gid: 123, - }, - }, - }, - attr: "workload.unix.gid", - want: "123", - requireErr: require.NoError, - }, - { - name: "non-string final field", - in: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "user", - }, - }, - attr: "user", - requireErr: func(t require.TestingT, err error, i ...interface{}) { - require.ErrorContains(t, err, "attribute \"user\" of type \"message\" cannot be converted to string") - }, - }, - { - // We mostly just want this to not panic. - name: "nil root", - in: nil, - attr: "user.name", - want: "", - requireErr: require.NoError, - }, - { - // We mostly just want this to not panic. - name: "nil submessage", - in: &workloadidentityv1pb.Attrs{ - User: nil, - }, - attr: "user.name", - want: "", - requireErr: require.NoError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, gotErr := getFieldStringValue(tt.in, tt.attr) - tt.requireErr(t, gotErr) - require.Equal(t, tt.want, got) - }) - } -} - -func Test_templateString(t *testing.T) { - tests := []struct { - name string - in string - want string - attrs *workloadidentityv1pb.Attrs - requireErr require.ErrorAssertionFunc - }{ - { - name: "success mixed", - in: "hello{{user.name}}.{{user.name}} {{ workload.kubernetes.pod_name }}//{{ workload.kubernetes.namespace}}", - want: "hellojeff.jeff pod1//default", - attrs: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "jeff", - }, - Workload: &workloadidentityv1pb.WorkloadAttrs{ - Kubernetes: &workloadidentityv1pb.WorkloadAttrsKubernetes{ - PodName: "pod1", - Namespace: "default", - }, - }, - }, - requireErr: require.NoError, - }, - { - name: "success with spaces", - in: "hello {{user.name}}", - want: "hello jeff", - attrs: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "jeff", - }, - }, - requireErr: require.NoError, - }, - { - name: "fail due to unset", - in: "hello {{workload.kubernetes.pod_name}}", - attrs: &workloadidentityv1pb.Attrs{ - User: &workloadidentityv1pb.UserAttrs{ - Name: "jeff", - }, - }, - requireErr: func(t require.TestingT, err error, i ...interface{}) { - require.ErrorContains(t, err, "attribute \"workload.kubernetes.pod_name\" unset") - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, gotErr := templateString(tt.in, tt.attrs) - tt.requireErr(t, gotErr) - require.Equal(t, tt.want, got) - }) - } -} - func Test_evaluateRules(t *testing.T) { attrs := &workloadidentityv1pb.Attrs{ User: &workloadidentityv1pb.UserAttrs{ @@ -537,6 +355,44 @@ func Test_evaluateRules(t *testing.T) { attrs: attrs, requireErr: noMatchRule, }, + { + name: "expression: pass", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + {Expression: `user.name == "foo"`}, + }, + }, + }, + }, + attrs: attrs, + requireErr: require.NoError, + }, + { + name: "expression: fail", + wid: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + {Expression: `user.name == "not-foo"`}, + }, + }, + }, + }, + attrs: attrs, + requireErr: noMatchRule, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/lib/auth/machineid/workloadidentityv1/expression/evaluate.go b/lib/auth/machineid/workloadidentityv1/expression/evaluate.go new file mode 100644 index 0000000000000..4f3d203c4f734 --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/expression/evaluate.go @@ -0,0 +1,51 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package expression + +import ( + "github.com/gravitational/trace" + + workloadidentityv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" +) + +// Evaluate the given boolean expression against the given attributes. +func Evaluate(expr string, attrs *workloadidentityv1.Attrs) (bool, error) { + e, err := booleanExpressionParser.Parse(expr) + if err != nil { + return false, trace.Wrap(err, "parsing expression: %s", expr) + } + + rsp, err := e.Evaluate(attrs) + if err != nil { + return false, trace.Wrap(err, "evaluating expression: %s", expr) + } + + if result, ok := rsp.(bool); ok { + return result, nil + } + + return false, trace.Errorf("expression evaluated to %T instead of boolean: %s", rsp, expr) +} + +// Validate the given boolean expression is syntactically correct, and does not +// refer to any unknown attributes. +func Validate(expr string) error { + _, err := booleanExpressionParser.Parse(expr) + return err +} diff --git a/lib/auth/machineid/workloadidentityv1/expression/evaluate_test.go b/lib/auth/machineid/workloadidentityv1/expression/evaluate_test.go new file mode 100644 index 0000000000000..b32e9dd77fc7c --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/expression/evaluate_test.go @@ -0,0 +1,134 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package expression_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + traitv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1" + workloadidentityv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1/expression" +) + +func TestEvaluate(t *testing.T) { + // True result. + result, err := expression.Evaluate( + `user.is_bot && user.name == "Larry"`, + &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Name: "Larry", + IsBot: true, + }, + }, + ) + require.NoError(t, err) + require.True(t, result) + + // False result. + result, err = expression.Evaluate( + `user.name != user.name`, + &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Name: "Bobby", + }, + }, + ) + require.NoError(t, err) + require.False(t, result) + + // Unset field (allowed in boolean expressions). + result, err = expression.Evaluate( + `user.name == ""`, + &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Name: "", + }, + }, + ) + require.NoError(t, err) + require.True(t, result) +} + +func TestEvaluate_Errors(t *testing.T) { + testCases := map[string]struct { + expr string + attrs *workloadidentityv1.Attrs + err string + }{ + "unset sub-message": { + expr: `user.name == "Bobby"`, + attrs: &workloadidentityv1.Attrs{}, + err: "user is unset", + }, + "unset map key": { + expr: `workload.kubernetes.labels["foo"] == "bar"`, + attrs: &workloadidentityv1.Attrs{ + Workload: &workloadidentityv1.WorkloadAttrs{ + Kubernetes: &workloadidentityv1.WorkloadAttrsKubernetes{ + Labels: map[string]string{"bar": "baz"}, + }, + }, + }, + err: `no value for key: "foo"`, + }, + "non-boolean expression": { + expr: `"chunky bacon"`, + attrs: &workloadidentityv1.Attrs{}, + err: "evaluated to string instead of boolean", + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + _, err := expression.Evaluate(tc.expr, tc.attrs) + require.ErrorContains(t, err, tc.err) + }) + } +} + +func TestEvaluate_Traits(t *testing.T) { + result, err := expression.Evaluate( + `user.traits.logins.contains("root")`, + &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Traits: []*traitv1.Trait{ + { + Key: "logins", + Values: []string{"root", "alice", "bob"}, + }, + }, + }, + }, + ) + require.NoError(t, err) + require.True(t, result) + + // Unset trait. + result, err = expression.Evaluate( + `is_empty(user.traits.logins)`, + &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Traits: []*traitv1.Trait{}, + }, + }, + ) + require.NoError(t, err) + require.True(t, result) +} diff --git a/lib/auth/machineid/workloadidentityv1/expression/expression.go b/lib/auth/machineid/workloadidentityv1/expression/expression.go new file mode 100644 index 0000000000000..d18e4fcf5badc --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/expression/expression.go @@ -0,0 +1,88 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package expression + +import ( + "strings" + + "github.com/gravitational/trace" + + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/lib/expression" + "github.com/gravitational/teleport/lib/utils/typical" +) + +var ( + templateExpressionParser = must(expression.NewTraitsExpressionParser[*workloadidentityv1pb.Attrs](templateVars)) + booleanExpressionParser = must(expression.NewTraitsExpressionParser[*workloadidentityv1pb.Attrs](expressionVars)) + + templateVars = func() map[string]typical.Variable { + vars := protoMessageVariables[*workloadidentityv1pb.Attrs](true) + + // In the context of a template, traits must be single-valued and we + // return an error if they're not. For completeness, it might better if + // you could say `{{user.traits.foo.contains("bar")}}` or index the + // values in a template — but we're optimizing for the most common case + // where you want to interpolate the trait's value. + vars["user.traits"] = typical.DynamicMapFunction(func(env *workloadidentityv1pb.Attrs, key string) (string, error) { + traits := make(map[string][]string) + for _, v := range env.GetUser().GetTraits() { + traits[v.GetKey()] = append(traits[v.GetKey()], v.Values...) + } + + vals := traits[key] + + switch len(vals) { + case 1: + return vals[0], nil + case 0: + return "", trace.Errorf("no value for trait: %q", key) + default: + return "", trace.Errorf( + "trait `%s` has multiple values (%s), only single-value traits may be used in a template", + key, + strings.Join(vals, ", "), + ) + } + }) + return vars + }() + + expressionVars = func() map[string]typical.Variable { + vars := protoMessageVariables[*workloadidentityv1pb.Attrs](false) + + // In the context of a binary expression, we support multi-valued traits + // so you can use `contains`. + vars["user.traits"] = typical.DynamicMapFunction(func(env *workloadidentityv1pb.Attrs, key string) (expression.Set, error) { + traits := make(map[string][]string) + for _, v := range env.GetUser().GetTraits() { + traits[v.GetKey()] = append(traits[v.GetKey()], v.Values...) + } + return expression.NewSet(traits[key]...), nil + }) + return vars + }() +) + +func must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} diff --git a/lib/auth/machineid/workloadidentityv1/expression/proto.go b/lib/auth/machineid/workloadidentityv1/expression/proto.go new file mode 100644 index 0000000000000..11f491d213414 --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/expression/proto.go @@ -0,0 +1,167 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package expression + +import ( + "strings" + + "github.com/gravitational/trace" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/gravitational/teleport/lib/utils/typical" +) + +// protoMessageVariables builds a map of `typical.Variable`s for the given proto +// message type. If zeroValError is true, accessing an "unset" string field will +// return an error instead of the zero value. +// +// We currently only support the following field types, and all other fields will +// be ignored: +// +// - string +// - int32 +// - int64 +// - uint32 +// - uint64 +// - bool +// - map +// +// Repeated fields are not yet supported. +// +// Note: we do not support self-referential messages or circular references, these +// fields will also be ignored. +func protoMessageVariables[TEnv proto.Message](zeroValError bool) map[string]typical.Variable { + // addMessageFields adds each of a messages fields as variables. If it + // encounters a sub-message, it recursively calls itself to add the sub + // message's fields, prefixed its ancestors' names. + var addMessageFields func(string, []protoreflect.FieldDescriptor, protoreflect.MessageDescriptor) + + // seen tracks which message types we've already processed, to prevent infinite + // recursion in self-referential messages. + seen := make(map[string]struct{}) + + vars := make(map[string]typical.Variable) + addMessageFields = func(prefix string, ancestors []protoreflect.FieldDescriptor, descriptor protoreflect.MessageDescriptor) { + // Do not process this message type if we've already seen it. + if _, ok := seen[string(descriptor.FullName())]; ok { + return + } + seen[string(descriptor.FullName())] = struct{}{} + + fields := descriptor.Fields() + for i := 0; i < fields.Len(); i++ { + field := fields.Get(i) + + name := field.TextName() + if prefix != "" { + name = prefix + "." + name + } + + // Read the field value from the given TEnv by reading each of the + // ancestor messages. + get := func(env TEnv) (protoreflect.Value, error) { + msg := env.ProtoReflect() + names := make([]string, 0) + for _, ancestor := range ancestors { + msg = msg.Get(ancestor).Message() + names = append(names, ancestor.TextName()) + + // Accessing a field on an unset sub-message is always an error. + if !msg.IsValid() { + return protoreflect.Value{}, trace.Errorf("%s is unset", strings.Join(names, ".")) + } + } + + return msg.Get(field), nil + } + + if field.IsMap() { + // We currently only support map[string]string. + if field.MapKey().Kind() != protoreflect.StringKind || + field.MapValue().Kind() != protoreflect.StringKind { + continue + } + vars[name] = typical.DynamicMapFunction(func(env TEnv, key string) (string, error) { + mapKey := protoreflect.ValueOf(key).MapKey() + + val, err := get(env) + if err != nil { + return "", err + } + mapVal := val.Map().Get(mapKey) + + if mapVal.IsValid() { + return mapVal.String(), nil + } + return "", trace.Errorf("no value for key: %q", key) + }) + } + + switch field.Kind() { + case protoreflect.MessageKind: + addMessageFields(name, append(ancestors, field), field.Message()) + case protoreflect.StringKind: + vars[name] = typical.DynamicVariable(func(env TEnv) (string, error) { + v, err := get(env) + if err != nil { + return "", err + } + s := v.String() + if s == "" && zeroValError { + return "", trace.Errorf("%s is unset", name) + } + return s, nil + }) + case protoreflect.Int32Kind, protoreflect.Int64Kind: + vars[name] = typical.DynamicVariable(func(env TEnv) (int, error) { + if v, err := get(env); err == nil { + return int(v.Int()), nil + } else { + return 0, err + } + }) + case protoreflect.Uint32Kind, protoreflect.Uint64Kind: + vars[name] = typical.DynamicVariable(func(env TEnv) (uint64, error) { + if v, err := get(env); err == nil { + return v.Uint(), nil + } else { + return 0, err + } + }) + case protoreflect.BoolKind: + vars[name] = typical.DynamicVariable(func(env TEnv) (bool, error) { + if v, err := get(env); err == nil { + return v.Bool(), nil + } else { + return false, err + } + }) + } + } + } + + var t TEnv + addMessageFields( + "", /* prefix */ + nil, /* ancestors */ + t.ProtoReflect().Descriptor(), + ) + return vars +} diff --git a/lib/auth/machineid/workloadidentityv1/expression/template.go b/lib/auth/machineid/workloadidentityv1/expression/template.go new file mode 100644 index 0000000000000..03671b5383a1b --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/expression/template.go @@ -0,0 +1,166 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package expression + +import ( + "regexp" + "strconv" + "strings" + + "github.com/gravitational/trace" + + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/lib/utils/typical" +) + +// RenderTemplate parses the given template and renders a string using the given +// attributes. +func RenderTemplate(tmpl string, attrs *workloadidentityv1pb.Attrs) (string, error) { + t, err := NewTemplate(tmpl) + if err != nil { + return "", err + } + return t.Render(attrs) +} + +// Template represents a string template with typical/predicate expressions in +// curly braces. +// +// Expressions can refer to fields on the workloadidentityv1pb.Attrs protobuf +// message by their "text" name, and use common functions such as `strings.lower` +// and `regex.replace`. +type Template struct { + fragments []fragment +} + +// NewTemplate parses the given template string and any interpolated expressions. +func NewTemplate(tmpl string) (*Template, error) { + matches := reInterpolation.FindAllStringIndex(tmpl, -1) + + // If there were no expressions in curly braces, treat the whole template + // string as a literal. + if len(matches) == 0 { + return &Template{ + fragments: []fragment{ + { + kind: kindLiteral, + text: tmpl, + }, + }, + }, nil + } + + t := &Template{} + + var pos int + for _, m := range matches { + start, end := m[0], m[1] + + // Treat everything between the previous expression and this one as a + // literal value. + if start != 0 { + t.fragments = append(t.fragments, fragment{ + kind: kindLiteral, + text: tmpl[pos:start], + }) + } + + // Chop off the curly braces. + text := tmpl[start+2 : end-2] + + // Special case: curly braces only contain spaces. + if len(strings.TrimSpace(text)) == 0 { + pos = end + continue + } + + // Parse the expression using the typical/predicate library. + expr, err := templateExpressionParser.Parse(text) + if err != nil { + return nil, trace.Wrap(err, "parsing expression [%d:%d]: %s", start+2, end-2, text) + } + t.fragments = append(t.fragments, fragment{ + kind: kindExpression, + text: tmpl[start+2 : end-2], + expression: expr, + }) + + pos = end + } + + // Catch any literal text after the final expression. + if pos < len(tmpl) { + t.fragments = append(t.fragments, fragment{ + kind: kindLiteral, + text: tmpl[pos:], + }) + } + + return t, nil +} + +// Render a string from the template with the given attributes. +func (t *Template) Render(attrs *workloadidentityv1pb.Attrs) (string, error) { + b := new(strings.Builder) + + for _, frag := range t.fragments { + switch frag.kind { + case kindLiteral: + _, _ = b.WriteString(frag.text) + case kindExpression: + result, err := frag.expression.Evaluate(attrs) + if err != nil { + return "", trace.Wrap(err, "evaluating expression: %s", frag.text) + } + + switch t := result.(type) { + case string: + _, _ = b.WriteString(t) + case bool: + _, _ = b.WriteString(strconv.FormatBool(t)) + case int: + _, _ = b.WriteString(strconv.Itoa(t)) + default: + return "", trace.Errorf("expression did not evaluate to a string: %s", frag.text) + } + } + } + + return b.String(), nil +} + +type fragmentKind int + +const ( + kindLiteral fragmentKind = iota + kindExpression +) + +type fragment struct { + kind fragmentKind + text string + expression typical.Expression[*workloadidentityv1pb.Attrs, any] +} + +// reInterpolation matches anything between curly braces that isn't a curly +// brace. The downside of matching interpolations like this is that it means +// users cannot use curly braces in their expressions (e.g. in regex.replace) +// so we should eventually replace this with a real parser which supports +// escape sequences. +var reInterpolation = regexp.MustCompile(`\{\{([^{}]+?)\}\}`) diff --git a/lib/auth/machineid/workloadidentityv1/expression/template_test.go b/lib/auth/machineid/workloadidentityv1/expression/template_test.go new file mode 100644 index 0000000000000..1970517cc54c9 --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/expression/template_test.go @@ -0,0 +1,214 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package expression_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + traitv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1" + workloadidentityv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1/expression" +) + +func TestTemplate_Success(t *testing.T) { + testCases := map[string]struct { + tmpl string + attrs *workloadidentityv1.Attrs + output string + }{ + "empty string": { + tmpl: "", + attrs: &workloadidentityv1.Attrs{}, + output: "", + }, + "simple interpolation": { + tmpl: "{{user.name}}", + attrs: &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Name: "Larry", + }, + }, + output: "Larry", + }, + "multiple interpolations": { + tmpl: "{{user.name}} {{user.bot_name}}", + attrs: &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Name: "Larry", + BotName: "LarryBot", + }, + }, + output: "Larry LarryBot", + }, + "map access": { + tmpl: `/region/{{workload.kubernetes.labels["com.mycloud/region"]}}/service`, + attrs: &workloadidentityv1.Attrs{ + Workload: &workloadidentityv1.WorkloadAttrs{ + Kubernetes: &workloadidentityv1.WorkloadAttrsKubernetes{ + Labels: map[string]string{ + "com.mycloud/region": "eu", + }, + }, + }, + }, + output: "/region/eu/service", + }, + "function calling": { + tmpl: `{{strings.upper("hello")}}`, + attrs: &workloadidentityv1.Attrs{}, + output: "HELLO", + }, + "incomplete curly braces": { + tmpl: "look at my mustache :-{{)", + attrs: &workloadidentityv1.Attrs{}, + output: "look at my mustache :-{{)", + }, + "empty expression": { + tmpl: "{{ }}", + attrs: &workloadidentityv1.Attrs{}, + output: "", + }, + "boolean expression": { + tmpl: `{{ "a" == "a" }}`, + attrs: &workloadidentityv1.Attrs{}, + output: "true", + }, + "integer expression": { + tmpl: `{{ 1234 }}`, + attrs: &workloadidentityv1.Attrs{}, + output: "1234", + }, + "user traits": { + tmpl: `{{user.traits.skill}}`, + attrs: &workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Traits: []*traitv1.Trait{ + { + Key: "skill", + Values: []string{"coffee-drinker"}, + }, + }, + }, + }, + output: "coffee-drinker", + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + template, err := expression.NewTemplate(tc.tmpl) + require.NoError(t, err) + + output, err := template.Render(tc.attrs) + require.NoError(t, err) + require.Equal(t, tc.output, output) + }) + } +} + +func TestTemplate_ParseError(t *testing.T) { + testCases := map[string]struct { + tmpl string + err string + }{ + "non-existent variable": { + tmpl: `{{foo.bar}}`, + err: `unknown identifier: "foo.bar"`, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + _, err := expression.NewTemplate(tc.tmpl) + require.ErrorContains(t, err, tc.err) + }) + } +} + +func TestTemplate_MultipleTraitValues(t *testing.T) { + tmpl, err := expression.NewTemplate(`{{user.traits.skills}}`) + require.NoError(t, err) + + _, err = tmpl.Render(&workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Traits: []*traitv1.Trait{ + { + Key: "skills", + Values: []string{"sword-fighting", "sonnet-writing"}, + }, + }, + }, + }) + require.ErrorContains(t, err, "multiple values") +} + +func TestTemplate_MissingSubmessage(t *testing.T) { + tmpl, err := expression.NewTemplate(`{{workload.kubernetes.pod_name}}`) + require.NoError(t, err) + + _, err = tmpl.Render(&workloadidentityv1.Attrs{ + Workload: &workloadidentityv1.WorkloadAttrs{}, + }) + require.ErrorContains(t, err, "workload.kubernetes is unset") +} + +func TestTemplate_MissingMapValue(t *testing.T) { + tmpl, err := expression.NewTemplate(`{{workload.kubernetes.labels.foo}}`) + require.NoError(t, err) + + _, err = tmpl.Render(&workloadidentityv1.Attrs{ + Workload: &workloadidentityv1.WorkloadAttrs{ + Kubernetes: &workloadidentityv1.WorkloadAttrsKubernetes{ + Labels: map[string]string{"bar": "baz"}, + }, + }, + }) + require.ErrorContains(t, err, `no value for key: "foo"`) +} + +func TestTemplate_MissingTrait(t *testing.T) { + tmpl, err := expression.NewTemplate(`{{user.traits.foo}}`) + require.NoError(t, err) + + _, err = tmpl.Render(&workloadidentityv1.Attrs{ + User: &workloadidentityv1.UserAttrs{ + Traits: []*traitv1.Trait{ + { + Key: "bar", + Values: []string{"baz"}, + }, + }, + }, + }) + require.ErrorContains(t, err, `no value for trait: "foo"`) +} + +func TestTemplate_UnsetValue(t *testing.T) { + tmpl, err := expression.NewTemplate(`{{workload.kubernetes.pod_name}}`) + require.NoError(t, err) + + _, err = tmpl.Render(&workloadidentityv1.Attrs{ + Workload: &workloadidentityv1.WorkloadAttrs{ + Kubernetes: &workloadidentityv1.WorkloadAttrsKubernetes{ + PodName: "", + }, + }, + }) + require.ErrorContains(t, err, "workload.kubernetes.pod_name is unset") +} diff --git a/lib/auth/machineid/workloadidentityv1/issuer_service.go b/lib/auth/machineid/workloadidentityv1/issuer_service.go index 66422268c8fcd..cb64207395bef 100644 --- a/lib/auth/machineid/workloadidentityv1/issuer_service.go +++ b/lib/auth/machineid/workloadidentityv1/issuer_service.go @@ -35,6 +35,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport" + traitv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/trait/v1" workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" "github.com/gravitational/teleport/api/observability/tracing" "github.com/gravitational/teleport/api/types" @@ -137,6 +138,13 @@ func (s *IssuanceService) deriveAttrs( Join: authzCtx.Identity.GetIdentity().JoinAttributes, } + for key, values := range authzCtx.Identity.GetIdentity().Traits { + attrs.User.Traits = append(attrs.User.Traits, &traitv1.Trait{ + Key: key, + Values: values, + }) + } + return attrs, nil } diff --git a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go index e8fe15811f8d0..db5c4feac42ea 100644 --- a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go +++ b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go @@ -199,7 +199,7 @@ func TestIssueWorkloadIdentityE2E(t *testing.T) { }, }, Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ - Id: "/example/{{ user.name }}/{{ join.kubernetes.service_account.namespace }}/{{ join.kubernetes.pod.name }}/{{ workload.unix.pid }}", + Id: `/example/{{ user.name }}/{{ user.traits["organizational-unit"] }}/{{ join.kubernetes.service_account.namespace }}/{{ join.kubernetes.pod.name }}/{{ workload.unix.pid }}`, }, }, }) @@ -215,6 +215,12 @@ func TestIssueWorkloadIdentityE2E(t *testing.T) { Roles: []string{ role.GetName(), }, + Traits: []*machineidv1.Trait{ + { + Name: "organizational-unit", + Values: []string{"finance-department"}, + }, + }, }, } @@ -336,7 +342,7 @@ func TestIssueWorkloadIdentityE2E(t *testing.T) { require.NoError(t, err) // Check included public key matches require.Equal(t, workloadKey.Public(), cert.PublicKey) - require.Equal(t, "spiffe://localhost/example/bot-my-bot/my-namespace/my-pod/123", cert.URIs[0].String()) + require.Equal(t, "spiffe://localhost/example/bot-my-bot/finance-department/my-namespace/my-pod/123", cert.URIs[0].String()) } func TestIssueWorkloadIdentity(t *testing.T) { diff --git a/lib/services/workload_identity.go b/lib/services/workload_identity.go index 826ab6540b0e6..97eb63b1d8dab 100644 --- a/lib/services/workload_identity.go +++ b/lib/services/workload_identity.go @@ -24,6 +24,7 @@ import ( workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1/expression" ) // WorkloadIdentities is an interface over the WorkloadIdentities service. This @@ -97,9 +98,19 @@ func ValidateWorkloadIdentity(s *workloadidentityv1pb.WorkloadIdentity) error { } for i, rule := range s.GetSpec().GetRules().GetAllow() { - if len(rule.Conditions) == 0 { - return trace.BadParameter("spec.rules.allow[%d].conditions: must be non-empty", i) + if rule.Expression == "" { + if len(rule.Conditions) == 0 { + return trace.BadParameter("spec.rules.allow[%d].conditions: must be non-empty", i) + } + } else { + if len(rule.Conditions) != 0 { + return trace.BadParameter("spec.rules.allow[%d].conditions: is mutually exclusive with expression", i) + } + if err := expression.Validate(rule.Expression); err != nil { + return trace.BadParameter("spec.rules.allow[%d].expression: invalid expression: %s", i, err.Error()) + } } + for j, condition := range rule.Conditions { if condition.Attribute == "" { return trace.BadParameter("spec.rules.allow[%d].conditions[%d].attribute: must be non-empty", i, j) diff --git a/lib/services/workload_identity_test.go b/lib/services/workload_identity_test.go index 27d0e1ec0261b..ec0d1ba078c70 100644 --- a/lib/services/workload_identity_test.go +++ b/lib/services/workload_identity_test.go @@ -228,6 +228,83 @@ func TestValidateWorkloadIdentity(t *testing.T) { }, requireErr: errContains("spec.rules.allow[0].conditions[0]: operator must be specified"), }, + { + name: "expression and conditions", + in: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Expression: `user.name == "Alan Partridge"`, + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "example", + Operator: &workloadidentityv1pb.WorkloadIdentityCondition_Eq{ + Eq: &workloadidentityv1pb.WorkloadIdentityConditionEq{ + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/example", + }, + }, + }, + requireErr: errContains("spec.rules.allow[0].conditions: is mutually exclusive with expression"), + }, + { + name: "neither expression or conditions", + in: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + {}, // Empty rule. + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/example", + }, + }, + }, + requireErr: errContains("spec.rules.allow[0].conditions: must be non-empty"), + }, + { + name: "invalid expression", + in: &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example", + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Expression: `does_not_exist`, + }, + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/example", + }, + }, + }, + requireErr: errContains(`unknown identifier: "does_not_exist"`), + }, } for _, tc := range testCases {