diff --git a/api/ateapi/v1/ateapi.pb.go b/api/ateapi/v1/ateapi.pb.go index 250a2f5968..43f2621dbe 100644 --- a/api/ateapi/v1/ateapi.pb.go +++ b/api/ateapi/v1/ateapi.pb.go @@ -535,6 +535,58 @@ func (WorkerState) EnumDescriptor() ([]byte, []int) { return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{8} } +// PrincipalKind is the authentication method that established a principal. +type PrincipalKind int32 + +const ( + PrincipalKind_PRINCIPAL_KIND_UNSPECIFIED PrincipalKind = 0 + // A bearer JWT verified against a configured JWT provider. + PrincipalKind_PRINCIPAL_KIND_JWT PrincipalKind = 1 + // An mTLS client certificate. + PrincipalKind_PRINCIPAL_KIND_MTLS PrincipalKind = 2 +) + +// Enum value maps for PrincipalKind. +var ( + PrincipalKind_name = map[int32]string{ + 0: "PRINCIPAL_KIND_UNSPECIFIED", + 1: "PRINCIPAL_KIND_JWT", + 2: "PRINCIPAL_KIND_MTLS", + } + PrincipalKind_value = map[string]int32{ + "PRINCIPAL_KIND_UNSPECIFIED": 0, + "PRINCIPAL_KIND_JWT": 1, + "PRINCIPAL_KIND_MTLS": 2, + } +) + +func (x PrincipalKind) Enum() *PrincipalKind { + p := new(PrincipalKind) + *p = x + return p +} + +func (x PrincipalKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PrincipalKind) Descriptor() protoreflect.EnumDescriptor { + return file_api_ateapi_v1_ateapi_proto_enumTypes[9].Descriptor() +} + +func (PrincipalKind) Type() protoreflect.EnumType { + return &file_api_ateapi_v1_ateapi_proto_enumTypes[9] +} + +func (x PrincipalKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PrincipalKind.Descriptor instead. +func (PrincipalKind) EnumDescriptor() ([]byte, []int) { + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{9} +} + // Scope is whether a top-level resource belongs to an atespace. type Scope int32 @@ -572,11 +624,11 @@ func (x Scope) String() string { } func (Scope) Descriptor() protoreflect.EnumDescriptor { - return file_api_ateapi_v1_ateapi_proto_enumTypes[9].Descriptor() + return file_api_ateapi_v1_ateapi_proto_enumTypes[10].Descriptor() } func (Scope) Type() protoreflect.EnumType { - return &file_api_ateapi_v1_ateapi_proto_enumTypes[9] + return &file_api_ateapi_v1_ateapi_proto_enumTypes[10] } func (x Scope) Number() protoreflect.EnumNumber { @@ -585,7 +637,7 @@ func (x Scope) Number() protoreflect.EnumNumber { // Deprecated: Use Scope.Descriptor instead. func (Scope) EnumDescriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{9} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{10} } // ObjectSnapshot addresses a snapshot held in object storage. @@ -8131,6 +8183,106 @@ func (x *ActorAssignment) GetResources() *Resources { return nil } +type WhoAmIRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WhoAmIRequest) Reset() { + *x = WhoAmIRequest{} + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[112] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WhoAmIRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WhoAmIRequest) ProtoMessage() {} + +func (x *WhoAmIRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[112] + 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 WhoAmIRequest.ProtoReflect.Descriptor instead. +func (*WhoAmIRequest) Descriptor() ([]byte, []int) { + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{112} +} + +type WhoAmIResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The principal ID that authorization policies name: the subject of a + // bearer JWT, or the first URI SAN of an mTLS client certificate. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // How the caller authenticated. + Kind PrincipalKind `protobuf:"varint,2,opt,name=kind,proto3,enum=substrate.ateapi.v1.PrincipalKind" json:"kind,omitempty"` + // The issuer of the caller's bearer JWT. Empty for mTLS callers. + Issuer string `protobuf:"bytes,3,opt,name=issuer,proto3" json:"issuer,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WhoAmIResponse) Reset() { + *x = WhoAmIResponse{} + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[113] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WhoAmIResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WhoAmIResponse) ProtoMessage() {} + +func (x *WhoAmIResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[113] + 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 WhoAmIResponse.ProtoReflect.Descriptor instead. +func (*WhoAmIResponse) Descriptor() ([]byte, []int) { + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{113} +} + +func (x *WhoAmIResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *WhoAmIResponse) GetKind() PrincipalKind { + if x != nil { + return x.Kind + } + return PrincipalKind_PRINCIPAL_KIND_UNSPECIFIED +} + +func (x *WhoAmIResponse) GetIssuer() string { + if x != nil { + return x.Issuer + } + return "" +} + type AcquireWorkerEpochRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // The Worker whose worker process is starting. atespace is always empty; @@ -8145,7 +8297,7 @@ type AcquireWorkerEpochRequest struct { func (x *AcquireWorkerEpochRequest) Reset() { *x = AcquireWorkerEpochRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[112] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8157,7 +8309,7 @@ func (x *AcquireWorkerEpochRequest) String() string { func (*AcquireWorkerEpochRequest) ProtoMessage() {} func (x *AcquireWorkerEpochRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[112] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8170,7 +8322,7 @@ func (x *AcquireWorkerEpochRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AcquireWorkerEpochRequest.ProtoReflect.Descriptor instead. func (*AcquireWorkerEpochRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{112} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{114} } func (x *AcquireWorkerEpochRequest) GetWorker() *ObjectRef { @@ -8190,7 +8342,7 @@ type AcquireWorkerEpochResponse struct { func (x *AcquireWorkerEpochResponse) Reset() { *x = AcquireWorkerEpochResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[113] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8202,7 +8354,7 @@ func (x *AcquireWorkerEpochResponse) String() string { func (*AcquireWorkerEpochResponse) ProtoMessage() {} func (x *AcquireWorkerEpochResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[113] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8215,7 +8367,7 @@ func (x *AcquireWorkerEpochResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AcquireWorkerEpochResponse.ProtoReflect.Descriptor instead. func (*AcquireWorkerEpochResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{113} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{115} } func (x *AcquireWorkerEpochResponse) GetEpoch() int64 { @@ -8258,7 +8410,7 @@ type RegisterWorkerRequest struct { func (x *RegisterWorkerRequest) Reset() { *x = RegisterWorkerRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[114] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8270,7 +8422,7 @@ func (x *RegisterWorkerRequest) String() string { func (*RegisterWorkerRequest) ProtoMessage() {} func (x *RegisterWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[114] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8283,7 +8435,7 @@ func (x *RegisterWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterWorkerRequest.ProtoReflect.Descriptor instead. func (*RegisterWorkerRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{114} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{116} } func (x *RegisterWorkerRequest) GetWorker() *ObjectRef { @@ -8324,7 +8476,7 @@ type RegisterWorkerResponse struct { func (x *RegisterWorkerResponse) Reset() { *x = RegisterWorkerResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[115] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8336,7 +8488,7 @@ func (x *RegisterWorkerResponse) String() string { func (*RegisterWorkerResponse) ProtoMessage() {} func (x *RegisterWorkerResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[115] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8349,7 +8501,7 @@ func (x *RegisterWorkerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterWorkerResponse.ProtoReflect.Descriptor instead. func (*RegisterWorkerResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{115} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{117} } func (x *RegisterWorkerResponse) GetWorker() *Worker { @@ -8389,7 +8541,7 @@ type SetWorkerSandboxRuntimesRequest struct { func (x *SetWorkerSandboxRuntimesRequest) Reset() { *x = SetWorkerSandboxRuntimesRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[116] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8401,7 +8553,7 @@ func (x *SetWorkerSandboxRuntimesRequest) String() string { func (*SetWorkerSandboxRuntimesRequest) ProtoMessage() {} func (x *SetWorkerSandboxRuntimesRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[116] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8414,7 +8566,7 @@ func (x *SetWorkerSandboxRuntimesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerSandboxRuntimesRequest.ProtoReflect.Descriptor instead. func (*SetWorkerSandboxRuntimesRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{116} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{118} } func (x *SetWorkerSandboxRuntimesRequest) GetWorker() *ObjectRef { @@ -8448,7 +8600,7 @@ type SetWorkerSandboxRuntimesResponse struct { func (x *SetWorkerSandboxRuntimesResponse) Reset() { *x = SetWorkerSandboxRuntimesResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[117] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8460,7 +8612,7 @@ func (x *SetWorkerSandboxRuntimesResponse) String() string { func (*SetWorkerSandboxRuntimesResponse) ProtoMessage() {} func (x *SetWorkerSandboxRuntimesResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[117] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8473,7 +8625,7 @@ func (x *SetWorkerSandboxRuntimesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerSandboxRuntimesResponse.ProtoReflect.Descriptor instead. func (*SetWorkerSandboxRuntimesResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{117} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{119} } func (x *SetWorkerSandboxRuntimesResponse) GetWorker() *Worker { @@ -8520,7 +8672,7 @@ type MintWorkerActorCertificateRequest struct { func (x *MintWorkerActorCertificateRequest) Reset() { *x = MintWorkerActorCertificateRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[118] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8532,7 +8684,7 @@ func (x *MintWorkerActorCertificateRequest) String() string { func (*MintWorkerActorCertificateRequest) ProtoMessage() {} func (x *MintWorkerActorCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[118] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8545,7 +8697,7 @@ func (x *MintWorkerActorCertificateRequest) ProtoReflect() protoreflect.Message // Deprecated: Use MintWorkerActorCertificateRequest.ProtoReflect.Descriptor instead. func (*MintWorkerActorCertificateRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{118} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{120} } func (x *MintWorkerActorCertificateRequest) GetActor() *ObjectRef { @@ -8589,7 +8741,7 @@ type MintWorkerActorCertificateResponse struct { func (x *MintWorkerActorCertificateResponse) Reset() { *x = MintWorkerActorCertificateResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[119] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8601,7 +8753,7 @@ func (x *MintWorkerActorCertificateResponse) String() string { func (*MintWorkerActorCertificateResponse) ProtoMessage() {} func (x *MintWorkerActorCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[119] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8614,7 +8766,7 @@ func (x *MintWorkerActorCertificateResponse) ProtoReflect() protoreflect.Message // Deprecated: Use MintWorkerActorCertificateResponse.ProtoReflect.Descriptor instead. func (*MintWorkerActorCertificateResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{119} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{121} } func (x *MintWorkerActorCertificateResponse) GetActorCertificates() [][]byte { @@ -8655,7 +8807,7 @@ type MintWorkerActorJwtRequest struct { func (x *MintWorkerActorJwtRequest) Reset() { *x = MintWorkerActorJwtRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[120] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8667,7 +8819,7 @@ func (x *MintWorkerActorJwtRequest) String() string { func (*MintWorkerActorJwtRequest) ProtoMessage() {} func (x *MintWorkerActorJwtRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[120] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8680,7 +8832,7 @@ func (x *MintWorkerActorJwtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintWorkerActorJwtRequest.ProtoReflect.Descriptor instead. func (*MintWorkerActorJwtRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{120} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{122} } func (x *MintWorkerActorJwtRequest) GetActor() *ObjectRef { @@ -8728,7 +8880,7 @@ type MintWorkerActorJwtResponse struct { func (x *MintWorkerActorJwtResponse) Reset() { *x = MintWorkerActorJwtResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[121] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8740,7 +8892,7 @@ func (x *MintWorkerActorJwtResponse) String() string { func (*MintWorkerActorJwtResponse) ProtoMessage() {} func (x *MintWorkerActorJwtResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[121] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8753,7 +8905,7 @@ func (x *MintWorkerActorJwtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintWorkerActorJwtResponse.ProtoReflect.Descriptor instead. func (*MintWorkerActorJwtResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{121} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{123} } func (x *MintWorkerActorJwtResponse) GetActorJwt() string { @@ -8801,7 +8953,7 @@ type RequestActorSuspendRequest struct { func (x *RequestActorSuspendRequest) Reset() { *x = RequestActorSuspendRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[122] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8813,7 +8965,7 @@ func (x *RequestActorSuspendRequest) String() string { func (*RequestActorSuspendRequest) ProtoMessage() {} func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[122] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8826,7 +8978,7 @@ func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendRequest.ProtoReflect.Descriptor instead. func (*RequestActorSuspendRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{122} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{124} } func (x *RequestActorSuspendRequest) GetWorker() *ObjectRef { @@ -8860,7 +9012,7 @@ type RequestActorSuspendResponse struct { func (x *RequestActorSuspendResponse) Reset() { *x = RequestActorSuspendResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[123] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8872,7 +9024,7 @@ func (x *RequestActorSuspendResponse) String() string { func (*RequestActorSuspendResponse) ProtoMessage() {} func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[123] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8885,7 +9037,7 @@ func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendResponse.ProtoReflect.Descriptor instead. func (*RequestActorSuspendResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{123} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{125} } func (x *RequestActorSuspendResponse) GetActor() *Actor { @@ -8925,7 +9077,7 @@ type ReportActorCrashedRequest struct { func (x *ReportActorCrashedRequest) Reset() { *x = ReportActorCrashedRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[124] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8937,7 +9089,7 @@ func (x *ReportActorCrashedRequest) String() string { func (*ReportActorCrashedRequest) ProtoMessage() {} func (x *ReportActorCrashedRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[124] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8950,7 +9102,7 @@ func (x *ReportActorCrashedRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportActorCrashedRequest.ProtoReflect.Descriptor instead. func (*ReportActorCrashedRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{124} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{126} } func (x *ReportActorCrashedRequest) GetWorker() *ObjectRef { @@ -8991,7 +9143,7 @@ type ReportActorCrashedResponse struct { func (x *ReportActorCrashedResponse) Reset() { *x = ReportActorCrashedResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[125] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9003,7 +9155,7 @@ func (x *ReportActorCrashedResponse) String() string { func (*ReportActorCrashedResponse) ProtoMessage() {} func (x *ReportActorCrashedResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[125] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9016,7 +9168,7 @@ func (x *ReportActorCrashedResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportActorCrashedResponse.ProtoReflect.Descriptor instead. func (*ReportActorCrashedResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{125} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{127} } func (x *ReportActorCrashedResponse) GetActor() *Actor { @@ -9047,7 +9199,7 @@ type ReportActorSnapshotStateRequest struct { func (x *ReportActorSnapshotStateRequest) Reset() { *x = ReportActorSnapshotStateRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[126] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9059,7 +9211,7 @@ func (x *ReportActorSnapshotStateRequest) String() string { func (*ReportActorSnapshotStateRequest) ProtoMessage() {} func (x *ReportActorSnapshotStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[126] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9072,7 +9224,7 @@ func (x *ReportActorSnapshotStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportActorSnapshotStateRequest.ProtoReflect.Descriptor instead. func (*ReportActorSnapshotStateRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{126} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{128} } func (x *ReportActorSnapshotStateRequest) GetActor() *ObjectRef { @@ -9104,7 +9256,7 @@ type ReportActorSnapshotStateResponse struct { func (x *ReportActorSnapshotStateResponse) Reset() { *x = ReportActorSnapshotStateResponse{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[127] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9116,7 +9268,7 @@ func (x *ReportActorSnapshotStateResponse) String() string { func (*ReportActorSnapshotStateResponse) ProtoMessage() {} func (x *ReportActorSnapshotStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[127] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9129,7 +9281,7 @@ func (x *ReportActorSnapshotStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReportActorSnapshotStateResponse.ProtoReflect.Descriptor instead. func (*ReportActorSnapshotStateResponse) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{127} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{129} } // ActorSnapshotState is what atelet holds for one Actor on its node. It @@ -9163,7 +9315,7 @@ type ActorSnapshotState struct { func (x *ActorSnapshotState) Reset() { *x = ActorSnapshotState{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[128] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9175,7 +9327,7 @@ func (x *ActorSnapshotState) String() string { func (*ActorSnapshotState) ProtoMessage() {} func (x *ActorSnapshotState) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[128] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9188,7 +9340,7 @@ func (x *ActorSnapshotState) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorSnapshotState.ProtoReflect.Descriptor instead. func (*ActorSnapshotState) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{128} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{130} } func (x *ActorSnapshotState) GetLatestLocal() *SnapshotReport { @@ -9260,7 +9412,7 @@ type SnapshotReport struct { func (x *SnapshotReport) Reset() { *x = SnapshotReport{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[129] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9272,7 +9424,7 @@ func (x *SnapshotReport) String() string { func (*SnapshotReport) ProtoMessage() {} func (x *SnapshotReport) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[129] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9285,7 +9437,7 @@ func (x *SnapshotReport) ProtoReflect() protoreflect.Message { // Deprecated: Use SnapshotReport.ProtoReflect.Descriptor instead. func (*SnapshotReport) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{129} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{131} } func (x *SnapshotReport) GetUuid() string { @@ -9355,7 +9507,7 @@ type AccessPolicy struct { func (x *AccessPolicy) Reset() { *x = AccessPolicy{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[130] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9367,7 +9519,7 @@ func (x *AccessPolicy) String() string { func (*AccessPolicy) ProtoMessage() {} func (x *AccessPolicy) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[130] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9380,7 +9532,7 @@ func (x *AccessPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessPolicy.ProtoReflect.Descriptor instead. func (*AccessPolicy) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{130} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{132} } func (x *AccessPolicy) GetMetadata() *ResourceMetadata { @@ -9422,7 +9574,7 @@ type Binding struct { func (x *Binding) Reset() { *x = Binding{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[131] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9434,7 +9586,7 @@ func (x *Binding) String() string { func (*Binding) ProtoMessage() {} func (x *Binding) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[131] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9447,7 +9599,7 @@ func (x *Binding) ProtoReflect() protoreflect.Message { // Deprecated: Use Binding.ProtoReflect.Descriptor instead. func (*Binding) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{131} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{133} } func (x *Binding) GetRole() string { @@ -9479,7 +9631,7 @@ type GetGlobalAccessPolicyRequest struct { func (x *GetGlobalAccessPolicyRequest) Reset() { *x = GetGlobalAccessPolicyRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[132] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9491,7 +9643,7 @@ func (x *GetGlobalAccessPolicyRequest) String() string { func (*GetGlobalAccessPolicyRequest) ProtoMessage() {} func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[132] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9504,7 +9656,7 @@ func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{132} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{134} } func (x *GetGlobalAccessPolicyRequest) GetName() string { @@ -9528,7 +9680,7 @@ type CreateGlobalAccessPolicyRequest struct { func (x *CreateGlobalAccessPolicyRequest) Reset() { *x = CreateGlobalAccessPolicyRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[133] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9540,7 +9692,7 @@ func (x *CreateGlobalAccessPolicyRequest) String() string { func (*CreateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[133] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9553,7 +9705,7 @@ func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{133} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{135} } func (x *CreateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -9577,7 +9729,7 @@ type UpdateGlobalAccessPolicyRequest struct { func (x *UpdateGlobalAccessPolicyRequest) Reset() { *x = UpdateGlobalAccessPolicyRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[134] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9589,7 +9741,7 @@ func (x *UpdateGlobalAccessPolicyRequest) String() string { func (*UpdateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[134] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9602,7 +9754,7 @@ func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{134} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{136} } func (x *UpdateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -9632,7 +9784,7 @@ type GetAtespaceAccessPolicyRequest struct { func (x *GetAtespaceAccessPolicyRequest) Reset() { *x = GetAtespaceAccessPolicyRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[135] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9644,7 +9796,7 @@ func (x *GetAtespaceAccessPolicyRequest) String() string { func (*GetAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[135] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9657,7 +9809,7 @@ func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{135} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{137} } func (x *GetAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -9693,7 +9845,7 @@ type CreateAtespaceAccessPolicyRequest struct { func (x *CreateAtespaceAccessPolicyRequest) Reset() { *x = CreateAtespaceAccessPolicyRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[136] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9705,7 +9857,7 @@ func (x *CreateAtespaceAccessPolicyRequest) String() string { func (*CreateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[136] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9718,7 +9870,7 @@ func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use CreateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{136} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{138} } func (x *CreateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -9754,7 +9906,7 @@ type UpdateAtespaceAccessPolicyRequest struct { func (x *UpdateAtespaceAccessPolicyRequest) Reset() { *x = UpdateAtespaceAccessPolicyRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[137] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9766,7 +9918,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) String() string { func (*UpdateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[137] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9779,7 +9931,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{137} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{139} } func (x *UpdateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -9820,7 +9972,7 @@ type DeleteAtespaceAccessPolicyRequest struct { func (x *DeleteAtespaceAccessPolicyRequest) Reset() { *x = DeleteAtespaceAccessPolicyRequest{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[138] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9832,7 +9984,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) String() string { func (*DeleteAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[138] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9845,7 +9997,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{138} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{140} } func (x *DeleteAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -9891,7 +10043,7 @@ type ResourceAnnotation struct { func (x *ResourceAnnotation) Reset() { *x = ResourceAnnotation{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[139] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9903,7 +10055,7 @@ func (x *ResourceAnnotation) String() string { func (*ResourceAnnotation) ProtoMessage() {} func (x *ResourceAnnotation) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[139] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9916,7 +10068,7 @@ func (x *ResourceAnnotation) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceAnnotation.ProtoReflect.Descriptor instead. func (*ResourceAnnotation) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{139} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{141} } func (x *ResourceAnnotation) GetParents() []string { @@ -9959,7 +10111,7 @@ type MethodAnnotation struct { func (x *MethodAnnotation) Reset() { *x = MethodAnnotation{} - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[140] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9971,7 +10123,7 @@ func (x *MethodAnnotation) String() string { func (*MethodAnnotation) ProtoMessage() {} func (x *MethodAnnotation) ProtoReflect() protoreflect.Message { - mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[140] + mi := &file_api_ateapi_v1_ateapi_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9984,7 +10136,7 @@ func (x *MethodAnnotation) ProtoReflect() protoreflect.Message { // Deprecated: Use MethodAnnotation.ProtoReflect.Descriptor instead. func (*MethodAnnotation) Descriptor() ([]byte, []int) { - return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{140} + return file_api_ateapi_v1_ateapi_proto_rawDescGZIP(), []int{142} } func (x *MethodAnnotation) GetResource() string { @@ -10431,7 +10583,12 @@ const file_api_ateapi_v1_ateapi_proto_rawDesc = "" + "\x05actor\x18\x02 \x01(\v2\x1e.substrate.ateapi.v1.ObjectRefR\x05actor\x12\x1b\n" + "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x12<\n" + "\tresources\x18\x04 \x01(\v2\x1e.substrate.ateapi.v1.ResourcesR\tresources:\x1e\x82\xb5\x18\x1a\n" + - "\x06Worker\"\x10ActorAssignments\"S\n" + + "\x06Worker\"\x10ActorAssignments\"\x0f\n" + + "\rWhoAmIRequest\"p\n" + + "\x0eWhoAmIResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x126\n" + + "\x04kind\x18\x02 \x01(\x0e2\".substrate.ateapi.v1.PrincipalKindR\x04kind\x12\x16\n" + + "\x06issuer\x18\x03 \x01(\tR\x06issuer\"S\n" + "\x19AcquireWorkerEpochRequest\x126\n" + "\x06worker\x18\x01 \x01(\v2\x1e.substrate.ateapi.v1.ObjectRefR\x06worker\"2\n" + "\x1aAcquireWorkerEpochResponse\x12\x14\n" + @@ -10574,7 +10731,11 @@ const file_api_ateapi_v1_ateapi_proto_rawDesc = "" + "\x18WORKER_STATE_UNSPECIFIED\x10\x00\x12\x17\n" + "\x13WORKER_STATE_ACTIVE\x10\x01\x12\x19\n" + "\x15WORKER_STATE_DRAINING\x10\x02\x12\x1c\n" + - "\x18WORKER_STATE_UNAVAILABLE\x10\x03*D\n" + + "\x18WORKER_STATE_UNAVAILABLE\x10\x03*`\n" + + "\rPrincipalKind\x12\x1e\n" + + "\x1aPRINCIPAL_KIND_UNSPECIFIED\x10\x00\x12\x16\n" + + "\x12PRINCIPAL_KIND_JWT\x10\x01\x12\x17\n" + + "\x13PRINCIPAL_KIND_MTLS\x10\x02*D\n" + "\x05Scope\x12\x15\n" + "\x11SCOPE_UNSPECIFIED\x10\x00\x12\x12\n" + "\x0eSCOPE_ATESPACE\x10\x01\x12\x10\n" + @@ -10670,7 +10831,9 @@ const file_api_ateapi_v1_ateapi_proto_rawDesc = "" + "\x1aUpdateAtespaceAccessPolicy\x126.substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest\x1a!.substrate.ateapi.v1.AccessPolicy\"\x12\x82\xb5\x18\x0e\n" + "\fAccessPolicy\x12\x8b\x01\n" + "\x1aDeleteAtespaceAccessPolicy\x126.substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest\x1a!.substrate.ateapi.v1.AccessPolicy\"\x12\x82\xb5\x18\x0e\n" + - "\fAccessPolicy2\xfd\a\n" + + "\fAccessPolicy2c\n" + + "\x0eAuthentication\x12Q\n" + + "\x06WhoAmI\x12\".substrate.ateapi.v1.WhoAmIRequest\x1a#.substrate.ateapi.v1.WhoAmIResponse2\xfd\a\n" + "\rWorkerService\x12u\n" + "\x12AcquireWorkerEpoch\x12..substrate.ateapi.v1.AcquireWorkerEpochRequest\x1a/.substrate.ateapi.v1.AcquireWorkerEpochResponse\x12i\n" + "\x0eRegisterWorker\x12*.substrate.ateapi.v1.RegisterWorkerRequest\x1a+.substrate.ateapi.v1.RegisterWorkerResponse\x12\x87\x01\n" + @@ -10695,8 +10858,8 @@ func file_api_ateapi_v1_ateapi_proto_rawDescGZIP() []byte { return file_api_ateapi_v1_ateapi_proto_rawDescData } -var file_api_ateapi_v1_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 10) -var file_api_ateapi_v1_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 144) +var file_api_ateapi_v1_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 11) +var file_api_ateapi_v1_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 146) var file_api_ateapi_v1_ateapi_proto_goTypes = []any{ (SnapshotOwner)(0), // 0: substrate.ateapi.v1.SnapshotOwner (SnapshotStorageStatus)(0), // 1: substrate.ateapi.v1.SnapshotStorageStatus @@ -10707,458 +10870,464 @@ var file_api_ateapi_v1_ateapi_proto_goTypes = []any{ (ActorMetadataField)(0), // 6: substrate.ateapi.v1.ActorMetadataField (DurabilityMode)(0), // 7: substrate.ateapi.v1.DurabilityMode (WorkerState)(0), // 8: substrate.ateapi.v1.WorkerState - (Scope)(0), // 9: substrate.ateapi.v1.Scope - (*ObjectSnapshot)(nil), // 10: substrate.ateapi.v1.ObjectSnapshot - (*SnapshotStorage)(nil), // 11: substrate.ateapi.v1.SnapshotStorage - (*Snapshot)(nil), // 12: substrate.ateapi.v1.Snapshot - (*Selector)(nil), // 13: substrate.ateapi.v1.Selector - (*ResourceMetadata)(nil), // 14: substrate.ateapi.v1.ResourceMetadata - (*ExternalVolumes)(nil), // 15: substrate.ateapi.v1.ExternalVolumes - (*ExternalVolumeStatus)(nil), // 16: substrate.ateapi.v1.ExternalVolumeStatus - (*Actor)(nil), // 17: substrate.ateapi.v1.Actor - (*EgressPolicy)(nil), // 18: substrate.ateapi.v1.EgressPolicy - (*EgressPolicyTemplate)(nil), // 19: substrate.ateapi.v1.EgressPolicyTemplate - (*EgressRule)(nil), // 20: substrate.ateapi.v1.EgressRule - (*HttpRule)(nil), // 21: substrate.ateapi.v1.HttpRule - (*HttpsRule)(nil), // 22: substrate.ateapi.v1.HttpsRule - (*TlsPassthroughRule)(nil), // 23: substrate.ateapi.v1.TlsPassthroughRule - (*Ports)(nil), // 24: substrate.ateapi.v1.Ports - (*AllPorts)(nil), // 25: substrate.ateapi.v1.AllPorts - (*HttpRuleEffects)(nil), // 26: substrate.ateapi.v1.HttpRuleEffects - (*CredentialHeader)(nil), // 27: substrate.ateapi.v1.CredentialHeader - (*ActorJwtSource)(nil), // 28: substrate.ateapi.v1.ActorJwtSource - (*ActorStatus)(nil), // 29: substrate.ateapi.v1.ActorStatus - (*ActorCrash)(nil), // 30: substrate.ateapi.v1.ActorCrash - (*WorkerAssignment)(nil), // 31: substrate.ateapi.v1.WorkerAssignment - (*TagStatus)(nil), // 32: substrate.ateapi.v1.TagStatus - (*Tag)(nil), // 33: substrate.ateapi.v1.Tag - (*Atespace)(nil), // 34: substrate.ateapi.v1.Atespace - (*ObjectRef)(nil), // 35: substrate.ateapi.v1.ObjectRef - (*ActorTemplate)(nil), // 36: substrate.ateapi.v1.ActorTemplate - (*ResourceRequirements)(nil), // 37: substrate.ateapi.v1.ResourceRequirements - (*Resources)(nil), // 38: substrate.ateapi.v1.Resources - (*GoldenSnapshotStatus)(nil), // 39: substrate.ateapi.v1.GoldenSnapshotStatus - (*ActorTemplateStatus)(nil), // 40: substrate.ateapi.v1.ActorTemplateStatus - (*SnapshotConfig)(nil), // 41: substrate.ateapi.v1.SnapshotConfig - (*GoldenSnapshotConfig)(nil), // 42: substrate.ateapi.v1.GoldenSnapshotConfig - (*Container)(nil), // 43: substrate.ateapi.v1.Container - (*SecurityContext)(nil), // 44: substrate.ateapi.v1.SecurityContext - (*Capabilities)(nil), // 45: substrate.ateapi.v1.Capabilities - (*EnvVar)(nil), // 46: substrate.ateapi.v1.EnvVar - (*WakeupProbe)(nil), // 47: substrate.ateapi.v1.WakeupProbe - (*HttpGetAction)(nil), // 48: substrate.ateapi.v1.HttpGetAction - (*Volume)(nil), // 49: substrate.ateapi.v1.Volume - (*ExternalVolumeSource)(nil), // 50: substrate.ateapi.v1.ExternalVolumeSource - (*ImageVolumeSource)(nil), // 51: substrate.ateapi.v1.ImageVolumeSource - (*DurableDirVolumeSource)(nil), // 52: substrate.ateapi.v1.DurableDirVolumeSource - (*ExternalVolumeTemplate)(nil), // 53: substrate.ateapi.v1.ExternalVolumeTemplate - (*SystemInfoVolumeSource)(nil), // 54: substrate.ateapi.v1.SystemInfoVolumeSource - (*SystemInfoDataSource)(nil), // 55: substrate.ateapi.v1.SystemInfoDataSource - (*ActorMetadataDataSource)(nil), // 56: substrate.ateapi.v1.ActorMetadataDataSource - (*ActorMetadataItem)(nil), // 57: substrate.ateapi.v1.ActorMetadataItem - (*TrustBundleDataSource)(nil), // 58: substrate.ateapi.v1.TrustBundleDataSource - (*VolumeMount)(nil), // 59: substrate.ateapi.v1.VolumeMount - (*CreateAtespaceRequest)(nil), // 60: substrate.ateapi.v1.CreateAtespaceRequest - (*GetAtespaceRequest)(nil), // 61: substrate.ateapi.v1.GetAtespaceRequest - (*ListAtespacesRequest)(nil), // 62: substrate.ateapi.v1.ListAtespacesRequest - (*ListAtespacesResponse)(nil), // 63: substrate.ateapi.v1.ListAtespacesResponse - (*DeleteAtespaceRequest)(nil), // 64: substrate.ateapi.v1.DeleteAtespaceRequest - (*CreateActorTemplateRequest)(nil), // 65: substrate.ateapi.v1.CreateActorTemplateRequest - (*GetActorTemplateRequest)(nil), // 66: substrate.ateapi.v1.GetActorTemplateRequest - (*ListActorTemplatesRequest)(nil), // 67: substrate.ateapi.v1.ListActorTemplatesRequest - (*ListActorTemplatesResponse)(nil), // 68: substrate.ateapi.v1.ListActorTemplatesResponse - (*DeleteActorTemplateRequest)(nil), // 69: substrate.ateapi.v1.DeleteActorTemplateRequest - (*GetActorRequest)(nil), // 70: substrate.ateapi.v1.GetActorRequest - (*CreateActorRequest)(nil), // 71: substrate.ateapi.v1.CreateActorRequest - (*UpdateActorRequest)(nil), // 72: substrate.ateapi.v1.UpdateActorRequest - (*SuspendActorRequest)(nil), // 73: substrate.ateapi.v1.SuspendActorRequest - (*SuspendActorResponse)(nil), // 74: substrate.ateapi.v1.SuspendActorResponse - (*SuspendOptions)(nil), // 75: substrate.ateapi.v1.SuspendOptions - (*ResumeActorRequest)(nil), // 76: substrate.ateapi.v1.ResumeActorRequest - (*ResumeActorResponse)(nil), // 77: substrate.ateapi.v1.ResumeActorResponse - (*RevertActorRequest)(nil), // 78: substrate.ateapi.v1.RevertActorRequest - (*RevertActorResponse)(nil), // 79: substrate.ateapi.v1.RevertActorResponse - (*DeleteActorRequest)(nil), // 80: substrate.ateapi.v1.DeleteActorRequest - (*GetActorEgressPolicyRequest)(nil), // 81: substrate.ateapi.v1.GetActorEgressPolicyRequest - (*CreateActorEgressPolicyRequest)(nil), // 82: substrate.ateapi.v1.CreateActorEgressPolicyRequest - (*UpdateActorEgressPolicyRequest)(nil), // 83: substrate.ateapi.v1.UpdateActorEgressPolicyRequest - (*DeleteActorEgressPolicyRequest)(nil), // 84: substrate.ateapi.v1.DeleteActorEgressPolicyRequest - (*GetTagRequest)(nil), // 85: substrate.ateapi.v1.GetTagRequest - (*MintActorJwtRequest)(nil), // 86: substrate.ateapi.v1.MintActorJwtRequest - (*MintActorJwtResponse)(nil), // 87: substrate.ateapi.v1.MintActorJwtResponse - (*MintActorCertificateRequest)(nil), // 88: substrate.ateapi.v1.MintActorCertificateRequest - (*MintActorCertificateResponse)(nil), // 89: substrate.ateapi.v1.MintActorCertificateResponse - (*ListTagsRequest)(nil), // 90: substrate.ateapi.v1.ListTagsRequest - (*ListTagsResponse)(nil), // 91: substrate.ateapi.v1.ListTagsResponse - (*CreateTagRequest)(nil), // 92: substrate.ateapi.v1.CreateTagRequest - (*UpdateTagRequest)(nil), // 93: substrate.ateapi.v1.UpdateTagRequest - (*DeleteTagRequest)(nil), // 94: substrate.ateapi.v1.DeleteTagRequest - (*DeleteOptions)(nil), // 95: substrate.ateapi.v1.DeleteOptions - (*ListOptions)(nil), // 96: substrate.ateapi.v1.ListOptions - (*ListWorkerActorAssignmentsRequest)(nil), // 97: substrate.ateapi.v1.ListWorkerActorAssignmentsRequest - (*ListWorkerActorAssignmentsResponse)(nil), // 98: substrate.ateapi.v1.ListWorkerActorAssignmentsResponse - (*ListWorkersRequest)(nil), // 99: substrate.ateapi.v1.ListWorkersRequest - (*ListWorkersResponse)(nil), // 100: substrate.ateapi.v1.ListWorkersResponse - (*GetWorkerRequest)(nil), // 101: substrate.ateapi.v1.GetWorkerRequest - (*CreateWorkerRequest)(nil), // 102: substrate.ateapi.v1.CreateWorkerRequest - (*UpdateWorkerRequest)(nil), // 103: substrate.ateapi.v1.UpdateWorkerRequest - (*DeleteWorkerRequest)(nil), // 104: substrate.ateapi.v1.DeleteWorkerRequest - (*DrainWorkerRequest)(nil), // 105: substrate.ateapi.v1.DrainWorkerRequest - (*DrainWorkerResponse)(nil), // 106: substrate.ateapi.v1.DrainWorkerResponse - (*MarkWorkerAvailableRequest)(nil), // 107: substrate.ateapi.v1.MarkWorkerAvailableRequest - (*MarkWorkerAvailableResponse)(nil), // 108: substrate.ateapi.v1.MarkWorkerAvailableResponse - (*MarkWorkerUnavailableRequest)(nil), // 109: substrate.ateapi.v1.MarkWorkerUnavailableRequest - (*MarkWorkerUnavailableResponse)(nil), // 110: substrate.ateapi.v1.MarkWorkerUnavailableResponse - (*ListActorsRequest)(nil), // 111: substrate.ateapi.v1.ListActorsRequest - (*ListActorsResponse)(nil), // 112: substrate.ateapi.v1.ListActorsResponse - (*Worker)(nil), // 113: substrate.ateapi.v1.Worker - (*WorkerStatus)(nil), // 114: substrate.ateapi.v1.WorkerStatus - (*WorkerSandboxClass)(nil), // 115: substrate.ateapi.v1.WorkerSandboxClass - (*WorkerSandboxRuntime)(nil), // 116: substrate.ateapi.v1.WorkerSandboxRuntime - (*ActorSandboxRuntime)(nil), // 117: substrate.ateapi.v1.ActorSandboxRuntime - (*VersionedSandboxCompat)(nil), // 118: substrate.ateapi.v1.VersionedSandboxCompat - (*AttributeEntry)(nil), // 119: substrate.ateapi.v1.AttributeEntry - (*WorkerResources)(nil), // 120: substrate.ateapi.v1.WorkerResources - (*ActorAssignment)(nil), // 121: substrate.ateapi.v1.ActorAssignment - (*AcquireWorkerEpochRequest)(nil), // 122: substrate.ateapi.v1.AcquireWorkerEpochRequest - (*AcquireWorkerEpochResponse)(nil), // 123: substrate.ateapi.v1.AcquireWorkerEpochResponse - (*RegisterWorkerRequest)(nil), // 124: substrate.ateapi.v1.RegisterWorkerRequest - (*RegisterWorkerResponse)(nil), // 125: substrate.ateapi.v1.RegisterWorkerResponse - (*SetWorkerSandboxRuntimesRequest)(nil), // 126: substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest - (*SetWorkerSandboxRuntimesResponse)(nil), // 127: substrate.ateapi.v1.SetWorkerSandboxRuntimesResponse - (*MintWorkerActorCertificateRequest)(nil), // 128: substrate.ateapi.v1.MintWorkerActorCertificateRequest - (*MintWorkerActorCertificateResponse)(nil), // 129: substrate.ateapi.v1.MintWorkerActorCertificateResponse - (*MintWorkerActorJwtRequest)(nil), // 130: substrate.ateapi.v1.MintWorkerActorJwtRequest - (*MintWorkerActorJwtResponse)(nil), // 131: substrate.ateapi.v1.MintWorkerActorJwtResponse - (*RequestActorSuspendRequest)(nil), // 132: substrate.ateapi.v1.RequestActorSuspendRequest - (*RequestActorSuspendResponse)(nil), // 133: substrate.ateapi.v1.RequestActorSuspendResponse - (*ReportActorCrashedRequest)(nil), // 134: substrate.ateapi.v1.ReportActorCrashedRequest - (*ReportActorCrashedResponse)(nil), // 135: substrate.ateapi.v1.ReportActorCrashedResponse - (*ReportActorSnapshotStateRequest)(nil), // 136: substrate.ateapi.v1.ReportActorSnapshotStateRequest - (*ReportActorSnapshotStateResponse)(nil), // 137: substrate.ateapi.v1.ReportActorSnapshotStateResponse - (*ActorSnapshotState)(nil), // 138: substrate.ateapi.v1.ActorSnapshotState - (*SnapshotReport)(nil), // 139: substrate.ateapi.v1.SnapshotReport - (*AccessPolicy)(nil), // 140: substrate.ateapi.v1.AccessPolicy - (*Binding)(nil), // 141: substrate.ateapi.v1.Binding - (*GetGlobalAccessPolicyRequest)(nil), // 142: substrate.ateapi.v1.GetGlobalAccessPolicyRequest - (*CreateGlobalAccessPolicyRequest)(nil), // 143: substrate.ateapi.v1.CreateGlobalAccessPolicyRequest - (*UpdateGlobalAccessPolicyRequest)(nil), // 144: substrate.ateapi.v1.UpdateGlobalAccessPolicyRequest - (*GetAtespaceAccessPolicyRequest)(nil), // 145: substrate.ateapi.v1.GetAtespaceAccessPolicyRequest - (*CreateAtespaceAccessPolicyRequest)(nil), // 146: substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest - (*UpdateAtespaceAccessPolicyRequest)(nil), // 147: substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest - (*DeleteAtespaceAccessPolicyRequest)(nil), // 148: substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest - (*ResourceAnnotation)(nil), // 149: substrate.ateapi.v1.ResourceAnnotation - (*MethodAnnotation)(nil), // 150: substrate.ateapi.v1.MethodAnnotation - nil, // 151: substrate.ateapi.v1.Selector.MatchLabelsEntry - nil, // 152: substrate.ateapi.v1.ExternalVolumeStatus.VolumeContextEntry - nil, // 153: substrate.ateapi.v1.Worker.LabelsEntry - (*timestamppb.Timestamp)(nil), // 154: google.protobuf.Timestamp - (*descriptorpb.MessageOptions)(nil), // 155: google.protobuf.MessageOptions - (*descriptorpb.MethodOptions)(nil), // 156: google.protobuf.MethodOptions + (PrincipalKind)(0), // 9: substrate.ateapi.v1.PrincipalKind + (Scope)(0), // 10: substrate.ateapi.v1.Scope + (*ObjectSnapshot)(nil), // 11: substrate.ateapi.v1.ObjectSnapshot + (*SnapshotStorage)(nil), // 12: substrate.ateapi.v1.SnapshotStorage + (*Snapshot)(nil), // 13: substrate.ateapi.v1.Snapshot + (*Selector)(nil), // 14: substrate.ateapi.v1.Selector + (*ResourceMetadata)(nil), // 15: substrate.ateapi.v1.ResourceMetadata + (*ExternalVolumes)(nil), // 16: substrate.ateapi.v1.ExternalVolumes + (*ExternalVolumeStatus)(nil), // 17: substrate.ateapi.v1.ExternalVolumeStatus + (*Actor)(nil), // 18: substrate.ateapi.v1.Actor + (*EgressPolicy)(nil), // 19: substrate.ateapi.v1.EgressPolicy + (*EgressPolicyTemplate)(nil), // 20: substrate.ateapi.v1.EgressPolicyTemplate + (*EgressRule)(nil), // 21: substrate.ateapi.v1.EgressRule + (*HttpRule)(nil), // 22: substrate.ateapi.v1.HttpRule + (*HttpsRule)(nil), // 23: substrate.ateapi.v1.HttpsRule + (*TlsPassthroughRule)(nil), // 24: substrate.ateapi.v1.TlsPassthroughRule + (*Ports)(nil), // 25: substrate.ateapi.v1.Ports + (*AllPorts)(nil), // 26: substrate.ateapi.v1.AllPorts + (*HttpRuleEffects)(nil), // 27: substrate.ateapi.v1.HttpRuleEffects + (*CredentialHeader)(nil), // 28: substrate.ateapi.v1.CredentialHeader + (*ActorJwtSource)(nil), // 29: substrate.ateapi.v1.ActorJwtSource + (*ActorStatus)(nil), // 30: substrate.ateapi.v1.ActorStatus + (*ActorCrash)(nil), // 31: substrate.ateapi.v1.ActorCrash + (*WorkerAssignment)(nil), // 32: substrate.ateapi.v1.WorkerAssignment + (*TagStatus)(nil), // 33: substrate.ateapi.v1.TagStatus + (*Tag)(nil), // 34: substrate.ateapi.v1.Tag + (*Atespace)(nil), // 35: substrate.ateapi.v1.Atespace + (*ObjectRef)(nil), // 36: substrate.ateapi.v1.ObjectRef + (*ActorTemplate)(nil), // 37: substrate.ateapi.v1.ActorTemplate + (*ResourceRequirements)(nil), // 38: substrate.ateapi.v1.ResourceRequirements + (*Resources)(nil), // 39: substrate.ateapi.v1.Resources + (*GoldenSnapshotStatus)(nil), // 40: substrate.ateapi.v1.GoldenSnapshotStatus + (*ActorTemplateStatus)(nil), // 41: substrate.ateapi.v1.ActorTemplateStatus + (*SnapshotConfig)(nil), // 42: substrate.ateapi.v1.SnapshotConfig + (*GoldenSnapshotConfig)(nil), // 43: substrate.ateapi.v1.GoldenSnapshotConfig + (*Container)(nil), // 44: substrate.ateapi.v1.Container + (*SecurityContext)(nil), // 45: substrate.ateapi.v1.SecurityContext + (*Capabilities)(nil), // 46: substrate.ateapi.v1.Capabilities + (*EnvVar)(nil), // 47: substrate.ateapi.v1.EnvVar + (*WakeupProbe)(nil), // 48: substrate.ateapi.v1.WakeupProbe + (*HttpGetAction)(nil), // 49: substrate.ateapi.v1.HttpGetAction + (*Volume)(nil), // 50: substrate.ateapi.v1.Volume + (*ExternalVolumeSource)(nil), // 51: substrate.ateapi.v1.ExternalVolumeSource + (*ImageVolumeSource)(nil), // 52: substrate.ateapi.v1.ImageVolumeSource + (*DurableDirVolumeSource)(nil), // 53: substrate.ateapi.v1.DurableDirVolumeSource + (*ExternalVolumeTemplate)(nil), // 54: substrate.ateapi.v1.ExternalVolumeTemplate + (*SystemInfoVolumeSource)(nil), // 55: substrate.ateapi.v1.SystemInfoVolumeSource + (*SystemInfoDataSource)(nil), // 56: substrate.ateapi.v1.SystemInfoDataSource + (*ActorMetadataDataSource)(nil), // 57: substrate.ateapi.v1.ActorMetadataDataSource + (*ActorMetadataItem)(nil), // 58: substrate.ateapi.v1.ActorMetadataItem + (*TrustBundleDataSource)(nil), // 59: substrate.ateapi.v1.TrustBundleDataSource + (*VolumeMount)(nil), // 60: substrate.ateapi.v1.VolumeMount + (*CreateAtespaceRequest)(nil), // 61: substrate.ateapi.v1.CreateAtespaceRequest + (*GetAtespaceRequest)(nil), // 62: substrate.ateapi.v1.GetAtespaceRequest + (*ListAtespacesRequest)(nil), // 63: substrate.ateapi.v1.ListAtespacesRequest + (*ListAtespacesResponse)(nil), // 64: substrate.ateapi.v1.ListAtespacesResponse + (*DeleteAtespaceRequest)(nil), // 65: substrate.ateapi.v1.DeleteAtespaceRequest + (*CreateActorTemplateRequest)(nil), // 66: substrate.ateapi.v1.CreateActorTemplateRequest + (*GetActorTemplateRequest)(nil), // 67: substrate.ateapi.v1.GetActorTemplateRequest + (*ListActorTemplatesRequest)(nil), // 68: substrate.ateapi.v1.ListActorTemplatesRequest + (*ListActorTemplatesResponse)(nil), // 69: substrate.ateapi.v1.ListActorTemplatesResponse + (*DeleteActorTemplateRequest)(nil), // 70: substrate.ateapi.v1.DeleteActorTemplateRequest + (*GetActorRequest)(nil), // 71: substrate.ateapi.v1.GetActorRequest + (*CreateActorRequest)(nil), // 72: substrate.ateapi.v1.CreateActorRequest + (*UpdateActorRequest)(nil), // 73: substrate.ateapi.v1.UpdateActorRequest + (*SuspendActorRequest)(nil), // 74: substrate.ateapi.v1.SuspendActorRequest + (*SuspendActorResponse)(nil), // 75: substrate.ateapi.v1.SuspendActorResponse + (*SuspendOptions)(nil), // 76: substrate.ateapi.v1.SuspendOptions + (*ResumeActorRequest)(nil), // 77: substrate.ateapi.v1.ResumeActorRequest + (*ResumeActorResponse)(nil), // 78: substrate.ateapi.v1.ResumeActorResponse + (*RevertActorRequest)(nil), // 79: substrate.ateapi.v1.RevertActorRequest + (*RevertActorResponse)(nil), // 80: substrate.ateapi.v1.RevertActorResponse + (*DeleteActorRequest)(nil), // 81: substrate.ateapi.v1.DeleteActorRequest + (*GetActorEgressPolicyRequest)(nil), // 82: substrate.ateapi.v1.GetActorEgressPolicyRequest + (*CreateActorEgressPolicyRequest)(nil), // 83: substrate.ateapi.v1.CreateActorEgressPolicyRequest + (*UpdateActorEgressPolicyRequest)(nil), // 84: substrate.ateapi.v1.UpdateActorEgressPolicyRequest + (*DeleteActorEgressPolicyRequest)(nil), // 85: substrate.ateapi.v1.DeleteActorEgressPolicyRequest + (*GetTagRequest)(nil), // 86: substrate.ateapi.v1.GetTagRequest + (*MintActorJwtRequest)(nil), // 87: substrate.ateapi.v1.MintActorJwtRequest + (*MintActorJwtResponse)(nil), // 88: substrate.ateapi.v1.MintActorJwtResponse + (*MintActorCertificateRequest)(nil), // 89: substrate.ateapi.v1.MintActorCertificateRequest + (*MintActorCertificateResponse)(nil), // 90: substrate.ateapi.v1.MintActorCertificateResponse + (*ListTagsRequest)(nil), // 91: substrate.ateapi.v1.ListTagsRequest + (*ListTagsResponse)(nil), // 92: substrate.ateapi.v1.ListTagsResponse + (*CreateTagRequest)(nil), // 93: substrate.ateapi.v1.CreateTagRequest + (*UpdateTagRequest)(nil), // 94: substrate.ateapi.v1.UpdateTagRequest + (*DeleteTagRequest)(nil), // 95: substrate.ateapi.v1.DeleteTagRequest + (*DeleteOptions)(nil), // 96: substrate.ateapi.v1.DeleteOptions + (*ListOptions)(nil), // 97: substrate.ateapi.v1.ListOptions + (*ListWorkerActorAssignmentsRequest)(nil), // 98: substrate.ateapi.v1.ListWorkerActorAssignmentsRequest + (*ListWorkerActorAssignmentsResponse)(nil), // 99: substrate.ateapi.v1.ListWorkerActorAssignmentsResponse + (*ListWorkersRequest)(nil), // 100: substrate.ateapi.v1.ListWorkersRequest + (*ListWorkersResponse)(nil), // 101: substrate.ateapi.v1.ListWorkersResponse + (*GetWorkerRequest)(nil), // 102: substrate.ateapi.v1.GetWorkerRequest + (*CreateWorkerRequest)(nil), // 103: substrate.ateapi.v1.CreateWorkerRequest + (*UpdateWorkerRequest)(nil), // 104: substrate.ateapi.v1.UpdateWorkerRequest + (*DeleteWorkerRequest)(nil), // 105: substrate.ateapi.v1.DeleteWorkerRequest + (*DrainWorkerRequest)(nil), // 106: substrate.ateapi.v1.DrainWorkerRequest + (*DrainWorkerResponse)(nil), // 107: substrate.ateapi.v1.DrainWorkerResponse + (*MarkWorkerAvailableRequest)(nil), // 108: substrate.ateapi.v1.MarkWorkerAvailableRequest + (*MarkWorkerAvailableResponse)(nil), // 109: substrate.ateapi.v1.MarkWorkerAvailableResponse + (*MarkWorkerUnavailableRequest)(nil), // 110: substrate.ateapi.v1.MarkWorkerUnavailableRequest + (*MarkWorkerUnavailableResponse)(nil), // 111: substrate.ateapi.v1.MarkWorkerUnavailableResponse + (*ListActorsRequest)(nil), // 112: substrate.ateapi.v1.ListActorsRequest + (*ListActorsResponse)(nil), // 113: substrate.ateapi.v1.ListActorsResponse + (*Worker)(nil), // 114: substrate.ateapi.v1.Worker + (*WorkerStatus)(nil), // 115: substrate.ateapi.v1.WorkerStatus + (*WorkerSandboxClass)(nil), // 116: substrate.ateapi.v1.WorkerSandboxClass + (*WorkerSandboxRuntime)(nil), // 117: substrate.ateapi.v1.WorkerSandboxRuntime + (*ActorSandboxRuntime)(nil), // 118: substrate.ateapi.v1.ActorSandboxRuntime + (*VersionedSandboxCompat)(nil), // 119: substrate.ateapi.v1.VersionedSandboxCompat + (*AttributeEntry)(nil), // 120: substrate.ateapi.v1.AttributeEntry + (*WorkerResources)(nil), // 121: substrate.ateapi.v1.WorkerResources + (*ActorAssignment)(nil), // 122: substrate.ateapi.v1.ActorAssignment + (*WhoAmIRequest)(nil), // 123: substrate.ateapi.v1.WhoAmIRequest + (*WhoAmIResponse)(nil), // 124: substrate.ateapi.v1.WhoAmIResponse + (*AcquireWorkerEpochRequest)(nil), // 125: substrate.ateapi.v1.AcquireWorkerEpochRequest + (*AcquireWorkerEpochResponse)(nil), // 126: substrate.ateapi.v1.AcquireWorkerEpochResponse + (*RegisterWorkerRequest)(nil), // 127: substrate.ateapi.v1.RegisterWorkerRequest + (*RegisterWorkerResponse)(nil), // 128: substrate.ateapi.v1.RegisterWorkerResponse + (*SetWorkerSandboxRuntimesRequest)(nil), // 129: substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest + (*SetWorkerSandboxRuntimesResponse)(nil), // 130: substrate.ateapi.v1.SetWorkerSandboxRuntimesResponse + (*MintWorkerActorCertificateRequest)(nil), // 131: substrate.ateapi.v1.MintWorkerActorCertificateRequest + (*MintWorkerActorCertificateResponse)(nil), // 132: substrate.ateapi.v1.MintWorkerActorCertificateResponse + (*MintWorkerActorJwtRequest)(nil), // 133: substrate.ateapi.v1.MintWorkerActorJwtRequest + (*MintWorkerActorJwtResponse)(nil), // 134: substrate.ateapi.v1.MintWorkerActorJwtResponse + (*RequestActorSuspendRequest)(nil), // 135: substrate.ateapi.v1.RequestActorSuspendRequest + (*RequestActorSuspendResponse)(nil), // 136: substrate.ateapi.v1.RequestActorSuspendResponse + (*ReportActorCrashedRequest)(nil), // 137: substrate.ateapi.v1.ReportActorCrashedRequest + (*ReportActorCrashedResponse)(nil), // 138: substrate.ateapi.v1.ReportActorCrashedResponse + (*ReportActorSnapshotStateRequest)(nil), // 139: substrate.ateapi.v1.ReportActorSnapshotStateRequest + (*ReportActorSnapshotStateResponse)(nil), // 140: substrate.ateapi.v1.ReportActorSnapshotStateResponse + (*ActorSnapshotState)(nil), // 141: substrate.ateapi.v1.ActorSnapshotState + (*SnapshotReport)(nil), // 142: substrate.ateapi.v1.SnapshotReport + (*AccessPolicy)(nil), // 143: substrate.ateapi.v1.AccessPolicy + (*Binding)(nil), // 144: substrate.ateapi.v1.Binding + (*GetGlobalAccessPolicyRequest)(nil), // 145: substrate.ateapi.v1.GetGlobalAccessPolicyRequest + (*CreateGlobalAccessPolicyRequest)(nil), // 146: substrate.ateapi.v1.CreateGlobalAccessPolicyRequest + (*UpdateGlobalAccessPolicyRequest)(nil), // 147: substrate.ateapi.v1.UpdateGlobalAccessPolicyRequest + (*GetAtespaceAccessPolicyRequest)(nil), // 148: substrate.ateapi.v1.GetAtespaceAccessPolicyRequest + (*CreateAtespaceAccessPolicyRequest)(nil), // 149: substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest + (*UpdateAtespaceAccessPolicyRequest)(nil), // 150: substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest + (*DeleteAtespaceAccessPolicyRequest)(nil), // 151: substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest + (*ResourceAnnotation)(nil), // 152: substrate.ateapi.v1.ResourceAnnotation + (*MethodAnnotation)(nil), // 153: substrate.ateapi.v1.MethodAnnotation + nil, // 154: substrate.ateapi.v1.Selector.MatchLabelsEntry + nil, // 155: substrate.ateapi.v1.ExternalVolumeStatus.VolumeContextEntry + nil, // 156: substrate.ateapi.v1.Worker.LabelsEntry + (*timestamppb.Timestamp)(nil), // 157: google.protobuf.Timestamp + (*descriptorpb.MessageOptions)(nil), // 158: google.protobuf.MessageOptions + (*descriptorpb.MethodOptions)(nil), // 159: google.protobuf.MethodOptions } var file_api_ateapi_v1_ateapi_proto_depIdxs = []int32{ 1, // 0: substrate.ateapi.v1.SnapshotStorage.status:type_name -> substrate.ateapi.v1.SnapshotStorageStatus - 10, // 1: substrate.ateapi.v1.SnapshotStorage.object:type_name -> substrate.ateapi.v1.ObjectSnapshot + 11, // 1: substrate.ateapi.v1.SnapshotStorage.object:type_name -> substrate.ateapi.v1.ObjectSnapshot 0, // 2: substrate.ateapi.v1.Snapshot.owner:type_name -> substrate.ateapi.v1.SnapshotOwner 2, // 3: substrate.ateapi.v1.Snapshot.fidelity:type_name -> substrate.ateapi.v1.SnapshotFidelity - 11, // 4: substrate.ateapi.v1.Snapshot.durable_snapshot:type_name -> substrate.ateapi.v1.SnapshotStorage - 117, // 5: substrate.ateapi.v1.Snapshot.sandbox_runtime:type_name -> substrate.ateapi.v1.ActorSandboxRuntime - 151, // 6: substrate.ateapi.v1.Selector.match_labels:type_name -> substrate.ateapi.v1.Selector.MatchLabelsEntry - 154, // 7: substrate.ateapi.v1.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp - 154, // 8: substrate.ateapi.v1.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp - 154, // 9: substrate.ateapi.v1.ResourceMetadata.delete_time:type_name -> google.protobuf.Timestamp - 16, // 10: substrate.ateapi.v1.ExternalVolumes.volumes:type_name -> substrate.ateapi.v1.ExternalVolumeStatus - 152, // 11: substrate.ateapi.v1.ExternalVolumeStatus.volume_context:type_name -> substrate.ateapi.v1.ExternalVolumeStatus.VolumeContextEntry - 14, // 12: substrate.ateapi.v1.Actor.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 35, // 13: substrate.ateapi.v1.Actor.actor_template:type_name -> substrate.ateapi.v1.ObjectRef - 13, // 14: substrate.ateapi.v1.Actor.worker_selector:type_name -> substrate.ateapi.v1.Selector - 35, // 15: substrate.ateapi.v1.Actor.source_tag:type_name -> substrate.ateapi.v1.ObjectRef - 29, // 16: substrate.ateapi.v1.Actor.status:type_name -> substrate.ateapi.v1.ActorStatus - 14, // 17: substrate.ateapi.v1.EgressPolicy.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 20, // 18: substrate.ateapi.v1.EgressPolicy.rules:type_name -> substrate.ateapi.v1.EgressRule - 20, // 19: substrate.ateapi.v1.EgressPolicyTemplate.rules:type_name -> substrate.ateapi.v1.EgressRule - 21, // 20: substrate.ateapi.v1.EgressRule.http:type_name -> substrate.ateapi.v1.HttpRule - 22, // 21: substrate.ateapi.v1.EgressRule.https:type_name -> substrate.ateapi.v1.HttpsRule - 23, // 22: substrate.ateapi.v1.EgressRule.tls_passthrough:type_name -> substrate.ateapi.v1.TlsPassthroughRule - 24, // 23: substrate.ateapi.v1.HttpRule.ports:type_name -> substrate.ateapi.v1.Ports - 26, // 24: substrate.ateapi.v1.HttpRule.effects:type_name -> substrate.ateapi.v1.HttpRuleEffects - 24, // 25: substrate.ateapi.v1.HttpsRule.ports:type_name -> substrate.ateapi.v1.Ports - 26, // 26: substrate.ateapi.v1.HttpsRule.effects:type_name -> substrate.ateapi.v1.HttpRuleEffects - 24, // 27: substrate.ateapi.v1.TlsPassthroughRule.ports:type_name -> substrate.ateapi.v1.Ports - 25, // 28: substrate.ateapi.v1.Ports.all:type_name -> substrate.ateapi.v1.AllPorts - 27, // 29: substrate.ateapi.v1.HttpRuleEffects.replace_headers:type_name -> substrate.ateapi.v1.CredentialHeader - 28, // 30: substrate.ateapi.v1.CredentialHeader.actor_jwt:type_name -> substrate.ateapi.v1.ActorJwtSource + 12, // 4: substrate.ateapi.v1.Snapshot.durable_snapshot:type_name -> substrate.ateapi.v1.SnapshotStorage + 118, // 5: substrate.ateapi.v1.Snapshot.sandbox_runtime:type_name -> substrate.ateapi.v1.ActorSandboxRuntime + 154, // 6: substrate.ateapi.v1.Selector.match_labels:type_name -> substrate.ateapi.v1.Selector.MatchLabelsEntry + 157, // 7: substrate.ateapi.v1.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp + 157, // 8: substrate.ateapi.v1.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp + 157, // 9: substrate.ateapi.v1.ResourceMetadata.delete_time:type_name -> google.protobuf.Timestamp + 17, // 10: substrate.ateapi.v1.ExternalVolumes.volumes:type_name -> substrate.ateapi.v1.ExternalVolumeStatus + 155, // 11: substrate.ateapi.v1.ExternalVolumeStatus.volume_context:type_name -> substrate.ateapi.v1.ExternalVolumeStatus.VolumeContextEntry + 15, // 12: substrate.ateapi.v1.Actor.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 36, // 13: substrate.ateapi.v1.Actor.actor_template:type_name -> substrate.ateapi.v1.ObjectRef + 14, // 14: substrate.ateapi.v1.Actor.worker_selector:type_name -> substrate.ateapi.v1.Selector + 36, // 15: substrate.ateapi.v1.Actor.source_tag:type_name -> substrate.ateapi.v1.ObjectRef + 30, // 16: substrate.ateapi.v1.Actor.status:type_name -> substrate.ateapi.v1.ActorStatus + 15, // 17: substrate.ateapi.v1.EgressPolicy.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 21, // 18: substrate.ateapi.v1.EgressPolicy.rules:type_name -> substrate.ateapi.v1.EgressRule + 21, // 19: substrate.ateapi.v1.EgressPolicyTemplate.rules:type_name -> substrate.ateapi.v1.EgressRule + 22, // 20: substrate.ateapi.v1.EgressRule.http:type_name -> substrate.ateapi.v1.HttpRule + 23, // 21: substrate.ateapi.v1.EgressRule.https:type_name -> substrate.ateapi.v1.HttpsRule + 24, // 22: substrate.ateapi.v1.EgressRule.tls_passthrough:type_name -> substrate.ateapi.v1.TlsPassthroughRule + 25, // 23: substrate.ateapi.v1.HttpRule.ports:type_name -> substrate.ateapi.v1.Ports + 27, // 24: substrate.ateapi.v1.HttpRule.effects:type_name -> substrate.ateapi.v1.HttpRuleEffects + 25, // 25: substrate.ateapi.v1.HttpsRule.ports:type_name -> substrate.ateapi.v1.Ports + 27, // 26: substrate.ateapi.v1.HttpsRule.effects:type_name -> substrate.ateapi.v1.HttpRuleEffects + 25, // 27: substrate.ateapi.v1.TlsPassthroughRule.ports:type_name -> substrate.ateapi.v1.Ports + 26, // 28: substrate.ateapi.v1.Ports.all:type_name -> substrate.ateapi.v1.AllPorts + 28, // 29: substrate.ateapi.v1.HttpRuleEffects.replace_headers:type_name -> substrate.ateapi.v1.CredentialHeader + 29, // 30: substrate.ateapi.v1.CredentialHeader.actor_jwt:type_name -> substrate.ateapi.v1.ActorJwtSource 4, // 31: substrate.ateapi.v1.ActorStatus.state:type_name -> substrate.ateapi.v1.ActorState - 31, // 32: substrate.ateapi.v1.ActorStatus.worker_assignment:type_name -> substrate.ateapi.v1.WorkerAssignment - 15, // 33: substrate.ateapi.v1.ActorStatus.external_volumes:type_name -> substrate.ateapi.v1.ExternalVolumes - 30, // 34: substrate.ateapi.v1.ActorStatus.crash:type_name -> substrate.ateapi.v1.ActorCrash - 12, // 35: substrate.ateapi.v1.ActorStatus.snapshots:type_name -> substrate.ateapi.v1.Snapshot - 154, // 36: substrate.ateapi.v1.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp - 35, // 37: substrate.ateapi.v1.WorkerAssignment.worker:type_name -> substrate.ateapi.v1.ObjectRef - 117, // 38: substrate.ateapi.v1.WorkerAssignment.sandbox_runtime:type_name -> substrate.ateapi.v1.ActorSandboxRuntime - 12, // 39: substrate.ateapi.v1.TagStatus.snapshot:type_name -> substrate.ateapi.v1.Snapshot - 14, // 40: substrate.ateapi.v1.Tag.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 32, // 41: substrate.ateapi.v1.Tag.status:type_name -> substrate.ateapi.v1.TagStatus + 32, // 32: substrate.ateapi.v1.ActorStatus.worker_assignment:type_name -> substrate.ateapi.v1.WorkerAssignment + 16, // 33: substrate.ateapi.v1.ActorStatus.external_volumes:type_name -> substrate.ateapi.v1.ExternalVolumes + 31, // 34: substrate.ateapi.v1.ActorStatus.crash:type_name -> substrate.ateapi.v1.ActorCrash + 13, // 35: substrate.ateapi.v1.ActorStatus.snapshots:type_name -> substrate.ateapi.v1.Snapshot + 157, // 36: substrate.ateapi.v1.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp + 36, // 37: substrate.ateapi.v1.WorkerAssignment.worker:type_name -> substrate.ateapi.v1.ObjectRef + 118, // 38: substrate.ateapi.v1.WorkerAssignment.sandbox_runtime:type_name -> substrate.ateapi.v1.ActorSandboxRuntime + 13, // 39: substrate.ateapi.v1.TagStatus.snapshot:type_name -> substrate.ateapi.v1.Snapshot + 15, // 40: substrate.ateapi.v1.Tag.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 33, // 41: substrate.ateapi.v1.Tag.status:type_name -> substrate.ateapi.v1.TagStatus 3, // 42: substrate.ateapi.v1.Tag.scope:type_name -> substrate.ateapi.v1.TagScope - 35, // 43: substrate.ateapi.v1.Tag.source_actor:type_name -> substrate.ateapi.v1.ObjectRef - 14, // 44: substrate.ateapi.v1.Atespace.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 14, // 45: substrate.ateapi.v1.ActorTemplate.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 13, // 46: substrate.ateapi.v1.ActorTemplate.worker_selector:type_name -> substrate.ateapi.v1.Selector - 43, // 47: substrate.ateapi.v1.ActorTemplate.containers:type_name -> substrate.ateapi.v1.Container - 49, // 48: substrate.ateapi.v1.ActorTemplate.volumes:type_name -> substrate.ateapi.v1.Volume - 41, // 49: substrate.ateapi.v1.ActorTemplate.snapshot_config:type_name -> substrate.ateapi.v1.SnapshotConfig - 37, // 50: substrate.ateapi.v1.ActorTemplate.resources:type_name -> substrate.ateapi.v1.ResourceRequirements - 19, // 51: substrate.ateapi.v1.ActorTemplate.golden_egress_policy:type_name -> substrate.ateapi.v1.EgressPolicyTemplate - 40, // 52: substrate.ateapi.v1.ActorTemplate.status:type_name -> substrate.ateapi.v1.ActorTemplateStatus - 38, // 53: substrate.ateapi.v1.ResourceRequirements.limits:type_name -> substrate.ateapi.v1.Resources - 35, // 54: substrate.ateapi.v1.GoldenSnapshotStatus.golden_tag:type_name -> substrate.ateapi.v1.ObjectRef - 154, // 55: substrate.ateapi.v1.GoldenSnapshotStatus.snapshot_time:type_name -> google.protobuf.Timestamp - 39, // 56: substrate.ateapi.v1.ActorTemplateStatus.golden_snapshot_status:type_name -> substrate.ateapi.v1.GoldenSnapshotStatus + 36, // 43: substrate.ateapi.v1.Tag.source_actor:type_name -> substrate.ateapi.v1.ObjectRef + 15, // 44: substrate.ateapi.v1.Atespace.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 15, // 45: substrate.ateapi.v1.ActorTemplate.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 14, // 46: substrate.ateapi.v1.ActorTemplate.worker_selector:type_name -> substrate.ateapi.v1.Selector + 44, // 47: substrate.ateapi.v1.ActorTemplate.containers:type_name -> substrate.ateapi.v1.Container + 50, // 48: substrate.ateapi.v1.ActorTemplate.volumes:type_name -> substrate.ateapi.v1.Volume + 42, // 49: substrate.ateapi.v1.ActorTemplate.snapshot_config:type_name -> substrate.ateapi.v1.SnapshotConfig + 38, // 50: substrate.ateapi.v1.ActorTemplate.resources:type_name -> substrate.ateapi.v1.ResourceRequirements + 20, // 51: substrate.ateapi.v1.ActorTemplate.golden_egress_policy:type_name -> substrate.ateapi.v1.EgressPolicyTemplate + 41, // 52: substrate.ateapi.v1.ActorTemplate.status:type_name -> substrate.ateapi.v1.ActorTemplateStatus + 39, // 53: substrate.ateapi.v1.ResourceRequirements.limits:type_name -> substrate.ateapi.v1.Resources + 36, // 54: substrate.ateapi.v1.GoldenSnapshotStatus.golden_tag:type_name -> substrate.ateapi.v1.ObjectRef + 157, // 55: substrate.ateapi.v1.GoldenSnapshotStatus.snapshot_time:type_name -> google.protobuf.Timestamp + 40, // 56: substrate.ateapi.v1.ActorTemplateStatus.golden_snapshot_status:type_name -> substrate.ateapi.v1.GoldenSnapshotStatus 2, // 57: substrate.ateapi.v1.SnapshotConfig.preferred_fidelity:type_name -> substrate.ateapi.v1.SnapshotFidelity - 42, // 58: substrate.ateapi.v1.SnapshotConfig.golden_snapshot_config:type_name -> substrate.ateapi.v1.GoldenSnapshotConfig + 43, // 58: substrate.ateapi.v1.SnapshotConfig.golden_snapshot_config:type_name -> substrate.ateapi.v1.GoldenSnapshotConfig 5, // 59: substrate.ateapi.v1.GoldenSnapshotConfig.mode:type_name -> substrate.ateapi.v1.GoldenSnapshotMode - 46, // 60: substrate.ateapi.v1.Container.env:type_name -> substrate.ateapi.v1.EnvVar - 47, // 61: substrate.ateapi.v1.Container.wakeup_probe:type_name -> substrate.ateapi.v1.WakeupProbe - 59, // 62: substrate.ateapi.v1.Container.volume_mounts:type_name -> substrate.ateapi.v1.VolumeMount - 44, // 63: substrate.ateapi.v1.Container.security_context:type_name -> substrate.ateapi.v1.SecurityContext - 37, // 64: substrate.ateapi.v1.Container.resources:type_name -> substrate.ateapi.v1.ResourceRequirements - 45, // 65: substrate.ateapi.v1.SecurityContext.capabilities:type_name -> substrate.ateapi.v1.Capabilities - 48, // 66: substrate.ateapi.v1.WakeupProbe.http_get:type_name -> substrate.ateapi.v1.HttpGetAction - 52, // 67: substrate.ateapi.v1.Volume.durable_dir:type_name -> substrate.ateapi.v1.DurableDirVolumeSource - 50, // 68: substrate.ateapi.v1.Volume.external:type_name -> substrate.ateapi.v1.ExternalVolumeSource - 54, // 69: substrate.ateapi.v1.Volume.system_info:type_name -> substrate.ateapi.v1.SystemInfoVolumeSource - 51, // 70: substrate.ateapi.v1.Volume.image:type_name -> substrate.ateapi.v1.ImageVolumeSource - 53, // 71: substrate.ateapi.v1.ExternalVolumeSource.template:type_name -> substrate.ateapi.v1.ExternalVolumeTemplate - 55, // 72: substrate.ateapi.v1.SystemInfoVolumeSource.data_sources:type_name -> substrate.ateapi.v1.SystemInfoDataSource - 56, // 73: substrate.ateapi.v1.SystemInfoDataSource.actor_metadata:type_name -> substrate.ateapi.v1.ActorMetadataDataSource - 58, // 74: substrate.ateapi.v1.SystemInfoDataSource.trust_bundle:type_name -> substrate.ateapi.v1.TrustBundleDataSource - 57, // 75: substrate.ateapi.v1.ActorMetadataDataSource.items:type_name -> substrate.ateapi.v1.ActorMetadataItem + 47, // 60: substrate.ateapi.v1.Container.env:type_name -> substrate.ateapi.v1.EnvVar + 48, // 61: substrate.ateapi.v1.Container.wakeup_probe:type_name -> substrate.ateapi.v1.WakeupProbe + 60, // 62: substrate.ateapi.v1.Container.volume_mounts:type_name -> substrate.ateapi.v1.VolumeMount + 45, // 63: substrate.ateapi.v1.Container.security_context:type_name -> substrate.ateapi.v1.SecurityContext + 38, // 64: substrate.ateapi.v1.Container.resources:type_name -> substrate.ateapi.v1.ResourceRequirements + 46, // 65: substrate.ateapi.v1.SecurityContext.capabilities:type_name -> substrate.ateapi.v1.Capabilities + 49, // 66: substrate.ateapi.v1.WakeupProbe.http_get:type_name -> substrate.ateapi.v1.HttpGetAction + 53, // 67: substrate.ateapi.v1.Volume.durable_dir:type_name -> substrate.ateapi.v1.DurableDirVolumeSource + 51, // 68: substrate.ateapi.v1.Volume.external:type_name -> substrate.ateapi.v1.ExternalVolumeSource + 55, // 69: substrate.ateapi.v1.Volume.system_info:type_name -> substrate.ateapi.v1.SystemInfoVolumeSource + 52, // 70: substrate.ateapi.v1.Volume.image:type_name -> substrate.ateapi.v1.ImageVolumeSource + 54, // 71: substrate.ateapi.v1.ExternalVolumeSource.template:type_name -> substrate.ateapi.v1.ExternalVolumeTemplate + 56, // 72: substrate.ateapi.v1.SystemInfoVolumeSource.data_sources:type_name -> substrate.ateapi.v1.SystemInfoDataSource + 57, // 73: substrate.ateapi.v1.SystemInfoDataSource.actor_metadata:type_name -> substrate.ateapi.v1.ActorMetadataDataSource + 59, // 74: substrate.ateapi.v1.SystemInfoDataSource.trust_bundle:type_name -> substrate.ateapi.v1.TrustBundleDataSource + 58, // 75: substrate.ateapi.v1.ActorMetadataDataSource.items:type_name -> substrate.ateapi.v1.ActorMetadataItem 6, // 76: substrate.ateapi.v1.ActorMetadataItem.field:type_name -> substrate.ateapi.v1.ActorMetadataField - 34, // 77: substrate.ateapi.v1.CreateAtespaceRequest.atespace:type_name -> substrate.ateapi.v1.Atespace - 35, // 78: substrate.ateapi.v1.GetAtespaceRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef - 96, // 79: substrate.ateapi.v1.ListAtespacesRequest.options:type_name -> substrate.ateapi.v1.ListOptions - 34, // 80: substrate.ateapi.v1.ListAtespacesResponse.atespaces:type_name -> substrate.ateapi.v1.Atespace - 35, // 81: substrate.ateapi.v1.DeleteAtespaceRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef - 95, // 82: substrate.ateapi.v1.DeleteAtespaceRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions - 36, // 83: substrate.ateapi.v1.CreateActorTemplateRequest.actor_template:type_name -> substrate.ateapi.v1.ActorTemplate - 35, // 84: substrate.ateapi.v1.GetActorTemplateRequest.actor_template:type_name -> substrate.ateapi.v1.ObjectRef - 96, // 85: substrate.ateapi.v1.ListActorTemplatesRequest.options:type_name -> substrate.ateapi.v1.ListOptions - 36, // 86: substrate.ateapi.v1.ListActorTemplatesResponse.actor_templates:type_name -> substrate.ateapi.v1.ActorTemplate - 35, // 87: substrate.ateapi.v1.DeleteActorTemplateRequest.actor_template:type_name -> substrate.ateapi.v1.ObjectRef - 95, // 88: substrate.ateapi.v1.DeleteActorTemplateRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions - 35, // 89: substrate.ateapi.v1.GetActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 17, // 90: substrate.ateapi.v1.CreateActorRequest.actor:type_name -> substrate.ateapi.v1.Actor - 17, // 91: substrate.ateapi.v1.UpdateActorRequest.actor:type_name -> substrate.ateapi.v1.Actor - 35, // 92: substrate.ateapi.v1.SuspendActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 75, // 93: substrate.ateapi.v1.SuspendActorRequest.options:type_name -> substrate.ateapi.v1.SuspendOptions - 17, // 94: substrate.ateapi.v1.SuspendActorResponse.actor:type_name -> substrate.ateapi.v1.Actor + 35, // 77: substrate.ateapi.v1.CreateAtespaceRequest.atespace:type_name -> substrate.ateapi.v1.Atespace + 36, // 78: substrate.ateapi.v1.GetAtespaceRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef + 97, // 79: substrate.ateapi.v1.ListAtespacesRequest.options:type_name -> substrate.ateapi.v1.ListOptions + 35, // 80: substrate.ateapi.v1.ListAtespacesResponse.atespaces:type_name -> substrate.ateapi.v1.Atespace + 36, // 81: substrate.ateapi.v1.DeleteAtespaceRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef + 96, // 82: substrate.ateapi.v1.DeleteAtespaceRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions + 37, // 83: substrate.ateapi.v1.CreateActorTemplateRequest.actor_template:type_name -> substrate.ateapi.v1.ActorTemplate + 36, // 84: substrate.ateapi.v1.GetActorTemplateRequest.actor_template:type_name -> substrate.ateapi.v1.ObjectRef + 97, // 85: substrate.ateapi.v1.ListActorTemplatesRequest.options:type_name -> substrate.ateapi.v1.ListOptions + 37, // 86: substrate.ateapi.v1.ListActorTemplatesResponse.actor_templates:type_name -> substrate.ateapi.v1.ActorTemplate + 36, // 87: substrate.ateapi.v1.DeleteActorTemplateRequest.actor_template:type_name -> substrate.ateapi.v1.ObjectRef + 96, // 88: substrate.ateapi.v1.DeleteActorTemplateRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions + 36, // 89: substrate.ateapi.v1.GetActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 18, // 90: substrate.ateapi.v1.CreateActorRequest.actor:type_name -> substrate.ateapi.v1.Actor + 18, // 91: substrate.ateapi.v1.UpdateActorRequest.actor:type_name -> substrate.ateapi.v1.Actor + 36, // 92: substrate.ateapi.v1.SuspendActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 76, // 93: substrate.ateapi.v1.SuspendActorRequest.options:type_name -> substrate.ateapi.v1.SuspendOptions + 18, // 94: substrate.ateapi.v1.SuspendActorResponse.actor:type_name -> substrate.ateapi.v1.Actor 7, // 95: substrate.ateapi.v1.SuspendOptions.mode:type_name -> substrate.ateapi.v1.DurabilityMode - 35, // 96: substrate.ateapi.v1.ResumeActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 17, // 97: substrate.ateapi.v1.ResumeActorResponse.actor:type_name -> substrate.ateapi.v1.Actor - 35, // 98: substrate.ateapi.v1.RevertActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 17, // 99: substrate.ateapi.v1.RevertActorResponse.actor:type_name -> substrate.ateapi.v1.Actor - 35, // 100: substrate.ateapi.v1.DeleteActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 95, // 101: substrate.ateapi.v1.DeleteActorRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions - 35, // 102: substrate.ateapi.v1.GetActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 103: substrate.ateapi.v1.CreateActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 18, // 104: substrate.ateapi.v1.CreateActorEgressPolicyRequest.egress_policy:type_name -> substrate.ateapi.v1.EgressPolicy - 35, // 105: substrate.ateapi.v1.UpdateActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 18, // 106: substrate.ateapi.v1.UpdateActorEgressPolicyRequest.egress_policy:type_name -> substrate.ateapi.v1.EgressPolicy - 35, // 107: substrate.ateapi.v1.DeleteActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 95, // 108: substrate.ateapi.v1.DeleteActorEgressPolicyRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions - 35, // 109: substrate.ateapi.v1.GetTagRequest.tag:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 110: substrate.ateapi.v1.MintActorJwtRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 154, // 111: substrate.ateapi.v1.MintActorJwtResponse.expire_time:type_name -> google.protobuf.Timestamp - 35, // 112: substrate.ateapi.v1.MintActorCertificateRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 96, // 113: substrate.ateapi.v1.ListTagsRequest.options:type_name -> substrate.ateapi.v1.ListOptions - 33, // 114: substrate.ateapi.v1.ListTagsResponse.tags:type_name -> substrate.ateapi.v1.Tag - 33, // 115: substrate.ateapi.v1.CreateTagRequest.tag:type_name -> substrate.ateapi.v1.Tag - 33, // 116: substrate.ateapi.v1.UpdateTagRequest.tag:type_name -> substrate.ateapi.v1.Tag - 35, // 117: substrate.ateapi.v1.DeleteTagRequest.tag:type_name -> substrate.ateapi.v1.ObjectRef - 95, // 118: substrate.ateapi.v1.DeleteTagRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions - 35, // 119: substrate.ateapi.v1.ListWorkerActorAssignmentsRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 96, // 120: substrate.ateapi.v1.ListWorkerActorAssignmentsRequest.options:type_name -> substrate.ateapi.v1.ListOptions - 121, // 121: substrate.ateapi.v1.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> substrate.ateapi.v1.ActorAssignment - 96, // 122: substrate.ateapi.v1.ListWorkersRequest.options:type_name -> substrate.ateapi.v1.ListOptions - 113, // 123: substrate.ateapi.v1.ListWorkersResponse.workers:type_name -> substrate.ateapi.v1.Worker - 35, // 124: substrate.ateapi.v1.GetWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 113, // 125: substrate.ateapi.v1.CreateWorkerRequest.worker:type_name -> substrate.ateapi.v1.Worker - 113, // 126: substrate.ateapi.v1.UpdateWorkerRequest.worker:type_name -> substrate.ateapi.v1.Worker - 35, // 127: substrate.ateapi.v1.DeleteWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 95, // 128: substrate.ateapi.v1.DeleteWorkerRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions - 35, // 129: substrate.ateapi.v1.DrainWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 113, // 130: substrate.ateapi.v1.DrainWorkerResponse.worker:type_name -> substrate.ateapi.v1.Worker - 35, // 131: substrate.ateapi.v1.MarkWorkerAvailableRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 113, // 132: substrate.ateapi.v1.MarkWorkerAvailableResponse.worker:type_name -> substrate.ateapi.v1.Worker - 35, // 133: substrate.ateapi.v1.MarkWorkerUnavailableRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 113, // 134: substrate.ateapi.v1.MarkWorkerUnavailableResponse.worker:type_name -> substrate.ateapi.v1.Worker - 96, // 135: substrate.ateapi.v1.ListActorsRequest.options:type_name -> substrate.ateapi.v1.ListOptions - 17, // 136: substrate.ateapi.v1.ListActorsResponse.actors:type_name -> substrate.ateapi.v1.Actor - 14, // 137: substrate.ateapi.v1.Worker.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 153, // 138: substrate.ateapi.v1.Worker.labels:type_name -> substrate.ateapi.v1.Worker.LabelsEntry - 114, // 139: substrate.ateapi.v1.Worker.status:type_name -> substrate.ateapi.v1.WorkerStatus + 36, // 96: substrate.ateapi.v1.ResumeActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 18, // 97: substrate.ateapi.v1.ResumeActorResponse.actor:type_name -> substrate.ateapi.v1.Actor + 36, // 98: substrate.ateapi.v1.RevertActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 18, // 99: substrate.ateapi.v1.RevertActorResponse.actor:type_name -> substrate.ateapi.v1.Actor + 36, // 100: substrate.ateapi.v1.DeleteActorRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 96, // 101: substrate.ateapi.v1.DeleteActorRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions + 36, // 102: substrate.ateapi.v1.GetActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 103: substrate.ateapi.v1.CreateActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 19, // 104: substrate.ateapi.v1.CreateActorEgressPolicyRequest.egress_policy:type_name -> substrate.ateapi.v1.EgressPolicy + 36, // 105: substrate.ateapi.v1.UpdateActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 19, // 106: substrate.ateapi.v1.UpdateActorEgressPolicyRequest.egress_policy:type_name -> substrate.ateapi.v1.EgressPolicy + 36, // 107: substrate.ateapi.v1.DeleteActorEgressPolicyRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 96, // 108: substrate.ateapi.v1.DeleteActorEgressPolicyRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions + 36, // 109: substrate.ateapi.v1.GetTagRequest.tag:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 110: substrate.ateapi.v1.MintActorJwtRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 157, // 111: substrate.ateapi.v1.MintActorJwtResponse.expire_time:type_name -> google.protobuf.Timestamp + 36, // 112: substrate.ateapi.v1.MintActorCertificateRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 97, // 113: substrate.ateapi.v1.ListTagsRequest.options:type_name -> substrate.ateapi.v1.ListOptions + 34, // 114: substrate.ateapi.v1.ListTagsResponse.tags:type_name -> substrate.ateapi.v1.Tag + 34, // 115: substrate.ateapi.v1.CreateTagRequest.tag:type_name -> substrate.ateapi.v1.Tag + 34, // 116: substrate.ateapi.v1.UpdateTagRequest.tag:type_name -> substrate.ateapi.v1.Tag + 36, // 117: substrate.ateapi.v1.DeleteTagRequest.tag:type_name -> substrate.ateapi.v1.ObjectRef + 96, // 118: substrate.ateapi.v1.DeleteTagRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions + 36, // 119: substrate.ateapi.v1.ListWorkerActorAssignmentsRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 97, // 120: substrate.ateapi.v1.ListWorkerActorAssignmentsRequest.options:type_name -> substrate.ateapi.v1.ListOptions + 122, // 121: substrate.ateapi.v1.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> substrate.ateapi.v1.ActorAssignment + 97, // 122: substrate.ateapi.v1.ListWorkersRequest.options:type_name -> substrate.ateapi.v1.ListOptions + 114, // 123: substrate.ateapi.v1.ListWorkersResponse.workers:type_name -> substrate.ateapi.v1.Worker + 36, // 124: substrate.ateapi.v1.GetWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 114, // 125: substrate.ateapi.v1.CreateWorkerRequest.worker:type_name -> substrate.ateapi.v1.Worker + 114, // 126: substrate.ateapi.v1.UpdateWorkerRequest.worker:type_name -> substrate.ateapi.v1.Worker + 36, // 127: substrate.ateapi.v1.DeleteWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 96, // 128: substrate.ateapi.v1.DeleteWorkerRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions + 36, // 129: substrate.ateapi.v1.DrainWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 114, // 130: substrate.ateapi.v1.DrainWorkerResponse.worker:type_name -> substrate.ateapi.v1.Worker + 36, // 131: substrate.ateapi.v1.MarkWorkerAvailableRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 114, // 132: substrate.ateapi.v1.MarkWorkerAvailableResponse.worker:type_name -> substrate.ateapi.v1.Worker + 36, // 133: substrate.ateapi.v1.MarkWorkerUnavailableRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 114, // 134: substrate.ateapi.v1.MarkWorkerUnavailableResponse.worker:type_name -> substrate.ateapi.v1.Worker + 97, // 135: substrate.ateapi.v1.ListActorsRequest.options:type_name -> substrate.ateapi.v1.ListOptions + 18, // 136: substrate.ateapi.v1.ListActorsResponse.actors:type_name -> substrate.ateapi.v1.Actor + 15, // 137: substrate.ateapi.v1.Worker.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 156, // 138: substrate.ateapi.v1.Worker.labels:type_name -> substrate.ateapi.v1.Worker.LabelsEntry + 115, // 139: substrate.ateapi.v1.Worker.status:type_name -> substrate.ateapi.v1.WorkerStatus 8, // 140: substrate.ateapi.v1.WorkerStatus.state:type_name -> substrate.ateapi.v1.WorkerState - 120, // 141: substrate.ateapi.v1.WorkerStatus.capacity:type_name -> substrate.ateapi.v1.WorkerResources - 120, // 142: substrate.ateapi.v1.WorkerStatus.allocated:type_name -> substrate.ateapi.v1.WorkerResources - 115, // 143: substrate.ateapi.v1.WorkerStatus.sandbox_classes:type_name -> substrate.ateapi.v1.WorkerSandboxClass - 116, // 144: substrate.ateapi.v1.WorkerSandboxClass.default_runtime:type_name -> substrate.ateapi.v1.WorkerSandboxRuntime - 116, // 145: substrate.ateapi.v1.WorkerSandboxClass.restorable_runtimes:type_name -> substrate.ateapi.v1.WorkerSandboxRuntime - 118, // 146: substrate.ateapi.v1.WorkerSandboxRuntime.version:type_name -> substrate.ateapi.v1.VersionedSandboxCompat - 118, // 147: substrate.ateapi.v1.ActorSandboxRuntime.version:type_name -> substrate.ateapi.v1.VersionedSandboxCompat - 119, // 148: substrate.ateapi.v1.VersionedSandboxCompat.attributes:type_name -> substrate.ateapi.v1.AttributeEntry - 14, // 149: substrate.ateapi.v1.ActorAssignment.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 35, // 150: substrate.ateapi.v1.ActorAssignment.actor:type_name -> substrate.ateapi.v1.ObjectRef - 38, // 151: substrate.ateapi.v1.ActorAssignment.resources:type_name -> substrate.ateapi.v1.Resources - 35, // 152: substrate.ateapi.v1.AcquireWorkerEpochRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 153: substrate.ateapi.v1.RegisterWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 120, // 154: substrate.ateapi.v1.RegisterWorkerRequest.capacity:type_name -> substrate.ateapi.v1.WorkerResources - 113, // 155: substrate.ateapi.v1.RegisterWorkerResponse.worker:type_name -> substrate.ateapi.v1.Worker - 35, // 156: substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 115, // 157: substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest.sandbox_classes:type_name -> substrate.ateapi.v1.WorkerSandboxClass - 113, // 158: substrate.ateapi.v1.SetWorkerSandboxRuntimesResponse.worker:type_name -> substrate.ateapi.v1.Worker - 35, // 159: substrate.ateapi.v1.MintWorkerActorCertificateRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 160: substrate.ateapi.v1.MintWorkerActorCertificateRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 161: substrate.ateapi.v1.MintWorkerActorJwtRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 154, // 162: substrate.ateapi.v1.MintWorkerActorJwtResponse.expire_time:type_name -> google.protobuf.Timestamp - 35, // 163: substrate.ateapi.v1.RequestActorSuspendRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 164: substrate.ateapi.v1.RequestActorSuspendRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 17, // 165: substrate.ateapi.v1.RequestActorSuspendResponse.actor:type_name -> substrate.ateapi.v1.Actor - 35, // 166: substrate.ateapi.v1.ReportActorCrashedRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 167: substrate.ateapi.v1.ReportActorCrashedRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 17, // 168: substrate.ateapi.v1.ReportActorCrashedResponse.actor:type_name -> substrate.ateapi.v1.Actor - 35, // 169: substrate.ateapi.v1.ReportActorSnapshotStateRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef - 138, // 170: substrate.ateapi.v1.ReportActorSnapshotStateRequest.state:type_name -> substrate.ateapi.v1.ActorSnapshotState - 139, // 171: substrate.ateapi.v1.ActorSnapshotState.latest_local:type_name -> substrate.ateapi.v1.SnapshotReport - 139, // 172: substrate.ateapi.v1.ActorSnapshotState.uploading:type_name -> substrate.ateapi.v1.SnapshotReport - 139, // 173: substrate.ateapi.v1.ActorSnapshotState.latest_durable:type_name -> substrate.ateapi.v1.SnapshotReport - 154, // 174: substrate.ateapi.v1.ActorSnapshotState.upload_due_time:type_name -> google.protobuf.Timestamp - 2, // 175: substrate.ateapi.v1.SnapshotReport.fidelity:type_name -> substrate.ateapi.v1.SnapshotFidelity - 10, // 176: substrate.ateapi.v1.SnapshotReport.object:type_name -> substrate.ateapi.v1.ObjectSnapshot - 154, // 177: substrate.ateapi.v1.SnapshotReport.local_commit_time:type_name -> google.protobuf.Timestamp - 154, // 178: substrate.ateapi.v1.SnapshotReport.durable_complete_time:type_name -> google.protobuf.Timestamp - 14, // 179: substrate.ateapi.v1.AccessPolicy.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata - 141, // 180: substrate.ateapi.v1.AccessPolicy.bindings:type_name -> substrate.ateapi.v1.Binding - 140, // 181: substrate.ateapi.v1.CreateGlobalAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy - 140, // 182: substrate.ateapi.v1.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy - 35, // 183: substrate.ateapi.v1.GetAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef - 35, // 184: substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef - 140, // 185: substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy - 35, // 186: substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef - 140, // 187: substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy - 35, // 188: substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef - 95, // 189: substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions - 9, // 190: substrate.ateapi.v1.ResourceAnnotation.scope:type_name -> substrate.ateapi.v1.Scope - 155, // 191: substrate.ateapi.v1.resource:extendee -> google.protobuf.MessageOptions - 156, // 192: substrate.ateapi.v1.method:extendee -> google.protobuf.MethodOptions - 149, // 193: substrate.ateapi.v1.resource:type_name -> substrate.ateapi.v1.ResourceAnnotation - 150, // 194: substrate.ateapi.v1.method:type_name -> substrate.ateapi.v1.MethodAnnotation - 70, // 195: substrate.ateapi.v1.Control.GetActor:input_type -> substrate.ateapi.v1.GetActorRequest - 71, // 196: substrate.ateapi.v1.Control.CreateActor:input_type -> substrate.ateapi.v1.CreateActorRequest - 72, // 197: substrate.ateapi.v1.Control.UpdateActor:input_type -> substrate.ateapi.v1.UpdateActorRequest - 73, // 198: substrate.ateapi.v1.Control.SuspendActor:input_type -> substrate.ateapi.v1.SuspendActorRequest - 76, // 199: substrate.ateapi.v1.Control.ResumeActor:input_type -> substrate.ateapi.v1.ResumeActorRequest - 78, // 200: substrate.ateapi.v1.Control.RevertActor:input_type -> substrate.ateapi.v1.RevertActorRequest - 80, // 201: substrate.ateapi.v1.Control.DeleteActor:input_type -> substrate.ateapi.v1.DeleteActorRequest - 81, // 202: substrate.ateapi.v1.Control.GetActorEgressPolicy:input_type -> substrate.ateapi.v1.GetActorEgressPolicyRequest - 82, // 203: substrate.ateapi.v1.Control.CreateActorEgressPolicy:input_type -> substrate.ateapi.v1.CreateActorEgressPolicyRequest - 83, // 204: substrate.ateapi.v1.Control.UpdateActorEgressPolicy:input_type -> substrate.ateapi.v1.UpdateActorEgressPolicyRequest - 84, // 205: substrate.ateapi.v1.Control.DeleteActorEgressPolicy:input_type -> substrate.ateapi.v1.DeleteActorEgressPolicyRequest - 86, // 206: substrate.ateapi.v1.Control.MintActorJwt:input_type -> substrate.ateapi.v1.MintActorJwtRequest - 88, // 207: substrate.ateapi.v1.Control.MintActorCertificate:input_type -> substrate.ateapi.v1.MintActorCertificateRequest - 92, // 208: substrate.ateapi.v1.Control.CreateTag:input_type -> substrate.ateapi.v1.CreateTagRequest - 85, // 209: substrate.ateapi.v1.Control.GetTag:input_type -> substrate.ateapi.v1.GetTagRequest - 90, // 210: substrate.ateapi.v1.Control.ListTags:input_type -> substrate.ateapi.v1.ListTagsRequest - 93, // 211: substrate.ateapi.v1.Control.UpdateTag:input_type -> substrate.ateapi.v1.UpdateTagRequest - 94, // 212: substrate.ateapi.v1.Control.DeleteTag:input_type -> substrate.ateapi.v1.DeleteTagRequest - 99, // 213: substrate.ateapi.v1.Control.ListWorkers:input_type -> substrate.ateapi.v1.ListWorkersRequest - 101, // 214: substrate.ateapi.v1.Control.GetWorker:input_type -> substrate.ateapi.v1.GetWorkerRequest - 102, // 215: substrate.ateapi.v1.Control.CreateWorker:input_type -> substrate.ateapi.v1.CreateWorkerRequest - 103, // 216: substrate.ateapi.v1.Control.UpdateWorker:input_type -> substrate.ateapi.v1.UpdateWorkerRequest - 104, // 217: substrate.ateapi.v1.Control.DeleteWorker:input_type -> substrate.ateapi.v1.DeleteWorkerRequest - 105, // 218: substrate.ateapi.v1.Control.DrainWorker:input_type -> substrate.ateapi.v1.DrainWorkerRequest - 107, // 219: substrate.ateapi.v1.Control.MarkWorkerAvailable:input_type -> substrate.ateapi.v1.MarkWorkerAvailableRequest - 109, // 220: substrate.ateapi.v1.Control.MarkWorkerUnavailable:input_type -> substrate.ateapi.v1.MarkWorkerUnavailableRequest - 97, // 221: substrate.ateapi.v1.Control.ListWorkerActorAssignments:input_type -> substrate.ateapi.v1.ListWorkerActorAssignmentsRequest - 111, // 222: substrate.ateapi.v1.Control.ListActors:input_type -> substrate.ateapi.v1.ListActorsRequest - 60, // 223: substrate.ateapi.v1.Control.CreateAtespace:input_type -> substrate.ateapi.v1.CreateAtespaceRequest - 61, // 224: substrate.ateapi.v1.Control.GetAtespace:input_type -> substrate.ateapi.v1.GetAtespaceRequest - 62, // 225: substrate.ateapi.v1.Control.ListAtespaces:input_type -> substrate.ateapi.v1.ListAtespacesRequest - 64, // 226: substrate.ateapi.v1.Control.DeleteAtespace:input_type -> substrate.ateapi.v1.DeleteAtespaceRequest - 65, // 227: substrate.ateapi.v1.Control.CreateActorTemplate:input_type -> substrate.ateapi.v1.CreateActorTemplateRequest - 66, // 228: substrate.ateapi.v1.Control.GetActorTemplate:input_type -> substrate.ateapi.v1.GetActorTemplateRequest - 67, // 229: substrate.ateapi.v1.Control.ListActorTemplates:input_type -> substrate.ateapi.v1.ListActorTemplatesRequest - 69, // 230: substrate.ateapi.v1.Control.DeleteActorTemplate:input_type -> substrate.ateapi.v1.DeleteActorTemplateRequest - 142, // 231: substrate.ateapi.v1.Control.GetGlobalAccessPolicy:input_type -> substrate.ateapi.v1.GetGlobalAccessPolicyRequest - 143, // 232: substrate.ateapi.v1.Control.CreateGlobalAccessPolicy:input_type -> substrate.ateapi.v1.CreateGlobalAccessPolicyRequest - 144, // 233: substrate.ateapi.v1.Control.UpdateGlobalAccessPolicy:input_type -> substrate.ateapi.v1.UpdateGlobalAccessPolicyRequest - 145, // 234: substrate.ateapi.v1.Control.GetAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.GetAtespaceAccessPolicyRequest - 146, // 235: substrate.ateapi.v1.Control.CreateAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest - 147, // 236: substrate.ateapi.v1.Control.UpdateAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest - 148, // 237: substrate.ateapi.v1.Control.DeleteAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest - 122, // 238: substrate.ateapi.v1.WorkerService.AcquireWorkerEpoch:input_type -> substrate.ateapi.v1.AcquireWorkerEpochRequest - 124, // 239: substrate.ateapi.v1.WorkerService.RegisterWorker:input_type -> substrate.ateapi.v1.RegisterWorkerRequest - 126, // 240: substrate.ateapi.v1.WorkerService.SetWorkerSandboxRuntimes:input_type -> substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest - 128, // 241: substrate.ateapi.v1.WorkerService.MintWorkerActorCertificate:input_type -> substrate.ateapi.v1.MintWorkerActorCertificateRequest - 130, // 242: substrate.ateapi.v1.WorkerService.MintWorkerActorJwt:input_type -> substrate.ateapi.v1.MintWorkerActorJwtRequest - 132, // 243: substrate.ateapi.v1.WorkerService.RequestActorSuspend:input_type -> substrate.ateapi.v1.RequestActorSuspendRequest - 136, // 244: substrate.ateapi.v1.WorkerService.ReportActorSnapshotState:input_type -> substrate.ateapi.v1.ReportActorSnapshotStateRequest - 134, // 245: substrate.ateapi.v1.WorkerService.ReportActorCrashed:input_type -> substrate.ateapi.v1.ReportActorCrashedRequest - 17, // 246: substrate.ateapi.v1.Control.GetActor:output_type -> substrate.ateapi.v1.Actor - 17, // 247: substrate.ateapi.v1.Control.CreateActor:output_type -> substrate.ateapi.v1.Actor - 17, // 248: substrate.ateapi.v1.Control.UpdateActor:output_type -> substrate.ateapi.v1.Actor - 74, // 249: substrate.ateapi.v1.Control.SuspendActor:output_type -> substrate.ateapi.v1.SuspendActorResponse - 77, // 250: substrate.ateapi.v1.Control.ResumeActor:output_type -> substrate.ateapi.v1.ResumeActorResponse - 79, // 251: substrate.ateapi.v1.Control.RevertActor:output_type -> substrate.ateapi.v1.RevertActorResponse - 17, // 252: substrate.ateapi.v1.Control.DeleteActor:output_type -> substrate.ateapi.v1.Actor - 18, // 253: substrate.ateapi.v1.Control.GetActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy - 18, // 254: substrate.ateapi.v1.Control.CreateActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy - 18, // 255: substrate.ateapi.v1.Control.UpdateActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy - 18, // 256: substrate.ateapi.v1.Control.DeleteActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy - 87, // 257: substrate.ateapi.v1.Control.MintActorJwt:output_type -> substrate.ateapi.v1.MintActorJwtResponse - 89, // 258: substrate.ateapi.v1.Control.MintActorCertificate:output_type -> substrate.ateapi.v1.MintActorCertificateResponse - 33, // 259: substrate.ateapi.v1.Control.CreateTag:output_type -> substrate.ateapi.v1.Tag - 33, // 260: substrate.ateapi.v1.Control.GetTag:output_type -> substrate.ateapi.v1.Tag - 91, // 261: substrate.ateapi.v1.Control.ListTags:output_type -> substrate.ateapi.v1.ListTagsResponse - 33, // 262: substrate.ateapi.v1.Control.UpdateTag:output_type -> substrate.ateapi.v1.Tag - 33, // 263: substrate.ateapi.v1.Control.DeleteTag:output_type -> substrate.ateapi.v1.Tag - 100, // 264: substrate.ateapi.v1.Control.ListWorkers:output_type -> substrate.ateapi.v1.ListWorkersResponse - 113, // 265: substrate.ateapi.v1.Control.GetWorker:output_type -> substrate.ateapi.v1.Worker - 113, // 266: substrate.ateapi.v1.Control.CreateWorker:output_type -> substrate.ateapi.v1.Worker - 113, // 267: substrate.ateapi.v1.Control.UpdateWorker:output_type -> substrate.ateapi.v1.Worker - 113, // 268: substrate.ateapi.v1.Control.DeleteWorker:output_type -> substrate.ateapi.v1.Worker - 106, // 269: substrate.ateapi.v1.Control.DrainWorker:output_type -> substrate.ateapi.v1.DrainWorkerResponse - 108, // 270: substrate.ateapi.v1.Control.MarkWorkerAvailable:output_type -> substrate.ateapi.v1.MarkWorkerAvailableResponse - 110, // 271: substrate.ateapi.v1.Control.MarkWorkerUnavailable:output_type -> substrate.ateapi.v1.MarkWorkerUnavailableResponse - 98, // 272: substrate.ateapi.v1.Control.ListWorkerActorAssignments:output_type -> substrate.ateapi.v1.ListWorkerActorAssignmentsResponse - 112, // 273: substrate.ateapi.v1.Control.ListActors:output_type -> substrate.ateapi.v1.ListActorsResponse - 34, // 274: substrate.ateapi.v1.Control.CreateAtespace:output_type -> substrate.ateapi.v1.Atespace - 34, // 275: substrate.ateapi.v1.Control.GetAtespace:output_type -> substrate.ateapi.v1.Atespace - 63, // 276: substrate.ateapi.v1.Control.ListAtespaces:output_type -> substrate.ateapi.v1.ListAtespacesResponse - 34, // 277: substrate.ateapi.v1.Control.DeleteAtespace:output_type -> substrate.ateapi.v1.Atespace - 36, // 278: substrate.ateapi.v1.Control.CreateActorTemplate:output_type -> substrate.ateapi.v1.ActorTemplate - 36, // 279: substrate.ateapi.v1.Control.GetActorTemplate:output_type -> substrate.ateapi.v1.ActorTemplate - 68, // 280: substrate.ateapi.v1.Control.ListActorTemplates:output_type -> substrate.ateapi.v1.ListActorTemplatesResponse - 36, // 281: substrate.ateapi.v1.Control.DeleteActorTemplate:output_type -> substrate.ateapi.v1.ActorTemplate - 140, // 282: substrate.ateapi.v1.Control.GetGlobalAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy - 140, // 283: substrate.ateapi.v1.Control.CreateGlobalAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy - 140, // 284: substrate.ateapi.v1.Control.UpdateGlobalAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy - 140, // 285: substrate.ateapi.v1.Control.GetAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy - 140, // 286: substrate.ateapi.v1.Control.CreateAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy - 140, // 287: substrate.ateapi.v1.Control.UpdateAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy - 140, // 288: substrate.ateapi.v1.Control.DeleteAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy - 123, // 289: substrate.ateapi.v1.WorkerService.AcquireWorkerEpoch:output_type -> substrate.ateapi.v1.AcquireWorkerEpochResponse - 125, // 290: substrate.ateapi.v1.WorkerService.RegisterWorker:output_type -> substrate.ateapi.v1.RegisterWorkerResponse - 127, // 291: substrate.ateapi.v1.WorkerService.SetWorkerSandboxRuntimes:output_type -> substrate.ateapi.v1.SetWorkerSandboxRuntimesResponse - 129, // 292: substrate.ateapi.v1.WorkerService.MintWorkerActorCertificate:output_type -> substrate.ateapi.v1.MintWorkerActorCertificateResponse - 131, // 293: substrate.ateapi.v1.WorkerService.MintWorkerActorJwt:output_type -> substrate.ateapi.v1.MintWorkerActorJwtResponse - 133, // 294: substrate.ateapi.v1.WorkerService.RequestActorSuspend:output_type -> substrate.ateapi.v1.RequestActorSuspendResponse - 137, // 295: substrate.ateapi.v1.WorkerService.ReportActorSnapshotState:output_type -> substrate.ateapi.v1.ReportActorSnapshotStateResponse - 135, // 296: substrate.ateapi.v1.WorkerService.ReportActorCrashed:output_type -> substrate.ateapi.v1.ReportActorCrashedResponse - 246, // [246:297] is the sub-list for method output_type - 195, // [195:246] is the sub-list for method input_type - 193, // [193:195] is the sub-list for extension type_name - 191, // [191:193] is the sub-list for extension extendee - 0, // [0:191] is the sub-list for field type_name + 121, // 141: substrate.ateapi.v1.WorkerStatus.capacity:type_name -> substrate.ateapi.v1.WorkerResources + 121, // 142: substrate.ateapi.v1.WorkerStatus.allocated:type_name -> substrate.ateapi.v1.WorkerResources + 116, // 143: substrate.ateapi.v1.WorkerStatus.sandbox_classes:type_name -> substrate.ateapi.v1.WorkerSandboxClass + 117, // 144: substrate.ateapi.v1.WorkerSandboxClass.default_runtime:type_name -> substrate.ateapi.v1.WorkerSandboxRuntime + 117, // 145: substrate.ateapi.v1.WorkerSandboxClass.restorable_runtimes:type_name -> substrate.ateapi.v1.WorkerSandboxRuntime + 119, // 146: substrate.ateapi.v1.WorkerSandboxRuntime.version:type_name -> substrate.ateapi.v1.VersionedSandboxCompat + 119, // 147: substrate.ateapi.v1.ActorSandboxRuntime.version:type_name -> substrate.ateapi.v1.VersionedSandboxCompat + 120, // 148: substrate.ateapi.v1.VersionedSandboxCompat.attributes:type_name -> substrate.ateapi.v1.AttributeEntry + 15, // 149: substrate.ateapi.v1.ActorAssignment.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 36, // 150: substrate.ateapi.v1.ActorAssignment.actor:type_name -> substrate.ateapi.v1.ObjectRef + 39, // 151: substrate.ateapi.v1.ActorAssignment.resources:type_name -> substrate.ateapi.v1.Resources + 9, // 152: substrate.ateapi.v1.WhoAmIResponse.kind:type_name -> substrate.ateapi.v1.PrincipalKind + 36, // 153: substrate.ateapi.v1.AcquireWorkerEpochRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 154: substrate.ateapi.v1.RegisterWorkerRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 121, // 155: substrate.ateapi.v1.RegisterWorkerRequest.capacity:type_name -> substrate.ateapi.v1.WorkerResources + 114, // 156: substrate.ateapi.v1.RegisterWorkerResponse.worker:type_name -> substrate.ateapi.v1.Worker + 36, // 157: substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 116, // 158: substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest.sandbox_classes:type_name -> substrate.ateapi.v1.WorkerSandboxClass + 114, // 159: substrate.ateapi.v1.SetWorkerSandboxRuntimesResponse.worker:type_name -> substrate.ateapi.v1.Worker + 36, // 160: substrate.ateapi.v1.MintWorkerActorCertificateRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 161: substrate.ateapi.v1.MintWorkerActorCertificateRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 162: substrate.ateapi.v1.MintWorkerActorJwtRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 157, // 163: substrate.ateapi.v1.MintWorkerActorJwtResponse.expire_time:type_name -> google.protobuf.Timestamp + 36, // 164: substrate.ateapi.v1.RequestActorSuspendRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 165: substrate.ateapi.v1.RequestActorSuspendRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 18, // 166: substrate.ateapi.v1.RequestActorSuspendResponse.actor:type_name -> substrate.ateapi.v1.Actor + 36, // 167: substrate.ateapi.v1.ReportActorCrashedRequest.worker:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 168: substrate.ateapi.v1.ReportActorCrashedRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 18, // 169: substrate.ateapi.v1.ReportActorCrashedResponse.actor:type_name -> substrate.ateapi.v1.Actor + 36, // 170: substrate.ateapi.v1.ReportActorSnapshotStateRequest.actor:type_name -> substrate.ateapi.v1.ObjectRef + 141, // 171: substrate.ateapi.v1.ReportActorSnapshotStateRequest.state:type_name -> substrate.ateapi.v1.ActorSnapshotState + 142, // 172: substrate.ateapi.v1.ActorSnapshotState.latest_local:type_name -> substrate.ateapi.v1.SnapshotReport + 142, // 173: substrate.ateapi.v1.ActorSnapshotState.uploading:type_name -> substrate.ateapi.v1.SnapshotReport + 142, // 174: substrate.ateapi.v1.ActorSnapshotState.latest_durable:type_name -> substrate.ateapi.v1.SnapshotReport + 157, // 175: substrate.ateapi.v1.ActorSnapshotState.upload_due_time:type_name -> google.protobuf.Timestamp + 2, // 176: substrate.ateapi.v1.SnapshotReport.fidelity:type_name -> substrate.ateapi.v1.SnapshotFidelity + 11, // 177: substrate.ateapi.v1.SnapshotReport.object:type_name -> substrate.ateapi.v1.ObjectSnapshot + 157, // 178: substrate.ateapi.v1.SnapshotReport.local_commit_time:type_name -> google.protobuf.Timestamp + 157, // 179: substrate.ateapi.v1.SnapshotReport.durable_complete_time:type_name -> google.protobuf.Timestamp + 15, // 180: substrate.ateapi.v1.AccessPolicy.metadata:type_name -> substrate.ateapi.v1.ResourceMetadata + 144, // 181: substrate.ateapi.v1.AccessPolicy.bindings:type_name -> substrate.ateapi.v1.Binding + 143, // 182: substrate.ateapi.v1.CreateGlobalAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy + 143, // 183: substrate.ateapi.v1.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy + 36, // 184: substrate.ateapi.v1.GetAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef + 36, // 185: substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef + 143, // 186: substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy + 36, // 187: substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef + 143, // 188: substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> substrate.ateapi.v1.AccessPolicy + 36, // 189: substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> substrate.ateapi.v1.ObjectRef + 96, // 190: substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest.options:type_name -> substrate.ateapi.v1.DeleteOptions + 10, // 191: substrate.ateapi.v1.ResourceAnnotation.scope:type_name -> substrate.ateapi.v1.Scope + 158, // 192: substrate.ateapi.v1.resource:extendee -> google.protobuf.MessageOptions + 159, // 193: substrate.ateapi.v1.method:extendee -> google.protobuf.MethodOptions + 152, // 194: substrate.ateapi.v1.resource:type_name -> substrate.ateapi.v1.ResourceAnnotation + 153, // 195: substrate.ateapi.v1.method:type_name -> substrate.ateapi.v1.MethodAnnotation + 71, // 196: substrate.ateapi.v1.Control.GetActor:input_type -> substrate.ateapi.v1.GetActorRequest + 72, // 197: substrate.ateapi.v1.Control.CreateActor:input_type -> substrate.ateapi.v1.CreateActorRequest + 73, // 198: substrate.ateapi.v1.Control.UpdateActor:input_type -> substrate.ateapi.v1.UpdateActorRequest + 74, // 199: substrate.ateapi.v1.Control.SuspendActor:input_type -> substrate.ateapi.v1.SuspendActorRequest + 77, // 200: substrate.ateapi.v1.Control.ResumeActor:input_type -> substrate.ateapi.v1.ResumeActorRequest + 79, // 201: substrate.ateapi.v1.Control.RevertActor:input_type -> substrate.ateapi.v1.RevertActorRequest + 81, // 202: substrate.ateapi.v1.Control.DeleteActor:input_type -> substrate.ateapi.v1.DeleteActorRequest + 82, // 203: substrate.ateapi.v1.Control.GetActorEgressPolicy:input_type -> substrate.ateapi.v1.GetActorEgressPolicyRequest + 83, // 204: substrate.ateapi.v1.Control.CreateActorEgressPolicy:input_type -> substrate.ateapi.v1.CreateActorEgressPolicyRequest + 84, // 205: substrate.ateapi.v1.Control.UpdateActorEgressPolicy:input_type -> substrate.ateapi.v1.UpdateActorEgressPolicyRequest + 85, // 206: substrate.ateapi.v1.Control.DeleteActorEgressPolicy:input_type -> substrate.ateapi.v1.DeleteActorEgressPolicyRequest + 87, // 207: substrate.ateapi.v1.Control.MintActorJwt:input_type -> substrate.ateapi.v1.MintActorJwtRequest + 89, // 208: substrate.ateapi.v1.Control.MintActorCertificate:input_type -> substrate.ateapi.v1.MintActorCertificateRequest + 93, // 209: substrate.ateapi.v1.Control.CreateTag:input_type -> substrate.ateapi.v1.CreateTagRequest + 86, // 210: substrate.ateapi.v1.Control.GetTag:input_type -> substrate.ateapi.v1.GetTagRequest + 91, // 211: substrate.ateapi.v1.Control.ListTags:input_type -> substrate.ateapi.v1.ListTagsRequest + 94, // 212: substrate.ateapi.v1.Control.UpdateTag:input_type -> substrate.ateapi.v1.UpdateTagRequest + 95, // 213: substrate.ateapi.v1.Control.DeleteTag:input_type -> substrate.ateapi.v1.DeleteTagRequest + 100, // 214: substrate.ateapi.v1.Control.ListWorkers:input_type -> substrate.ateapi.v1.ListWorkersRequest + 102, // 215: substrate.ateapi.v1.Control.GetWorker:input_type -> substrate.ateapi.v1.GetWorkerRequest + 103, // 216: substrate.ateapi.v1.Control.CreateWorker:input_type -> substrate.ateapi.v1.CreateWorkerRequest + 104, // 217: substrate.ateapi.v1.Control.UpdateWorker:input_type -> substrate.ateapi.v1.UpdateWorkerRequest + 105, // 218: substrate.ateapi.v1.Control.DeleteWorker:input_type -> substrate.ateapi.v1.DeleteWorkerRequest + 106, // 219: substrate.ateapi.v1.Control.DrainWorker:input_type -> substrate.ateapi.v1.DrainWorkerRequest + 108, // 220: substrate.ateapi.v1.Control.MarkWorkerAvailable:input_type -> substrate.ateapi.v1.MarkWorkerAvailableRequest + 110, // 221: substrate.ateapi.v1.Control.MarkWorkerUnavailable:input_type -> substrate.ateapi.v1.MarkWorkerUnavailableRequest + 98, // 222: substrate.ateapi.v1.Control.ListWorkerActorAssignments:input_type -> substrate.ateapi.v1.ListWorkerActorAssignmentsRequest + 112, // 223: substrate.ateapi.v1.Control.ListActors:input_type -> substrate.ateapi.v1.ListActorsRequest + 61, // 224: substrate.ateapi.v1.Control.CreateAtespace:input_type -> substrate.ateapi.v1.CreateAtespaceRequest + 62, // 225: substrate.ateapi.v1.Control.GetAtespace:input_type -> substrate.ateapi.v1.GetAtespaceRequest + 63, // 226: substrate.ateapi.v1.Control.ListAtespaces:input_type -> substrate.ateapi.v1.ListAtespacesRequest + 65, // 227: substrate.ateapi.v1.Control.DeleteAtespace:input_type -> substrate.ateapi.v1.DeleteAtespaceRequest + 66, // 228: substrate.ateapi.v1.Control.CreateActorTemplate:input_type -> substrate.ateapi.v1.CreateActorTemplateRequest + 67, // 229: substrate.ateapi.v1.Control.GetActorTemplate:input_type -> substrate.ateapi.v1.GetActorTemplateRequest + 68, // 230: substrate.ateapi.v1.Control.ListActorTemplates:input_type -> substrate.ateapi.v1.ListActorTemplatesRequest + 70, // 231: substrate.ateapi.v1.Control.DeleteActorTemplate:input_type -> substrate.ateapi.v1.DeleteActorTemplateRequest + 145, // 232: substrate.ateapi.v1.Control.GetGlobalAccessPolicy:input_type -> substrate.ateapi.v1.GetGlobalAccessPolicyRequest + 146, // 233: substrate.ateapi.v1.Control.CreateGlobalAccessPolicy:input_type -> substrate.ateapi.v1.CreateGlobalAccessPolicyRequest + 147, // 234: substrate.ateapi.v1.Control.UpdateGlobalAccessPolicy:input_type -> substrate.ateapi.v1.UpdateGlobalAccessPolicyRequest + 148, // 235: substrate.ateapi.v1.Control.GetAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.GetAtespaceAccessPolicyRequest + 149, // 236: substrate.ateapi.v1.Control.CreateAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.CreateAtespaceAccessPolicyRequest + 150, // 237: substrate.ateapi.v1.Control.UpdateAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.UpdateAtespaceAccessPolicyRequest + 151, // 238: substrate.ateapi.v1.Control.DeleteAtespaceAccessPolicy:input_type -> substrate.ateapi.v1.DeleteAtespaceAccessPolicyRequest + 123, // 239: substrate.ateapi.v1.Authentication.WhoAmI:input_type -> substrate.ateapi.v1.WhoAmIRequest + 125, // 240: substrate.ateapi.v1.WorkerService.AcquireWorkerEpoch:input_type -> substrate.ateapi.v1.AcquireWorkerEpochRequest + 127, // 241: substrate.ateapi.v1.WorkerService.RegisterWorker:input_type -> substrate.ateapi.v1.RegisterWorkerRequest + 129, // 242: substrate.ateapi.v1.WorkerService.SetWorkerSandboxRuntimes:input_type -> substrate.ateapi.v1.SetWorkerSandboxRuntimesRequest + 131, // 243: substrate.ateapi.v1.WorkerService.MintWorkerActorCertificate:input_type -> substrate.ateapi.v1.MintWorkerActorCertificateRequest + 133, // 244: substrate.ateapi.v1.WorkerService.MintWorkerActorJwt:input_type -> substrate.ateapi.v1.MintWorkerActorJwtRequest + 135, // 245: substrate.ateapi.v1.WorkerService.RequestActorSuspend:input_type -> substrate.ateapi.v1.RequestActorSuspendRequest + 139, // 246: substrate.ateapi.v1.WorkerService.ReportActorSnapshotState:input_type -> substrate.ateapi.v1.ReportActorSnapshotStateRequest + 137, // 247: substrate.ateapi.v1.WorkerService.ReportActorCrashed:input_type -> substrate.ateapi.v1.ReportActorCrashedRequest + 18, // 248: substrate.ateapi.v1.Control.GetActor:output_type -> substrate.ateapi.v1.Actor + 18, // 249: substrate.ateapi.v1.Control.CreateActor:output_type -> substrate.ateapi.v1.Actor + 18, // 250: substrate.ateapi.v1.Control.UpdateActor:output_type -> substrate.ateapi.v1.Actor + 75, // 251: substrate.ateapi.v1.Control.SuspendActor:output_type -> substrate.ateapi.v1.SuspendActorResponse + 78, // 252: substrate.ateapi.v1.Control.ResumeActor:output_type -> substrate.ateapi.v1.ResumeActorResponse + 80, // 253: substrate.ateapi.v1.Control.RevertActor:output_type -> substrate.ateapi.v1.RevertActorResponse + 18, // 254: substrate.ateapi.v1.Control.DeleteActor:output_type -> substrate.ateapi.v1.Actor + 19, // 255: substrate.ateapi.v1.Control.GetActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy + 19, // 256: substrate.ateapi.v1.Control.CreateActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy + 19, // 257: substrate.ateapi.v1.Control.UpdateActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy + 19, // 258: substrate.ateapi.v1.Control.DeleteActorEgressPolicy:output_type -> substrate.ateapi.v1.EgressPolicy + 88, // 259: substrate.ateapi.v1.Control.MintActorJwt:output_type -> substrate.ateapi.v1.MintActorJwtResponse + 90, // 260: substrate.ateapi.v1.Control.MintActorCertificate:output_type -> substrate.ateapi.v1.MintActorCertificateResponse + 34, // 261: substrate.ateapi.v1.Control.CreateTag:output_type -> substrate.ateapi.v1.Tag + 34, // 262: substrate.ateapi.v1.Control.GetTag:output_type -> substrate.ateapi.v1.Tag + 92, // 263: substrate.ateapi.v1.Control.ListTags:output_type -> substrate.ateapi.v1.ListTagsResponse + 34, // 264: substrate.ateapi.v1.Control.UpdateTag:output_type -> substrate.ateapi.v1.Tag + 34, // 265: substrate.ateapi.v1.Control.DeleteTag:output_type -> substrate.ateapi.v1.Tag + 101, // 266: substrate.ateapi.v1.Control.ListWorkers:output_type -> substrate.ateapi.v1.ListWorkersResponse + 114, // 267: substrate.ateapi.v1.Control.GetWorker:output_type -> substrate.ateapi.v1.Worker + 114, // 268: substrate.ateapi.v1.Control.CreateWorker:output_type -> substrate.ateapi.v1.Worker + 114, // 269: substrate.ateapi.v1.Control.UpdateWorker:output_type -> substrate.ateapi.v1.Worker + 114, // 270: substrate.ateapi.v1.Control.DeleteWorker:output_type -> substrate.ateapi.v1.Worker + 107, // 271: substrate.ateapi.v1.Control.DrainWorker:output_type -> substrate.ateapi.v1.DrainWorkerResponse + 109, // 272: substrate.ateapi.v1.Control.MarkWorkerAvailable:output_type -> substrate.ateapi.v1.MarkWorkerAvailableResponse + 111, // 273: substrate.ateapi.v1.Control.MarkWorkerUnavailable:output_type -> substrate.ateapi.v1.MarkWorkerUnavailableResponse + 99, // 274: substrate.ateapi.v1.Control.ListWorkerActorAssignments:output_type -> substrate.ateapi.v1.ListWorkerActorAssignmentsResponse + 113, // 275: substrate.ateapi.v1.Control.ListActors:output_type -> substrate.ateapi.v1.ListActorsResponse + 35, // 276: substrate.ateapi.v1.Control.CreateAtespace:output_type -> substrate.ateapi.v1.Atespace + 35, // 277: substrate.ateapi.v1.Control.GetAtespace:output_type -> substrate.ateapi.v1.Atespace + 64, // 278: substrate.ateapi.v1.Control.ListAtespaces:output_type -> substrate.ateapi.v1.ListAtespacesResponse + 35, // 279: substrate.ateapi.v1.Control.DeleteAtespace:output_type -> substrate.ateapi.v1.Atespace + 37, // 280: substrate.ateapi.v1.Control.CreateActorTemplate:output_type -> substrate.ateapi.v1.ActorTemplate + 37, // 281: substrate.ateapi.v1.Control.GetActorTemplate:output_type -> substrate.ateapi.v1.ActorTemplate + 69, // 282: substrate.ateapi.v1.Control.ListActorTemplates:output_type -> substrate.ateapi.v1.ListActorTemplatesResponse + 37, // 283: substrate.ateapi.v1.Control.DeleteActorTemplate:output_type -> substrate.ateapi.v1.ActorTemplate + 143, // 284: substrate.ateapi.v1.Control.GetGlobalAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy + 143, // 285: substrate.ateapi.v1.Control.CreateGlobalAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy + 143, // 286: substrate.ateapi.v1.Control.UpdateGlobalAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy + 143, // 287: substrate.ateapi.v1.Control.GetAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy + 143, // 288: substrate.ateapi.v1.Control.CreateAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy + 143, // 289: substrate.ateapi.v1.Control.UpdateAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy + 143, // 290: substrate.ateapi.v1.Control.DeleteAtespaceAccessPolicy:output_type -> substrate.ateapi.v1.AccessPolicy + 124, // 291: substrate.ateapi.v1.Authentication.WhoAmI:output_type -> substrate.ateapi.v1.WhoAmIResponse + 126, // 292: substrate.ateapi.v1.WorkerService.AcquireWorkerEpoch:output_type -> substrate.ateapi.v1.AcquireWorkerEpochResponse + 128, // 293: substrate.ateapi.v1.WorkerService.RegisterWorker:output_type -> substrate.ateapi.v1.RegisterWorkerResponse + 130, // 294: substrate.ateapi.v1.WorkerService.SetWorkerSandboxRuntimes:output_type -> substrate.ateapi.v1.SetWorkerSandboxRuntimesResponse + 132, // 295: substrate.ateapi.v1.WorkerService.MintWorkerActorCertificate:output_type -> substrate.ateapi.v1.MintWorkerActorCertificateResponse + 134, // 296: substrate.ateapi.v1.WorkerService.MintWorkerActorJwt:output_type -> substrate.ateapi.v1.MintWorkerActorJwtResponse + 136, // 297: substrate.ateapi.v1.WorkerService.RequestActorSuspend:output_type -> substrate.ateapi.v1.RequestActorSuspendResponse + 140, // 298: substrate.ateapi.v1.WorkerService.ReportActorSnapshotState:output_type -> substrate.ateapi.v1.ReportActorSnapshotStateResponse + 138, // 299: substrate.ateapi.v1.WorkerService.ReportActorCrashed:output_type -> substrate.ateapi.v1.ReportActorCrashedResponse + 248, // [248:300] is the sub-list for method output_type + 196, // [196:248] is the sub-list for method input_type + 194, // [194:196] is the sub-list for extension type_name + 192, // [192:194] is the sub-list for extension extendee + 0, // [0:192] is the sub-list for field type_name } func init() { file_api_ateapi_v1_ateapi_proto_init() } @@ -11171,10 +11340,10 @@ func file_api_ateapi_v1_ateapi_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_ateapi_v1_ateapi_proto_rawDesc), len(file_api_ateapi_v1_ateapi_proto_rawDesc)), - NumEnums: 10, - NumMessages: 144, + NumEnums: 11, + NumMessages: 146, NumExtensions: 2, - NumServices: 2, + NumServices: 3, }, GoTypes: file_api_ateapi_v1_ateapi_proto_goTypes, DependencyIndexes: file_api_ateapi_v1_ateapi_proto_depIdxs, diff --git a/api/ateapi/v1/ateapi.proto b/api/ateapi/v1/ateapi.proto index a5e777a4a6..e1f48cab4d 100644 --- a/api/ateapi/v1/ateapi.proto +++ b/api/ateapi/v1/ateapi.proto @@ -2636,6 +2636,37 @@ message ActorAssignment { Resources resources = 4; } +// Authentication reports how ateapi authenticated the caller. It is separate +// from Control because it operates on no resource. +service Authentication { + // WhoAmI returns the principal ateapi authenticated the caller as. Any + // authenticated caller may call it. + rpc WhoAmI(WhoAmIRequest) returns (WhoAmIResponse); +} + +message WhoAmIRequest {} + +message WhoAmIResponse { + // The principal ID that authorization policies name: the subject of a + // bearer JWT, or the first URI SAN of an mTLS client certificate. + string id = 1; + + // How the caller authenticated. + PrincipalKind kind = 2; + + // The issuer of the caller's bearer JWT. Empty for mTLS callers. + string issuer = 3; +} + +// PrincipalKind is the authentication method that established a principal. +enum PrincipalKind { + PRINCIPAL_KIND_UNSPECIFIED = 0; + // A bearer JWT verified against a configured JWT provider. + PRINCIPAL_KIND_JWT = 1; + // An mTLS client certificate. + PRINCIPAL_KIND_MTLS = 2; +} + // WorkerService is how a Worker tells the control plane what it observes. It is // separate from Control because the two have different callers and different // authorization: Control is the client-facing API, while these RPCs are served diff --git a/api/ateapi/v1/ateapi_grpc.pb.go b/api/ateapi/v1/ateapi_grpc.pb.go index 4c2cadb5d9..3519d3ef32 100644 --- a/api/ateapi/v1/ateapi_grpc.pb.go +++ b/api/ateapi/v1/ateapi_grpc.pb.go @@ -1886,6 +1886,118 @@ var Control_ServiceDesc = grpc.ServiceDesc{ Metadata: "api/ateapi/v1/ateapi.proto", } +const ( + Authentication_WhoAmI_FullMethodName = "/substrate.ateapi.v1.Authentication/WhoAmI" +) + +// AuthenticationClient is the client API for Authentication service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Authentication reports how ateapi authenticated the caller. It is separate +// from Control because it operates on no resource. +type AuthenticationClient interface { + // WhoAmI returns the principal ateapi authenticated the caller as. Any + // authenticated caller may call it. + WhoAmI(ctx context.Context, in *WhoAmIRequest, opts ...grpc.CallOption) (*WhoAmIResponse, error) +} + +type authenticationClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthenticationClient(cc grpc.ClientConnInterface) AuthenticationClient { + return &authenticationClient{cc} +} + +func (c *authenticationClient) WhoAmI(ctx context.Context, in *WhoAmIRequest, opts ...grpc.CallOption) (*WhoAmIResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WhoAmIResponse) + err := c.cc.Invoke(ctx, Authentication_WhoAmI_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthenticationServer is the server API for Authentication service. +// All implementations must embed UnimplementedAuthenticationServer +// for forward compatibility. +// +// Authentication reports how ateapi authenticated the caller. It is separate +// from Control because it operates on no resource. +type AuthenticationServer interface { + // WhoAmI returns the principal ateapi authenticated the caller as. Any + // authenticated caller may call it. + WhoAmI(context.Context, *WhoAmIRequest) (*WhoAmIResponse, error) + mustEmbedUnimplementedAuthenticationServer() +} + +// UnimplementedAuthenticationServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAuthenticationServer struct{} + +func (UnimplementedAuthenticationServer) WhoAmI(context.Context, *WhoAmIRequest) (*WhoAmIResponse, error) { + return nil, status.Error(codes.Unimplemented, "method WhoAmI not implemented") +} +func (UnimplementedAuthenticationServer) mustEmbedUnimplementedAuthenticationServer() {} +func (UnimplementedAuthenticationServer) testEmbeddedByValue() {} + +// UnsafeAuthenticationServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthenticationServer will +// result in compilation errors. +type UnsafeAuthenticationServer interface { + mustEmbedUnimplementedAuthenticationServer() +} + +func RegisterAuthenticationServer(s grpc.ServiceRegistrar, srv AuthenticationServer) { + // If the following call panics, it indicates UnimplementedAuthenticationServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Authentication_ServiceDesc, srv) +} + +func _Authentication_WhoAmI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WhoAmIRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthenticationServer).WhoAmI(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Authentication_WhoAmI_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthenticationServer).WhoAmI(ctx, req.(*WhoAmIRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Authentication_ServiceDesc is the grpc.ServiceDesc for Authentication service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Authentication_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "substrate.ateapi.v1.Authentication", + HandlerType: (*AuthenticationServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "WhoAmI", + Handler: _Authentication_WhoAmI_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/ateapi/v1/ateapi.proto", +} + const ( WorkerService_AcquireWorkerEpoch_FullMethodName = "/substrate.ateapi.v1.WorkerService/AcquireWorkerEpoch" WorkerService_RegisterWorker_FullMethodName = "/substrate.ateapi.v1.WorkerService/RegisterWorker" diff --git a/cmd/ateapi/internal/apiauthn/whoami.go b/cmd/ateapi/internal/apiauthn/whoami.go new file mode 100644 index 0000000000..30a1d6963f --- /dev/null +++ b/cmd/ateapi/internal/apiauthn/whoami.go @@ -0,0 +1,44 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiauthn + +import ( + "context" + + "go.ate.dev/substrate/api/ateapi/v1" + "go.ate.dev/substrate/internal/apierror" + "go.ate.dev/substrate/internal/principal" +) + +// AuthenticationService serves the Authentication gRPC service. It reads the +// principal that the interceptor attached, so it must be served behind it. +type AuthenticationService struct { + ateapi.UnimplementedAuthenticationServer +} + +func (AuthenticationService) WhoAmI(ctx context.Context, _ *ateapi.WhoAmIRequest) (*ateapi.WhoAmIResponse, error) { + p, ok := principal.FromContext(ctx) + if !ok || p.ID == "" { + return nil, apierror.Unauthenticated("unauthenticated: missing principal in context") + } + kind := ateapi.PrincipalKind_PRINCIPAL_KIND_UNSPECIFIED + switch p.Kind { + case principal.KindJWT: + kind = ateapi.PrincipalKind_PRINCIPAL_KIND_JWT + case principal.KindMTLS: + kind = ateapi.PrincipalKind_PRINCIPAL_KIND_MTLS + } + return &ateapi.WhoAmIResponse{Id: p.ID, Kind: kind, Issuer: p.Issuer}, nil +} diff --git a/cmd/ateapi/internal/apiauthn/whoami_test.go b/cmd/ateapi/internal/apiauthn/whoami_test.go new file mode 100644 index 0000000000..e3ec8010e8 --- /dev/null +++ b/cmd/ateapi/internal/apiauthn/whoami_test.go @@ -0,0 +1,64 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiauthn + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "go.ate.dev/substrate/api/ateapi/v1" + "go.ate.dev/substrate/internal/apierror" + "go.ate.dev/substrate/internal/principal" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestWhoAmI(t *testing.T) { + tests := []struct { + name string + p principal.PrincipalInfo + want *ateapi.WhoAmIResponse + }{ + { + name: "jwt", + p: principal.PrincipalInfo{ID: "system:serviceaccount:ate-users:alice", Kind: principal.KindJWT, Issuer: "https://kubernetes.default.svc"}, + want: &ateapi.WhoAmIResponse{Id: "system:serviceaccount:ate-users:alice", Kind: ateapi.PrincipalKind_PRINCIPAL_KIND_JWT, Issuer: "https://kubernetes.default.svc"}, + }, + { + name: "mtls", + p: principal.PrincipalInfo{ID: "spiffe://cluster.local/ns/ate-system/sa/atelet", Kind: principal.KindMTLS}, + want: &ateapi.WhoAmIResponse{Id: "spiffe://cluster.local/ns/ate-system/sa/atelet", Kind: ateapi.PrincipalKind_PRINCIPAL_KIND_MTLS}, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := AuthenticationService{}.WhoAmI(principal.InjectContext(context.Background(), tc.p), &ateapi.WhoAmIRequest{}) + if err != nil { + t.Fatalf("WhoAmI: %v", err) + } + if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" { + t.Errorf("WhoAmI (-want +got):\n%s", diff) + } + }) + } +} + +func TestWhoAmIWithoutPrincipal(t *testing.T) { + _, err := AuthenticationService{}.WhoAmI(context.Background(), &ateapi.WhoAmIRequest{}) + if got := apierror.Code(err); got != codes.Unauthenticated { + t.Errorf("WhoAmI without principal: code %v, want %v", got, codes.Unauthenticated) + } +} diff --git a/cmd/ateapi/internal/authz/registry.go b/cmd/ateapi/internal/authz/registry.go index 741e716bbd..7168e99693 100644 --- a/cmd/ateapi/internal/authz/registry.go +++ b/cmd/ateapi/internal/authz/registry.go @@ -324,6 +324,9 @@ var defaultRPCPermissions = map[string]rpcRule{ return onTag(RelationCanDelete, r.GetTag(), field.NewPath("tag")) })), + // WhoAmI reveals only the caller's own principal. + ateapi.Authentication_WhoAmI_FullMethodName: inHandler("any authenticated caller"), + // WorkerService is served only to atelet, which each handler // authenticates by its mTLS SPIFFE ID. ateapi.WorkerService_AcquireWorkerEpoch_FullMethodName: inHandler("ateletauth: caller is atelet on the Worker's node"), diff --git a/cmd/ateapi/main.go b/cmd/ateapi/main.go index da71b00d13..8b8292cfb1 100644 --- a/cmd/ateapi/main.go +++ b/cmd/ateapi/main.go @@ -404,6 +404,7 @@ func main() { ) reflection.Register(mux) ateapi.RegisterControlServer(mux, controlSrv) + ateapi.RegisterAuthenticationServer(mux, apiauthn.AuthenticationService{}) ateapi.RegisterWorkerServiceServer(mux, workerservice.New(persistence, controlSrv, ateletSPIFFEID, actorIDCAPool, *actorJWTIssuer, actorIDJWTAuthorityPool)) // Every RPC needs the store, so a replica that cannot reach it leaves the diff --git a/cmd/kubectl-ate/README.md b/cmd/kubectl-ate/README.md index 21c27a77e4..3d3a79053d 100644 --- a/cmd/kubectl-ate/README.md +++ b/cmd/kubectl-ate/README.md @@ -26,6 +26,26 @@ By default, `kubectl-ate` will automatically read your `~/.kube/config`, discove If you prefer to route traffic directly (e.g., through a LoadBalancer or when running natively inside a cluster pod), simply provide the `--endpoint` flag to bypass the tunnel. +## Authentication +`kubectl-ate` authenticates to ate-api with the first of these it is given: + +1. `--token-file`: a bearer token file, re-read on every RPC, or `-` for stdin. +2. `--credential-plugin`, else `$ATE_CREDENTIAL_PLUGIN`: a [kubectl credential plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins). A path to an existing file runs with no arguments; any other value is split into a command and its arguments, honoring shell quoting. The plugin receives `--kubeconfig` and `--context` as `$ATE_KUBECONFIG` and `$ATE_KUBE_CONTEXT`. `kubectl-ate` caches its credential until the `expirationTimestamp`, and runs it again when ate-api rejects the credential. +3. A token minted for the shared `ate-client` ServiceAccount. + +Passing both `--token-file` and `--credential-plugin` is an error. + +```bash +# Authenticate as your own ServiceAccount (see docs/authentication.md) +export ATE_CREDENTIAL_PLUGIN="kubectl-ate credential --service-account=ate-users/alice" + +# Show the principal ate-api authenticated you as +kubectl ate whoami + +# Print a raw bearer token, e.g. for grpcurl +kubectl ate credential -o token +``` + ## Tracing The CLI supports on-demand tracing using the `--trace` flag. When enabled, the CLI will generate a trace ID and signal to the server that it wants the request to be traced. @@ -67,6 +87,7 @@ These flags can be appended to any command: | `--context` | | Name of the kubeconfig context to use | current context | | `--endpoint` | | Manual gRPC endpoint override (e.g., `localhost:8080`) | | | `--token-file` | | Path to a bearer token for ate-api authentication, or `-` for stdin | Kubernetes ServiceAccount token | +| `--credential-plugin` | | kubectl credential plugin that provides the ate-api credential: an executable's path, or a command line | `$ATE_CREDENTIAL_PLUGIN` | | `--output` | `-o` | Output format (`table`, `json`, `yaml`) | `table` | | `--trace` | | Enable on-demand tracing for the request | `false` | | `--verbose` | `-v` | Increase output verbosity; may be repeated or stacked (e.g., `-v`, `-vv`, `-vvvvv`) | `0` | diff --git a/cmd/kubectl-ate/internal/cmd/credential.go b/cmd/kubectl-ate/internal/cmd/credential.go new file mode 100644 index 0000000000..769e87ee30 --- /dev/null +++ b/cmd/kubectl-ate/internal/cmd/credential.go @@ -0,0 +1,213 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" + + "github.com/spf13/cobra" + "go.ate.dev/substrate/cmd/kubectl-ate/internal/credplugin" + "go.ate.dev/substrate/internal/ateclient" + "k8s.io/client-go/kubernetes" +) + +const ( + credentialLifetime = time.Hour + // credentialMinRemaining is how long a cached token must still be valid + // to be handed out again. + credentialMinRemaining = 5 * time.Minute +) + +var ( + credentialServiceAccount string + credentialAudience string + credentialOutput string + credentialRefresh bool + + // mintServiceAccountToken and newCredentialCache are replaced in tests. + mintServiceAccountToken = ateclient.MintServiceAccountToken + newCredentialCache = credplugin.DefaultCache +) + +var credentialCmd = &cobra.Command{ + Use: "credential", + Short: "Print a credential for ate-api, as a kubeconfig exec credential plugin", + Long: `credential mints a Kubernetes ServiceAccount token for ate-api and prints it +as an ExecCredential, so it can be passed to --credential-plugin (or named in a +kubeconfig exec stanza). Tokens are cached under ~/.ate/cache until shortly +before they expire. + +Without --kubeconfig it uses $` + kubeconfigEnv + `, and without --context +$` + kubeContextEnv + `; kubectl ate sets both from its own flags when it runs the +plugin. The audience is --audience, else spec.cluster.config.audience from +KUBERNETES_EXEC_INFO, else ate-api's in-cluster Service name.`, + Example: ` # Act as a per-user ServiceAccount you are allowed to create tokens for + export ` + credentialPluginEnv + `="kubectl-ate credential --service-account=ate-users/alice" + kubectl ate whoami + + # Print the shared ate-client ServiceAccount's credential + kubectl ate credential --context=my-cluster + + # A raw token for grpcurl + grpcurl -H "authorization: Bearer $(kubectl ate credential -o token)" ...`, + Args: noArgs(""), + RunE: func(cmd *cobra.Command, _ []string) error { + info, err := credentialInfo() + if err != nil { + return err + } + token, expiry, err := serviceAccountCredential(cmd, info.Audience) + if err != nil { + return err + } + return writeCredential(cmd.OutOrStdout(), info, token, expiry) + }, +} + +var credentialWrapCmd = &cobra.Command{ + Use: "wrap -- COMMAND [ARG...]", + Short: "Run a command that prints a JWT and print it as an ExecCredential", + Long: `wrap runs COMMAND, reads a JWT from its standard output, and prints it as an +ExecCredential that expires at the JWT's exp claim. The token is not cached: +COMMAND is run every time the client needs a credential. + +Only wrap commands whose issuer limits who can obtain a token for ate-api's +configured audience; see docs/authentication.md.`, + Example: ` kubectl ate credential wrap -- my-corp-sso print-token --audience=ate`, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + info, err := credentialInfo() + if err != nil { + return err + } + c := exec.CommandContext(cmd.Context(), args[0], args[1:]...) + c.Stderr = cmd.ErrOrStderr() + if info.Interactive { + c.Stdin = os.Stdin + } + out, err := c.Output() + if err != nil { + return fmt.Errorf("run %s: %w", args[0], err) + } + token := strings.TrimSpace(string(out)) + if token == "" { + return fmt.Errorf("%s printed no token", args[0]) + } + expiry, err := credplugin.JWTExpiry(token) + if err != nil { + return fmt.Errorf("%s did not print a JWT: %w", args[0], err) + } + return writeCredential(cmd.OutOrStdout(), info, token, expiry) + }, +} + +func credentialInfo() (credplugin.Info, error) { + if credentialOutput != "json" && credentialOutput != "token" { + return credplugin.Info{}, usageErrorf("invalid output format %q. Must be one of: json, token", credentialOutput) + } + return credplugin.ParseInfo(os.Getenv(credplugin.InfoEnv)) +} + +func writeCredential(out io.Writer, info credplugin.Info, token string, expiry time.Time) error { + if credentialOutput == "token" { + _, err := fmt.Fprintln(out, token) + return err + } + return credplugin.WriteCredential(out, info.APIVersion, token, expiry) +} + +// serviceAccountCredential returns a cached or newly minted token for the +// --service-account ServiceAccount. +func serviceAccountCredential(cmd *cobra.Command, execAudience string) (string, time.Time, error) { + errOut := cmd.ErrOrStderr() + namespace, name := ateclient.DefaultServiceAccount() + if credentialServiceAccount != "" { + var ok bool + namespace, name, ok = strings.Cut(credentialServiceAccount, "/") + if !ok || namespace == "" || name == "" { + return "", time.Time{}, usageErrorf("--service-account must be NAMESPACE/NAME, got %q", credentialServiceAccount) + } + } else { + warnSharedIdentityCredential(errOut) + } + audience := credentialAudience + if audience == "" { + audience = execAudience + } + if audience == "" { + audience = ateclient.DefaultAudience() + } + + kubeconfigPath, kubeContext := kubeconfig, k8sContext + if kubeconfigPath == "" { + kubeconfigPath = os.Getenv(kubeconfigEnv) + } + if kubeContext == "" { + kubeContext = os.Getenv(kubeContextEnv) + } + config, err := loadKubeConfig(errOut, kubeconfigPath, kubeContext) + if err != nil { + return "", time.Time{}, fmt.Errorf("while reading kubeconfig: %w", err) + } + // Keyed by the kubeconfig too: whoever can mint here is not necessarily + // whoever can mint through another kubeconfig context on the same host. + key := []string{config.Host, kubeconfigPath, kubeContext, namespace, name, audience} + cache, err := newCredentialCache() + if err != nil { + vlogf(errOut, vBasic, "Not caching the credential: %v", err) + } else if !credentialRefresh && os.Getenv(ateclient.RefreshCredentialEnv) == "" { + if token, expiry, ok := cache.Get(key, credentialMinRemaining); ok { + vlogf(errOut, vFlow, "Using the cached token for %s/%s, valid until %s", namespace, name, expiry.Format(time.RFC3339)) + return token, expiry, nil + } + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return "", time.Time{}, fmt.Errorf("while creating Kubernetes client: %w", err) + } + token, expiry, err := mintServiceAccountToken(cmd.Context(), clientset, namespace, name, audience, credentialLifetime) + if err != nil { + return "", time.Time{}, err + } + if cache != nil { + if err := cache.Put(key, token, expiry); err != nil { + vlogf(errOut, vBasic, "Failed to cache the credential: %v", err) + } + } + return token, expiry, nil +} + +// warnSharedIdentityCredential warns that the token is for the shared +// fallback ServiceAccount; see warnSharedIdentity. +func warnSharedIdentityCredential(errOut io.Writer) { + fmt.Fprintf(errOut, "Warning: using a token for the shared identity %s rather than one for you. Pass --service-account to use a ServiceAccount of your own.\n", + ateclient.FallbackPrincipal()) +} + +func init() { + credentialCmd.Flags().StringVar(&credentialServiceAccount, "service-account", "", "NAMESPACE/NAME of the ServiceAccount to mint a token for (defaults to the shared ate-client ServiceAccount)") + credentialCmd.Flags().StringVar(&credentialAudience, "audience", "", "audience of the minted token (overrides KUBERNETES_EXEC_INFO)") + credentialCmd.Flags().BoolVar(&credentialRefresh, "refresh", false, "mint a new token and replace the cached one") + // Shadows the root --output, whose formats do not apply here. + credentialCmd.PersistentFlags().StringVarP(&credentialOutput, "output", "o", "json", "output format: json (an ExecCredential) or token (the raw bearer token)") + credentialCmd.AddCommand(credentialWrapCmd) + rootCmd.AddCommand(credentialCmd) +} diff --git a/cmd/kubectl-ate/internal/cmd/credential_test.go b/cmd/kubectl-ate/internal/cmd/credential_test.go new file mode 100644 index 0000000000..735bf8ecf6 --- /dev/null +++ b/cmd/kubectl-ate/internal/cmd/credential_test.go @@ -0,0 +1,208 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "go.ate.dev/substrate/cmd/kubectl-ate/internal/credplugin" + "go.ate.dev/substrate/internal/ateclient" + "k8s.io/client-go/kubernetes" + clientauthv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" + clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" +) + +const testKubeconfig = `apiVersion: v1 +kind: Config +current-context: kind-a +clusters: +- name: a + cluster: {server: "https://a.invalid"} +- name: b + cluster: {server: "https://b.invalid"} +users: +- name: u + user: {token: kube-token} +contexts: +- name: kind-a + context: {cluster: a, user: u} +- name: kind-b + context: {cluster: b, user: u} +` + +// setupKubeconfigEnv points KUBECONFIG and HOME at fresh files, clears the +// credential plugin variables, and returns the kubeconfig path. +func setupKubeconfigEnv(t *testing.T) string { + t.Helper() + dir := t.TempDir() + kubeconfigPath := filepath.Join(dir, "kubeconfig") + if err := os.WriteFile(kubeconfigPath, []byte(testKubeconfig), 0o600); err != nil { + t.Fatal(err) + } + t.Setenv("KUBECONFIG", kubeconfigPath) + t.Setenv("HOME", filepath.Join(dir, "home")) + for _, env := range []string{credentialPluginEnv, kubeconfigEnv, kubeContextEnv} { + t.Setenv(env, "") + } + return kubeconfigPath +} + +type mintCall struct{ host, namespace, name, audience string } + +// fakeMinter replaces the ServiceAccount token minter and the credential +// cache for the test, and records each mint. +func fakeMinter(t *testing.T) *[]mintCall { + t.Helper() + var calls []mintCall + expiry := time.Now().Add(time.Hour).Truncate(time.Second) + origMint, origCache := mintServiceAccountToken, newCredentialCache + mintServiceAccountToken = func(_ context.Context, clientset kubernetes.Interface, namespace, name, audience string, _ time.Duration) (string, time.Time, error) { + calls = append(calls, mintCall{clientset.CoreV1().RESTClient().Get().URL().Host, namespace, name, audience}) + return fmt.Sprintf("minted-%d", len(calls)), expiry, nil + } + cacheDir := filepath.Join(t.TempDir(), "cache") + newCredentialCache = func() (*credplugin.Cache, error) { + return &credplugin.Cache{Dir: cacheDir, Now: time.Now}, nil + } + t.Cleanup(func() { mintServiceAccountToken, newCredentialCache = origMint, origCache }) + return &calls +} + +func TestCredential(t *testing.T) { + setupKubeconfigEnv(t) + calls := fakeMinter(t) + t.Setenv(credplugin.InfoEnv, `{"apiVersion":"client.authentication.k8s.io/v1","kind":"ExecCredential","spec":{"interactive":false,"cluster":{"server":"https://api.ate-system.svc","config":{"audience":"exec-audience"}}}}`) + + out, errOut, err := runRootCLI(t, "credential") + if err != nil { + t.Fatalf("credential: %v", err) + } + var cred clientauthv1.ExecCredential + if err := json.Unmarshal([]byte(out), &cred); err != nil { + t.Fatalf("output is not an ExecCredential: %v\n%s", err, out) + } + if cred.APIVersion != credplugin.APIVersionV1 || cred.Status.Token != "minted-1" || cred.Status.ExpirationTimestamp == nil { + t.Errorf("ExecCredential = %+v %+v", cred.TypeMeta, cred.Status) + } + if !strings.Contains(errOut, "shared identity") { + t.Errorf("stderr = %q, want the shared identity warning", errOut) + } + + if _, _, err := runRootCLI(t, "credential"); err != nil { + t.Fatalf("second credential: %v", err) + } + if out, _, err = runRootCLI(t, "credential", "--service-account=ate-users/alice", "--audience=flag-audience", "-o", "token"); err != nil { + t.Fatalf("credential --service-account: %v", err) + } + if out != "minted-2\n" { + t.Errorf("-o token output = %q, want minted-2", out) + } + + want := []mintCall{ + {"a.invalid", "ate-system", "ate-client", "exec-audience"}, + {"a.invalid", "ate-users", "alice", "flag-audience"}, + } + if fmt.Sprint(*calls) != fmt.Sprint(want) { + t.Errorf("mints = %v, want %v (the second default run should hit the cache)", *calls, want) + } + + if _, _, err := runRootCLI(t, "credential", "--refresh"); err != nil { + t.Fatal(err) + } + if out, _, err = runRootCLI(t, "credential", "-o", "token"); err != nil || out != "minted-3\n" || len(*calls) != 3 { + t.Errorf("after --refresh: token %q (err %v) after %d mints, want the cached minted-3 after 3", out, err, len(*calls)) + } + + t.Setenv(ateclient.RefreshCredentialEnv, "1") + if out, _, err = runRootCLI(t, "credential", "-o", "token"); err != nil || out != "minted-4\n" { + t.Errorf("with %s set: token %q (err %v), want a new minted-4", ateclient.RefreshCredentialEnv, out, err) + } + t.Setenv(ateclient.RefreshCredentialEnv, "") + if out, _, err = runRootCLI(t, "credential", "-o", "token"); err != nil || out != "minted-4\n" || len(*calls) != 4 { + t.Errorf("after %s: token %q (err %v) after %d mints, want the cached minted-4 after 4", ateclient.RefreshCredentialEnv, out, err, len(*calls)) + } +} + +func TestCredentialKubeEnv(t *testing.T) { + kubeconfigPath := setupKubeconfigEnv(t) + calls := fakeMinter(t) + t.Setenv("KUBECONFIG", filepath.Join(t.TempDir(), "missing")) + t.Setenv(kubeconfigEnv, kubeconfigPath) + + if _, _, err := runRootCLI(t, "credential"); err != nil { + t.Fatalf("credential with %s: %v", kubeconfigEnv, err) + } + t.Setenv(kubeContextEnv, "kind-b") + if _, _, err := runRootCLI(t, "credential"); err != nil { + t.Fatalf("credential with %s: %v", kubeContextEnv, err) + } + if _, _, err := runRootCLI(t, "credential", "--context=kind-a", "--refresh"); err != nil { + t.Fatalf("credential --context: %v", err) + } + var hosts []string + for _, c := range *calls { + hosts = append(hosts, c.host) + } + if want := []string{"a.invalid", "b.invalid", "a.invalid"}; fmt.Sprint(hosts) != fmt.Sprint(want) { + t.Errorf("minted through %v, want %v", hosts, want) + } +} + +func TestCredentialErrors(t *testing.T) { + setupKubeconfigEnv(t) + fakeMinter(t) + for _, args := range [][]string{ + {"credential", "--service-account=alice"}, + {"credential", "-o", "yaml"}, + {"credential", "extra"}, + } { + if _, _, err := runRootCLI(t, args...); err == nil { + t.Errorf("%q succeeded, want error", args) + } + } +} + +func TestCredentialWrap(t *testing.T) { + setupKubeconfigEnv(t) + t.Setenv(credplugin.InfoEnv, `{"apiVersion":"client.authentication.k8s.io/v1beta1","kind":"ExecCredential","spec":{}}`) + jwt := "e30." + base64.RawURLEncoding.EncodeToString([]byte(`{"iss":"https://idp.example","exp":1760616000}`)) + ".sig" + + out, _, err := runRootCLI(t, "credential", "wrap", "--", "sh", "-c", "echo "+jwt) + if err != nil { + t.Fatalf("credential wrap: %v", err) + } + var cred clientauthv1beta1.ExecCredential + if err := json.Unmarshal([]byte(out), &cred); err != nil { + t.Fatalf("output is not an ExecCredential: %v\n%s", err, out) + } + if cred.APIVersion != credplugin.APIVersionV1beta1 || cred.Status.Token != jwt || cred.Status.ExpirationTimestamp.Unix() != 1760616000 { + t.Errorf("ExecCredential = %+v %+v", cred.TypeMeta, cred.Status) + } + + if _, _, err := runRootCLI(t, "credential", "wrap", "--", "sh", "-c", "echo opaque"); err == nil { + t.Error("wrapping a command that prints a non-JWT succeeded, want error") + } + if _, _, err := runRootCLI(t, "credential", "wrap", "--", "sh", "-c", "exit 3"); err == nil { + t.Error("wrapping a failing command succeeded, want error") + } +} diff --git a/cmd/kubectl-ate/internal/cmd/credentialplugin.go b/cmd/kubectl-ate/internal/cmd/credentialplugin.go new file mode 100644 index 0000000000..7b3a9f1ceb --- /dev/null +++ b/cmd/kubectl-ate/internal/cmd/credentialplugin.go @@ -0,0 +1,148 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "unicode" + + "go.ate.dev/substrate/cmd/kubectl-ate/internal/credplugin" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +const ( + // credentialPluginEnv names the credential plugin when --credential-plugin + // is not given. + credentialPluginEnv = "ATE_CREDENTIAL_PLUGIN" + // kubeconfigEnv and kubeContextEnv pass --kubeconfig and --context to a + // credential plugin; `kubectl ate credential` uses each when it is not + // given that flag itself. + kubeconfigEnv = "ATE_KUBECONFIG" + kubeContextEnv = "ATE_KUBE_CONTEXT" +) + +// credentialPluginExec returns the exec stanza for --credential-plugin, else +// for $ATE_CREDENTIAL_PLUGIN. It returns nil when --token-file is given or +// neither names a plugin. +func credentialPluginExec() (*clientcmdapi.ExecConfig, error) { + if tokenFile != "" { + if credentialPlugin != "" { + return nil, usageErrorf("--token-file cannot be combined with --credential-plugin") + } + return nil, nil + } + value := credentialPlugin + if value == "" { + value = os.Getenv(credentialPluginEnv) + } + if value == "" { + return nil, nil + } + + // An existing file runs with no arguments, so its path may hold spaces. + command, args := value, []string(nil) + if _, err := os.Stat(value); err != nil { + words, err := splitCommandLine(value) + if err != nil { + return nil, usageErrorf("credential plugin: %w", err) + } + if len(words) == 0 { + return nil, usageErrorf("credential plugin is blank") + } + command, args = words[0], words[1:] + } + // client-go cleans the command, which would turn ./plugin into a name + // looked up on PATH. + if strings.ContainsRune(command, filepath.Separator) { + abs, err := filepath.Abs(command) + if err != nil { + return nil, fmt.Errorf("credential plugin %s: %w", command, err) + } + command = abs + } + return &clientcmdapi.ExecConfig{ + APIVersion: credplugin.APIVersionV1, + Command: command, + Args: args, + // Set even when empty, so that the plugin does not inherit values + // from the user's environment that kubectl ate itself ignores. + Env: []clientcmdapi.ExecEnvVar{ + {Name: kubeconfigEnv, Value: kubeconfig}, + {Name: kubeContextEnv, Value: k8sContext}, + }, + InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, + ProvideClusterInfo: true, + }, nil +} + +// splitCommandLine splits s into words with shell quoting and backslash +// escapes, expanding nothing. +func splitCommandLine(s string) ([]string, error) { + var words []string + var word strings.Builder + inWord := false + var quote rune + rs := []rune(s) + for i := 0; i < len(rs); i++ { + r := rs[i] + switch { + case quote == '\'': + if r == '\'' { + quote = 0 + } else { + word.WriteRune(r) + } + case quote == '"': + switch { + case r == '"': + quote = 0 + case r == '\\' && i+1 < len(rs) && strings.ContainsRune("$`\"\\\n", rs[i+1]): + i++ + word.WriteRune(rs[i]) + default: + word.WriteRune(r) + } + case r == '\\': + if i+1 == len(rs) { + return nil, errors.New("trailing backslash") + } + i++ + word.WriteRune(rs[i]) + inWord = true + case r == '\'' || r == '"': + quote, inWord = r, true + case unicode.IsSpace(r): + if inWord { + words = append(words, word.String()) + word.Reset() + inWord = false + } + default: + word.WriteRune(r) + inWord = true + } + } + if quote != 0 { + return nil, fmt.Errorf("unterminated %c quote", quote) + } + if inWord { + words = append(words, word.String()) + } + return words, nil +} diff --git a/cmd/kubectl-ate/internal/cmd/credentialplugin_test.go b/cmd/kubectl-ate/internal/cmd/credentialplugin_test.go new file mode 100644 index 0000000000..ee02a568ee --- /dev/null +++ b/cmd/kubectl-ate/internal/cmd/credentialplugin_test.go @@ -0,0 +1,137 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +func setString(t *testing.T, p *string, v string) { + t.Helper() + old := *p + *p = v + t.Cleanup(func() { *p = old }) +} + +func TestCredentialPluginExec(t *testing.T) { + spaced := filepath.Join(t.TempDir(), "my plugin") + if err := os.WriteFile(spaced, nil, 0o700); err != nil { + t.Fatal(err) + } + t.Chdir(t.TempDir()) + if err := os.WriteFile("rel-plugin", nil, 0o700); err != nil { + t.Fatal(err) + } + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + relPlugin := filepath.Join(wd, "rel-plugin") + execConfigEnv := func(command string, args []string, kubeconfig, kubeContext string) *clientcmdapi.ExecConfig { + return &clientcmdapi.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1", + Command: command, + Args: args, + Env: []clientcmdapi.ExecEnvVar{ + {Name: kubeconfigEnv, Value: kubeconfig}, + {Name: kubeContextEnv, Value: kubeContext}, + }, + InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, + ProvideClusterInfo: true, + } + } + execConfig := func(command string, args []string) *clientcmdapi.ExecConfig { + return execConfigEnv(command, args, "", "") + } + for _, tc := range []struct { + name string + flag, env, tokenFile, kubeconfig, kubeContext string + want *clientcmdapi.ExecConfig + wantErr bool + }{ + {name: "unset"}, + {name: "token file over env", env: "plugin", tokenFile: "/token"}, + {name: "token file and flag", flag: "plugin", tokenFile: "/token", wantErr: true}, + { + name: "command line", + flag: "kubectl-ate credential --service-account='ate-users/alice'", + want: execConfig("kubectl-ate", []string{"credential", "--service-account=ate-users/alice"}), + }, + {name: "env", env: "plugin --x", want: execConfig("plugin", []string{"--x"})}, + {name: "flag over env", flag: "a", env: "b", want: execConfig("a", nil)}, + {name: "existing file", flag: spaced, want: execConfig(spaced, nil)}, + {name: "relative file", flag: "./rel-plugin", want: execConfig(relPlugin, nil)}, + {name: "relative command line", flag: "./rel-plugin --x", want: execConfig(relPlugin, []string{"--x"})}, + {name: "relative missing file", flag: "bin/plugin", want: execConfig(filepath.Join(wd, "bin", "plugin"), nil)}, + { + name: "kube flags", flag: spaced, kubeconfig: "/kubeconfig", kubeContext: "kind-a", + want: execConfigEnv(spaced, nil, "/kubeconfig", "kind-a"), + }, + {name: "unterminated quote", flag: `plugin "x`, wantErr: true}, + {name: "blank", flag: " ", wantErr: true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Setenv(credentialPluginEnv, tc.env) + setString(t, &credentialPlugin, tc.flag) + setString(t, &tokenFile, tc.tokenFile) + setString(t, &kubeconfig, tc.kubeconfig) + setString(t, &k8sContext, tc.kubeContext) + + got, err := credentialPluginExec() + if (err != nil) != tc.wantErr { + t.Fatalf("credentialPluginExec() error = %v, want error %t", err, tc.wantErr) + } + if diff := cmp.Diff(tc.want, got, cmpopts.EquateEmpty()); diff != "" { + t.Errorf("credentialPluginExec() (-want +got):\n%s", diff) + } + }) + } +} + +func TestSplitCommandLine(t *testing.T) { + for _, tc := range []struct { + in string + want []string + }{ + {"", nil}, + {" a b\tc ", []string{"a", "b", "c"}}, + {`'a b' "c d"`, []string{"a b", "c d"}}, + {`--flag="a b"`, []string{"--flag=a b"}}, + {`a\ b`, []string{"a b"}}, + {`"a\"b" "C:\path"`, []string{`a"b`, `C:\path`}}, + {`'a\b' 'it''s' ''`, []string{`a\b`, "its", ""}}, + {"x#y $HOME", []string{"x#y", "$HOME"}}, + } { + got, err := splitCommandLine(tc.in) + if err != nil { + t.Errorf("splitCommandLine(%q): %v", tc.in, err) + continue + } + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("splitCommandLine(%q) (-want +got):\n%s", tc.in, diff) + } + } + for _, in := range []string{`"a`, `'a`, `a\`} { + if got, err := splitCommandLine(in); err == nil { + t.Errorf("splitCommandLine(%q) = %q, want an error", in, got) + } + } +} diff --git a/cmd/kubectl-ate/internal/cmd/root.go b/cmd/kubectl-ate/internal/cmd/root.go index 14e8db801c..831002aaa0 100644 --- a/cmd/kubectl-ate/internal/cmd/root.go +++ b/cmd/kubectl-ate/internal/cmd/root.go @@ -28,14 +28,15 @@ import ( ) var ( - kubeconfig string - k8sContext string - endpoint string - tokenFile string - outputFmt string - traceEnabled bool - verbosity int - usePreview bool + kubeconfig string + k8sContext string + endpoint string + tokenFile string + credentialPlugin string + outputFmt string + traceEnabled bool + verbosity int + usePreview bool ) var rootCmd = &cobra.Command{ @@ -107,6 +108,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&k8sContext, "context", "", "kubeconfig context to use") rootCmd.PersistentFlags().StringVar(&endpoint, "endpoint", "", "manual override for the gRPC target (e.g., localhost:8080); if omitted, automatically port-forwards") rootCmd.PersistentFlags().StringVar(&tokenFile, "token-file", "", "path to a bearer token for ate-api authentication, or - for stdin (defaults to a Kubernetes ServiceAccount token)") + rootCmd.PersistentFlags().StringVar(&credentialPlugin, "credential-plugin", "", "kubectl credential plugin that provides the ate-api credential: an executable's path, or a command line (defaults to $"+credentialPluginEnv+")") rootCmd.PersistentFlags().StringVarP(&outputFmt, "output", "o", "table", "output format: table, json, or yaml") rootCmd.PersistentFlags().BoolVar(&traceEnabled, "trace", false, "enable tracing for the request") rootCmd.PersistentFlags().CountVarP(&verbosity, "verbose", "v", "output verbosity; may be repeated (e.g. -v, -vv, -vvvvv)") diff --git a/cmd/kubectl-ate/internal/cmd/verbose.go b/cmd/kubectl-ate/internal/cmd/verbose.go index c6ef24a6e9..57741d60cc 100644 --- a/cmd/kubectl-ate/internal/cmd/verbose.go +++ b/cmd/kubectl-ate/internal/cmd/verbose.go @@ -29,6 +29,7 @@ import ( "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) // Verbosity levels for -v / --verbose. @@ -75,11 +76,19 @@ func loadKubeConfig(errOut io.Writer, kubeconfigPath, contextName string) (*rest func newAPIClient(cmd *cobra.Command) (*ateclient.Client, error) { ctx := cmd.Context() errOut := cmd.ErrOrStderr() - warnSharedIdentity(errOut, tokenFile) + plugin, err := credentialPluginExec() + if err != nil { + return nil, err + } + warnSharedIdentity(errOut, tokenFile, plugin) if verbosity >= vFlow { - vlogf(errOut, vFlow, "Connecting to ate-api-server (endpoint=%q, kubeconfig=%q, context=%q, token-file=%q, trace=%t)", - endpoint, kubeconfig, k8sContext, tokenFile, traceEnabled) + pluginCommand := "" + if plugin != nil { + pluginCommand = plugin.Command + } + vlogf(errOut, vFlow, "Connecting to ate-api-server (endpoint=%q, kubeconfig=%q, context=%q, token-file=%q, credential-plugin=%q, trace=%t)", + endpoint, kubeconfig, k8sContext, tokenFile, pluginCommand, traceEnabled) } else if endpoint != "" { vlogf(errOut, vBasic, "Connecting to ate-api-server at %s", endpoint) } else { @@ -87,10 +96,15 @@ func newAPIClient(cmd *cobra.Command) (*ateclient.Client, error) { } start := time.Now() - cli, err := ateclient.NewClient( - ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled, - grpc.WithChainUnaryInterceptor(newLoggingUnaryInterceptor(errOut)), - ) + cli, err := ateclient.Dial(ctx, ateclient.DialOptions{ + Kubeconfig: kubeconfig, + KubeContext: k8sContext, + Endpoint: endpoint, + TokenFile: tokenFile, + CredentialPlugin: plugin, + Trace: traceEnabled, + GRPCOptions: []grpc.DialOption{grpc.WithChainUnaryInterceptor(newLoggingUnaryInterceptor(errOut))}, + }) elapsed := time.Since(start).Round(time.Millisecond) if err != nil { vlogf(errOut, vBasic, "Connection to ate-api-server failed after %s: %v", elapsed, err) @@ -109,12 +123,13 @@ func newAPIClient(cmd *cobra.Command) (*ateclient.Client, error) { // warnSharedIdentity warns on errOut when the command will act as the shared // fallback ServiceAccount rather than as the user. Everyone relying on the // fallback is the same principal, holding whatever that principal is granted: -// global owner wherever the install names it a bootstrap owner. -func warnSharedIdentity(errOut io.Writer, tokenFile string) { - if tokenFile != "" { +// global owner wherever the install names it a bootstrap owner. A plugin's +// identity is unknown here; `kubectl ate credential` warns itself. +func warnSharedIdentity(errOut io.Writer, tokenFile string, plugin *clientcmdapi.ExecConfig) { + if tokenFile != "" || plugin != nil { return } - fmt.Fprintf(errOut, "Warning: no --token-file given; acting as the shared identity %s rather than as yourself. Pass --token-file to act as yourself.\n", + fmt.Fprintf(errOut, "Warning: no --credential-plugin or --token-file given; acting as the shared identity %s rather than as yourself. Pass either to act as yourself.\n", ateclient.FallbackPrincipal()) } diff --git a/cmd/kubectl-ate/internal/cmd/verbose_test.go b/cmd/kubectl-ate/internal/cmd/verbose_test.go index 66dc39863b..6fbf9e1ad7 100644 --- a/cmd/kubectl-ate/internal/cmd/verbose_test.go +++ b/cmd/kubectl-ate/internal/cmd/verbose_test.go @@ -31,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) @@ -289,11 +290,11 @@ func TestTopWorkersRunner_VerboseSurfacesMetricsIssues(t *testing.T) { } func TestWarnSharedIdentity(t *testing.T) { - t.Run("warns without a token file", func(t *testing.T) { + t.Run("warns without a credential", func(t *testing.T) { var buf bytes.Buffer - warnSharedIdentity(&buf, "") + warnSharedIdentity(&buf, "", nil) got := buf.String() - for _, want := range []string{"Warning:", "ate-client", "shared identity", "--token-file"} { + for _, want := range []string{"Warning:", "ate-client", "shared identity", "--credential-plugin", "--token-file"} { if !strings.Contains(got, want) { t.Errorf("warning %q does not mention %q", got, want) } @@ -301,7 +302,14 @@ func TestWarnSharedIdentity(t *testing.T) { }) t.Run("silent with a token file", func(t *testing.T) { var buf bytes.Buffer - warnSharedIdentity(&buf, "/path/to/token") + warnSharedIdentity(&buf, "/path/to/token", nil) + if got := buf.String(); got != "" { + t.Errorf("warning = %q, want none", got) + } + }) + t.Run("silent with a credential plugin", func(t *testing.T) { + var buf bytes.Buffer + warnSharedIdentity(&buf, "", &clientcmdapi.ExecConfig{Command: "plugin"}) if got := buf.String(); got != "" { t.Errorf("warning = %q, want none", got) } diff --git a/cmd/kubectl-ate/internal/cmd/whoami.go b/cmd/kubectl-ate/internal/cmd/whoami.go new file mode 100644 index 0000000000..426e8c5eb4 --- /dev/null +++ b/cmd/kubectl-ate/internal/cmd/whoami.go @@ -0,0 +1,43 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/spf13/cobra" + "go.ate.dev/substrate/api/ateapi/v1" + "go.ate.dev/substrate/cmd/kubectl-ate/internal/printer" +) + +var whoamiCmd = &cobra.Command{ + Use: "whoami", + Short: "Show the principal ate-api authenticates you as", + Args: noArgs(""), + RunE: func(cmd *cobra.Command, _ []string) error { + cli, err := newAPIClient(cmd) + if err != nil { + return err + } + defer cli.Close() + resp, err := cli.Authentication().WhoAmI(cmd.Context(), &ateapi.WhoAmIRequest{}) + if err != nil { + return err + } + return printer.PrintWhoAmITo(cmd.OutOrStdout(), resp, outputFmt) + }, +} + +func init() { + rootCmd.AddCommand(whoamiCmd) +} diff --git a/cmd/kubectl-ate/internal/credplugin/cache.go b/cmd/kubectl-ate/internal/credplugin/cache.go new file mode 100644 index 0000000000..c86726feec --- /dev/null +++ b/cmd/kubectl-ate/internal/credplugin/cache.go @@ -0,0 +1,90 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package credplugin + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + "strings" + "time" +) + +// Cache keeps tokens on disk so that consecutive CLI invocations, each a new +// plugin process, reuse one token instead of minting one per command. +type Cache struct { + Dir string + Now func() time.Time +} + +// DefaultCache is the cache under ~/.ate/cache. +func DefaultCache() (*Cache, error) { + home, err := os.UserHomeDir() + if err != nil { + return nil, err + } + return &Cache{Dir: filepath.Join(home, ".ate", "cache", "credentials"), Now: time.Now}, nil +} + +type cacheEntry struct { + Token string `json:"token"` + Expiry time.Time `json:"expiry"` +} + +// path names the file for the token identified by keyParts. +func (c *Cache) path(keyParts []string) string { + sum := sha256.Sum256([]byte(strings.Join(keyParts, "\x00"))) + return filepath.Join(c.Dir, hex.EncodeToString(sum[:])+".json") +} + +// Get returns the cached token for keyParts if it is valid for at least +// minRemaining. +func (c *Cache) Get(keyParts []string, minRemaining time.Duration) (string, time.Time, bool) { + b, err := os.ReadFile(c.path(keyParts)) + if err != nil { + return "", time.Time{}, false + } + var e cacheEntry + if err := json.Unmarshal(b, &e); err != nil || e.Token == "" || c.Now().Add(minRemaining).After(e.Expiry) { + return "", time.Time{}, false + } + return e.Token, e.Expiry, true +} + +// Put caches token for keyParts. The file is mode 0600 in a 0700 directory. +func (c *Cache) Put(keyParts []string, token string, expiry time.Time) error { + b, err := json.Marshal(cacheEntry{Token: token, Expiry: expiry}) + if err != nil { + return err + } + if err := os.MkdirAll(c.Dir, 0o700); err != nil { + return err + } + tmp, err := os.CreateTemp(c.Dir, ".token-*") + if err != nil { + return err + } + defer os.Remove(tmp.Name()) + if _, err := tmp.Write(b); err != nil { + tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + return os.Rename(tmp.Name(), c.path(keyParts)) +} diff --git a/cmd/kubectl-ate/internal/credplugin/credplugin_test.go b/cmd/kubectl-ate/internal/credplugin/credplugin_test.go new file mode 100644 index 0000000000..cc0e85340e --- /dev/null +++ b/cmd/kubectl-ate/internal/credplugin/credplugin_test.go @@ -0,0 +1,139 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package credplugin + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "os" + "path/filepath" + "testing" + "time" + + clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" +) + +func TestParseInfo(t *testing.T) { + for _, tc := range []struct { + name string + env string + want Info + wantErr bool + }{ + {name: "unset", want: Info{APIVersion: APIVersionV1}}, + { + name: "v1 with audience", + env: `{"apiVersion":"client.authentication.k8s.io/v1","kind":"ExecCredential","spec":{"interactive":true,"cluster":{"server":"https://x","config":{"audience":"api.ate-system.svc"}}}}`, + want: Info{APIVersion: APIVersionV1, Interactive: true, Audience: "api.ate-system.svc"}, + }, + { + name: "v1beta1 without cluster", + env: `{"apiVersion":"client.authentication.k8s.io/v1beta1","kind":"ExecCredential","spec":{}}`, + want: Info{APIVersion: APIVersionV1beta1}, + }, + {name: "v1alpha1", env: `{"apiVersion":"client.authentication.k8s.io/v1alpha1","kind":"ExecCredential"}`, wantErr: true}, + {name: "malformed", env: `{`, wantErr: true}, + } { + t.Run(tc.name, func(t *testing.T) { + got, err := ParseInfo(tc.env) + if (err != nil) != tc.wantErr { + t.Fatalf("ParseInfo err = %v, wantErr %t", err, tc.wantErr) + } + if got != tc.want { + t.Errorf("ParseInfo = %+v, want %+v", got, tc.want) + } + }) + } +} + +func TestWriteCredential(t *testing.T) { + expiry := time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC) + var buf bytes.Buffer + if err := WriteCredential(&buf, APIVersionV1beta1, "tok", expiry); err != nil { + t.Fatal(err) + } + var got clientauthv1beta1.ExecCredential + if err := json.Unmarshal(buf.Bytes(), &got); err != nil { + t.Fatal(err) + } + if got.APIVersion != APIVersionV1beta1 || got.Kind != "ExecCredential" { + t.Errorf("apiVersion/kind = %s/%s", got.APIVersion, got.Kind) + } + if got.Status.Token != "tok" || !got.Status.ExpirationTimestamp.Time.Equal(expiry) { + t.Errorf("status = %+v", got.Status) + } + + buf.Reset() + if err := WriteCredential(&buf, APIVersionV1, "tok", time.Time{}); err != nil { + t.Fatal(err) + } + if bytes.Contains(buf.Bytes(), []byte("expirationTimestamp")) { + t.Errorf("credential without expiry = %s, want no expirationTimestamp", buf.String()) + } +} + +func jwt(payload string) string { + return "e30." + base64.RawURLEncoding.EncodeToString([]byte(payload)) + ".sig" +} + +func TestJWTExpiry(t *testing.T) { + if got, err := JWTExpiry(jwt(`{"exp":1760616000}`)); err != nil || got.Unix() != 1760616000 { + t.Errorf("JWTExpiry = (%v, %v), want 1760616000", got, err) + } + if got, err := JWTExpiry(jwt(`{"sub":"a"}`)); err != nil || !got.IsZero() { + t.Errorf("JWTExpiry without exp = (%v, %v), want zero", got, err) + } + if _, err := JWTExpiry("opaque-access-token"); err == nil { + t.Error("JWTExpiry(opaque token) succeeded, want error") + } +} + +func TestCache(t *testing.T) { + now := time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC) + c := &Cache{Dir: filepath.Join(t.TempDir(), "cache"), Now: func() time.Time { return now }} + key := []string{"https://kube", "ctx", "ate-system/ate-client", "api.ate-system.svc"} + + if _, _, ok := c.Get(key, time.Minute); ok { + t.Fatal("Get on an empty cache hit") + } + if err := c.Put(key, "tok", now.Add(time.Hour)); err != nil { + t.Fatal(err) + } + if tok, _, ok := c.Get(key, time.Minute); !ok || tok != "tok" { + t.Errorf("Get = (%q, %t), want tok", tok, ok) + } + if _, _, ok := c.Get([]string{"https://kube", "ctx", "ate-system/ate-client", "other"}, time.Minute); ok { + t.Error("Get for another audience hit") + } + if _, _, ok := c.Get(key, 2*time.Hour); ok { + t.Error("Get hit for a token expiring within minRemaining") + } + + dirInfo, err := os.Stat(c.Dir) + if err != nil { + t.Fatal(err) + } + if mode := dirInfo.Mode().Perm(); mode != 0o700 { + t.Errorf("cache dir mode = %o, want 700", mode) + } + fileInfo, err := os.Stat(c.path(key)) + if err != nil { + t.Fatal(err) + } + if mode := fileInfo.Mode().Perm(); mode != 0o600 { + t.Errorf("cache file mode = %o, want 600", mode) + } +} diff --git a/cmd/kubectl-ate/internal/credplugin/execinfo.go b/cmd/kubectl-ate/internal/credplugin/execinfo.go new file mode 100644 index 0000000000..3b4b06d6f9 --- /dev/null +++ b/cmd/kubectl-ate/internal/credplugin/execinfo.go @@ -0,0 +1,116 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package credplugin implements the plugin side of the kubeconfig exec +// credential protocol for `kubectl ate credential`. +package credplugin + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientauthv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" +) + +const ( + // InfoEnv carries the ExecCredential a client passes to its plugin. + InfoEnv = "KUBERNETES_EXEC_INFO" + + APIVersionV1 = "client.authentication.k8s.io/v1" + APIVersionV1beta1 = "client.authentication.k8s.io/v1beta1" +) + +// Info is what the client told the plugin through InfoEnv. +type Info struct { + // APIVersion is the version the response must use. + APIVersion string + Interactive bool + // Audience is spec.cluster.config.audience, if the client sent cluster + // info. + Audience string +} + +// ParseInfo parses the value of InfoEnv. An empty value, as when the plugin +// is run by hand, yields APIVersionV1 and no audience. +func ParseInfo(env string) (Info, error) { + if env == "" { + return Info{APIVersion: APIVersionV1}, nil + } + // v1 and v1beta1 share the fields read here. + var cred clientauthv1.ExecCredential + if err := json.Unmarshal([]byte(env), &cred); err != nil { + return Info{}, fmt.Errorf("parse %s: %w", InfoEnv, err) + } + if cred.APIVersion != APIVersionV1 && cred.APIVersion != APIVersionV1beta1 { + return Info{}, fmt.Errorf("%s has unsupported apiVersion %q", InfoEnv, cred.APIVersion) + } + info := Info{APIVersion: cred.APIVersion, Interactive: cred.Spec.Interactive} + if c := cred.Spec.Cluster; c != nil && len(c.Config.Raw) > 0 { + var config struct { + Audience string `json:"audience"` + } + if err := json.Unmarshal(c.Config.Raw, &config); err != nil { + return Info{}, fmt.Errorf("parse %s spec.cluster.config: %w", InfoEnv, err) + } + info.Audience = config.Audience + } + return info, nil +} + +// WriteCredential writes an ExecCredential carrying token. A zero expiry is +// left out, telling the client to use the token until it is rejected. +func WriteCredential(w io.Writer, apiVersion, token string, expiry time.Time) error { + cred := clientauthv1.ExecCredential{ + TypeMeta: metav1.TypeMeta{APIVersion: apiVersion, Kind: "ExecCredential"}, + Status: &clientauthv1.ExecCredentialStatus{Token: token}, + } + if !expiry.IsZero() { + exp := metav1.NewTime(expiry) + cred.Status.ExpirationTimestamp = &exp + } + return json.NewEncoder(w).Encode(cred) +} + +// JWTExpiry returns the exp claim of a JWT without verifying it, or the zero +// time if it has none. +func JWTExpiry(token string) (time.Time, error) { + parts := strings.Split(token, ".") + if len(parts) != 3 { + return time.Time{}, errors.New("not a JWT") + } + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return time.Time{}, fmt.Errorf("decode JWT payload: %w", err) + } + var claims struct { + Exp *json.Number `json:"exp"` + } + if err := json.Unmarshal(payload, &claims); err != nil { + return time.Time{}, fmt.Errorf("parse JWT payload: %w", err) + } + if claims.Exp == nil { + return time.Time{}, nil + } + exp, err := claims.Exp.Float64() + if err != nil { + return time.Time{}, fmt.Errorf("parse JWT exp: %w", err) + } + return time.Unix(int64(exp), 0), nil +} diff --git a/cmd/kubectl-ate/internal/printer/printer.go b/cmd/kubectl-ate/internal/printer/printer.go index 8d418c46c9..d61de40b4a 100644 --- a/cmd/kubectl-ate/internal/printer/printer.go +++ b/cmd/kubectl-ate/internal/printer/printer.go @@ -264,6 +264,18 @@ func PrintWorkerTopYAML(out io.Writer, items []*WorkerTopItem) error { return err } +// PrintWhoAmITo prints the caller's principal to the provided writer. +func PrintWhoAmITo(out io.Writer, resp *ateapi.WhoAmIResponse, format string) error { + if format == "json" || format == "yaml" { + return printProto(out, resp, format) + } + kind := strings.ToLower(strings.TrimPrefix(resp.GetKind().String(), "PRINCIPAL_KIND_")) + w := tabwriter.NewWriter(out, 0, 0, 3, ' ', 0) + fmt.Fprintln(w, "PRINCIPAL\tMETHOD\tISSUER") + fmt.Fprintf(w, "%s\t%s\t%s\n", resp.GetId(), kind, resp.GetIssuer()) + return w.Flush() +} + // PrintActorTo prints a single actor to the provided writer. func PrintActorTo(out io.Writer, actor *ateapi.Actor, format string) error { if format == "json" || format == "yaml" { diff --git a/cmd/kubectl-ate/internal/printer/printer_test.go b/cmd/kubectl-ate/internal/printer/printer_test.go index bcbf0a3bfd..051ada1390 100644 --- a/cmd/kubectl-ate/internal/printer/printer_test.go +++ b/cmd/kubectl-ate/internal/printer/printer_test.go @@ -941,3 +941,29 @@ func TestPrintWorkerTopTo_Invalid(t *testing.T) { t.Errorf("expected error for invalid format, got nil") } } + +func TestPrintWhoAmITo(t *testing.T) { + resp := &ateapi.WhoAmIResponse{ + Id: "system:serviceaccount:ate-users:alice", + Kind: ateapi.PrincipalKind_PRINCIPAL_KIND_JWT, + Issuer: "https://kubernetes.default.svc", + } + var buf bytes.Buffer + if err := PrintWhoAmITo(&buf, resp, "table"); err != nil { + t.Fatal(err) + } + want := "PRINCIPAL METHOD ISSUER\n" + + "system:serviceaccount:ate-users:alice jwt https://kubernetes.default.svc\n" + if diff := cmp.Diff(want, buf.String()); diff != "" { + t.Errorf("table (-want +got):\n%s", diff) + } + + buf.Reset() + if err := PrintWhoAmITo(&buf, resp, "yaml"); err != nil { + t.Fatal(err) + } + want = "id: system:serviceaccount:ate-users:alice\nissuer: https://kubernetes.default.svc\nkind: PRINCIPAL_KIND_JWT\n" + if diff := cmp.Diff(want, buf.String()); diff != "" { + t.Errorf("yaml (-want +got):\n%s", diff) + } +} diff --git a/docs/authentication.md b/docs/authentication.md index 4bccc4fa52..8d47540b74 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -27,24 +27,132 @@ What an authenticated caller may do is decided by authorization; see provider issued the token, so only configure providers you trust not to issue a token whose subject is another principal's ID. +## Client credentials + +`kubectl-ate` gets its credential from the kubectl credential plugin that +`--credential-plugin` or `$ATE_CREDENTIAL_PLUGIN` names; see the +[kubectl-ate README](../cmd/kubectl-ate/README.md#authentication). It caches +the plugin's token until its `expirationTimestamp`, and runs the plugin again +when `ate-api` returns `UNAUTHENTICATED`. If the plugin returns the +rejected token again, the client runs it once more, and on every later run, +with `ATE_REFRESH_CREDENTIAL=1`, which tells a plugin that caches tokens to +return a new one. + +`ate-api` checks only a JWT's issuer, audience, signature, and expiry. A +credential source is therefore safe only when its issuer limits who can get a +token for the configured audience. Kubernetes ServiceAccount tokens qualify, +because RBAC decides who may create them. Google ID tokens for the Cloud SDK's +shared client ID do not, since any Google account can get one. Neither do +Google service account ID tokens or GitHub Actions OIDC tokens: any service +account or workflow can request one for any audience. + +`kubectl ate credential` is the built-in plugin. It mints a ServiceAccount +token through your kubeconfig and caches it under `~/.ate/cache`. +`kubectl ate credential wrap -- COMMAND` adapts a command that prints a JWT. +`kubectl ate whoami` shows the principal `ate-api` authenticated you as. + +### Per-user ServiceAccounts + +Without a credential, every user acts as the shared `ate-client` +ServiceAccount. To give a user their own identity, let them create tokens for +a ServiceAccount of their own: + +```sh +kubectl create namespace ate-users +kubectl -n ate-users create serviceaccount alice +kubectl -n ate-users create role alice-token --verb=create --resource=serviceaccounts/token --resource-name=alice +kubectl -n ate-users create rolebinding alice-token --role=alice-token --user=alice@example.com +``` + +Alice then sets +`ATE_CREDENTIAL_PLUGIN="kubectl-ate credential --service-account=ate-users/alice"`, +and `kubectl ate whoami` reports `system:serviceaccount:ate-users:alice`. +Access policies grant to that ID. The token carries the ServiceAccount's +groups, not Alice's. + +A user who is not a cluster admin also needs to port-forward to `ate-api` and +read its trust bundle. Create these roles once: + +```sh +kubectl apply -f - <<'EOF' +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: ate-api-client + namespace: ate-system +rules: +- apiGroups: [""] + resources: [services] + resourceNames: [api] + verbs: [get] +- apiGroups: [""] + resources: [pods] + verbs: [list] +- apiGroups: [""] + resources: [pods/portforward] + verbs: [get, create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ate-api-client +rules: +- apiGroups: [certificates.k8s.io] + resources: [clustertrustbundles] + verbs: [list] +EOF +``` + +and bind them to each user: + +```sh +kubectl -n ate-system create rolebinding alice-ate-api-client --role=ate-api-client --user=alice@example.com +kubectl create clusterrolebinding alice-ate-api-client --clusterrole=ate-api-client --user=alice@example.com +``` + +`ate-api` verifies a token's signature and expiry without asking Kubernetes +whether its ServiceAccount still exists. Deleting Alice's ServiceAccount stops +her getting new tokens, but one she already holds keeps working until it +expires, plus `ate-api`'s five-minute allowance for clock skew. +`kubectl ate credential` requests one-hour tokens; Alice can request longer +ones herself, up to the cluster's maximum token lifetime. To cut off her access +at once, remove the access policy grants to her ID. A ServiceAccount recreated +with the same name gets the old one's grants. + +### In-cluster workloads + +A pod can act as its own ServiceAccount with +`--endpoint=api.ate-system.svc:443` and a `--token-file` pointing at a +projected ServiceAccount token with audience `api.ate-system.svc`. The client +rereads the file on every RPC, so it picks up the kubelet's rotations. The +pod's ServiceAccount must be able to list ClusterTrustBundles, which verify +`ate-api`. The install's NetworkPolicy admits only pods in namespaces labeled +`ate.dev/api-access=true`. + ## Google Cloud CLI tokens The Google Cloud CLI currently issues user identity tokens with issuer `https://accounts.google.com` and audience `32555940559.apps.googleusercontent.com`, the Cloud SDK's shared client ID. These values are examples rather than built-in defaults; verify the claims -issued by your identity provider and configure them explicitly. +issued by your identity provider and configure them explicitly. Any Google +account can obtain such a token, so this provider admits every Google account. + +With the provider configured, wrap the token as a credential plugin: + +```sh +export ATE_CREDENTIAL_PLUGIN="kubectl-ate credential wrap -- gcloud auth print-identity-token" +``` -With the provider configured, pipe the token to `kubectl-ate`: +or pass it for one command with `--token-file`, which accepts a file path or +`-` for stdin: ```sh gcloud auth print-identity-token | kubectl ate --token-file=- get actors ``` -`--token-file` accepts either a file path or `-` for stdin and only replaces the -credential sent to `ate-api`. `kubectl-ate` -still uses kubeconfig access to establish its port-forward and obtain the -server trust bundle. +Either way, `kubectl-ate` still uses kubeconfig access to establish its +port-forward and obtain the server trust bundle. For a manifest-based installation, replace the authentication ConfigMap and restart the deployment: diff --git a/docs/authorization.md b/docs/authorization.md index 271123faa9..73ae9493f4 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -93,8 +93,10 @@ kubectl ate --token-file me.token get atespaces ## `kubectl ate` and `ate-setup` identities -`kubectl ate` sends the token from `--token-file` (`-` for stdin). Without -one, it uses your kubeconfig to mint a token for `ate-client` (or +`kubectl ate` sends the token from `--token-file` (`-` for stdin), else the +credential from `--credential-plugin`; see +[authentication.md](authentication.md#client-credentials). Without either, it +uses your kubeconfig to mint a token for `ate-client` (or `ATE_CLIENT_SERVICE_ACCOUNT`) and warns that it is acting as a shared identity. It still needs Kubernetes rights to port-forward to `ate-api-server` unless you pass `--endpoint`. @@ -315,8 +317,9 @@ For a shared cluster, beyond replacing the default bootstrap owner: ### Not yet supported -- **Signing in.** `kubectl ate` has no login flow or credential plugin; people - obtain tokens themselves. +- **Signing in.** `kubectl ate` has no login flow of its own; it runs the + kubectl credential plugin `--credential-plugin` names, or people obtain + tokens themselves. - **JWT providers at install time.** `ate-setup` configures only the cluster's ServiceAccount issuer; add others to the ConfigMap afterwards. - **Groups.** Policy members are individual principals. diff --git a/internal/ateclient/builder.go b/internal/ateclient/builder.go index f267e2dedc..f3aca786f2 100644 --- a/internal/ateclient/builder.go +++ b/internal/ateclient/builder.go @@ -19,10 +19,9 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io" "os" - "strings" "sync" + "time" "go.ate.dev/substrate/api/ateapi/v1" "go.ate.dev/substrate/internal/clustertrustbundle" @@ -36,11 +35,11 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.40.0" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - authv1 "k8s.io/api/authentication/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" metricsv1beta1 "k8s.io/metrics/pkg/client/clientset/versioned" ) @@ -81,6 +80,18 @@ func FallbackPrincipal() string { return fmt.Sprintf("system:serviceaccount:%s:%s", systemNamespace(), clientServiceAccount()) } +// DefaultServiceAccount is the ServiceAccount a client mints its token from +// when it has no other credential. +func DefaultServiceAccount() (namespace, name string) { + return systemNamespace(), clientServiceAccount() +} + +// DefaultAudience is the audience of tokens minted for ateapi: the in-cluster +// DNS name of its Service. +func DefaultAudience() string { + return apiServerName() +} + // systemNamespace is the namespace the client expects ateapi to be running in. func systemNamespace() string { if ns := os.Getenv(NamespaceEnv); ns != "" { @@ -140,35 +151,66 @@ func (c *Client) WorkerService() ateapi.WorkerServiceClient { return ateapi.NewWorkerServiceClient(c.conn) } +// Authentication returns an Authentication client on the same connection. +func (c *Client) Authentication() ateapi.AuthenticationClient { + return ateapi.NewAuthenticationClient(c.conn) +} + // NewClient creates a new Ate API client. If endpoint is empty, it automatically -// port-forwards to the ate-api-server pod in substrate's system namespace. -// extraOpts are appended to the options used to dial the server. +// port-forwards to the ate-api-server pod in substrate's system namespace. It +// authenticates with tokenFile if set, and otherwise with a token minted for +// the default ServiceAccount, refreshed before it expires. extraOpts are +// appended to the options used to dial the server. func NewClient(ctx context.Context, kubeconfigPath, k8sContext, endpoint, tokenFile string, traceEnabled bool, extraOpts ...grpc.DialOption) (*Client, error) { - tp, err := initTracing(ctx, traceEnabled) + return Dial(ctx, DialOptions{ + Kubeconfig: kubeconfigPath, + KubeContext: k8sContext, + Endpoint: endpoint, + TokenFile: tokenFile, + Trace: traceEnabled, + GRPCOptions: extraOpts, + }) +} + +// DialOptions configures Dial. +type DialOptions struct { + // Kubeconfig and KubeContext select the cluster substrate runs in. Empty + // values use the default loading rules and the current context. + Kubeconfig string + KubeContext string + // Endpoint is dialed directly instead of port-forwarding to the ateapi + // Service. + Endpoint string + // TokenFile is a bearer token file, re-read on every RPC, or "-" to read + // one token from stdin. + TokenFile string + // CredentialPlugin is a kubeconfig exec credential plugin, used when + // TokenFile is empty. Without either, the client mints a token for the + // default ServiceAccount. + CredentialPlugin *clientcmdapi.ExecConfig + Trace bool + GRPCOptions []grpc.DialOption +} + +// Dial creates a new Ate API client as opts describes. +func Dial(ctx context.Context, opts DialOptions) (*Client, error) { + tp, err := initTracing(ctx, opts.Trace) if err != nil { return nil, fmt.Errorf("failed to initialize tracing: %w", err) } - - var cli *Client - if endpoint != "" { - cli, err = dialDirect(ctx, kubeconfigPath, k8sContext, endpoint, tokenFile, traceEnabled, extraOpts...) - } else { - cli, err = dialPortForward(ctx, kubeconfigPath, k8sContext, tokenFile, traceEnabled, extraOpts...) - } - + cli, err := dial(ctx, opts) if err != nil { if tp != nil { _ = tp.Shutdown(ctx) } return nil, err } - cli.tracerProvider = tp return cli, nil } -func dialDirect(ctx context.Context, kubeconfigPath, k8sContext, endpoint, tokenFile string, traceEnabled bool, extraOpts ...grpc.DialOption) (*Client, error) { - config, err := LoadKubeConfig(kubeconfigPath, k8sContext) +func dial(ctx context.Context, opts DialOptions) (*Client, error) { + config, err := LoadKubeConfig(opts.Kubeconfig, opts.KubeContext) if err != nil { return nil, fmt.Errorf("failed to load kubeconfig: %w", err) } @@ -182,102 +224,74 @@ func dialDirect(ctx context.Context, kubeconfigPath, k8sContext, endpoint, token return nil, fmt.Errorf("failed to create k8s client: %w", err) } - // Verify the server before attaching the bearer token below: the token - // must never be sent over an unauthenticated channel. + // Verify the server before attaching the credential below: a token must + // never be sent over an unauthenticated channel. tlsCfg, err := serverTLSConfig(ctx, clientset) if err != nil { return nil, err } - - var opts []grpc.DialOption - opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))) - opts = append(opts, grpc.WithStatsHandler(otelgrpc.NewClientHandler())) - opts = append(opts, grpc.WithDefaultServiceConfig(roundRobinServiceConfig)) - tokenOpt, err := bearerTokenDialOption(ctx, clientset, tokenFile) + creds, err := clientCredentials(opts, clientset) if err != nil { return nil, err } - opts = append(opts, tokenOpt) - if traceEnabled { - opts = append(opts, grpc.WithChainUnaryInterceptor(newTraceInterceptor())) + target, stopForward := opts.Endpoint, func() {} + if target == "" { + // TODO: Should we special-case a LoadBalancer "api" Service and dial its + // address directly instead of port-forwarding? + localPort, stop, err := portforward.ServicePortForward(ctx, config, clientset, systemNamespace(), apiServiceName(), 443) + if err != nil { + return nil, err + } + target, stopForward = fmt.Sprintf("127.0.0.1:%d", localPort), stop } - opts = append(opts, extraOpts...) - - conn, err := grpc.NewClient(endpoint, opts...) + conn, err := connect(target, tlsCfg, creds, opts) if err != nil { - return nil, fmt.Errorf("failed to dial manual endpoint: %w", err) + stopForward() + return nil, err } return &Client{ ControlClient: ateapi.NewControlClient(conn), conn: conn, - cancel: func() {}, + cancel: stopForward, }, nil } -// LoadKubeConfig loads a Kubernetes client configuration from the specified kubeconfig path and context. -func LoadKubeConfig(kubeconfigPath, k8sContext string) (*rest.Config, error) { - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - loadingRules.ExplicitPath = kubeconfigPath - configOverrides := &clientcmd.ConfigOverrides{CurrentContext: k8sContext} - return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides).ClientConfig() -} - -func dialPortForward(ctx context.Context, kubeconfigPath, k8sContext, tokenFile string, traceEnabled bool, extraOpts ...grpc.DialOption) (*Client, error) { - config, err := LoadKubeConfig(kubeconfigPath, k8sContext) - if err != nil { - return nil, fmt.Errorf("failed to load kubeconfig: %w", err) +// connect dials target, attaching creds to every RPC and retrying an RPC once +// when ateapi rejects a refreshable credential. +func connect(target string, tlsCfg *tls.Config, creds credentials.PerRPCCredentials, opts DialOptions) (*grpc.ClientConn, error) { + if cc, ok := creds.(interface { + clientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) + }); ok { + tlsCfg.GetClientCertificate = cc.clientCertificate } - - // Older clusters use the deprecated beta ClusterTrustBundle API. - // Suppress its warnings when falling back from v1. - config.WarningHandlerWithContext = &rest.NoWarnings{} - - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf("failed to create k8s client: %w", err) + dialOpts := []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + grpc.WithPerRPCCredentials(creds), + grpc.WithChainUnaryInterceptor(retryUnauthenticated(creds)), } - - // TODO: Should we special-case a LoadBalancer "api" Service and dial its - // address directly instead of port-forwarding? - localPort, stopForward, err := portforward.ServicePortForward(ctx, config, clientset, systemNamespace(), apiServiceName(), 443) - if err != nil { - return nil, err + if opts.Endpoint != "" { + dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(roundRobinServiceConfig)) } - localEndpoint := fmt.Sprintf("127.0.0.1:%d", localPort) - - tlsCfg, err := serverTLSConfig(ctx, clientset) - if err != nil { - stopForward() - return nil, err - } - - var opts []grpc.DialOption - opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))) - opts = append(opts, grpc.WithStatsHandler(otelgrpc.NewClientHandler())) - tokenOpt, err := bearerTokenDialOption(ctx, clientset, tokenFile) - if err != nil { - stopForward() - return nil, err - } - opts = append(opts, tokenOpt) - - if traceEnabled { - opts = append(opts, grpc.WithChainUnaryInterceptor(newTraceInterceptor())) + if opts.Trace { + dialOpts = append(dialOpts, grpc.WithChainUnaryInterceptor(newTraceInterceptor())) } - opts = append(opts, extraOpts...) + dialOpts = append(dialOpts, opts.GRPCOptions...) - conn, err := grpc.NewClient(localEndpoint, opts...) + conn, err := grpc.NewClient(target, dialOpts...) if err != nil { - stopForward() - return nil, fmt.Errorf("failed to dial gRPC over tunnel: %w", err) + return nil, fmt.Errorf("failed to dial ateapi at %s: %w", target, err) } + return conn, nil +} - return &Client{ - ControlClient: ateapi.NewControlClient(conn), - conn: conn, - cancel: stopForward, - }, nil +// LoadKubeConfig loads a Kubernetes client configuration from the specified kubeconfig path and context. +func LoadKubeConfig(kubeconfigPath, k8sContext string) (*rest.Config, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = kubeconfigPath + configOverrides := &clientcmd.ConfigOverrides{CurrentContext: k8sContext} + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides).ClientConfig() } func serverTLSConfig(ctx context.Context, clientset kubernetes.Interface) (*tls.Config, error) { @@ -314,79 +328,37 @@ func serverTLSConfig(ctx context.Context, clientset kubernetes.Interface) (*tls. }, nil } -// bearerTokenDialOption attaches the configured token, or mints an ate-client -// ServiceAccount token when tokenFile is empty. +// clientCredentials returns the credential source opts selects. // // The minted token authenticates as FallbackPrincipal, not as whoever runs // the client: anyone Kubernetes RBAC lets create a token for that // ServiceAccount gets the same identity, and whatever it is granted. -func bearerTokenDialOption(ctx context.Context, clientset *kubernetes.Clientset, tokenFile string) (grpc.DialOption, error) { - if tokenFile == "-" { +func clientCredentials(opts DialOptions, clientset kubernetes.Interface) (credentials.PerRPCCredentials, error) { + switch { + case opts.TokenFile == "-": creds, err := readBearerToken(os.Stdin) if err != nil { return nil, fmt.Errorf("read bearer token from stdin: %w", err) } - return grpc.WithPerRPCCredentials(creds), nil - } - if tokenFile != "" { - return grpc.WithPerRPCCredentials(fileBearerTokenCreds(tokenFile)), nil - } - expirationSeconds := int64(3600) - tokenRequest := &authv1.TokenRequest{ - Spec: authv1.TokenRequestSpec{ - Audiences: []string{apiServerName()}, - ExpirationSeconds: &expirationSeconds, - }, - } - token, err := clientset.CoreV1().ServiceAccounts(systemNamespace()).CreateToken(ctx, clientServiceAccount(), tokenRequest, metav1.CreateOptions{}) - if err != nil { - return nil, fmt.Errorf("failed to request ateapi bearer token: %w", err) - } - if token.Status.Token == "" { - return nil, fmt.Errorf("failed to request ateapi bearer token: token response was empty") - } - return grpc.WithPerRPCCredentials(bearerTokenCreds(token.Status.Token)), nil -} - -func readBearerToken(r io.Reader) (bearerTokenCreds, error) { - b, err := io.ReadAll(r) - if err != nil { - return "", err - } - token := strings.TrimSpace(string(b)) - if token == "" { - return "", fmt.Errorf("bearer token is empty") - } - return bearerTokenCreds(token), nil -} - -type bearerTokenCreds string - -func (c bearerTokenCreds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { - if c == "" { - return nil, fmt.Errorf("bearer token is empty") - } - return map[string]string{"authorization": "Bearer " + string(c)}, nil -} - -func (c bearerTokenCreds) RequireTransportSecurity() bool { return true } - -type fileBearerTokenCreds string - -func (c fileBearerTokenCreds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { - b, err := os.ReadFile(string(c)) - if err != nil { - return nil, fmt.Errorf("read bearer token file %q: %w", c, err) - } - token := strings.TrimSpace(string(b)) - if token == "" { - return nil, fmt.Errorf("bearer token file %q is empty", c) + return creds, nil + case opts.TokenFile != "": + return fileBearerTokenCreds(opts.TokenFile), nil + case opts.CredentialPlugin != nil: + server := "https://" + apiServerName() + if opts.Endpoint != "" { + server = "https://" + opts.Endpoint + } + cluster, err := execCluster(server, apiServerName(), DefaultAudience()) + if err != nil { + return nil, err + } + return newExecCreds(opts.CredentialPlugin, cluster) } - return map[string]string{"authorization": "Bearer " + token}, nil + return newMintedTokenCreds(func(ctx context.Context) (string, time.Time, error) { + return MintServiceAccountToken(ctx, clientset, systemNamespace(), clientServiceAccount(), DefaultAudience(), time.Hour) + }), nil } -func (c fileBearerTokenCreds) RequireTransportSecurity() bool { return true } - // initTracing returns (nil, nil) when tracing is disabled: the OTel globals // stay noop so no traceparent is injected, the server roots the trace, and the // server side sampling ratio applies. A NeverSample provider here would diff --git a/internal/ateclient/credentials.go b/internal/ateclient/credentials.go new file mode 100644 index 0000000000..cc50a90f4a --- /dev/null +++ b/internal/ateclient/credentials.go @@ -0,0 +1,314 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ateclient + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "strings" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" + authv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/pkg/apis/clientauthentication" + "k8s.io/client-go/plugin/pkg/client/auth/exec" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "k8s.io/client-go/transport" +) + +// tokenExpiryMargin is how long before expiry a minted token is replaced. +const tokenExpiryMargin = time.Minute + +// RefreshCredentialEnv is set to "1" on every exec plugin run after the +// plugin returned a token ate-api had just rejected. A plugin that caches +// tokens should then return a new one. +const RefreshCredentialEnv = "ATE_REFRESH_CREDENTIAL" + +// refreshableCredentials attaches a credential to each RPC and can replace it +// when ateapi rejects it. +type refreshableCredentials interface { + credentials.PerRPCCredentials + // refresh discards the credential if it is still rejected, the + // authorization metadata ateapi refused, so the next RPC gets a fresh + // one. It reports whether the next RPC will send a different credential. + refresh(rejected string) bool +} + +// retryUnauthenticated reports a credential it cannot fetch as a local error, +// and retries an RPC once when ateapi rejects a refreshable credential. The +// retry relies on ateapi rejecting a credential before the RPC changes anything. +func retryUnauthenticated(creds credentials.PerRPCCredentials) grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + // Refreshable credentials cache, so for them this is what the RPC + // will send. + md, err := creds.GetRequestMetadata(ctx) + if err != nil { + return fmt.Errorf("get credentials for ate-api: %w", err) + } + err = invoker(ctx, method, req, reply, cc, opts...) + rc, ok := creds.(refreshableCredentials) + if status.Code(err) != codes.Unauthenticated || !ok || !rc.refresh(md["authorization"]) { + return err + } + return invoker(ctx, method, req, reply, cc, opts...) + } +} + +func readBearerToken(r io.Reader) (bearerTokenCreds, error) { + b, err := io.ReadAll(r) + if err != nil { + return "", err + } + token := strings.TrimSpace(string(b)) + if token == "" { + return "", fmt.Errorf("bearer token is empty") + } + return bearerTokenCreds(token), nil +} + +type bearerTokenCreds string + +func (c bearerTokenCreds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + if c == "" { + return nil, fmt.Errorf("bearer token is empty") + } + return map[string]string{"authorization": "Bearer " + string(c)}, nil +} + +func (c bearerTokenCreds) RequireTransportSecurity() bool { return true } + +type fileBearerTokenCreds string + +func (c fileBearerTokenCreds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { + b, err := os.ReadFile(string(c)) + if err != nil { + return nil, fmt.Errorf("read bearer token file %q: %w", c, err) + } + token := strings.TrimSpace(string(b)) + if token == "" { + return nil, fmt.Errorf("bearer token file %q is empty", c) + } + return map[string]string{"authorization": "Bearer " + token}, nil +} + +func (c fileBearerTokenCreds) RequireTransportSecurity() bool { return true } + +// MintServiceAccountToken requests a token for the ServiceAccount +// namespace/name, bound to audience, and returns it with its expiry. +func MintServiceAccountToken(ctx context.Context, clientset kubernetes.Interface, namespace, name, audience string, lifetime time.Duration) (string, time.Time, error) { + seconds := int64(lifetime / time.Second) + tr, err := clientset.CoreV1().ServiceAccounts(namespace).CreateToken(ctx, name, &authv1.TokenRequest{ + Spec: authv1.TokenRequestSpec{ + Audiences: []string{audience}, + ExpirationSeconds: &seconds, + }, + }, metav1.CreateOptions{}) + if err != nil { + return "", time.Time{}, fmt.Errorf("request a token for ServiceAccount %s/%s: %w", namespace, name, err) + } + if tr.Status.Token == "" { + return "", time.Time{}, fmt.Errorf("request a token for ServiceAccount %s/%s: token response was empty", namespace, name) + } + return tr.Status.Token, tr.Status.ExpirationTimestamp.Time, nil +} + +// mintedTokenCreds mints a token on first use and again once it nears expiry. +type mintedTokenCreds struct { + mint func(context.Context) (string, time.Time, error) + now func() time.Time + + mu sync.Mutex + token string + expiry time.Time +} + +func newMintedTokenCreds(mint func(context.Context) (string, time.Time, error)) *mintedTokenCreds { + return &mintedTokenCreds{mint: mint, now: time.Now} +} + +func (c *mintedTokenCreds) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) { + c.mu.Lock() + defer c.mu.Unlock() + if c.token == "" || !c.now().Before(c.expiry.Add(-tokenExpiryMargin)) { + token, expiry, err := c.mint(ctx) + if err != nil { + return nil, err + } + c.token, c.expiry = token, expiry + } + return map[string]string{"authorization": "Bearer " + c.token}, nil +} + +func (c *mintedTokenCreds) RequireTransportSecurity() bool { return true } + +// execCreds runs a kubeconfig exec credential plugin through client-go's +// authenticator, which caches the credential until its expirationTimestamp. +// That authenticator is built for net/http, so execCreds drives it with +// in-memory requests to a stub transport. +type execCreds struct { + config *clientcmdapi.ExecConfig + cluster *clientauthentication.Cluster + + // refreshMu serializes refresh, so that RPCs rejected together share one + // refresh. + refreshMu sync.Mutex + + mu sync.Mutex + wrap transport.WrapperFunc + getCert func() (*tls.Certificate, error) +} + +// execCluster is passed to a plugin as spec.cluster in KUBERNETES_EXEC_INFO +// when it sets provideClusterInfo. +func execCluster(server, serverName, audience string) (*clientauthentication.Cluster, error) { + config, err := json.Marshal(map[string]string{"audience": audience}) + if err != nil { + return nil, err + } + return &clientauthentication.Cluster{ + Server: server, + TLSServerName: serverName, + Config: &runtime.Unknown{Raw: config, ContentType: runtime.ContentTypeJSON}, + }, nil +} + +func newExecCreds(config *clientcmdapi.ExecConfig, cluster *clientauthentication.Cluster) (*execCreds, error) { + c := &execCreds{config: config, cluster: cluster} + if err := c.use(config); err != nil { + return nil, err + } + return c, nil +} + +// use switches to client-go's authenticator for config. +func (c *execCreds) use(config *clientcmdapi.ExecConfig) error { + a, err := exec.GetAuthenticator(config, c.cluster) + if err != nil { + return fmt.Errorf("exec credential plugin %s: %w", config.Command, err) + } + var tc transport.Config + if err := a.UpdateTransportConfig(&tc); err != nil { + return fmt.Errorf("exec credential plugin %s: %w", config.Command, err) + } + if tc.WrapTransport == nil || tc.TLS.GetCertHolder == nil { + return errors.New("exec credential plugin: authenticator did not install its transport hooks") + } + c.mu.Lock() + defer c.mu.Unlock() + c.wrap, c.getCert = tc.WrapTransport, tc.TLS.GetCertHolder.GetCert + return nil +} + +// stubTransport answers every request with code and records the +// Authorization header it was sent. +type stubTransport struct { + code int + authorization string +} + +func (s *stubTransport) RoundTrip(req *http.Request) (*http.Response, error) { + s.authorization = req.Header.Get("Authorization") + return &http.Response{StatusCode: s.code, Body: http.NoBody, Request: req}, nil +} + +// authorization returns the Authorization header the authenticator attaches. +// A 401 response makes the authenticator rerun the plugin, unless another +// caller already replaced the credential it attached. +func (c *execCreds) authorization(ctx context.Context, code int) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://ateapi.invalid/", nil) + if err != nil { + return "", err + } + c.mu.Lock() + wrap := c.wrap + c.mu.Unlock() + stub := &stubTransport{code: code} + if _, err := wrap(stub).RoundTrip(req); err != nil { + return "", err + } + return stub.authorization, nil +} + +func (c *execCreds) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) { + auth, err := c.authorization(ctx, http.StatusOK) + if err != nil { + return nil, err + } + if auth == "" { + // The plugin returned only a client certificate. + return nil, nil + } + return map[string]string{"authorization": auth}, nil +} + +func (c *execCreds) RequireTransportSecurity() bool { return true } + +func (c *execCreds) refresh(rejected string) bool { + c.refreshMu.Lock() + defer c.refreshMu.Unlock() + ctx := context.Background() + current, err := c.authorization(ctx, http.StatusOK) + if err != nil { + return false + } + if current == rejected { + if _, err := c.authorization(ctx, http.StatusUnauthorized); err != nil { + return false + } + if current, err = c.authorization(ctx, http.StatusOK); err != nil { + return false + } + } + if current == rejected { + // The plugin served the rejected token again, likely from its own + // cache. + config := c.config.DeepCopy() + config.Env = append(config.Env, clientcmdapi.ExecEnvVar{Name: RefreshCredentialEnv, Value: "1"}) + if err := c.use(config); err != nil { + return false + } + if current, err = c.authorization(ctx, http.StatusOK); err != nil { + return false + } + } + return current != rejected +} + +// clientCertificate presents the plugin's client certificate, if it returned +// one. A plugin failure sends no certificate rather than failing the +// handshake, so the error surfaces from GetRequestMetadata instead. +func (c *execCreds) clientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + c.mu.Lock() + getCert := c.getCert + c.mu.Unlock() + cert, err := getCert() + if err != nil || cert == nil { + return &tls.Certificate{}, nil + } + return cert, nil +} diff --git a/internal/ateclient/credentials_test.go b/internal/ateclient/credentials_test.go new file mode 100644 index 0000000000..8bdb92702d --- /dev/null +++ b/internal/ateclient/credentials_test.go @@ -0,0 +1,724 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ateclient + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "go.ate.dev/substrate/api/ateapi/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + clientauthv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +// The test binary doubles as an exec credential plugin when pluginEnv is set. +// Run n writes its KUBERNETES_EXEC_INFO to info-.json in pluginStateEnv's +// directory and returns "token-". With pluginSameTokenEnv it always +// returns "token-1"; with pluginCachedTokenEnv it returns "token-1" unless +// RefreshCredentialEnv is set. +const ( + pluginEnv = "ATECLIENT_TEST_PLUGIN" + pluginStateEnv = "ATECLIENT_TEST_PLUGIN_STATE" + pluginTTLEnv = "ATECLIENT_TEST_PLUGIN_TTL" + pluginSameTokenEnv = "ATECLIENT_TEST_PLUGIN_SAME_TOKEN" + pluginCachedTokenEnv = "ATECLIENT_TEST_PLUGIN_CACHED_TOKEN" + pluginCertEnv = "ATECLIENT_TEST_PLUGIN_CERT" +) + +func TestMain(m *testing.M) { + if os.Getenv(pluginEnv) != "" { + if err := runTestPlugin(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + } + os.Exit(m.Run()) +} + +func runTestPlugin() error { + dir := os.Getenv(pluginStateEnv) + info := os.Getenv("KUBERNETES_EXEC_INFO") + n := 1 + for ; ; n++ { + f, err := os.OpenFile(filepath.Join(dir, fmt.Sprintf("info-%d.json", n)), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) + if errors.Is(err, os.ErrExist) { + continue + } + if err != nil { + return err + } + _, err = f.WriteString(info) + if cerr := f.Close(); err == nil { + err = cerr + } + if err != nil { + return err + } + break + } + var in clientauthv1.ExecCredential + if err := json.Unmarshal([]byte(info), &in); err != nil { + return err + } + + token := fmt.Sprintf("token-%d", n) + if os.Getenv(pluginSameTokenEnv) != "" || os.Getenv(pluginCachedTokenEnv) != "" && os.Getenv(RefreshCredentialEnv) == "" { + token = "token-1" + } + out := clientauthv1.ExecCredential{ + TypeMeta: in.TypeMeta, + Status: &clientauthv1.ExecCredentialStatus{Token: token}, + } + if ttl := os.Getenv(pluginTTLEnv); ttl != "" { + d, err := time.ParseDuration(ttl) + if err != nil { + return err + } + exp := metav1.NewTime(time.Now().Add(d)) + out.Status.ExpirationTimestamp = &exp + } + if certDir := os.Getenv(pluginCertEnv); certDir != "" { + cert, err := os.ReadFile(filepath.Join(certDir, "tls.crt")) + if err != nil { + return err + } + key, err := os.ReadFile(filepath.Join(certDir, "tls.key")) + if err != nil { + return err + } + out.Status.ClientCertificateData, out.Status.ClientKeyData = string(cert), string(key) + } + return json.NewEncoder(os.Stdout).Encode(out) +} + +// testPlugin is an exec stanza that runs the test binary as a plugin. +func testPlugin(t *testing.T, apiVersion string, env ...clientcmdapi.ExecEnvVar) (*clientcmdapi.ExecConfig, string) { + t.Helper() + self, err := os.Executable() + if err != nil { + t.Fatal(err) + } + dir := t.TempDir() + return &clientcmdapi.ExecConfig{ + APIVersion: apiVersion, + Command: self, + Env: append([]clientcmdapi.ExecEnvVar{{Name: pluginEnv, Value: "1"}, {Name: pluginStateEnv, Value: dir}}, env...), + InteractiveMode: clientcmdapi.NeverExecInteractiveMode, + ProvideClusterInfo: true, + }, dir +} + +func pluginRuns(t *testing.T, dir string) int { + t.Helper() + runs, err := filepath.Glob(filepath.Join(dir, "info-*.json")) + if err != nil { + t.Fatal(err) + } + return len(runs) +} + +func authorization(t *testing.T, creds credentials.PerRPCCredentials) string { + t.Helper() + md, err := creds.GetRequestMetadata(context.Background()) + if err != nil { + t.Fatalf("GetRequestMetadata: %v", err) + } + return md["authorization"] +} + +func TestExecCreds(t *testing.T) { + t.Parallel() + for _, apiVersion := range []string{"client.authentication.k8s.io/v1", "client.authentication.k8s.io/v1beta1"} { + t.Run(apiVersion, func(t *testing.T) { + plugin, dir := testPlugin(t, apiVersion, clientcmdapi.ExecEnvVar{Name: pluginTTLEnv, Value: "1h"}) + cluster, err := execCluster("https://api.ate-system.svc", "api.ate-system.svc", "my-audience") + if err != nil { + t.Fatal(err) + } + creds, err := newExecCreds(plugin, cluster) + if err != nil { + t.Fatalf("newExecCreds: %v", err) + } + + if got := authorization(t, creds); got != "Bearer token-1" { + t.Errorf("authorization = %q, want Bearer token-1", got) + } + if got := authorization(t, creds); got != "Bearer token-1" { + t.Errorf("second authorization = %q, want the cached Bearer token-1", got) + } + if n := pluginRuns(t, dir); n != 1 { + t.Errorf("plugin ran %d times, want 1", n) + } + + b, err := os.ReadFile(filepath.Join(dir, "info-1.json")) + if err != nil { + t.Fatal(err) + } + var info clientauthv1.ExecCredential + if err := json.Unmarshal(b, &info); err != nil { + t.Fatal(err) + } + if info.APIVersion != apiVersion { + t.Errorf("KUBERNETES_EXEC_INFO apiVersion = %q, want %q", info.APIVersion, apiVersion) + } + var config struct{ Audience string } + if err := json.Unmarshal(info.Spec.Cluster.Config.Raw, &config); err != nil { + t.Fatalf("spec.cluster.config: %v", err) + } + if config.Audience != "my-audience" { + t.Errorf("spec.cluster.config.audience = %q, want my-audience", config.Audience) + } + + if !creds.refresh("Bearer token-1") { + t.Error("refresh = false, want true for a plugin that returns a new token") + } + if got := authorization(t, creds); got != "Bearer token-2" { + t.Errorf("authorization after refresh = %q, want Bearer token-2", got) + } + if n := pluginRuns(t, dir); n != 2 { + t.Errorf("plugin ran %d times, want 2", n) + } + }) + } +} + +func TestExecCredsExpiry(t *testing.T) { + t.Parallel() + plugin, dir := testPlugin(t, "client.authentication.k8s.io/v1", clientcmdapi.ExecEnvVar{Name: pluginTTLEnv, Value: "-1s"}) + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + authorization(t, creds) + if got := authorization(t, creds); got != "Bearer token-2" { + t.Errorf("authorization after expiry = %q, want Bearer token-2", got) + } + if n := pluginRuns(t, dir); n != 2 { + t.Errorf("plugin ran %d times, want 2", n) + } +} + +func TestExecCredsRefreshSameToken(t *testing.T) { + t.Parallel() + plugin, _ := testPlugin(t, "client.authentication.k8s.io/v1", clientcmdapi.ExecEnvVar{Name: pluginSameTokenEnv, Value: "1"}) + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + authorization(t, creds) + if creds.refresh("Bearer token-1") { + t.Error("refresh = true, want false when the plugin returns the rejected token again") + } +} + +func TestExecCredsRefreshCachedToken(t *testing.T) { + t.Parallel() + plugin, dir := testPlugin(t, "client.authentication.k8s.io/v1", clientcmdapi.ExecEnvVar{Name: pluginCachedTokenEnv, Value: "1"}) + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + authorization(t, creds) + if !creds.refresh("Bearer token-1") { + t.Error("refresh = false, want true once the plugin skips the rejected token") + } + if got := authorization(t, creds); got != "Bearer token-3" { + t.Errorf("authorization after refresh = %q, want Bearer token-3", got) + } + if n := pluginRuns(t, dir); n != 3 { + t.Errorf("plugin ran %d times, want 3", n) + } + + if !creds.refresh("Bearer token-3") { + t.Error("second refresh = false, want true") + } + if got := authorization(t, creds); got != "Bearer token-4" { + t.Errorf("authorization after the second refresh = %q, want Bearer token-4", got) + } + if n := pluginRuns(t, dir); n != 4 { + t.Errorf("plugin ran %d times, want 4", n) + } +} + +func TestExecCredsConcurrentRefresh(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + env clientcmdapi.ExecEnvVar + wantRuns int + }{ + {"new token", clientcmdapi.ExecEnvVar{Name: pluginTTLEnv, Value: "1h"}, 2}, + {"cached token", clientcmdapi.ExecEnvVar{Name: pluginCachedTokenEnv, Value: "1"}, 3}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + plugin, dir := testPlugin(t, "client.authentication.k8s.io/v1", tc.env) + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + authorization(t, creds) + var readers, refreshers sync.WaitGroup + done := make(chan struct{}) + for range 4 { + readers.Go(func() { + for { + select { + case <-done: + return + default: + } + if _, err := creds.GetRequestMetadata(context.Background()); err != nil { + t.Error(err) + } + if _, err := creds.clientCertificate(nil); err != nil { + t.Error(err) + } + } + }) + } + for range 8 { + refreshers.Go(func() { + if !creds.refresh("Bearer token-1") { + t.Error("refresh = false, want true") + } + }) + } + refreshers.Wait() + close(done) + readers.Wait() + if n := pluginRuns(t, dir); n != tc.wantRuns { + t.Errorf("plugin ran %d times, want %d", n, tc.wantRuns) + } + }) + } +} + +func TestExecCredsRefreshAlreadyRotated(t *testing.T) { + t.Parallel() + plugin, dir := testPlugin(t, "client.authentication.k8s.io/v1") + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + authorization(t, creds) + if !creds.refresh("Bearer token-0") { + t.Error("refresh = false, want true") + } + if n := pluginRuns(t, dir); n != 1 { + t.Errorf("plugin ran %d times, want 1: the rejected token was already replaced", n) + } +} + +func TestExecCredsClientCertificate(t *testing.T) { + t.Parallel() + ca := newTestCA(t) + certDir := t.TempDir() + certPEM, keyPEM := ca.issue(t, "client", nil) + if err := os.WriteFile(filepath.Join(certDir, "tls.crt"), certPEM, 0o600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(certDir, "tls.key"), keyPEM, 0o600); err != nil { + t.Fatal(err) + } + plugin, _ := testPlugin(t, "client.authentication.k8s.io/v1", clientcmdapi.ExecEnvVar{Name: pluginCertEnv, Value: certDir}) + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + cert, err := creds.clientCertificate(nil) + if err != nil { + t.Fatalf("clientCertificate: %v", err) + } + if cert.Leaf == nil || cert.Leaf.Subject.CommonName != "client" { + t.Errorf("clientCertificate leaf = %v, want CN=client", cert.Leaf) + } +} + +func TestExecCredsMissingPlugin(t *testing.T) { + t.Parallel() + creds, err := newExecCreds(&clientcmdapi.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1", + Command: "ateclient-test-missing-plugin", + InteractiveMode: clientcmdapi.NeverExecInteractiveMode, + }, nil) + if err != nil { + t.Fatal(err) + } + if cert, err := creds.clientCertificate(nil); err != nil || len(cert.Certificate) != 0 { + t.Errorf("clientCertificate = (%v, %v), want an empty certificate and no error", cert, err) + } + if _, err := creds.GetRequestMetadata(context.Background()); err == nil || !strings.Contains(err.Error(), "ateclient-test-missing-plugin") { + t.Errorf("GetRequestMetadata error = %v, want one naming the missing plugin", err) + } +} + +func TestMintedTokenCreds(t *testing.T) { + now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + mints := 0 + creds := newMintedTokenCreds(func(context.Context) (string, time.Time, error) { + mints++ + return fmt.Sprintf("token-%d", mints), now.Add(time.Hour), nil + }) + creds.now = func() time.Time { return now } + + if got := authorization(t, creds); got != "Bearer token-1" { + t.Errorf("authorization = %q, want Bearer token-1", got) + } + now = now.Add(58 * time.Minute) + if got := authorization(t, creds); got != "Bearer token-1" { + t.Errorf("authorization before the expiry margin = %q, want Bearer token-1", got) + } + now = now.Add(time.Minute) + if got := authorization(t, creds); got != "Bearer token-2" { + t.Errorf("authorization within the expiry margin = %q, want Bearer token-2", got) + } +} + +type failingCreds struct{ fakeCreds } + +func (failingCreds) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { + return nil, errors.New("plugin failed") +} + +func TestRetryUnauthenticatedCredentialError(t *testing.T) { + calls := 0 + invoker := func(context.Context, string, any, any, *grpc.ClientConn, ...grpc.CallOption) error { + calls++ + return nil + } + err := retryUnauthenticated(&failingCreds{})(context.Background(), "/m", nil, nil, nil, invoker) + if _, isStatus := status.FromError(err); isStatus || !strings.Contains(err.Error(), "plugin failed") { + t.Errorf("err = %v, want a local error, not a gRPC status, carrying the credential error", err) + } + if calls != 0 { + t.Errorf("invoker called %d times, want 0", calls) + } +} + +type fakeCreds struct { + bearerTokenCreds + refreshes int + canRetry bool +} + +func (f *fakeCreds) refresh(string) bool { + f.refreshes++ + return f.canRetry +} + +func TestRetryUnauthenticated(t *testing.T) { + for _, tc := range []struct { + name string + errs []error + canRetry bool + wantCode codes.Code + wantCalls int + }{ + {"success", []error{nil}, true, codes.OK, 1}, + {"retried", []error{status.Error(codes.Unauthenticated, ""), nil}, true, codes.OK, 2}, + {"retried once", []error{status.Error(codes.Unauthenticated, ""), status.Error(codes.Unauthenticated, "")}, true, codes.Unauthenticated, 2}, + {"cannot retry", []error{status.Error(codes.Unauthenticated, "")}, false, codes.Unauthenticated, 1}, + {"other error", []error{status.Error(codes.PermissionDenied, "")}, true, codes.PermissionDenied, 1}, + } { + t.Run(tc.name, func(t *testing.T) { + creds := &fakeCreds{bearerTokenCreds: "t", canRetry: tc.canRetry} + calls := 0 + invoker := func(context.Context, string, any, any, *grpc.ClientConn, ...grpc.CallOption) error { + err := tc.errs[calls] + calls++ + return err + } + err := retryUnauthenticated(creds)(context.Background(), "/m", nil, nil, nil, invoker) + if got := status.Code(err); got != tc.wantCode { + t.Errorf("code = %v, want %v", got, tc.wantCode) + } + if calls != tc.wantCalls { + t.Errorf("calls = %d, want %d", calls, tc.wantCalls) + } + }) + } +} + +// fakeAuthentication rejects the tokens in rejected and reports the bearer +// token as the caller's ID. +type fakeAuthentication struct { + ateapi.UnimplementedAuthenticationServer + mu sync.Mutex + rejected map[string]bool + calls int +} + +func (f *fakeAuthentication) WhoAmI(ctx context.Context, _ *ateapi.WhoAmIRequest) (*ateapi.WhoAmIResponse, error) { + md, _ := metadata.FromIncomingContext(ctx) + token := strings.TrimPrefix(strings.Join(md.Get("authorization"), ""), "Bearer ") + f.mu.Lock() + defer f.mu.Unlock() + f.calls++ + if token == "" || f.rejected[token] { + return nil, status.Error(codes.Unauthenticated, "rejected") + } + return &ateapi.WhoAmIResponse{Id: token, Kind: ateapi.PrincipalKind_PRINCIPAL_KIND_JWT}, nil +} + +func TestConnectExecCredential(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + env clientcmdapi.ExecEnvVar + wantID string + wantRuns int + }{ + {"new token", clientcmdapi.ExecEnvVar{Name: pluginTTLEnv, Value: "1h"}, "token-2", 2}, + {"cached token", clientcmdapi.ExecEnvVar{Name: pluginCachedTokenEnv, Value: "1"}, "token-3", 3}, + } { + t.Run(tc.name, func(t *testing.T) { + testConnectExecCredential(t, tc.env, tc.wantID, tc.wantRuns) + }) + } +} + +// connectFakeAuthentication connects with creds to a fakeAuthentication +// server that rejects token-1. +func connectFakeAuthentication(t *testing.T, creds credentials.PerRPCCredentials) (*fakeAuthentication, ateapi.AuthenticationClient) { + t.Helper() + ca := newTestCA(t) + certPEM, keyPEM := ca.issue(t, "ateapi.test", []string{"ateapi.test"}) + serverCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + t.Fatal(err) + } + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + srv := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{serverCert}, MinVersion: tls.VersionTLS13}))) + authn := &fakeAuthentication{rejected: map[string]bool{"token-1": true}} + ateapi.RegisterAuthenticationServer(srv, authn) + go srv.Serve(lis) + t.Cleanup(srv.Stop) + + roots := x509.NewCertPool() + roots.AppendCertsFromPEM(ca.pem) + tlsCfg := &tls.Config{MinVersion: tls.VersionTLS13, RootCAs: roots, ServerName: "ateapi.test"} + conn, err := connect(lis.Addr().String(), tlsCfg, creds, DialOptions{Endpoint: lis.Addr().String()}) + if err != nil { + t.Fatalf("connect: %v", err) + } + t.Cleanup(func() { conn.Close() }) + return authn, ateapi.NewAuthenticationClient(conn) +} + +func testConnectExecCredential(t *testing.T, env clientcmdapi.ExecEnvVar, wantID string, wantRuns int) { + plugin, dir := testPlugin(t, "client.authentication.k8s.io/v1", env) + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + _, client := connectFakeAuthentication(t, creds) + + resp, err := client.WhoAmI(context.Background(), &ateapi.WhoAmIRequest{}) + if err != nil { + t.Fatalf("WhoAmI: %v", err) + } + if resp.GetId() != wantID { + t.Errorf("WhoAmI id = %q, want %s after the rejected token-1 was replaced", resp.GetId(), wantID) + } + if n := pluginRuns(t, dir); n != wantRuns { + t.Errorf("plugin ran %d times, want %d", n, wantRuns) + } +} + +func TestConnectMintedTokenNotRetried(t *testing.T) { + t.Parallel() + mints := 0 + creds := newMintedTokenCreds(func(context.Context) (string, time.Time, error) { + mints++ + return fmt.Sprintf("token-%d", mints), time.Now().Add(time.Hour), nil + }) + authn, client := connectFakeAuthentication(t, creds) + + if _, err := client.WhoAmI(context.Background(), &ateapi.WhoAmIRequest{}); status.Code(err) != codes.Unauthenticated { + t.Fatalf("WhoAmI error = %v, want Unauthenticated for the rejected token-1", err) + } + authn.mu.Lock() + defer authn.mu.Unlock() + if authn.calls != 1 || mints != 1 { + t.Errorf("WhoAmI reached the server %d times after %d mints, want 1 and 1", authn.calls, mints) + } +} + +func TestConnectCredentialError(t *testing.T) { + t.Parallel() + authn, client := connectFakeAuthentication(t, fileBearerTokenCreds(filepath.Join(t.TempDir(), "missing"))) + _, err := client.WhoAmI(context.Background(), &ateapi.WhoAmIRequest{}) + if _, isStatus := status.FromError(err); isStatus || !strings.Contains(err.Error(), "missing") { + t.Errorf("WhoAmI error = %v, want a local error, not a gRPC status, naming the token file", err) + } + authn.mu.Lock() + defer authn.mu.Unlock() + if authn.calls != 0 { + t.Errorf("WhoAmI reached the server %d times, want 0", authn.calls) + } +} + +type testCA struct { + cert *x509.Certificate + key *ecdsa.PrivateKey + pem []byte +} + +func newTestCA(t *testing.T) *testCA { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "test-ca"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + IsCA: true, + KeyUsage: x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) + if err != nil { + t.Fatal(err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + t.Fatal(err) + } + return &testCA{cert: cert, key: key, pem: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})} +} + +func (ca *testCA) issue(t *testing.T, cn string, dnsNames []string) (certPEM, keyPEM []byte) { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{CommonName: cn}, + DNSNames: dnsNames, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, ca.cert, &key.PublicKey, ca.key) + if err != nil { + t.Fatal(err) + } + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + t.Fatal(err) + } + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) +} + +func TestClientCredentials(t *testing.T) { + plugin := &clientcmdapi.ExecConfig{APIVersion: "client.authentication.k8s.io/v1", Command: "plugin", InteractiveMode: clientcmdapi.NeverExecInteractiveMode} + clientset := fake.NewClientset() + + creds, err := clientCredentials(DialOptions{TokenFile: "/token", CredentialPlugin: plugin}, clientset) + if _, ok := creds.(fileBearerTokenCreds); err != nil || !ok { + t.Errorf("with a token file and a plugin: %T, %v; want the token file", creds, err) + } + creds, err = clientCredentials(DialOptions{Endpoint: "ateapi.example:443", CredentialPlugin: plugin}, clientset) + if c, ok := creds.(*execCreds); err != nil || !ok || c.cluster.Server != "https://ateapi.example:443" { + t.Errorf("with a plugin: %T, %v; want the plugin, told the endpoint", creds, err) + } + creds, err = clientCredentials(DialOptions{}, clientset) + if _, ok := creds.(*mintedTokenCreds); err != nil || !ok { + t.Errorf("with neither: %T, %v; want a minted token", creds, err) + } +} + +func TestConnectClientCertificate(t *testing.T) { + t.Parallel() + ca := newTestCA(t) + serverCertPEM, serverKeyPEM := ca.issue(t, "ateapi.test", []string{"ateapi.test"}) + serverCert, err := tls.X509KeyPair(serverCertPEM, serverKeyPEM) + if err != nil { + t.Fatal(err) + } + roots := x509.NewCertPool() + roots.AppendCertsFromPEM(ca.pem) + lis, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + srv := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{serverCert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: roots, + MinVersion: tls.VersionTLS13, + }))) + ateapi.RegisterAuthenticationServer(srv, &fakeAuthentication{}) + go srv.Serve(lis) + t.Cleanup(srv.Stop) + + certDir := t.TempDir() + clientCertPEM, clientKeyPEM := ca.issue(t, "client", nil) + if err := os.WriteFile(filepath.Join(certDir, "tls.crt"), clientCertPEM, 0o600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(certDir, "tls.key"), clientKeyPEM, 0o600); err != nil { + t.Fatal(err) + } + plugin, _ := testPlugin(t, "client.authentication.k8s.io/v1", clientcmdapi.ExecEnvVar{Name: pluginCertEnv, Value: certDir}) + creds, err := newExecCreds(plugin, nil) + if err != nil { + t.Fatal(err) + } + tlsCfg := &tls.Config{MinVersion: tls.VersionTLS13, RootCAs: roots, ServerName: "ateapi.test"} + conn, err := connect(lis.Addr().String(), tlsCfg, creds, DialOptions{Endpoint: lis.Addr().String()}) + if err != nil { + t.Fatalf("connect: %v", err) + } + defer conn.Close() + if _, err := ateapi.NewAuthenticationClient(conn).WhoAmI(context.Background(), &ateapi.WhoAmIRequest{}); err != nil { + t.Errorf("WhoAmI through a server that requires the plugin's client certificate: %v", err) + } +} diff --git a/internal/e2e/suites/authz/authz_test.go b/internal/e2e/suites/authz/authz_test.go index f6c9709833..70b771d5e3 100644 --- a/internal/e2e/suites/authz/authz_test.go +++ b/internal/e2e/suites/authz/authz_test.go @@ -31,8 +31,9 @@ import ( ) // TestAuthz deploys one fixture, the expensive part, and runs every check -// against it. The subtests run in order, not in parallel: each sets the -// fixture atespace's AccessPolicy for its own principals. +// against it. The subtests run in order, not in parallel: BasicFlow and +// SecurityRegression each set the fixture atespace's AccessPolicy for their +// own principals. func TestAuthz(t *testing.T) { env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") if err != nil { @@ -43,4 +44,5 @@ func TestAuthz(t *testing.T) { t.Run("BasicFlow", func(t *testing.T) { testBasicFlow(t, ctx, f) }) t.Run("SecurityRegression", func(t *testing.T) { testSecurityRegression(t, ctx, f) }) + t.Run("WhoAmI", func(t *testing.T) { testWhoAmI(t, ctx, f) }) } diff --git a/internal/e2e/suites/authz/helpers_test.go b/internal/e2e/suites/authz/helpers_test.go index f7d3137d28..0beac87cb7 100644 --- a/internal/e2e/suites/authz/helpers_test.go +++ b/internal/e2e/suites/authz/helpers_test.go @@ -24,7 +24,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - authv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,7 +31,6 @@ import ( "go.ate.dev/substrate/api/ateapi/v1" "go.ate.dev/substrate/internal/ateclient" "go.ate.dev/substrate/internal/e2e" - "go.ate.dev/substrate/internal/installdefaults" "go.ate.dev/substrate/internal/resources" ) @@ -150,21 +148,21 @@ func newPrincipal(t *testing.T, ctx context.Context, clients *e2e.Clients, names // for the existing ServiceAccount namespace/name. func principalFor(t *testing.T, ctx context.Context, clients *e2e.Clients, namespace, name string) *principal { t.Helper() - expiration := int64(3600) - token, err := clients.K8s.CoreV1().ServiceAccounts(namespace).CreateToken(ctx, name, &authv1.TokenRequest{ - Spec: authv1.TokenRequestSpec{ - Audiences: []string{installdefaults.APIServiceName + "." + installdefaults.SystemNamespace + ".svc"}, - ExpirationSeconds: &expiration, - }, - }, metav1.CreateOptions{}) - if err != nil { - t.Fatalf("minting a token for %s/%s: %v", namespace, name, err) - } return &principal{ name: name, member: fmt.Sprintf("user:system:serviceaccount:%s:%s", namespace, name), - api: clientWithToken(t, ctx, token.Status.Token), + api: clientWithToken(t, ctx, mintToken(t, ctx, clients, namespace, name)), + } +} + +// mintToken returns an ateapi token for the ServiceAccount namespace/name. +func mintToken(t *testing.T, ctx context.Context, clients *e2e.Clients, namespace, name string) string { + t.Helper() + token, _, err := ateclient.MintServiceAccountToken(ctx, clients.K8s, namespace, name, ateclient.DefaultAudience(), time.Hour) + if err != nil { + t.Fatal(err) } + return token } // clientWithToken returns an ateapi client that sends token as its bearer @@ -175,11 +173,19 @@ func clientWithToken(t *testing.T, ctx context.Context, token string) *ateclient if err := os.WriteFile(tokenFile, []byte(token), 0o600); err != nil { t.Fatalf("writing token: %v", err) } + return dial(t, ctx, ateclient.DialOptions{TokenFile: tokenFile}) +} + +// dial returns an ateapi client for the cluster under test, authenticated as +// opts describes. +func dial(t *testing.T, ctx context.Context, opts ateclient.DialOptions) *ateclient.Client { + t.Helper() + opts.Kubeconfig, opts.KubeContext = e2e.KubeConfig, e2e.KubeContext connectCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - api, err := ateclient.NewClient(connectCtx, e2e.KubeConfig, e2e.KubeContext, "", tokenFile, false) + api, err := ateclient.Dial(connectCtx, opts) if err != nil { - t.Fatalf("ateclient.NewClient: %v", err) + t.Fatalf("ateclient.Dial: %v", err) } t.Cleanup(api.Close) return api diff --git a/internal/e2e/suites/authz/whoami_test.go b/internal/e2e/suites/authz/whoami_test.go new file mode 100644 index 0000000000..6ea7349e9f --- /dev/null +++ b/internal/e2e/suites/authz/whoami_test.go @@ -0,0 +1,80 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authz + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientauthv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + + "go.ate.dev/substrate/api/ateapi/v1" + "go.ate.dev/substrate/internal/ateclient" +) + +// testWhoAmI checks the principal ateapi reports for one ServiceAccount's +// token, sent from a token file and from a credential plugin. +func testWhoAmI(t *testing.T, ctx context.Context, f *fixture) { + const name = "whoami" + p := newPrincipal(t, ctx, f.clients, f.atespace, name) + plugin := pluginClient(t, ctx, mintToken(t, ctx, f.clients, f.atespace, name)) + + want := strings.TrimPrefix(p.member, "user:") + for _, c := range []struct { + how string + api *ateclient.Client + }{ + {"token file", p.api}, + {"credential plugin", plugin}, + } { + resp, err := c.api.Authentication().WhoAmI(ctx, &ateapi.WhoAmIRequest{}) + if err != nil { + t.Errorf("%s: WhoAmI: %v", c.how, err) + continue + } + if resp.GetId() != want || resp.GetKind() != ateapi.PrincipalKind_PRINCIPAL_KIND_JWT || resp.GetIssuer() == "" { + t.Errorf("%s: WhoAmI = %v, want id %q, kind JWT, and an issuer", c.how, resp, want) + } + } +} + +// pluginClient returns an ateapi client whose credential plugin prints an +// ExecCredential carrying token. +func pluginClient(t *testing.T, ctx context.Context, token string) *ateclient.Client { + t.Helper() + cred, err := json.Marshal(clientauthv1.ExecCredential{ + TypeMeta: metav1.TypeMeta{APIVersion: "client.authentication.k8s.io/v1", Kind: "ExecCredential"}, + Status: &clientauthv1.ExecCredentialStatus{Token: token}, + }) + if err != nil { + t.Fatal(err) + } + credFile := filepath.Join(t.TempDir(), "credential.json") + if err := os.WriteFile(credFile, cred, 0o600); err != nil { + t.Fatalf("writing credential: %v", err) + } + return dial(t, ctx, ateclient.DialOptions{CredentialPlugin: &clientcmdapi.ExecConfig{ + APIVersion: "client.authentication.k8s.io/v1", + Command: "cat", + Args: []string{credFile}, + InteractiveMode: clientcmdapi.NeverExecInteractiveMode, + }}) +}