From 9a8f0701d064a55c59ca985c5c72a58adc2cd9de Mon Sep 17 00:00:00 2001 From: lhchavez Date: Thu, 31 Aug 2023 12:50:33 -0700 Subject: [PATCH] Add runtime information to the token (#24) We're going to use this soon. Added more fields. --- api/signing.pb.go | 547 +++++++++++++++++++++++++++++++++++----------- api/signing.proto | 46 +++- auth.go | 27 ++- sign.go | 3 + sign_test.go | 117 +++++++++- verify.go | 90 +++++++- 6 files changed, 673 insertions(+), 157 deletions(-) diff --git a/api/signing.pb.go b/api/signing.pb.go index 561bfaa..62e5e82 100644 --- a/api/signing.pb.go +++ b/api/signing.pb.go @@ -1,15 +1,15 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.19.4 +// protoc-gen-go v1.30.0 +// protoc v3.12.4 // source: api/signing.proto package api import ( + timestamp "github.com/golang/protobuf/ptypes/timestamp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -88,11 +88,19 @@ const ( // Cert has the authority to sign GovalToken messages that authorizes the // bearer to use Ghostwriter. FlagClaim_GHOSTWRITER FlagClaim = 6 + // Cert has the authority to sign ReplToken messages that claim to be from + // a deployment. + FlagClaim_DEPLOYMENTS FlagClaim = 10 // Cert has the authority to sign ReplToken messages for any ReplID. If this // claim is not set, the cert will only be able to emit tokens only for the // list explicitly enumerated by the other claims. If that list is empty, the // cert has no ability to sign any tokens. FlagClaim_ANY_REPLID FlagClaim = 2 + // Cert has the authority to sign ReplToken messages for any user id. If this + // claim is not set, the cert will only be able to emit tokens only for the + // list explicitly enumerated by the other claims. If that list is empty, the + // cert has no ability to sign any tokens that have a user id. + FlagClaim_ANY_USER_ID FlagClaim = 11 // Cert has the authority to sign ReplToken messages for any user. If this // claim is not set, the cert will only be able to emit tokens only for the // list explicitly enumerated by the other claims. If that list is empty, the @@ -103,20 +111,28 @@ const ( // list explicitly enumerated by the other claims. If that list is empty, the // cert has no ability to sign any tokens. FlagClaim_ANY_CLUSTER FlagClaim = 4 + // Cert has the authority to sign ReplToken messages for any subcluster. If + // this claim is not set, the cert will only be able to emit tokens only for + // the list explicitly enumerated by the other claims. If that list is empty, + // the cert has no ability to sign any tokens that have a subcluster. + FlagClaim_ANY_SUBCLUSTER FlagClaim = 9 ) // Enum value maps for FlagClaim. var ( FlagClaim_name = map[int32]string{ - 0: "MINT_GOVAL_TOKEN", - 1: "SIGN_INTERMEDIATE_CERT", - 5: "IDENTITY", - 7: "RENEW_IDENTITY", - 8: "RENEW_KV", - 6: "GHOSTWRITER", - 2: "ANY_REPLID", - 3: "ANY_USER", - 4: "ANY_CLUSTER", + 0: "MINT_GOVAL_TOKEN", + 1: "SIGN_INTERMEDIATE_CERT", + 5: "IDENTITY", + 7: "RENEW_IDENTITY", + 8: "RENEW_KV", + 6: "GHOSTWRITER", + 10: "DEPLOYMENTS", + 2: "ANY_REPLID", + 11: "ANY_USER_ID", + 3: "ANY_USER", + 4: "ANY_CLUSTER", + 9: "ANY_SUBCLUSTER", } FlagClaim_value = map[string]int32{ "MINT_GOVAL_TOKEN": 0, @@ -125,9 +141,12 @@ var ( "RENEW_IDENTITY": 7, "RENEW_KV": 8, "GHOSTWRITER": 6, + "DEPLOYMENTS": 10, "ANY_REPLID": 2, + "ANY_USER_ID": 11, "ANY_USER": 3, "ANY_CLUSTER": 4, + "ANY_SUBCLUSTER": 9, } ) @@ -166,6 +185,7 @@ type GovalSigningAuthority struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Cert: + // // *GovalSigningAuthority_KeyId // *GovalSigningAuthority_SignedCert Cert isGovalSigningAuthority_Cert `protobuf_oneof:"cert"` @@ -281,9 +301,13 @@ type CertificateClaim struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Claim: + // // *CertificateClaim_Replid // *CertificateClaim_User + // *CertificateClaim_UserId // *CertificateClaim_Cluster + // *CertificateClaim_Subcluster + // *CertificateClaim_Deployment // *CertificateClaim_Flag Claim isCertificateClaim_Claim `protobuf_oneof:"claim"` } @@ -341,6 +365,13 @@ func (x *CertificateClaim) GetUser() string { return "" } +func (x *CertificateClaim) GetUserId() int64 { + if x, ok := x.GetClaim().(*CertificateClaim_UserId); ok { + return x.UserId + } + return 0 +} + func (x *CertificateClaim) GetCluster() string { if x, ok := x.GetClaim().(*CertificateClaim_Cluster); ok { return x.Cluster @@ -348,6 +379,20 @@ func (x *CertificateClaim) GetCluster() string { return "" } +func (x *CertificateClaim) GetSubcluster() string { + if x, ok := x.GetClaim().(*CertificateClaim_Subcluster); ok { + return x.Subcluster + } + return "" +} + +func (x *CertificateClaim) GetDeployment() bool { + if x, ok := x.GetClaim().(*CertificateClaim_Deployment); ok { + return x.Deployment + } + return false +} + func (x *CertificateClaim) GetFlag() FlagClaim { if x, ok := x.GetClaim().(*CertificateClaim_Flag); ok { return x.Flag @@ -369,11 +414,27 @@ type CertificateClaim_User struct { User string `protobuf:"bytes,2,opt,name=user,proto3,oneof"` } +type CertificateClaim_UserId struct { + // This cert has the authority to sign messages on behalf of a user id + UserId int64 `protobuf:"varint,7,opt,name=user_id,json=userId,proto3,oneof"` +} + type CertificateClaim_Cluster struct { // This cert has the authority to sign messages in a certain cluster Cluster string `protobuf:"bytes,4,opt,name=cluster,proto3,oneof"` } +type CertificateClaim_Subcluster struct { + // This cert has the authority to sign messages in a certain cluster + Subcluster string `protobuf:"bytes,5,opt,name=subcluster,proto3,oneof"` +} + +type CertificateClaim_Deployment struct { + // This cert has the authority to sign messages that claim to come + // from a deployment. + Deployment bool `protobuf:"varint,6,opt,name=deployment,proto3,oneof"` +} + type CertificateClaim_Flag struct { // This cert has the authority to perform an action as described in // FlagClaim @@ -384,24 +445,31 @@ func (*CertificateClaim_Replid) isCertificateClaim_Claim() {} func (*CertificateClaim_User) isCertificateClaim_Claim() {} +func (*CertificateClaim_UserId) isCertificateClaim_Claim() {} + func (*CertificateClaim_Cluster) isCertificateClaim_Claim() {} +func (*CertificateClaim_Subcluster) isCertificateClaim_Claim() {} + +func (*CertificateClaim_Deployment) isCertificateClaim_Claim() {} + func (*CertificateClaim_Flag) isCertificateClaim_Claim() {} // GovalCert provides a mechanism of establishing a chain of trust without // requiring a single private key to be duplciated to all services that send // messages. The processes of generating intermediate certs is as follows: -// - A PASETO `v2.public` root keypair is generated and added to GSM with an -// arbitrary key id. -// - The root public key id is encoded in a GovalSigningAuthority -// - An intermediate PASETO `v2.public` keypair is generated -// - The intermediate public key is encoded in a GovalCert, along with -// information about the lifetime and claims of that cert. -// - The GovalCert is encoded in the body of a PASETO and signed with the root -// private key. The root signing authority is inserted into the footer of the -// PASETO to use for validation. -// - This signed PASETO is encoded in another GovalSigningAuthority and appended -// as the footer of PASETOs signed by the intermediate private key. +// - A PASETO `v2.public` root keypair is generated and added to GSM with an +// arbitrary key id. +// - The root public key id is encoded in a GovalSigningAuthority +// - An intermediate PASETO `v2.public` keypair is generated +// - The intermediate public key is encoded in a GovalCert, along with +// information about the lifetime and claims of that cert. +// - The GovalCert is encoded in the body of a PASETO and signed with the root +// private key. The root signing authority is inserted into the footer of the +// PASETO to use for validation. +// - This signed PASETO is encoded in another GovalSigningAuthority and appended +// as the footer of PASETOs signed by the intermediate private key. +// // Additional intermediate certs can be generated and signed by private key and // signing authority of the previous cert. // @@ -420,10 +488,10 @@ type GovalCert struct { // Issue timestamp. Equivalent to JWT's "iat" (Issued At) claim. Tokens with // no `iat` field will be treated as if they had been issed at the UNIX epoch // (1970-01-01T00:00:00Z). - Iat *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=iat,proto3" json:"iat,omitempty"` + Iat *timestamp.Timestamp `protobuf:"bytes,1,opt,name=iat,proto3" json:"iat,omitempty"` // Expiration timestamp. Equivalent to JWT's "exp" (Expiration Time) Claim. // If unset, will default to one hour after `iat`. - Exp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=exp,proto3" json:"exp,omitempty"` + Exp *timestamp.Timestamp `protobuf:"bytes,2,opt,name=exp,proto3" json:"exp,omitempty"` // A list of claims this cert can authorize Claims []*CertificateClaim `protobuf:"bytes,3,rep,name=claims,proto3" json:"claims,omitempty"` // The PASETO `v2.public` (Ed25519) public key authorized to sign requests in @@ -465,14 +533,14 @@ func (*GovalCert) Descriptor() ([]byte, []int) { return file_api_signing_proto_rawDescGZIP(), []int{2} } -func (x *GovalCert) GetIat() *timestamppb.Timestamp { +func (x *GovalCert) GetIat() *timestamp.Timestamp { if x != nil { return x.Iat } return nil } -func (x *GovalCert) GetExp() *timestamppb.Timestamp { +func (x *GovalCert) GetExp() *timestamp.Timestamp { if x != nil { return x.Exp } @@ -502,10 +570,10 @@ type GovalToken struct { // Issue timestamp. Equivalent to JWT's "iat" (Issued At) claim. Tokens with // no `iat` field will be treated as if they had been issed at the UNIX epoch // (1970-01-01T00:00:00Z). - Iat *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=iat,proto3" json:"iat,omitempty"` + Iat *timestamp.Timestamp `protobuf:"bytes,1,opt,name=iat,proto3" json:"iat,omitempty"` // Expiration timestamp. Equivalent to JWT's "exp" (Expiration Time) Claim. // If unset, will default to one hour after `iat`. - Exp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=exp,proto3" json:"exp,omitempty"` + Exp *timestamp.Timestamp `protobuf:"bytes,2,opt,name=exp,proto3" json:"exp,omitempty"` // Tokens are only allowed to act for a single repl, replid is the repl that // this token is authorized for. The validator must check that the replid of // this token agrees with the claims in any of the certs signing it. @@ -515,6 +583,7 @@ type GovalToken struct { // fields. ReplToken has its own iat, exp, and replid for legacy reasons. // // Types that are assignable to Token: + // // *GovalToken_ReplToken // *GovalToken_ReplIdentity Token isGovalToken_Token `protobuf_oneof:"Token"` @@ -552,14 +621,14 @@ func (*GovalToken) Descriptor() ([]byte, []int) { return file_api_signing_proto_rawDescGZIP(), []int{3} } -func (x *GovalToken) GetIat() *timestamppb.Timestamp { +func (x *GovalToken) GetIat() *timestamp.Timestamp { if x != nil { return x.Iat } return nil } -func (x *GovalToken) GetExp() *timestamppb.Timestamp { +func (x *GovalToken) GetExp() *timestamp.Timestamp { if x != nil { return x.Exp } @@ -641,6 +710,17 @@ type GovalReplIdentity struct { // If this is a build repl for a hosting deployment, include extra // information about the specs of the build BuildInfo *BuildInfo `protobuf:"bytes,8,opt,name=build_info,json=buildInfo,proto3" json:"build_info,omitempty"` + // A boolean indicating if the owner of the repl is a team. + IsTeam bool `protobuf:"varint,9,opt,name=is_team,json=isTeam,proto3" json:"is_team,omitempty"` + // A list of roles for the user who owns the repl. + Roles []string `protobuf:"bytes,10,rep,name=roles,proto3" json:"roles,omitempty"` + // Runtime information about the Repl. + // + // Types that are assignable to Runtime: + // + // *GovalReplIdentity_Interactive + // *GovalReplIdentity_Deployment + Runtime isGovalReplIdentity_Runtime `protobuf_oneof:"runtime"` } func (x *GovalReplIdentity) Reset() { @@ -731,6 +811,60 @@ func (x *GovalReplIdentity) GetBuildInfo() *BuildInfo { return nil } +func (x *GovalReplIdentity) GetIsTeam() bool { + if x != nil { + return x.IsTeam + } + return false +} + +func (x *GovalReplIdentity) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (m *GovalReplIdentity) GetRuntime() isGovalReplIdentity_Runtime { + if m != nil { + return m.Runtime + } + return nil +} + +func (x *GovalReplIdentity) GetInteractive() *ReplRuntimeInteractive { + if x, ok := x.GetRuntime().(*GovalReplIdentity_Interactive); ok { + return x.Interactive + } + return nil +} + +func (x *GovalReplIdentity) GetDeployment() *ReplRuntimeDeployment { + if x, ok := x.GetRuntime().(*GovalReplIdentity_Deployment); ok { + return x.Deployment + } + return nil +} + +type isGovalReplIdentity_Runtime interface { + isGovalReplIdentity_Runtime() +} + +type GovalReplIdentity_Interactive struct { + // This is set if the Repl is running interactively. This is not set when + // the Repl is running in hosting. + Interactive *ReplRuntimeInteractive `protobuf:"bytes,11,opt,name=interactive,proto3,oneof"` +} + +type GovalReplIdentity_Deployment struct { + // This is set if the Repl is running in a Deployment. + Deployment *ReplRuntimeDeployment `protobuf:"bytes,12,opt,name=deployment,proto3,oneof"` +} + +func (*GovalReplIdentity_Interactive) isGovalReplIdentity_Runtime() {} + +func (*GovalReplIdentity_Deployment) isGovalReplIdentity_Runtime() {} + // BuildInfo includes information about which deployment this repl is allowed to // create or update. type BuildInfo struct { @@ -809,6 +943,101 @@ func (x *BuildInfo) GetMachineTier() string { return "" } +type ReplRuntimeInteractive struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The cluster in which this Repl is running. + Cluster string `protobuf:"bytes,1,opt,name=cluster,proto3" json:"cluster,omitempty"` + // The subcluster in which this Repl is running. + Subcluster string `protobuf:"bytes,2,opt,name=subcluster,proto3" json:"subcluster,omitempty"` +} + +func (x *ReplRuntimeInteractive) Reset() { + *x = ReplRuntimeInteractive{} + if protoimpl.UnsafeEnabled { + mi := &file_api_signing_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReplRuntimeInteractive) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReplRuntimeInteractive) ProtoMessage() {} + +func (x *ReplRuntimeInteractive) ProtoReflect() protoreflect.Message { + mi := &file_api_signing_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReplRuntimeInteractive.ProtoReflect.Descriptor instead. +func (*ReplRuntimeInteractive) Descriptor() ([]byte, []int) { + return file_api_signing_proto_rawDescGZIP(), []int{6} +} + +func (x *ReplRuntimeInteractive) GetCluster() string { + if x != nil { + return x.Cluster + } + return "" +} + +func (x *ReplRuntimeInteractive) GetSubcluster() string { + if x != nil { + return x.Subcluster + } + return "" +} + +type ReplRuntimeDeployment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ReplRuntimeDeployment) Reset() { + *x = ReplRuntimeDeployment{} + if protoimpl.UnsafeEnabled { + mi := &file_api_signing_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReplRuntimeDeployment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReplRuntimeDeployment) ProtoMessage() {} + +func (x *ReplRuntimeDeployment) ProtoReflect() protoreflect.Message { + mi := &file_api_signing_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReplRuntimeDeployment.ProtoReflect.Descriptor instead. +func (*ReplRuntimeDeployment) Descriptor() ([]byte, []int) { + return file_api_signing_proto_rawDescGZIP(), []int{7} +} + var File_api_signing_proto protoreflect.FileDescriptor var file_api_signing_proto_rawDesc = []byte{ @@ -826,85 +1055,112 @@ var file_api_signing_proto_rawDesc = []byte{ 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x42, 0x06, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x22, 0x8d, + 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x42, 0x06, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x22, 0xec, 0x01, 0x0a, 0x10, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x18, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x75, - 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, - 0x24, 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x48, 0x00, 0x52, - 0x04, 0x66, 0x6c, 0x61, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x22, 0xb4, - 0x01, 0x0a, 0x09, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, - 0x69, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x69, 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x78, - 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x78, 0x70, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, - 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, - 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x0a, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c, 0x0a, 0x03, 0x69, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x69, - 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x78, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x78, 0x70, - 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6c, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x00, 0x52, 0x09, - 0x72, 0x65, 0x70, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x3d, 0x0a, 0x0d, 0x72, 0x65, 0x70, - 0x6c, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x70, 0x6c, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x65, 0x70, 0x6c, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x07, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0xef, 0x01, 0x0a, 0x11, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, - 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x75, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x75, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x70, 0x68, - 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x70, - 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, - 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, - 0x6e, 0x66, 0x6f, 0x22, 0x80, 0x01, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, - 0x64, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x74, - 0x69, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, - 0x6e, 0x65, 0x54, 0x69, 0x65, 0x72, 0x2a, 0x39, 0x0a, 0x0c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x0a, 0x0f, 0x42, 0x41, 0x52, 0x45, 0x5f, 0x52, - 0x45, 0x50, 0x4c, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x41, 0x57, 0x41, 0x52, 0x45, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, - 0x01, 0x2a, 0xad, 0x01, 0x0a, 0x09, 0x46, 0x6c, 0x61, 0x67, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, - 0x14, 0x0a, 0x10, 0x4d, 0x49, 0x4e, 0x54, 0x5f, 0x47, 0x4f, 0x56, 0x41, 0x4c, 0x5f, 0x54, 0x4f, - 0x4b, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x49, 0x4e, - 0x54, 0x45, 0x52, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x10, - 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0x05, 0x12, - 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x4e, 0x45, 0x57, 0x5f, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x54, - 0x59, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4e, 0x45, 0x57, 0x5f, 0x4b, 0x56, 0x10, - 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x48, 0x4f, 0x53, 0x54, 0x57, 0x52, 0x49, 0x54, 0x45, 0x52, - 0x10, 0x06, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x4e, 0x59, 0x5f, 0x52, 0x45, 0x50, 0x4c, 0x49, 0x44, - 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x4e, 0x59, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x03, - 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x59, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x10, - 0x04, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x72, 0x65, 0x70, 0x6c, 0x69, 0x74, 0x2f, 0x67, 0x6f, 0x2d, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x73, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0a, 0x73, 0x75, + 0x62, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x0a, 0x73, 0x75, 0x62, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0a, + 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, + 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x24, + 0x0a, 0x04, 0x66, 0x6c, 0x61, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x04, + 0x66, 0x6c, 0x61, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x22, 0xb4, 0x01, + 0x0a, 0x09, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x43, 0x65, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x69, + 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x69, 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x78, 0x70, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x03, 0x65, 0x78, 0x70, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x52, 0x06, + 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x0a, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x2c, 0x0a, 0x03, 0x69, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x69, 0x61, + 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x78, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x78, 0x70, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6c, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x00, 0x52, 0x09, 0x72, + 0x65, 0x70, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x3d, 0x0a, 0x0d, 0x72, 0x65, 0x70, 0x6c, + 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x65, 0x70, 0x6c, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x07, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0xa8, 0x03, 0x0a, 0x11, 0x47, 0x6f, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x75, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x75, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x70, 0x68, 0x65, + 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x70, 0x68, + 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, + 0x52, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x66, + 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x75, + 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x54, 0x65, 0x61, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x72, + 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x12, 0x3f, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x70, + 0x6c, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x12, 0x3c, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x70, + 0x6c, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x42, 0x09, 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x09, + 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x70, + 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x54, 0x69, 0x65, 0x72, 0x22, 0x52, + 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6c, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x70, 0x6c, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2a, 0x39, 0x0a, 0x0c, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x0a, 0x0f, 0x42, + 0x41, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x50, 0x4c, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x00, + 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x57, 0x41, 0x52, 0x45, 0x5f, 0x54, + 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x01, 0x2a, 0xe3, 0x01, 0x0a, 0x09, 0x46, 0x6c, 0x61, 0x67, 0x43, + 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x49, 0x4e, 0x54, 0x5f, 0x47, 0x4f, 0x56, + 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x49, + 0x47, 0x4e, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4d, 0x45, 0x44, 0x49, 0x41, 0x54, 0x45, 0x5f, + 0x43, 0x45, 0x52, 0x54, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x49, + 0x54, 0x59, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x4e, 0x45, 0x57, 0x5f, 0x49, 0x44, + 0x45, 0x4e, 0x54, 0x49, 0x54, 0x59, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4e, 0x45, + 0x57, 0x5f, 0x4b, 0x56, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x48, 0x4f, 0x53, 0x54, 0x57, + 0x52, 0x49, 0x54, 0x45, 0x52, 0x10, 0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x45, 0x50, 0x4c, 0x4f, + 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x53, 0x10, 0x0a, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x4e, 0x59, 0x5f, + 0x52, 0x45, 0x50, 0x4c, 0x49, 0x44, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x59, 0x5f, + 0x55, 0x53, 0x45, 0x52, 0x5f, 0x49, 0x44, 0x10, 0x0b, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x4e, 0x59, + 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x59, 0x5f, 0x43, + 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x4e, 0x59, 0x5f, + 0x53, 0x55, 0x42, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x10, 0x09, 0x42, 0x27, 0x5a, 0x25, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6c, 0x69, + 0x74, 0x2f, 0x67, 0x6f, 0x2d, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -920,35 +1176,39 @@ func file_api_signing_proto_rawDescGZIP() []byte { } var file_api_signing_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_api_signing_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_api_signing_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_api_signing_proto_goTypes = []interface{}{ - (TokenVersion)(0), // 0: api.TokenVersion - (FlagClaim)(0), // 1: api.FlagClaim - (*GovalSigningAuthority)(nil), // 2: api.GovalSigningAuthority - (*CertificateClaim)(nil), // 3: api.CertificateClaim - (*GovalCert)(nil), // 4: api.GovalCert - (*GovalToken)(nil), // 5: api.GovalToken - (*GovalReplIdentity)(nil), // 6: api.GovalReplIdentity - (*BuildInfo)(nil), // 7: api.BuildInfo - (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp - (*ReplToken)(nil), // 9: api.ReplToken + (TokenVersion)(0), // 0: api.TokenVersion + (FlagClaim)(0), // 1: api.FlagClaim + (*GovalSigningAuthority)(nil), // 2: api.GovalSigningAuthority + (*CertificateClaim)(nil), // 3: api.CertificateClaim + (*GovalCert)(nil), // 4: api.GovalCert + (*GovalToken)(nil), // 5: api.GovalToken + (*GovalReplIdentity)(nil), // 6: api.GovalReplIdentity + (*BuildInfo)(nil), // 7: api.BuildInfo + (*ReplRuntimeInteractive)(nil), // 8: api.ReplRuntimeInteractive + (*ReplRuntimeDeployment)(nil), // 9: api.ReplRuntimeDeployment + (*timestamp.Timestamp)(nil), // 10: google.protobuf.Timestamp + (*ReplToken)(nil), // 11: api.ReplToken } var file_api_signing_proto_depIdxs = []int32{ 0, // 0: api.GovalSigningAuthority.version:type_name -> api.TokenVersion 1, // 1: api.CertificateClaim.flag:type_name -> api.FlagClaim - 8, // 2: api.GovalCert.iat:type_name -> google.protobuf.Timestamp - 8, // 3: api.GovalCert.exp:type_name -> google.protobuf.Timestamp + 10, // 2: api.GovalCert.iat:type_name -> google.protobuf.Timestamp + 10, // 3: api.GovalCert.exp:type_name -> google.protobuf.Timestamp 3, // 4: api.GovalCert.claims:type_name -> api.CertificateClaim - 8, // 5: api.GovalToken.iat:type_name -> google.protobuf.Timestamp - 8, // 6: api.GovalToken.exp:type_name -> google.protobuf.Timestamp - 9, // 7: api.GovalToken.repl_token:type_name -> api.ReplToken + 10, // 5: api.GovalToken.iat:type_name -> google.protobuf.Timestamp + 10, // 6: api.GovalToken.exp:type_name -> google.protobuf.Timestamp + 11, // 7: api.GovalToken.repl_token:type_name -> api.ReplToken 6, // 8: api.GovalToken.repl_identity:type_name -> api.GovalReplIdentity 7, // 9: api.GovalReplIdentity.build_info:type_name -> api.BuildInfo - 10, // [10:10] is the sub-list for method output_type - 10, // [10:10] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 8, // 10: api.GovalReplIdentity.interactive:type_name -> api.ReplRuntimeInteractive + 9, // 11: api.GovalReplIdentity.deployment:type_name -> api.ReplRuntimeDeployment + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_api_signing_proto_init() } @@ -1030,6 +1290,30 @@ func file_api_signing_proto_init() { return nil } } + file_api_signing_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReplRuntimeInteractive); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_signing_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReplRuntimeDeployment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_api_signing_proto_msgTypes[0].OneofWrappers = []interface{}{ (*GovalSigningAuthority_KeyId)(nil), @@ -1038,20 +1322,27 @@ func file_api_signing_proto_init() { file_api_signing_proto_msgTypes[1].OneofWrappers = []interface{}{ (*CertificateClaim_Replid)(nil), (*CertificateClaim_User)(nil), + (*CertificateClaim_UserId)(nil), (*CertificateClaim_Cluster)(nil), + (*CertificateClaim_Subcluster)(nil), + (*CertificateClaim_Deployment)(nil), (*CertificateClaim_Flag)(nil), } file_api_signing_proto_msgTypes[3].OneofWrappers = []interface{}{ (*GovalToken_ReplToken)(nil), (*GovalToken_ReplIdentity)(nil), } + file_api_signing_proto_msgTypes[4].OneofWrappers = []interface{}{ + (*GovalReplIdentity_Interactive)(nil), + (*GovalReplIdentity_Deployment)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_signing_proto_rawDesc, NumEnums: 2, - NumMessages: 6, + NumMessages: 8, NumExtensions: 0, NumServices: 0, }, diff --git a/api/signing.proto b/api/signing.proto index 95654d2..1302e0b 100644 --- a/api/signing.proto +++ b/api/signing.proto @@ -58,12 +58,22 @@ enum FlagClaim { // bearer to use Ghostwriter. GHOSTWRITER = 6; + // Cert has the authority to sign ReplToken messages that claim to be from + // a deployment. + DEPLOYMENTS = 10; + // Cert has the authority to sign ReplToken messages for any ReplID. If this // claim is not set, the cert will only be able to emit tokens only for the // list explicitly enumerated by the other claims. If that list is empty, the // cert has no ability to sign any tokens. ANY_REPLID = 2; + // Cert has the authority to sign ReplToken messages for any user id. If this + // claim is not set, the cert will only be able to emit tokens only for the + // list explicitly enumerated by the other claims. If that list is empty, the + // cert has no ability to sign any tokens that have a user id. + ANY_USER_ID = 11; + // Cert has the authority to sign ReplToken messages for any user. If this // claim is not set, the cert will only be able to emit tokens only for the // list explicitly enumerated by the other claims. If that list is empty, the @@ -75,6 +85,12 @@ enum FlagClaim { // list explicitly enumerated by the other claims. If that list is empty, the // cert has no ability to sign any tokens. ANY_CLUSTER = 4; + + // Cert has the authority to sign ReplToken messages for any subcluster. If + // this claim is not set, the cert will only be able to emit tokens only for + // the list explicitly enumerated by the other claims. If that list is empty, + // the cert has no ability to sign any tokens that have a subcluster. + ANY_SUBCLUSTER = 9; } // Claims are actions that a cert is allowed to do. Claims can be repeated (e.g. @@ -95,8 +111,15 @@ message CertificateClaim { string replid = 1; // This cert has the authority to sign messages on behalf of a user string user = 2; + // This cert has the authority to sign messages on behalf of a user id + int64 user_id = 7; // This cert has the authority to sign messages in a certain cluster string cluster = 4; + // This cert has the authority to sign messages in a certain cluster + string subcluster = 5; + // This cert has the authority to sign messages that claim to come + // from a deployment. + bool deployment = 6; // This cert has the authority to perform an action as described in // FlagClaim FlagClaim flag = 3; @@ -200,6 +223,18 @@ message GovalReplIdentity { // If this is a build repl for a hosting deployment, include extra // information about the specs of the build BuildInfo build_info = 8; + // A boolean indicating if the owner of the repl is a team. + bool is_team = 9; + // A list of roles for the user who owns the repl. + repeated string roles = 10; + // Runtime information about the Repl. + oneof runtime { + // This is set if the Repl is running interactively. This is not set when + // the Repl is running in hosting. + ReplRuntimeInteractive interactive = 11; + // This is set if the Repl is running in a Deployment. + ReplRuntimeDeployment deployment = 12; + } } // BuildInfo includes information about which deployment this repl is allowed to @@ -217,4 +252,13 @@ message BuildInfo { // Tier refers to the GCE machine tier that will be used for the build string machine_tier = 4; -} \ No newline at end of file +} + +message ReplRuntimeInteractive { + // The cluster in which this Repl is running. + string cluster = 1; + // The subcluster in which this Repl is running. + string subcluster = 2; +} + +message ReplRuntimeDeployment {} diff --git a/auth.go b/auth.go index f65ebe5..25c5702 100644 --- a/auth.go +++ b/auth.go @@ -17,10 +17,12 @@ type PubKeySource func(keyid, issuer string) (ed25519.PublicKey, error) // MessageClaims is a collection of indexable claims that are made by a certificate. type MessageClaims struct { - Repls map[string]struct{} - Users map[string]struct{} - Clusters map[string]struct{} - Flags map[api.FlagClaim]struct{} + Repls map[string]struct{} + Users map[string]struct{} + UserIDs map[int64]struct{} + Clusters map[string]struct{} + Subclusters map[string]struct{} + Flags map[api.FlagClaim]struct{} } func parseClaims(cert *api.GovalCert) *MessageClaims { @@ -29,9 +31,12 @@ func parseClaims(cert *api.GovalCert) *MessageClaims { } claims := MessageClaims{ - Repls: map[string]struct{}{}, - Users: map[string]struct{}{}, - Flags: map[api.FlagClaim]struct{}{}, + Repls: map[string]struct{}{}, + Users: map[string]struct{}{}, + UserIDs: map[int64]struct{}{}, + Clusters: map[string]struct{}{}, + Subclusters: map[string]struct{}{}, + Flags: map[api.FlagClaim]struct{}{}, } for _, claim := range cert.Claims { @@ -42,8 +47,14 @@ func parseClaims(cert *api.GovalCert) *MessageClaims { case *api.CertificateClaim_User: claims.Users[typedClaim.User] = struct{}{} + case *api.CertificateClaim_UserId: + claims.UserIDs[typedClaim.UserId] = struct{}{} + case *api.CertificateClaim_Cluster: - claims.Users[typedClaim.Cluster] = struct{}{} + claims.Clusters[typedClaim.Cluster] = struct{}{} + + case *api.CertificateClaim_Subcluster: + claims.Subclusters[typedClaim.Subcluster] = struct{}{} case *api.CertificateClaim_Flag: claims.Flags[typedClaim.Flag] = struct{}{} diff --git a/sign.go b/sign.go index 8a1c633..e963e14 100644 --- a/sign.go +++ b/sign.go @@ -88,6 +88,9 @@ func (a *SigningAuthority) Sign(audience string) (string, error) { OriginReplid: a.identity.OriginReplid, UserId: a.identity.UserId, BuildInfo: a.identity.BuildInfo, + IsTeam: a.identity.IsTeam, + Roles: a.identity.Roles, + Runtime: a.identity.Runtime, } token, err := signIdentity(a.privateKey, a.signingAuthority, &replIdentity) diff --git a/sign_test.go b/sign_test.go index 6e92774..8c79a21 100644 --- a/sign_test.go +++ b/sign_test.go @@ -21,8 +21,8 @@ import ( const ( developmentKeyID = "dev:1" developmentPublicKey = "on0FkSmEC+ce40V9Vc4QABXSx6TXo+lhp99b6Ka0gro=" - conmanPrivateKey = "iRnuDeX+Dxum5UR8KMZbhtgUQ55PNOHzhJ5/RwqtiQw8AO6GbqzhvRRPB6SfYrev568iOAyJsiVgtdyiOB6YTQ==" - conmanCertificate = "GAEiBmNvbm1hbhLGAnYyLnB1YmxpYy5RMmR6U1d4bWRVaHZVVmxST1RaeVdsbFNTVXhEUzFScGFreEJSMFZLUTNneVYwVmhRV2huUWtkblNWbENVbTlEUjBGallVRm9aMGxIWjBsWlFXaHZRMGRCVFdGQmFHZEZTV3BXY2sxcE5YZGtWMHB6WVZkTmRWVkZSa1ZrVjJoMFRtNU5NRmxxUWxaV1NHUnNZVEkwZVZONlRubE1WMVl5VTFkd2JsUlhiR2xUVjNoYVZFWm9hbUl5Y0c1YVZ6RkdUVUU5UFhaTklYOURrRFlocGk0SE9jMng2V2lNdV9qY3Y3MEFQd3A2Q2ZpbU1oejVjYWJoU0wzckdqQjN1cDBaSVp5M0tzWW9NelJSNjRtNTFLRmZqbVFNQkFvLlIwRkZhVUp0VG5aaWJURm9ZbWR2UmxwSFZqSlBha1U5" + conmanPrivateKey = "RUHv3W4zXXdJ/3YqDm5xt1YLP823XrMoMt5xcDQ6ENeb1lim6G5Nry/KwfqucvyuIMGSIBpXYIlb/RH7mWnSLQ==" + conmanCertificate = "GAEiBmNvbm1hbhLhAnYyLnB1YmxpYy5RMmQzU1ROdk0wUndkMWxSZUdJM09USlJTVk5FUVdwMGVXRlBTRVZvUkV0NWRqTmFRV2h2UTBkQlJXRkJhR2RHUjJkSldVSjRiME5IUVdkaFFXaG5RMGRuU1ZsQmVHOURSMEZ6WVVSVFNVeGFSMVl5V2xkNGRtTkhNV3hpYmxGcFRsZHplVXh1UWpGWmJYaHdXWGsxZEU5V2NGcGpTRlp2WkZaU2FFOUlXalZqTUdjeVkyMDFUVTlJU25CU1JVcHlZVlZHYUZacVNrUlRiR1JtVFVaSmRFNVhlSGROUjJ0MzJnSlIyNW90ZDBRbGVXdVJnN2x5M2ozUXV3RUNJRDRMTWp6cTV6U2RtWDRNRlpKb2ZIa0lOSFMxNWlhSEFFZ05XTlJjUzRpOFk4T2xza0pCTjZtMkJnLlIwRkZhVUp0VG5aaWJURm9ZbWR2UmxwSFZqSlBha1U5" ) func generateIntermediateCert( @@ -84,16 +84,19 @@ func generateIntermediateCert( func identityToken( replID string, user string, + userID int64, slug string, ) (ed25519.PrivateKey, string, error) { return tokenWithClaims( replID, user, + userID, slug, []*api.CertificateClaim{ {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_IDENTITY}}, {Claim: &api.CertificateClaim_Replid{Replid: replID}}, {Claim: &api.CertificateClaim_User{User: user}}, + {Claim: &api.CertificateClaim_UserId{UserId: userID}}, }, ) } @@ -101,16 +104,19 @@ func identityToken( func renewalToken( replID string, user string, + userID int64, slug string, ) (ed25519.PrivateKey, string, error) { return tokenWithClaims( replID, user, + userID, slug, []*api.CertificateClaim{ {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_RENEW_IDENTITY}}, {Claim: &api.CertificateClaim_Replid{Replid: replID}}, {Claim: &api.CertificateClaim_User{User: user}}, + {Claim: &api.CertificateClaim_UserId{UserId: userID}}, }, ) } @@ -118,12 +124,14 @@ func renewalToken( func tokenWithClaims( replID string, user string, + userID int64, slug string, claims []*api.CertificateClaim, ) (ed25519.PrivateKey, string, error) { replIdentity := api.GovalReplIdentity{ Replid: replID, User: user, + UserId: userID, Slug: slug, Aud: replID, } @@ -167,12 +175,14 @@ func tokenWithClaims( func identityTokenWithOrigin( replID string, user string, + userID int64, slug string, originID string, ) (ed25519.PrivateKey, string, error) { replIdentity := api.GovalReplIdentity{ Replid: replID, User: user, + UserId: userID, Slug: slug, Aud: replID, OriginReplid: originID, @@ -200,6 +210,7 @@ func identityTokenWithOrigin( {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_IDENTITY}}, {Claim: &api.CertificateClaim_Replid{Replid: replIdentity.Replid}}, {Claim: &api.CertificateClaim_User{User: replIdentity.User}}, + {Claim: &api.CertificateClaim_UserId{UserId: replIdentity.UserId}}, }, "conman", 36*time.Hour, // Repls can not live for more than 20-ish hours at the moment. @@ -220,11 +231,13 @@ func identityTokenWithOrigin( func identityTokenAnyRepl( replID string, user string, + userID int64, slug string, ) (ed25519.PrivateKey, string, error) { replIdentity := api.GovalReplIdentity{ Replid: replID, User: user, + UserId: userID, Slug: slug, Aud: replID, } @@ -251,7 +264,9 @@ func identityTokenAnyRepl( {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_IDENTITY}}, {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_RENEW_IDENTITY}}, {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_ANY_REPLID}}, + {Claim: &api.CertificateClaim_Cluster{Cluster: "development"}}, {Claim: &api.CertificateClaim_User{User: replIdentity.User}}, + {Claim: &api.CertificateClaim_UserId{UserId: replIdentity.UserId}}, }, "conman", 36*time.Hour, // Repls can not live for more than 20-ish hours at the moment. @@ -273,11 +288,13 @@ func identityTokenAnyRepl( func multiTierIdentityToken( replID string, user string, + userID int64, slug string, ) (ed25519.PrivateKey, string, error) { replIdentity := api.GovalReplIdentity{ Replid: replID, User: user, + UserId: userID, Slug: slug, Aud: replID, } @@ -304,6 +321,7 @@ func multiTierIdentityToken( {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_IDENTITY}}, {Claim: &api.CertificateClaim_Replid{Replid: replIdentity.Replid}}, {Claim: &api.CertificateClaim_User{User: replIdentity.User}}, + {Claim: &api.CertificateClaim_UserId{UserId: replIdentity.UserId}}, }, "conman", 36*time.Hour, // Repls can not live for more than 20-ish hours at the moment. @@ -319,6 +337,7 @@ func multiTierIdentityToken( {Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_IDENTITY}}, {Claim: &api.CertificateClaim_Replid{Replid: replIdentity.Replid + "-spoofed"}}, {Claim: &api.CertificateClaim_User{User: replIdentity.User}}, + {Claim: &api.CertificateClaim_UserId{UserId: replIdentity.UserId}}, }, "conman", 36*time.Hour, // Repls can not live for more than 20-ish hours at the moment. @@ -336,7 +355,7 @@ func multiTierIdentityToken( } func TestIdentity(t *testing.T) { - privkey, identity, err := identityToken("repl", "user", "slug") + privkey, identity, err := identityToken("repl", "user", 1, "slug") require.NoError(t, err) getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) { @@ -380,6 +399,7 @@ func TestIdentity(t *testing.T) { assert.Equal(t, "repl", replIdentity.Replid) assert.Equal(t, "user", replIdentity.User) + assert.Equal(t, int64(1), replIdentity.UserId) assert.Equal(t, "slug", replIdentity.Slug) } @@ -389,10 +409,12 @@ func TestNoIdentityClaim(t *testing.T) { privkey, identity, err := tokenWithClaims( replID, user, + 1, "slug", // We're leaving out the IDENTITY claim []*api.CertificateClaim{ {Claim: &api.CertificateClaim_User{User: user}}, + {Claim: &api.CertificateClaim_UserId{UserId: 1}}, {Claim: &api.CertificateClaim_Replid{Replid: replID}}, }) require.NoError(t, err) @@ -430,7 +452,7 @@ func TestNoIdentityClaim(t *testing.T) { } func TestOriginIdentity(t *testing.T) { - privkey, identity, err := identityTokenWithOrigin("repl", "user", "slug", "origin") + privkey, identity, err := identityTokenWithOrigin("repl", "user", 1, "slug", "origin") require.NoError(t, err) getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) { @@ -473,6 +495,7 @@ func TestOriginIdentity(t *testing.T) { assert.Equal(t, "repl", replIdentity.Replid) assert.Equal(t, "user", replIdentity.User) + assert.Equal(t, int64(1), replIdentity.UserId) assert.Equal(t, "slug", replIdentity.Slug) assert.Equal(t, "origin", replIdentity.OriginReplid) } @@ -481,11 +504,12 @@ func TestLayeredIdentity(t *testing.T) { layeredReplIdentity := api.GovalReplIdentity{ Replid: "a-b-c-d", User: "spoof", + UserId: 2, Slug: "spoofed", Aud: "another-audience", } - privkey, identity, err := identityToken("repl", "user", "slug") + privkey, identity, err := identityToken("repl", "user", 1, "slug") require.NoError(t, err) getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) { @@ -524,7 +548,7 @@ func TestLayeredIdentity(t *testing.T) { } func TestLayeredIdentityWithSpoofedCert(t *testing.T) { - privkey, identity, err := multiTierIdentityToken("repl", "user", "slug") + privkey, identity, err := multiTierIdentityToken("repl", "user", 1, "slug") require.NoError(t, err) getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) { @@ -553,12 +577,18 @@ func TestAnyReplIDIdentity(t *testing.T) { layeredReplIdentity := api.GovalReplIdentity{ Replid: "a-b-c-d", User: "user", + UserId: 1, Slug: "slug", Aud: "another-audience", - UserId: 1, + Runtime: &api.GovalReplIdentity_Interactive{ + Interactive: &api.ReplRuntimeInteractive{ + Cluster: "development", + Subcluster: "", + }, + }, } - privkey, identity, err := identityTokenAnyRepl("repl", "user", "slug") + privkey, identity, err := identityTokenAnyRepl("repl", "user", 1, "slug") require.NoError(t, err) getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) { @@ -594,12 +624,77 @@ func TestAnyReplIDIdentity(t *testing.T) { assert.Equal(t, "a-b-c-d", replIdentity.Replid) assert.Equal(t, "user", replIdentity.User) - assert.Equal(t, "slug", replIdentity.Slug) assert.Equal(t, int64(1), replIdentity.UserId) + assert.Equal(t, "slug", replIdentity.Slug) +} + +func TestSpoofedRuntimeIdentity(t *testing.T) { + for i, layeredReplIdentity := range []*api.GovalReplIdentity{ + { + Replid: "a-b-c-d", + User: "user", + UserId: 1, + Slug: "slug", + Aud: "another-audience", + Runtime: &api.GovalReplIdentity_Interactive{ + Interactive: &api.ReplRuntimeInteractive{ + Cluster: "development", + Subcluster: "foo", + }, + }, + }, + { + Replid: "a-b-c-d", + User: "user", + UserId: 1, + Slug: "slug", + Aud: "another-audience", + Runtime: &api.GovalReplIdentity_Deployment{ + Deployment: &api.ReplRuntimeDeployment{}, + }, + }, + } { + layeredReplIdentity := layeredReplIdentity + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + privkey, identity, err := identityTokenAnyRepl("repl", "user", 1, "slug") + require.NoError(t, err) + + getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) { + if keyid != developmentKeyID { + return nil, nil + } + keyBytes, err := base64.StdEncoding.DecodeString(developmentPublicKey) + if err != nil { + return nil, fmt.Errorf("failed to parse public key as base64: %w", err) + } + + return ed25519.PublicKey(keyBytes), nil + } + + signingAuthority, err := NewSigningAuthority( + string(paserk.PrivateKeyToPASERKSecret(privkey)), + identity, + "repl", + getPubKey, + ) + require.NoError(t, err) + + // generate yet another layer using our key + token, err := signIdentity(privkey, signingAuthority.signingAuthority, layeredReplIdentity) + require.NoError(t, err) + + _, err = VerifyIdentity( + token, + "another-audience", + getPubKey, + ) + assert.Error(t, err) + }) + } } func TestRenew(t *testing.T) { - privkey, identity, err := renewalToken("repl", "user", "slug") + privkey, identity, err := renewalToken("repl", "user", 1, "slug") require.NoError(t, err) getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) { @@ -633,6 +728,7 @@ func TestRenew(t *testing.T) { assert.Equal(t, "repl", replIdentity.Replid) assert.Equal(t, "user", replIdentity.User) + assert.Equal(t, int64(1), replIdentity.UserId) assert.Equal(t, "slug", replIdentity.Slug) } @@ -640,6 +736,7 @@ func TestRenewNoClaim(t *testing.T) { privkey, identity, err := tokenWithClaims( "replid", "user", + 1, "slug", []*api.CertificateClaim{ {Claim: &api.CertificateClaim_Replid{Replid: "replid"}}, diff --git a/verify.go b/verify.go index cbe6825..c05c9a4 100644 --- a/verify.go +++ b/verify.go @@ -19,9 +19,12 @@ type verifier struct { claims *MessageClaims // signing certs can allow "any *" variants - anyReplid bool - anyUser bool - anyCluster bool + anyReplid bool + anyUser bool + anyUserID bool + anyCluster bool + anySubcluster bool + deployments bool } func (v *verifier) verifyToken(token string, pubkey ed25519.PublicKey) ([]byte, error) { @@ -77,7 +80,7 @@ func (v *verifier) verifyCert(certBytes []byte, signingCert *api.GovalCert) (*ap } // Verify that the cert is valid - err = verifyClaims(cert.Iat.AsTime(), cert.Exp.AsTime(), "", "", "", nil) + err = verifyClaims(cert.Iat.AsTime(), cert.Exp.AsTime(), "", "", "", "", false, nil) if err != nil { return nil, fmt.Errorf("cert is not valid: %w", err) } @@ -92,7 +95,7 @@ func (v *verifier) verifyCert(certBytes []byte, signingCert *api.GovalCert) (*ap // Verify the cert claims agrees with its signer authorizedClaims := map[string]struct{}{} - var anyReplid, anyUser, anyCluster bool + var anyReplid, anyUser, anyUserID, anyCluster, anySubcluster, deployments bool for _, claim := range signingCert.Claims { authorizedClaims[claim.String()] = struct{}{} switch tc := claim.Claim.(type) { @@ -103,9 +106,18 @@ func (v *verifier) verifyCert(certBytes []byte, signingCert *api.GovalCert) (*ap if tc.Flag == api.FlagClaim_ANY_USER { anyUser = true } + if tc.Flag == api.FlagClaim_ANY_USER_ID { + anyUserID = true + } if tc.Flag == api.FlagClaim_ANY_CLUSTER { anyCluster = true } + if tc.Flag == api.FlagClaim_ANY_SUBCLUSTER { + anySubcluster = true + } + if tc.Flag == api.FlagClaim_DEPLOYMENTS { + deployments = true + } } } @@ -118,9 +130,18 @@ func (v *verifier) verifyCert(certBytes []byte, signingCert *api.GovalCert) (*ap if tc.Flag == api.FlagClaim_ANY_USER { v.anyUser = true } + if tc.Flag == api.FlagClaim_ANY_USER_ID { + v.anyUserID = true + } if tc.Flag == api.FlagClaim_ANY_CLUSTER { v.anyCluster = true } + if tc.Flag == api.FlagClaim_ANY_SUBCLUSTER { + v.anySubcluster = true + } + if tc.Flag == api.FlagClaim_DEPLOYMENTS { + v.deployments = true + } case *api.CertificateClaim_Replid: if anyReplid { continue @@ -129,13 +150,25 @@ func (v *verifier) verifyCert(certBytes []byte, signingCert *api.GovalCert) (*ap if anyUser { continue } + case *api.CertificateClaim_UserId: + if anyUserID { + continue + } case *api.CertificateClaim_Cluster: if anyCluster { continue } + case *api.CertificateClaim_Subcluster: + if anySubcluster { + continue + } + case *api.CertificateClaim_Deployment: + if deployments || !tc.Deployment { + continue + } } if _, ok := authorizedClaims[claim.String()]; !ok { - return nil, fmt.Errorf("signing cert does not authorize claim: %s", claim) + return nil, fmt.Errorf("signing cert {%+v} does not authorize claim in {%+v}: %s", signingCert, cert, claim) } } } @@ -211,7 +244,29 @@ func (v *verifier) checkClaimsAgainstToken(token *api.GovalReplIdentity) error { return nil } - return verifyRawClaims(token.Replid, token.User, "", v.claims, v.anyReplid, v.anyUser, v.anyCluster) + var cluster, subcluster string + var deployment bool + switch v := token.Runtime.(type) { + case *api.GovalReplIdentity_Deployment: + deployment = true + case *api.GovalReplIdentity_Interactive: + cluster = v.Interactive.Cluster + subcluster = v.Interactive.Subcluster + } + + return verifyRawClaims( + token.Replid, + token.User, + cluster, + subcluster, + deployment, + v.claims, + v.anyReplid, + v.anyUser, + v.anyCluster, + v.anySubcluster, + v.deployments, + ) } // VerifyOption specifies an additional verification step to be performed on an identity. @@ -345,7 +400,12 @@ func VerifyToken(opts VerifyTokenOpts) (*api.GovalReplIdentity, error) { return &identity, nil } -func verifyRawClaims(replid, user, cluster string, claims *MessageClaims, anyReplid, anyUser, anyCluster bool) error { +func verifyRawClaims( + replid, user, cluster, subcluster string, + deployment bool, + claims *MessageClaims, + anyReplid, anyUser, anyCluster, anySubcluster, allowsDeployment bool, +) error { if claims != nil { if replid != "" && !anyReplid { if _, ok := claims.Repls[replid]; !ok { @@ -364,12 +424,22 @@ func verifyRawClaims(replid, user, cluster string, claims *MessageClaims, anyRep return errors.New("not authorized (cluster)") } } + + if subcluster != "" && !anySubcluster { + if _, ok := claims.Subclusters[subcluster]; !ok { + return errors.New("not authorized (subcluster)") + } + } + + if deployment && !allowsDeployment { + return errors.New("not authorized (deployment)") + } } return nil } -func verifyClaims(iat time.Time, exp time.Time, replid, user, cluster string, claims *MessageClaims) error { +func verifyClaims(iat time.Time, exp time.Time, replid, user, cluster, subcluster string, deployment bool, claims *MessageClaims) error { if iat.After(time.Now()) { return fmt.Errorf("not valid for %s", time.Until(iat)) } @@ -378,5 +448,5 @@ func verifyClaims(iat time.Time, exp time.Time, replid, user, cluster string, cl return fmt.Errorf("expired %s ago", time.Since(exp)) } - return verifyRawClaims(replid, user, cluster, claims, false, false, false) + return verifyRawClaims(replid, user, cluster, subcluster, deployment, claims, false, false, false, false, false) }