From e21eedaf9d20b646d89c63f2fb5a17e618e6df45 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Tue, 19 May 2026 17:33:36 +0200 Subject: [PATCH 01/22] Regenerate protobufs from message-attestation spec fork Re-point internal/opamp-spec submodule at truthbk/opamp-spec branch jaime/x509-spec-full-protocol (commit 65b74b8) and regenerate protobufs/. This pulls in the wire-level additions for OpAMP Message Attestation: * ServerToAgent.trust_chain_response = 12 (TrustChainResponse) * ServerToAgent.signature = 13 (bytes) * AgentCapabilities_RequiresPayloadTrustVerification = 0x00010000 * ServerCapabilities_OffersPayloadTrustVerification = 0x00000080 * New TrustChainResponse message (with nested Certificate) Generated locally with protoc 34.1 + protoc-gen-go (native arm64) to work around a SIGSEGV in the otel/build-protobuf Docker image under amd64 emulation on Apple Silicon. Output is wire-compatible with the older generator; the diff is mostly newer protoc-gen-go's more compact table-driven reflection style. No source-level call-site changes required: existing accessor methods and enum constants are preserved. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitmodules | 3 +- internal/opamp-spec | 2 +- protobufs/anyvalue.pb.go | 256 ++-- protobufs/opamp.pb.go | 2668 ++++++++++++++------------------------ 4 files changed, 1045 insertions(+), 1884 deletions(-) diff --git a/.gitmodules b/.gitmodules index 282d1963..820acdc7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "internal/opamp-spec"] path = internal/opamp-spec - url = git@github.com:open-telemetry/opamp-spec.git + url = git@github.com:truthbk/opamp-spec.git + branch = jaime/x509-spec-full-protocol diff --git a/internal/opamp-spec b/internal/opamp-spec index 49860fd6..65b74b88 160000 --- a/internal/opamp-spec +++ b/internal/opamp-spec @@ -1 +1 @@ -Subproject commit 49860fd67177e4f80d9778831472ab106e4ae951 +Subproject commit 65b74b886aadfc6610198a2a42a80ff7ce14df52 diff --git a/protobufs/anyvalue.pb.go b/protobufs/anyvalue.pb.go index 453fd583..c65e6d45 100644 --- a/protobufs/anyvalue.pb.go +++ b/protobufs/anyvalue.pb.go @@ -20,8 +20,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 -// protoc v3.17.3 +// protoc-gen-go v1.36.11 +// protoc v7.34.1 // source: anyvalue.proto package protobufs @@ -29,6 +29,7 @@ package protobufs import ( reflect "reflect" sync "sync" + unsafe "unsafe" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -45,14 +46,12 @@ const ( // primitive value such as a string or integer or it may contain an arbitrary nested // object containing arrays, key-value lists and primitives. type AnyValue struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The value is one of the listed fields. It is valid for all values to be unspecified // in which case this AnyValue is considered to be "null". // - // Types that are assignable to Value: + // Types that are valid to be assigned to Value: + // // *AnyValue_StringValue // *AnyValue_BoolValue // *AnyValue_IntValue @@ -60,16 +59,16 @@ type AnyValue struct { // *AnyValue_ArrayValue // *AnyValue_KvlistValue // *AnyValue_BytesValue - Value isAnyValue_Value `protobuf_oneof:"value"` + Value isAnyValue_Value `protobuf_oneof:"value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AnyValue) Reset() { *x = AnyValue{} - if protoimpl.UnsafeEnabled { - mi := &file_anyvalue_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_anyvalue_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AnyValue) String() string { @@ -80,7 +79,7 @@ func (*AnyValue) ProtoMessage() {} func (x *AnyValue) ProtoReflect() protoreflect.Message { mi := &file_anyvalue_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -95,58 +94,72 @@ func (*AnyValue) Descriptor() ([]byte, []int) { return file_anyvalue_proto_rawDescGZIP(), []int{0} } -func (m *AnyValue) GetValue() isAnyValue_Value { - if m != nil { - return m.Value +func (x *AnyValue) GetValue() isAnyValue_Value { + if x != nil { + return x.Value } return nil } func (x *AnyValue) GetStringValue() string { - if x, ok := x.GetValue().(*AnyValue_StringValue); ok { - return x.StringValue + if x != nil { + if x, ok := x.Value.(*AnyValue_StringValue); ok { + return x.StringValue + } } return "" } func (x *AnyValue) GetBoolValue() bool { - if x, ok := x.GetValue().(*AnyValue_BoolValue); ok { - return x.BoolValue + if x != nil { + if x, ok := x.Value.(*AnyValue_BoolValue); ok { + return x.BoolValue + } } return false } func (x *AnyValue) GetIntValue() int64 { - if x, ok := x.GetValue().(*AnyValue_IntValue); ok { - return x.IntValue + if x != nil { + if x, ok := x.Value.(*AnyValue_IntValue); ok { + return x.IntValue + } } return 0 } func (x *AnyValue) GetDoubleValue() float64 { - if x, ok := x.GetValue().(*AnyValue_DoubleValue); ok { - return x.DoubleValue + if x != nil { + if x, ok := x.Value.(*AnyValue_DoubleValue); ok { + return x.DoubleValue + } } return 0 } func (x *AnyValue) GetArrayValue() *ArrayValue { - if x, ok := x.GetValue().(*AnyValue_ArrayValue); ok { - return x.ArrayValue + if x != nil { + if x, ok := x.Value.(*AnyValue_ArrayValue); ok { + return x.ArrayValue + } } return nil } func (x *AnyValue) GetKvlistValue() *KeyValueList { - if x, ok := x.GetValue().(*AnyValue_KvlistValue); ok { - return x.KvlistValue + if x != nil { + if x, ok := x.Value.(*AnyValue_KvlistValue); ok { + return x.KvlistValue + } } return nil } func (x *AnyValue) GetBytesValue() []byte { - if x, ok := x.GetValue().(*AnyValue_BytesValue); ok { - return x.BytesValue + if x != nil { + if x, ok := x.Value.(*AnyValue_BytesValue); ok { + return x.BytesValue + } } return nil } @@ -200,21 +213,18 @@ func (*AnyValue_BytesValue) isAnyValue_Value() {} // ArrayValue is a list of AnyValue messages. We need ArrayValue as a message // since oneof in AnyValue does not allow repeated fields. type ArrayValue struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Array of values. The array may be empty (contain 0 elements). - Values []*AnyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + Values []*AnyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ArrayValue) Reset() { *x = ArrayValue{} - if protoimpl.UnsafeEnabled { - mi := &file_anyvalue_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_anyvalue_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ArrayValue) String() string { @@ -225,7 +235,7 @@ func (*ArrayValue) ProtoMessage() {} func (x *ArrayValue) ProtoReflect() protoreflect.Message { mi := &file_anyvalue_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -253,22 +263,19 @@ func (x *ArrayValue) GetValues() []*AnyValue { // avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches // are semantically equivalent. type KeyValueList struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A collection of key/value pairs of key-value pairs. The list may be empty (may // contain 0 elements). - Values []*KeyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + Values []*KeyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *KeyValueList) Reset() { *x = KeyValueList{} - if protoimpl.UnsafeEnabled { - mi := &file_anyvalue_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_anyvalue_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *KeyValueList) String() string { @@ -279,7 +286,7 @@ func (*KeyValueList) ProtoMessage() {} func (x *KeyValueList) ProtoReflect() protoreflect.Message { mi := &file_anyvalue_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -304,21 +311,18 @@ func (x *KeyValueList) GetValues() []*KeyValue { // KeyValue is a key-value pair that is used to store Span attributes, Link // attributes, etc. type KeyValue struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value *AnyValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields - - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value *AnyValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + sizeCache protoimpl.SizeCache } func (x *KeyValue) Reset() { *x = KeyValue{} - if protoimpl.UnsafeEnabled { - mi := &file_anyvalue_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_anyvalue_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *KeyValue) String() string { @@ -329,7 +333,7 @@ func (*KeyValue) ProtoMessage() {} func (x *KeyValue) ProtoReflect() protoreflect.Message { mi := &file_anyvalue_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -360,61 +364,44 @@ func (x *KeyValue) GetValue() *AnyValue { var File_anyvalue_proto protoreflect.FileDescriptor -var file_anyvalue_proto_rawDesc = []byte{ - 0x0a, 0x0e, 0x61, 0x6e, 0x79, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x0b, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbc, 0x02, - 0x0a, 0x08, 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x23, 0x0a, 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x0b, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x72, 0x61, 0x79, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x72, 0x72, 0x61, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x3e, 0x0a, 0x0c, 0x6b, 0x76, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x69, 0x73, - 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6b, 0x76, 0x6c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3b, 0x0a, 0x0a, - 0x41, 0x72, 0x72, 0x61, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70, 0x61, - 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x3d, 0x0a, 0x0c, 0x4b, 0x65, 0x79, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x49, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, - 0x2f, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_anyvalue_proto_rawDesc = "" + + "\n" + + "\x0eanyvalue.proto\x12\vopamp.proto\"\xbc\x02\n" + + "\bAnyValue\x12#\n" + + "\fstring_value\x18\x01 \x01(\tH\x00R\vstringValue\x12\x1f\n" + + "\n" + + "bool_value\x18\x02 \x01(\bH\x00R\tboolValue\x12\x1d\n" + + "\tint_value\x18\x03 \x01(\x03H\x00R\bintValue\x12#\n" + + "\fdouble_value\x18\x04 \x01(\x01H\x00R\vdoubleValue\x12:\n" + + "\varray_value\x18\x05 \x01(\v2\x17.opamp.proto.ArrayValueH\x00R\n" + + "arrayValue\x12>\n" + + "\fkvlist_value\x18\x06 \x01(\v2\x19.opamp.proto.KeyValueListH\x00R\vkvlistValue\x12!\n" + + "\vbytes_value\x18\a \x01(\fH\x00R\n" + + "bytesValueB\a\n" + + "\x05value\";\n" + + "\n" + + "ArrayValue\x12-\n" + + "\x06values\x18\x01 \x03(\v2\x15.opamp.proto.AnyValueR\x06values\"=\n" + + "\fKeyValueList\x12-\n" + + "\x06values\x18\x01 \x03(\v2\x15.opamp.proto.KeyValueR\x06values\"I\n" + + "\bKeyValue\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12+\n" + + "\x05value\x18\x02 \x01(\v2\x15.opamp.proto.AnyValueR\x05valueB.Z,github.com/open-telemetry/opamp-go/protobufsb\x06proto3" var ( file_anyvalue_proto_rawDescOnce sync.Once - file_anyvalue_proto_rawDescData = file_anyvalue_proto_rawDesc + file_anyvalue_proto_rawDescData []byte ) func file_anyvalue_proto_rawDescGZIP() []byte { file_anyvalue_proto_rawDescOnce.Do(func() { - file_anyvalue_proto_rawDescData = protoimpl.X.CompressGZIP(file_anyvalue_proto_rawDescData) + file_anyvalue_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_anyvalue_proto_rawDesc), len(file_anyvalue_proto_rawDesc))) }) return file_anyvalue_proto_rawDescData } var file_anyvalue_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_anyvalue_proto_goTypes = []interface{}{ +var file_anyvalue_proto_goTypes = []any{ (*AnyValue)(nil), // 0: opamp.proto.AnyValue (*ArrayValue)(nil), // 1: opamp.proto.ArrayValue (*KeyValueList)(nil), // 2: opamp.proto.KeyValueList @@ -438,57 +425,7 @@ func file_anyvalue_proto_init() { if File_anyvalue_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_anyvalue_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AnyValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_anyvalue_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ArrayValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_anyvalue_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyValueList); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_anyvalue_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyValue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_anyvalue_proto_msgTypes[0].OneofWrappers = []interface{}{ + file_anyvalue_proto_msgTypes[0].OneofWrappers = []any{ (*AnyValue_StringValue)(nil), (*AnyValue_BoolValue)(nil), (*AnyValue_IntValue)(nil), @@ -501,7 +438,7 @@ func file_anyvalue_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_anyvalue_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_anyvalue_proto_rawDesc), len(file_anyvalue_proto_rawDesc)), NumEnums: 0, NumMessages: 4, NumExtensions: 0, @@ -512,7 +449,6 @@ func file_anyvalue_proto_init() { MessageInfos: file_anyvalue_proto_msgTypes, }.Build() File_anyvalue_proto = out.File - file_anyvalue_proto_rawDesc = nil file_anyvalue_proto_goTypes = nil file_anyvalue_proto_depIdxs = nil } diff --git a/protobufs/opamp.pb.go b/protobufs/opamp.pb.go index e7f154d8..3f946b71 100644 --- a/protobufs/opamp.pb.go +++ b/protobufs/opamp.pb.go @@ -16,8 +16,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 -// protoc v3.17.3 +// protoc-gen-go v1.36.11 +// protoc v7.34.1 // source: opamp.proto package protobufs @@ -25,6 +25,7 @@ package protobufs import ( reflect "reflect" sync "sync" + unsafe "unsafe" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -169,19 +170,25 @@ const ( // The Server can accept ConnectionSettingsRequest and respond with an offer. // Status: [Development] ServerCapabilities_ServerCapabilities_AcceptsConnectionSettingsRequest ServerCapabilities = 64 + // The Server can respond to the payload trust verification handshake and + // sign every ServerToAgent message it sends after the handshake. See the + // Message Attestation section of the specification. + // Status: [Development] + ServerCapabilities_ServerCapabilities_OffersPayloadTrustVerification ServerCapabilities = 128 ) // Enum value maps for ServerCapabilities. var ( ServerCapabilities_name = map[int32]string{ - 0: "ServerCapabilities_Unspecified", - 1: "ServerCapabilities_AcceptsStatus", - 2: "ServerCapabilities_OffersRemoteConfig", - 4: "ServerCapabilities_AcceptsEffectiveConfig", - 8: "ServerCapabilities_OffersPackages", - 16: "ServerCapabilities_AcceptsPackagesStatus", - 32: "ServerCapabilities_OffersConnectionSettings", - 64: "ServerCapabilities_AcceptsConnectionSettingsRequest", + 0: "ServerCapabilities_Unspecified", + 1: "ServerCapabilities_AcceptsStatus", + 2: "ServerCapabilities_OffersRemoteConfig", + 4: "ServerCapabilities_AcceptsEffectiveConfig", + 8: "ServerCapabilities_OffersPackages", + 16: "ServerCapabilities_AcceptsPackagesStatus", + 32: "ServerCapabilities_OffersConnectionSettings", + 64: "ServerCapabilities_AcceptsConnectionSettingsRequest", + 128: "ServerCapabilities_OffersPayloadTrustVerification", } ServerCapabilities_value = map[string]int32{ "ServerCapabilities_Unspecified": 0, @@ -192,6 +199,7 @@ var ( "ServerCapabilities_AcceptsPackagesStatus": 16, "ServerCapabilities_OffersConnectionSettings": 32, "ServerCapabilities_AcceptsConnectionSettingsRequest": 64, + "ServerCapabilities_OffersPayloadTrustVerification": 128, } ) @@ -430,7 +438,13 @@ const ( AgentCapabilities_AgentCapabilities_ReportsAvailableComponents AgentCapabilities = 16384 // The agent will report ConnectionSettingsOffers status via AgentToServer.connection_settings_status field. // Status: [Development] - AgentCapabilities_AgentCapabilities_ReportsConnectionSettingsStatus AgentCapabilities = 32768 // Add new capabilities here, continuing with the least significant unused bit. + AgentCapabilities_AgentCapabilities_ReportsConnectionSettingsStatus AgentCapabilities = 32768 + // The Agent requires the payload trust verification handshake on connection + // and signature verification on every subsequent ServerToAgent message. + // If the Server does not offer this capability, the Agent MUST terminate + // the connection. See the Message Attestation section of the specification. + // Status: [Development] + AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification AgentCapabilities = 65536 // Add new capabilities here, continuing with the least significant unused bit. ) // Enum value maps for AgentCapabilities. @@ -453,25 +467,27 @@ var ( 8192: "AgentCapabilities_ReportsHeartbeat", 16384: "AgentCapabilities_ReportsAvailableComponents", 32768: "AgentCapabilities_ReportsConnectionSettingsStatus", + 65536: "AgentCapabilities_RequiresPayloadTrustVerification", } AgentCapabilities_value = map[string]int32{ - "AgentCapabilities_Unspecified": 0, - "AgentCapabilities_ReportsStatus": 1, - "AgentCapabilities_AcceptsRemoteConfig": 2, - "AgentCapabilities_ReportsEffectiveConfig": 4, - "AgentCapabilities_AcceptsPackages": 8, - "AgentCapabilities_ReportsPackageStatuses": 16, - "AgentCapabilities_ReportsOwnTraces": 32, - "AgentCapabilities_ReportsOwnMetrics": 64, - "AgentCapabilities_ReportsOwnLogs": 128, - "AgentCapabilities_AcceptsOpAMPConnectionSettings": 256, - "AgentCapabilities_AcceptsOtherConnectionSettings": 512, - "AgentCapabilities_AcceptsRestartCommand": 1024, - "AgentCapabilities_ReportsHealth": 2048, - "AgentCapabilities_ReportsRemoteConfig": 4096, - "AgentCapabilities_ReportsHeartbeat": 8192, - "AgentCapabilities_ReportsAvailableComponents": 16384, - "AgentCapabilities_ReportsConnectionSettingsStatus": 32768, + "AgentCapabilities_Unspecified": 0, + "AgentCapabilities_ReportsStatus": 1, + "AgentCapabilities_AcceptsRemoteConfig": 2, + "AgentCapabilities_ReportsEffectiveConfig": 4, + "AgentCapabilities_AcceptsPackages": 8, + "AgentCapabilities_ReportsPackageStatuses": 16, + "AgentCapabilities_ReportsOwnTraces": 32, + "AgentCapabilities_ReportsOwnMetrics": 64, + "AgentCapabilities_ReportsOwnLogs": 128, + "AgentCapabilities_AcceptsOpAMPConnectionSettings": 256, + "AgentCapabilities_AcceptsOtherConnectionSettings": 512, + "AgentCapabilities_AcceptsRestartCommand": 1024, + "AgentCapabilities_ReportsHealth": 2048, + "AgentCapabilities_ReportsRemoteConfig": 4096, + "AgentCapabilities_ReportsHeartbeat": 8192, + "AgentCapabilities_ReportsAvailableComponents": 16384, + "AgentCapabilities_ReportsConnectionSettingsStatus": 32768, + "AgentCapabilities_RequiresPayloadTrustVerification": 65536, } ) @@ -689,10 +705,7 @@ func (PackageStatusEnum) EnumDescriptor() ([]byte, []int) { } type AgentToServer struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Globally unique identifier of the running instance of the Agent. SHOULD remain // unchanged for the lifetime of the Agent process. // MUST be 16 bytes long and SHOULD be generated using the UUID v7 spec. @@ -757,15 +770,15 @@ type AgentToServer struct { // settings status is unchanged since the last AgentToServer message. // Status: [Development] ConnectionSettingsStatus *ConnectionSettingsStatus `protobuf:"bytes,15,opt,name=connection_settings_status,json=connectionSettingsStatus,proto3" json:"connection_settings_status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentToServer) Reset() { *x = AgentToServer{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentToServer) String() string { @@ -776,7 +789,7 @@ func (*AgentToServer) ProtoMessage() {} func (x *AgentToServer) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -904,18 +917,16 @@ func (x *AgentToServer) GetConnectionSettingsStatus() *ConnectionSettingsStatus // this message stream using AgentConnect message, even if the corresponding // AgentDisconnect message were not explicitly received from the Agent. type AgentDisconnect struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentDisconnect) Reset() { *x = AgentDisconnect{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentDisconnect) String() string { @@ -926,7 +937,7 @@ func (*AgentDisconnect) ProtoMessage() {} func (x *AgentDisconnect) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -945,23 +956,20 @@ func (*AgentDisconnect) Descriptor() ([]byte, []int) { // and respond with an offer of connection settings for the Agent. // Status: [Development] type ConnectionSettingsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Request for OpAMP connection settings. If this field is unset // then the ConnectionSettingsRequest message is empty and is not actionable // for the Server. - Opamp *OpAMPConnectionSettingsRequest `protobuf:"bytes,1,opt,name=opamp,proto3" json:"opamp,omitempty"` + Opamp *OpAMPConnectionSettingsRequest `protobuf:"bytes,1,opt,name=opamp,proto3" json:"opamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ConnectionSettingsRequest) Reset() { *x = ConnectionSettingsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ConnectionSettingsRequest) String() string { @@ -972,7 +980,7 @@ func (*ConnectionSettingsRequest) ProtoMessage() {} func (x *ConnectionSettingsRequest) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -998,23 +1006,20 @@ func (x *ConnectionSettingsRequest) GetOpamp() *OpAMPConnectionSettingsRequest { // a OpAMPConnectionSettings in its response. // Status: [Development] type OpAMPConnectionSettingsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A request to create a client certificate. This is used to initiate a // Client Signing Request (CSR) flow. // Required. CertificateRequest *CertificateRequest `protobuf:"bytes,1,opt,name=certificate_request,json=certificateRequest,proto3" json:"certificate_request,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *OpAMPConnectionSettingsRequest) Reset() { *x = OpAMPConnectionSettingsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OpAMPConnectionSettingsRequest) String() string { @@ -1025,7 +1030,7 @@ func (*OpAMPConnectionSettingsRequest) ProtoMessage() {} func (x *OpAMPConnectionSettingsRequest) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1049,24 +1054,21 @@ func (x *OpAMPConnectionSettingsRequest) GetCertificateRequest() *CertificateReq // Status: [Development] type CertificateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // PEM-encoded Client Certificate Signing Request (CSR), signed by client's private key. // The Server SHOULD validate the request and SHOULD respond with a // OpAMPConnectionSettings where the certificate.cert contains the issued // certificate. - Csr []byte `protobuf:"bytes,1,opt,name=csr,proto3" json:"csr,omitempty"` + Csr []byte `protobuf:"bytes,1,opt,name=csr,proto3" json:"csr,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CertificateRequest) Reset() { *x = CertificateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CertificateRequest) String() string { @@ -1077,7 +1079,7 @@ func (*CertificateRequest) ProtoMessage() {} func (x *CertificateRequest) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1103,27 +1105,24 @@ func (x *CertificateRequest) GetCsr() []byte { // within the agent. // status: [Development] type AvailableComponents struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A map of a unique component ID to details about the component. // This may be omitted from the message if the server has not // explicitly requested it be sent by setting the ReportAvailableComponents // flag in the previous ServerToAgent message. - Components map[string]*ComponentDetails `protobuf:"bytes,1,rep,name=components,proto3" json:"components,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Components map[string]*ComponentDetails `protobuf:"bytes,1,rep,name=components,proto3" json:"components,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Agent-calculated hash of the components. // This hash should be included in every AvailableComponents message. - Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` + Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AvailableComponents) Reset() { *x = AvailableComponents{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AvailableComponents) String() string { @@ -1134,7 +1133,7 @@ func (*AvailableComponents) ProtoMessage() {} func (x *AvailableComponents) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1164,10 +1163,7 @@ func (x *AvailableComponents) GetHash() []byte { } type ComponentDetails struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Extra key/value pairs that may be used to describe the component. // The key/value pairs are according to semantic conventions, see: // https://opentelemetry.io/docs/specs/semconv/ @@ -1182,16 +1178,16 @@ type ComponentDetails struct { Metadata []*KeyValue `protobuf:"bytes,1,rep,name=metadata,proto3" json:"metadata,omitempty"` // A map of component ID to sub components details. It can nest as deeply as needed to // describe the underlying system. - SubComponentMap map[string]*ComponentDetails `protobuf:"bytes,2,rep,name=sub_component_map,json=subComponentMap,proto3" json:"sub_component_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + SubComponentMap map[string]*ComponentDetails `protobuf:"bytes,2,rep,name=sub_component_map,json=subComponentMap,proto3" json:"sub_component_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ComponentDetails) Reset() { *x = ComponentDetails{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ComponentDetails) String() string { @@ -1202,7 +1198,7 @@ func (*ComponentDetails) ProtoMessage() {} func (x *ComponentDetails) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1232,10 +1228,7 @@ func (x *ComponentDetails) GetSubComponentMap() map[string]*ComponentDetails { } type ServerToAgent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Agent instance uid. MUST match the instance_uid field in AgentToServer message. // Used for multiplexing messages from/to multiple agents using one message stream. InstanceUid []byte `protobuf:"bytes,1,opt,name=instance_uid,json=instanceUid,proto3" json:"instance_uid,omitempty"` @@ -1278,15 +1271,30 @@ type ServerToAgent struct { // A custom message sent from the Server to an Agent. // Status: [Development] CustomMessage *CustomMessage `protobuf:"bytes,11,opt,name=custom_message,json=customMessage,proto3" json:"custom_message,omitempty"` + // Sent by the Server in its first ServerToAgent message in response to an + // Agent that has set the RequiresPayloadTrustVerification capability. + // Carries the signing certificate chain the Agent will use to verify + // subsequent ServerToAgent messages. If the Agent set + // RequiresPayloadTrustVerification but the first ServerToAgent does not + // include trust_chain_response, the Agent MUST terminate the connection. + // See the Message Attestation section of the specification. + // Status: [Development] + TrustChainResponse *TrustChainResponse `protobuf:"bytes,12,opt,name=trust_chain_response,json=trustChainResponse,proto3" json:"trust_chain_response,omitempty"` + // The signature of this ServerToAgent message. The exact bytes that are + // signed and the verification procedure are defined in the + // Message Attestation section of the specification. Set only after the + // payload trust verification handshake has completed successfully. + // Status: [Development] + Signature []byte `protobuf:"bytes,13,opt,name=signature,proto3" json:"signature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ServerToAgent) Reset() { *x = ServerToAgent{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerToAgent) String() string { @@ -1297,7 +1305,7 @@ func (*ServerToAgent) ProtoMessage() {} func (x *ServerToAgent) ProtoReflect() protoreflect.Message { mi := &file_opamp_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1389,15 +1397,90 @@ func (x *ServerToAgent) GetCustomMessage() *CustomMessage { return nil } +func (x *ServerToAgent) GetTrustChainResponse() *TrustChainResponse { + if x != nil { + return x.TrustChainResponse + } + return nil +} + +func (x *ServerToAgent) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +// TrustChainResponse carries the signing certificate chain used by the Server +// to sign subsequent ServerToAgent messages, as part of the payload trust +// verification handshake. See the Message Attestation section of the +// specification. +// Status: [Development] +type TrustChainResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The certificate chain, ordered from the first intermediate certificate + // down to the signing leaf certificate. The root certificate is excluded; + // the Agent already possesses the root as its pre-configured payload + // trust anchor. + CertificateChain []*TrustChainResponse_Certificate `protobuf:"bytes,1,rep,name=certificate_chain,json=certificateChain,proto3" json:"certificate_chain,omitempty"` + // Human-readable error message indicating why the Server could not + // satisfy the trust chain request. If error_message is set, the Agent + // MUST terminate the connection. + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TrustChainResponse) Reset() { + *x = TrustChainResponse{} + mi := &file_opamp_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TrustChainResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TrustChainResponse) ProtoMessage() {} + +func (x *TrustChainResponse) ProtoReflect() protoreflect.Message { + mi := &file_opamp_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TrustChainResponse.ProtoReflect.Descriptor instead. +func (*TrustChainResponse) Descriptor() ([]byte, []int) { + return file_opamp_proto_rawDescGZIP(), []int{8} +} + +func (x *TrustChainResponse) GetCertificateChain() []*TrustChainResponse_Certificate { + if x != nil { + return x.CertificateChain + } + return nil +} + +func (x *TrustChainResponse) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + // The OpAMPConnectionSettings message is a collection of fields which comprise an // offer from the Server to the Agent to use the specified settings for OpAMP // connection. // Status: [Beta] type OpAMPConnectionSettings struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // OpAMP Server URL This MUST be a WebSocket or HTTP URL and MUST be non-empty, for // example: "wss://example.com:4318/v1/opamp" DestinationEndpoint string `protobuf:"bytes,1,opt,name=destination_endpoint,json=destinationEndpoint,proto3" json:"destination_endpoint,omitempty"` @@ -1431,16 +1514,16 @@ type OpAMPConnectionSettings struct { Tls *TLSConnectionSettings `protobuf:"bytes,5,opt,name=tls,proto3" json:"tls,omitempty"` // Optional connection specific proxy settings. // Status: [Development] - Proxy *ProxyConnectionSettings `protobuf:"bytes,6,opt,name=proxy,proto3" json:"proxy,omitempty"` + Proxy *ProxyConnectionSettings `protobuf:"bytes,6,opt,name=proxy,proto3" json:"proxy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *OpAMPConnectionSettings) Reset() { *x = OpAMPConnectionSettings{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OpAMPConnectionSettings) String() string { @@ -1450,8 +1533,8 @@ func (x *OpAMPConnectionSettings) String() string { func (*OpAMPConnectionSettings) ProtoMessage() {} func (x *OpAMPConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[9] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1463,7 +1546,7 @@ func (x *OpAMPConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use OpAMPConnectionSettings.ProtoReflect.Descriptor instead. func (*OpAMPConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{8} + return file_opamp_proto_rawDescGZIP(), []int{9} } func (x *OpAMPConnectionSettings) GetDestinationEndpoint() string { @@ -1513,10 +1596,7 @@ func (x *OpAMPConnectionSettings) GetProxy() *ProxyConnectionSettings { // connection to report own telemetry. // Status: [Beta] type TelemetryConnectionSettings struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The value MUST be a full URL an OTLP/HTTP/Protobuf receiver with path. Schema // SHOULD begin with "https://", for example "https://example.com:4318/v1/metrics" // The Agent MAY refuse to send the telemetry if the URL begins with "http://". @@ -1539,16 +1619,16 @@ type TelemetryConnectionSettings struct { Tls *TLSConnectionSettings `protobuf:"bytes,4,opt,name=tls,proto3" json:"tls,omitempty"` // Optional connection specific proxy settings. // Status: [Development] - Proxy *ProxyConnectionSettings `protobuf:"bytes,5,opt,name=proxy,proto3" json:"proxy,omitempty"` + Proxy *ProxyConnectionSettings `protobuf:"bytes,5,opt,name=proxy,proto3" json:"proxy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TelemetryConnectionSettings) Reset() { *x = TelemetryConnectionSettings{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TelemetryConnectionSettings) String() string { @@ -1558,8 +1638,8 @@ func (x *TelemetryConnectionSettings) String() string { func (*TelemetryConnectionSettings) ProtoMessage() {} func (x *TelemetryConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[10] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1571,7 +1651,7 @@ func (x *TelemetryConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use TelemetryConnectionSettings.ProtoReflect.Descriptor instead. func (*TelemetryConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{9} + return file_opamp_proto_rawDescGZIP(), []int{10} } func (x *TelemetryConnectionSettings) GetDestinationEndpoint() string { @@ -1631,10 +1711,7 @@ func (x *TelemetryConnectionSettings) GetProxy() *ProxyConnectionSettings { // the field. // Status: [Beta] type OtherConnectionSettings struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A URL, host:port or some other destination specifier. DestinationEndpoint string `protobuf:"bytes,1,opt,name=destination_endpoint,json=destinationEndpoint,proto3" json:"destination_endpoint,omitempty"` // Optional headers to use when connecting. Typically used to set access tokens or @@ -1652,22 +1729,22 @@ type OtherConnectionSettings struct { Certificate *TLSCertificate `protobuf:"bytes,3,opt,name=certificate,proto3" json:"certificate,omitempty"` // Other connection settings. These are Agent-specific and are up to the Agent // interpret. - OtherSettings map[string]string `protobuf:"bytes,4,rep,name=other_settings,json=otherSettings,proto3" json:"other_settings,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + OtherSettings map[string]string `protobuf:"bytes,4,rep,name=other_settings,json=otherSettings,proto3" json:"other_settings,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Optional connection specific TLS settings. // Status: [Development] Tls *TLSConnectionSettings `protobuf:"bytes,5,opt,name=tls,proto3" json:"tls,omitempty"` // Optional connection specific proxy settings. // Status: [Development] - Proxy *ProxyConnectionSettings `protobuf:"bytes,6,opt,name=proxy,proto3" json:"proxy,omitempty"` + Proxy *ProxyConnectionSettings `protobuf:"bytes,6,opt,name=proxy,proto3" json:"proxy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *OtherConnectionSettings) Reset() { *x = OtherConnectionSettings{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OtherConnectionSettings) String() string { @@ -1677,8 +1754,8 @@ func (x *OtherConnectionSettings) String() string { func (*OtherConnectionSettings) ProtoMessage() {} func (x *OtherConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[11] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1690,7 +1767,7 @@ func (x *OtherConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use OtherConnectionSettings.ProtoReflect.Descriptor instead. func (*OtherConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{10} + return file_opamp_proto_rawDescGZIP(), []int{11} } func (x *OtherConnectionSettings) GetDestinationEndpoint() string { @@ -1739,31 +1816,28 @@ func (x *OtherConnectionSettings) GetProxy() *ProxyConnectionSettings { // the client in order to specify TLS configuration. // Status: [Development] type TLSConnectionSettings struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Provides CA cert contents as a string. CaPemContents string `protobuf:"bytes,1,opt,name=ca_pem_contents,json=caPemContents,proto3" json:"ca_pem_contents,omitempty"` // Load system CA pool alongside any passed CAs. IncludeSystemCaCertsPool bool `protobuf:"varint,2,opt,name=include_system_ca_certs_pool,json=includeSystemCaCertsPool,proto3" json:"include_system_ca_certs_pool,omitempty"` // skip certificate verification. InsecureSkipVerify bool `protobuf:"varint,3,opt,name=insecure_skip_verify,json=insecureSkipVerify,proto3" json:"insecure_skip_verify,omitempty"` - // Miniumum accepted TLS version; default "1.2". + // Minimum accepted TLS version; default "1.2". MinVersion string `protobuf:"bytes,4,opt,name=min_version,json=minVersion,proto3" json:"min_version,omitempty"` - // Maxiumum accepted TLS version; default "". + // Minimum accepted TLS version; default "". MaxVersion string `protobuf:"bytes,5,opt,name=max_version,json=maxVersion,proto3" json:"max_version,omitempty"` // Explicit list of cipher suites. - CipherSuites []string `protobuf:"bytes,6,rep,name=cipher_suites,json=cipherSuites,proto3" json:"cipher_suites,omitempty"` + CipherSuites []string `protobuf:"bytes,6,rep,name=cipher_suites,json=cipherSuites,proto3" json:"cipher_suites,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TLSConnectionSettings) Reset() { *x = TLSConnectionSettings{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TLSConnectionSettings) String() string { @@ -1773,8 +1847,8 @@ func (x *TLSConnectionSettings) String() string { func (*TLSConnectionSettings) ProtoMessage() {} func (x *TLSConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[12] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1786,7 +1860,7 @@ func (x *TLSConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use TLSConnectionSettings.ProtoReflect.Descriptor instead. func (*TLSConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{11} + return file_opamp_proto_rawDescGZIP(), []int{12} } func (x *TLSConnectionSettings) GetCaPemContents() string { @@ -1833,10 +1907,7 @@ func (x *TLSConnectionSettings) GetCipherSuites() []string { // Status: [Development] type ProxyConnectionSettings struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A URL, host:port or some other destination specifier. Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` // Optional headers to send to proxies during CONNECT requests. @@ -1844,15 +1915,15 @@ type ProxyConnectionSettings struct { // For example: // key="Authorization", Value="Basic YWxhZGRpbjpvcGVuc2VzYW1l". ConnectHeaders *Headers `protobuf:"bytes,2,opt,name=connect_headers,json=connectHeaders,proto3" json:"connect_headers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ProxyConnectionSettings) Reset() { *x = ProxyConnectionSettings{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ProxyConnectionSettings) String() string { @@ -1862,8 +1933,8 @@ func (x *ProxyConnectionSettings) String() string { func (*ProxyConnectionSettings) ProtoMessage() {} func (x *ProxyConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[13] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1875,7 +1946,7 @@ func (x *ProxyConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use ProxyConnectionSettings.ProtoReflect.Descriptor instead. func (*ProxyConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{12} + return file_opamp_proto_rawDescGZIP(), []int{13} } func (x *ProxyConnectionSettings) GetUrl() string { @@ -1894,20 +1965,17 @@ func (x *ProxyConnectionSettings) GetConnectHeaders() *Headers { // Status: [Beta] type Headers struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Headers []*Header `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty"` unknownFields protoimpl.UnknownFields - - Headers []*Header `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Headers) Reset() { *x = Headers{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Headers) String() string { @@ -1917,8 +1985,8 @@ func (x *Headers) String() string { func (*Headers) ProtoMessage() {} func (x *Headers) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[14] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1930,7 +1998,7 @@ func (x *Headers) ProtoReflect() protoreflect.Message { // Deprecated: Use Headers.ProtoReflect.Descriptor instead. func (*Headers) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{13} + return file_opamp_proto_rawDescGZIP(), []int{14} } func (x *Headers) GetHeaders() []*Header { @@ -1942,21 +2010,18 @@ func (x *Headers) GetHeaders() []*Header { // Status: [Beta] type Header struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields - - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Header) Reset() { *x = Header{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Header) String() string { @@ -1966,8 +2031,8 @@ func (x *Header) String() string { func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[15] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1979,7 +2044,7 @@ func (x *Header) ProtoReflect() protoreflect.Message { // Deprecated: Use Header.ProtoReflect.Descriptor instead. func (*Header) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{14} + return file_opamp_proto_rawDescGZIP(), []int{15} } func (x *Header) GetKey() string { @@ -1998,10 +2063,7 @@ func (x *Header) GetValue() string { // Status: [Beta] type TLSCertificate struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // PEM-encoded certificate. Required. Cert []byte `protobuf:"bytes,1,opt,name=cert,proto3" json:"cert,omitempty"` // PEM-encoded private key of the certificate. Required. @@ -2012,16 +2074,16 @@ type TLSCertificate struct { // the connecting client's certificate in the future. // It is not recommended that the Agent accepts this CA as an authority for // any purposes. - CaCert []byte `protobuf:"bytes,3,opt,name=ca_cert,json=caCert,proto3" json:"ca_cert,omitempty"` + CaCert []byte `protobuf:"bytes,3,opt,name=ca_cert,json=caCert,proto3" json:"ca_cert,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TLSCertificate) Reset() { *x = TLSCertificate{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TLSCertificate) String() string { @@ -2031,8 +2093,8 @@ func (x *TLSCertificate) String() string { func (*TLSCertificate) ProtoMessage() {} func (x *TLSCertificate) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[16] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2044,7 +2106,7 @@ func (x *TLSCertificate) ProtoReflect() protoreflect.Message { // Deprecated: Use TLSCertificate.ProtoReflect.Descriptor instead. func (*TLSCertificate) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{15} + return file_opamp_proto_rawDescGZIP(), []int{16} } func (x *TLSCertificate) GetCert() []byte { @@ -2070,10 +2132,7 @@ func (x *TLSCertificate) GetCaCert() []byte { // Status: [Beta] type ConnectionSettingsOffers struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Hash of all settings, including settings that may be omitted from this message // because they are unchanged. Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` @@ -2111,16 +2170,16 @@ type ConnectionSettingsOffers struct { // the name of the destination to connect to (as it is known to the Agent). // If this field is not set then the Agent should assume that the other_connections // settings are unchanged. - OtherConnections map[string]*OtherConnectionSettings `protobuf:"bytes,6,rep,name=other_connections,json=otherConnections,proto3" json:"other_connections,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + OtherConnections map[string]*OtherConnectionSettings `protobuf:"bytes,6,rep,name=other_connections,json=otherConnections,proto3" json:"other_connections,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ConnectionSettingsOffers) Reset() { *x = ConnectionSettingsOffers{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ConnectionSettingsOffers) String() string { @@ -2130,8 +2189,8 @@ func (x *ConnectionSettingsOffers) String() string { func (*ConnectionSettingsOffers) ProtoMessage() {} func (x *ConnectionSettingsOffers) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[17] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2143,7 +2202,7 @@ func (x *ConnectionSettingsOffers) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectionSettingsOffers.ProtoReflect.Descriptor instead. func (*ConnectionSettingsOffers) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{16} + return file_opamp_proto_rawDescGZIP(), []int{17} } func (x *ConnectionSettingsOffers) GetHash() []byte { @@ -2191,12 +2250,9 @@ func (x *ConnectionSettingsOffers) GetOtherConnections() map[string]*OtherConnec // List of packages that the Server offers to the Agent. // Status: [Beta] type PackagesAvailable struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Map of packages. Keys are package names, values are the packages available for download. - Packages map[string]*PackageAvailable `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Packages map[string]*PackageAvailable `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // Aggregate hash of all remotely installed packages. The Agent SHOULD include this // value in subsequent PackageStatuses messages. This in turn allows the management // Server to identify that a different set of packages is available for the Agent @@ -2207,15 +2263,15 @@ type PackagesAvailable struct { // // The hash is calculated as an aggregate of all packages names and content. AllPackagesHash []byte `protobuf:"bytes,2,opt,name=all_packages_hash,json=allPackagesHash,proto3" json:"all_packages_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *PackagesAvailable) Reset() { *x = PackagesAvailable{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackagesAvailable) String() string { @@ -2225,8 +2281,8 @@ func (x *PackagesAvailable) String() string { func (*PackagesAvailable) ProtoMessage() {} func (x *PackagesAvailable) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[18] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2238,7 +2294,7 @@ func (x *PackagesAvailable) ProtoReflect() protoreflect.Message { // Deprecated: Use PackagesAvailable.ProtoReflect.Descriptor instead. func (*PackagesAvailable) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{17} + return file_opamp_proto_rawDescGZIP(), []int{18} } func (x *PackagesAvailable) GetPackages() map[string]*PackageAvailable { @@ -2272,11 +2328,8 @@ func (x *PackagesAvailable) GetAllPackagesHash() []byte { // has the right version of the package. // Status: [Beta] type PackageAvailable struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type PackageType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.PackageType" json:"type,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Type PackageType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.PackageType" json:"type,omitempty"` // The package version that is available on the Server side. The Agent may for // example use this information to avoid downloading a package that was previously // already downloaded and failed to install. @@ -2287,16 +2340,16 @@ type PackageAvailable struct { // PackageAvailable message and content of the file of the package. The hash is // used by the Agent to determine if the package it has is different from the // package the Server is offering. - Hash []byte `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"` + Hash []byte `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *PackageAvailable) Reset() { *x = PackageAvailable{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageAvailable) String() string { @@ -2306,8 +2359,8 @@ func (x *PackageAvailable) String() string { func (*PackageAvailable) ProtoMessage() {} func (x *PackageAvailable) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[19] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2319,7 +2372,7 @@ func (x *PackageAvailable) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageAvailable.ProtoReflect.Descriptor instead. func (*PackageAvailable) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{18} + return file_opamp_proto_rawDescGZIP(), []int{19} } func (x *PackageAvailable) GetType() PackageType { @@ -2352,10 +2405,7 @@ func (x *PackageAvailable) GetHash() []byte { // Status: [Beta] type DownloadableFile struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The URL from which the file can be downloaded using HTTP GET request. // The Server at the specified URL SHOULD support range requests // to allow for resuming downloads. @@ -2376,16 +2426,16 @@ type DownloadableFile struct { // For example: // key="Authorization", Value="Basic YWxhZGRpbjpvcGVuc2VzYW1l". // Status: [Development] - Headers *Headers `protobuf:"bytes,4,opt,name=headers,proto3" json:"headers,omitempty"` + Headers *Headers `protobuf:"bytes,4,opt,name=headers,proto3" json:"headers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DownloadableFile) Reset() { *x = DownloadableFile{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DownloadableFile) String() string { @@ -2395,8 +2445,8 @@ func (x *DownloadableFile) String() string { func (*DownloadableFile) ProtoMessage() {} func (x *DownloadableFile) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[20] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2408,7 +2458,7 @@ func (x *DownloadableFile) ProtoReflect() protoreflect.Message { // Deprecated: Use DownloadableFile.ProtoReflect.Descriptor instead. func (*DownloadableFile) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{19} + return file_opamp_proto_rawDescGZIP(), []int{20} } func (x *DownloadableFile) GetDownloadUrl() string { @@ -2440,25 +2490,23 @@ func (x *DownloadableFile) GetHeaders() *Headers { } type ServerErrorResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type ServerErrorResponseType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.ServerErrorResponseType" json:"type,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Type ServerErrorResponseType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.ServerErrorResponseType" json:"type,omitempty"` // Error message in the string form, typically human readable. ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` - // Types that are assignable to Details: + // Types that are valid to be assigned to Details: + // // *ServerErrorResponse_RetryInfo - Details isServerErrorResponse_Details `protobuf_oneof:"Details"` + Details isServerErrorResponse_Details `protobuf_oneof:"Details"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ServerErrorResponse) Reset() { *x = ServerErrorResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerErrorResponse) String() string { @@ -2468,8 +2516,8 @@ func (x *ServerErrorResponse) String() string { func (*ServerErrorResponse) ProtoMessage() {} func (x *ServerErrorResponse) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[21] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2481,7 +2529,7 @@ func (x *ServerErrorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerErrorResponse.ProtoReflect.Descriptor instead. func (*ServerErrorResponse) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{20} + return file_opamp_proto_rawDescGZIP(), []int{21} } func (x *ServerErrorResponse) GetType() ServerErrorResponseType { @@ -2498,16 +2546,18 @@ func (x *ServerErrorResponse) GetErrorMessage() string { return "" } -func (m *ServerErrorResponse) GetDetails() isServerErrorResponse_Details { - if m != nil { - return m.Details +func (x *ServerErrorResponse) GetDetails() isServerErrorResponse_Details { + if x != nil { + return x.Details } return nil } func (x *ServerErrorResponse) GetRetryInfo() *RetryInfo { - if x, ok := x.GetDetails().(*ServerErrorResponse_RetryInfo); ok { - return x.RetryInfo + if x != nil { + if x, ok := x.Details.(*ServerErrorResponse_RetryInfo); ok { + return x.RetryInfo + } } return nil } @@ -2524,20 +2574,17 @@ type ServerErrorResponse_RetryInfo struct { func (*ServerErrorResponse_RetryInfo) isServerErrorResponse_Details() {} type RetryInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - RetryAfterNanoseconds uint64 `protobuf:"varint,1,opt,name=retry_after_nanoseconds,json=retryAfterNanoseconds,proto3" json:"retry_after_nanoseconds,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + RetryAfterNanoseconds uint64 `protobuf:"varint,1,opt,name=retry_after_nanoseconds,json=retryAfterNanoseconds,proto3" json:"retry_after_nanoseconds,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RetryInfo) Reset() { *x = RetryInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RetryInfo) String() string { @@ -2547,8 +2594,8 @@ func (x *RetryInfo) String() string { func (*RetryInfo) ProtoMessage() {} func (x *RetryInfo) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[22] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2560,7 +2607,7 @@ func (x *RetryInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryInfo.ProtoReflect.Descriptor instead. func (*RetryInfo) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{21} + return file_opamp_proto_rawDescGZIP(), []int{22} } func (x *RetryInfo) GetRetryAfterNanoseconds() uint64 { @@ -2574,20 +2621,17 @@ func (x *RetryInfo) GetRetryAfterNanoseconds() uint64 { // perform a command. // Status: [Beta] type ServerToAgentCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Type CommandType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.CommandType" json:"type,omitempty"` unknownFields protoimpl.UnknownFields - - Type CommandType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.CommandType" json:"type,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ServerToAgentCommand) Reset() { *x = ServerToAgentCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerToAgentCommand) String() string { @@ -2597,8 +2641,8 @@ func (x *ServerToAgentCommand) String() string { func (*ServerToAgentCommand) ProtoMessage() {} func (x *ServerToAgentCommand) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[23] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2610,7 +2654,7 @@ func (x *ServerToAgentCommand) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerToAgentCommand.ProtoReflect.Descriptor instead. func (*ServerToAgentCommand) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{22} + return file_opamp_proto_rawDescGZIP(), []int{23} } func (x *ServerToAgentCommand) GetType() CommandType { @@ -2621,25 +2665,22 @@ func (x *ServerToAgentCommand) GetType() CommandType { } type AgentDescription struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Attributes that identify the Agent. // Keys/values are according to OpenTelemetry semantic conventions, see: // https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions // // For standalone running Agents (such as OpenTelemetry Collector) the following // attributes SHOULD be specified: - // - service.name should be set to a reverse FQDN that uniquely identifies the - // Agent type, e.g. "io.opentelemetry.collector" - // - service.namespace if it is used in the environment where the Agent runs. - // - service.version should be set to version number of the Agent build. - // - service.instance.id should be set. It may be set equal to the Agent's - // instance uid (equal to ServerToAgent.instance_uid field) or any other value - // that uniquely identifies the Agent in combination with other attributes. - // - any other attributes that are necessary for uniquely identifying the Agent's - // own telemetry. + // - service.name should be set to a reverse FQDN that uniquely identifies the + // Agent type, e.g. "io.opentelemetry.collector" + // - service.namespace if it is used in the environment where the Agent runs. + // - service.version should be set to version number of the Agent build. + // - service.instance.id should be set. It may be set equal to the Agent's + // instance uid (equal to ServerToAgent.instance_uid field) or any other value + // that uniquely identifies the Agent in combination with other attributes. + // - any other attributes that are necessary for uniquely identifying the Agent's + // own telemetry. // // The Agent SHOULD also include these attributes in the Resource of its own // telemetry. The combination of identifying attributes SHOULD be sufficient to @@ -2649,23 +2690,23 @@ type AgentDescription struct { // Attributes that do not necessarily identify the Agent but help describe // where it runs. // The following attributes SHOULD be included: - // - os.type, os.version - to describe where the Agent runs. - // - host.* to describe the host the Agent runs on. - // - cloud.* to describe the cloud where the host is located. - // - any other relevant Resource attributes that describe this Agent and the - // environment it runs in. - // - any user-defined attributes that the end user would like to associate - // with this Agent. + // - os.type, os.version - to describe where the Agent runs. + // - host.* to describe the host the Agent runs on. + // - cloud.* to describe the cloud where the host is located. + // - any other relevant Resource attributes that describe this Agent and the + // environment it runs in. + // - any user-defined attributes that the end user would like to associate + // with this Agent. NonIdentifyingAttributes []*KeyValue `protobuf:"bytes,2,rep,name=non_identifying_attributes,json=nonIdentifyingAttributes,proto3" json:"non_identifying_attributes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentDescription) Reset() { *x = AgentDescription{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentDescription) String() string { @@ -2675,8 +2716,8 @@ func (x *AgentDescription) String() string { func (*AgentDescription) ProtoMessage() {} func (x *AgentDescription) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[24] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2688,7 +2729,7 @@ func (x *AgentDescription) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentDescription.ProtoReflect.Descriptor instead. func (*AgentDescription) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{23} + return file_opamp_proto_rawDescGZIP(), []int{24} } func (x *AgentDescription) GetIdentifyingAttributes() []*KeyValue { @@ -2708,10 +2749,7 @@ func (x *AgentDescription) GetNonIdentifyingAttributes() []*KeyValue { // The health of the Agent and sub-components // Status: [Beta] type ComponentHealth struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Set to true if the component is up and healthy. Healthy bool `protobuf:"varint,1,opt,name=healthy,proto3" json:"healthy,omitempty"` // Timestamp since the component is up, i.e. when the component was started. @@ -2729,16 +2767,16 @@ type ComponentHealth struct { StatusTimeUnixNano uint64 `protobuf:"fixed64,5,opt,name=status_time_unix_nano,json=statusTimeUnixNano,proto3" json:"status_time_unix_nano,omitempty"` // A map to store more granular, sub-component health. It can nest as deeply as needed to // describe the underlying system. - ComponentHealthMap map[string]*ComponentHealth `protobuf:"bytes,6,rep,name=component_health_map,json=componentHealthMap,proto3" json:"component_health_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + ComponentHealthMap map[string]*ComponentHealth `protobuf:"bytes,6,rep,name=component_health_map,json=componentHealthMap,proto3" json:"component_health_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ComponentHealth) Reset() { *x = ComponentHealth{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ComponentHealth) String() string { @@ -2748,8 +2786,8 @@ func (x *ComponentHealth) String() string { func (*ComponentHealth) ProtoMessage() {} func (x *ComponentHealth) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[25] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2761,7 +2799,7 @@ func (x *ComponentHealth) ProtoReflect() protoreflect.Message { // Deprecated: Use ComponentHealth.ProtoReflect.Descriptor instead. func (*ComponentHealth) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{24} + return file_opamp_proto_rawDescGZIP(), []int{25} } func (x *ComponentHealth) GetHealthy() bool { @@ -2807,21 +2845,18 @@ func (x *ComponentHealth) GetComponentHealthMap() map[string]*ComponentHealth { } type EffectiveConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The effective config of the Agent. - ConfigMap *AgentConfigMap `protobuf:"bytes,1,opt,name=config_map,json=configMap,proto3" json:"config_map,omitempty"` + ConfigMap *AgentConfigMap `protobuf:"bytes,1,opt,name=config_map,json=configMap,proto3" json:"config_map,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *EffectiveConfig) Reset() { *x = EffectiveConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EffectiveConfig) String() string { @@ -2831,8 +2866,8 @@ func (x *EffectiveConfig) String() string { func (*EffectiveConfig) ProtoMessage() {} func (x *EffectiveConfig) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[26] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2844,7 +2879,7 @@ func (x *EffectiveConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use EffectiveConfig.ProtoReflect.Descriptor instead. func (*EffectiveConfig) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{25} + return file_opamp_proto_rawDescGZIP(), []int{26} } func (x *EffectiveConfig) GetConfigMap() *AgentConfigMap { @@ -2855,10 +2890,7 @@ func (x *EffectiveConfig) GetConfigMap() *AgentConfigMap { } type RemoteConfigStatus struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The hash of the remote config that was last received by this Agent in the // AgentRemoteConfig.config_hash field. // The Server SHOULD compare this hash with the config hash @@ -2867,16 +2899,16 @@ type RemoteConfigStatus struct { LastRemoteConfigHash []byte `protobuf:"bytes,1,opt,name=last_remote_config_hash,json=lastRemoteConfigHash,proto3" json:"last_remote_config_hash,omitempty"` Status RemoteConfigStatuses `protobuf:"varint,2,opt,name=status,proto3,enum=opamp.proto.RemoteConfigStatuses" json:"status,omitempty"` // Optional error message if status==FAILED. - ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RemoteConfigStatus) Reset() { *x = RemoteConfigStatus{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RemoteConfigStatus) String() string { @@ -2886,8 +2918,8 @@ func (x *RemoteConfigStatus) String() string { func (*RemoteConfigStatus) ProtoMessage() {} func (x *RemoteConfigStatus) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[27] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2899,7 +2931,7 @@ func (x *RemoteConfigStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoteConfigStatus.ProtoReflect.Descriptor instead. func (*RemoteConfigStatus) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{26} + return file_opamp_proto_rawDescGZIP(), []int{27} } func (x *RemoteConfigStatus) GetLastRemoteConfigHash() []byte { @@ -2925,10 +2957,7 @@ func (x *RemoteConfigStatus) GetErrorMessage() string { // Status: [Development] type ConnectionSettingsStatus struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The hash of the connection settings that was last recieved by this Agent // in the connection_settings.hash field. The Server SHOULD compare this // hash with the OfferedConnectionSettings hash it has for the Agent and if @@ -2937,16 +2966,16 @@ type ConnectionSettingsStatus struct { LastConnectionSettingsHash []byte `protobuf:"bytes,1,opt,name=last_connection_settings_hash,json=lastConnectionSettingsHash,proto3" json:"last_connection_settings_hash,omitempty"` Status ConnectionSettingsStatuses `protobuf:"varint,2,opt,name=status,proto3,enum=opamp.proto.ConnectionSettingsStatuses" json:"status,omitempty"` // Optional error message if status==FAILED. - ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ConnectionSettingsStatus) Reset() { *x = ConnectionSettingsStatus{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ConnectionSettingsStatus) String() string { @@ -2956,8 +2985,8 @@ func (x *ConnectionSettingsStatus) String() string { func (*ConnectionSettingsStatus) ProtoMessage() {} func (x *ConnectionSettingsStatus) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[28] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2969,7 +2998,7 @@ func (x *ConnectionSettingsStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectionSettingsStatus.ProtoReflect.Descriptor instead. func (*ConnectionSettingsStatus) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{27} + return file_opamp_proto_rawDescGZIP(), []int{28} } func (x *ConnectionSettingsStatus) GetLastConnectionSettingsHash() []byte { @@ -2997,13 +3026,10 @@ func (x *ConnectionSettingsStatus) GetErrorMessage() string { // has or was offered. // Status: [Beta] type PackageStatuses struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A map of PackageStatus messages, where the keys are package names. // The key MUST match the name field of PackageStatus message. - Packages map[string]*PackageStatus `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Packages map[string]*PackageStatus `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // The aggregate hash of all packages that this Agent previously received from the // Server via PackagesAvailable message. // @@ -3015,16 +3041,16 @@ type PackageStatuses struct { // PackagesAvailable message and that error is not related to any particular single // package. // The field must be unset is there were no processing errors. - ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *PackageStatuses) Reset() { *x = PackageStatuses{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageStatuses) String() string { @@ -3034,8 +3060,8 @@ func (x *PackageStatuses) String() string { func (*PackageStatuses) ProtoMessage() {} func (x *PackageStatuses) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[29] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3047,7 +3073,7 @@ func (x *PackageStatuses) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageStatuses.ProtoReflect.Descriptor instead. func (*PackageStatuses) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{28} + return file_opamp_proto_rawDescGZIP(), []int{29} } func (x *PackageStatuses) GetPackages() map[string]*PackageStatus { @@ -3074,10 +3100,7 @@ func (x *PackageStatuses) GetErrorMessage() string { // The status of a single package. // Status: [Beta] type PackageStatus struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Package name. MUST be always set and MUST match the key in the packages field // of PackageStatuses message. Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -3124,15 +3147,15 @@ type PackageStatus struct { // Should only be set if status is Downloading. // Status: [Development] DownloadDetails *PackageDownloadDetails `protobuf:"bytes,8,opt,name=download_details,json=downloadDetails,proto3" json:"download_details,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *PackageStatus) Reset() { *x = PackageStatus{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageStatus) String() string { @@ -3142,8 +3165,8 @@ func (x *PackageStatus) String() string { func (*PackageStatus) ProtoMessage() {} func (x *PackageStatus) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[30] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3155,7 +3178,7 @@ func (x *PackageStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageStatus.ProtoReflect.Descriptor instead. func (*PackageStatus) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{29} + return file_opamp_proto_rawDescGZIP(), []int{30} } func (x *PackageStatus) GetName() string { @@ -3217,23 +3240,20 @@ func (x *PackageStatus) GetDownloadDetails() *PackageDownloadDetails { // Additional details that an agent can use to describe an in-progress package download. // Status: [Development] type PackageDownloadDetails struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // The package download progress as a percentage. DownloadPercent float64 `protobuf:"fixed64,1,opt,name=download_percent,json=downloadPercent,proto3" json:"download_percent,omitempty"` // The current package download rate in bytes per second. DownloadBytesPerSecond float64 `protobuf:"fixed64,2,opt,name=download_bytes_per_second,json=downloadBytesPerSecond,proto3" json:"download_bytes_per_second,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *PackageDownloadDetails) Reset() { *x = PackageDownloadDetails{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageDownloadDetails) String() string { @@ -3243,8 +3263,8 @@ func (x *PackageDownloadDetails) String() string { func (*PackageDownloadDetails) ProtoMessage() {} func (x *PackageDownloadDetails) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[30] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[31] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3256,7 +3276,7 @@ func (x *PackageDownloadDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageDownloadDetails.ProtoReflect.Descriptor instead. func (*PackageDownloadDetails) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{30} + return file_opamp_proto_rawDescGZIP(), []int{31} } func (x *PackageDownloadDetails) GetDownloadPercent() float64 { @@ -3276,23 +3296,20 @@ func (x *PackageDownloadDetails) GetDownloadBytesPerSecond() float64 { // Properties related to identification of the Agent, which can be overridden // by the Server if needed type AgentIdentification struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // When new_instance_uid is set, Agent MUST update instance_uid // to the value provided and use it for all further communication. // MUST be 16 bytes long and SHOULD be generated using the UUID v7 spec. NewInstanceUid []byte `protobuf:"bytes,1,opt,name=new_instance_uid,json=newInstanceUid,proto3" json:"new_instance_uid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentIdentification) Reset() { *x = AgentIdentification{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentIdentification) String() string { @@ -3302,8 +3319,8 @@ func (x *AgentIdentification) String() string { func (*AgentIdentification) ProtoMessage() {} func (x *AgentIdentification) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[32] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3315,7 +3332,7 @@ func (x *AgentIdentification) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentIdentification.ProtoReflect.Descriptor instead. func (*AgentIdentification) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{31} + return file_opamp_proto_rawDescGZIP(), []int{32} } func (x *AgentIdentification) GetNewInstanceUid() []byte { @@ -3326,10 +3343,7 @@ func (x *AgentIdentification) GetNewInstanceUid() []byte { } type AgentRemoteConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Agent config offered by the management Server to the Agent instance. SHOULD NOT be // set if the config for this Agent has not changed since it was last requested (i.e. // AgentConfigRequest.last_remote_config_hash field is equal to @@ -3344,16 +3358,16 @@ type AgentRemoteConfig struct { // // Management Server must choose a hashing function that guarantees lack of hash // collisions in practice. - ConfigHash []byte `protobuf:"bytes,2,opt,name=config_hash,json=configHash,proto3" json:"config_hash,omitempty"` + ConfigHash []byte `protobuf:"bytes,2,opt,name=config_hash,json=configHash,proto3" json:"config_hash,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentRemoteConfig) Reset() { *x = AgentRemoteConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentRemoteConfig) String() string { @@ -3363,8 +3377,8 @@ func (x *AgentRemoteConfig) String() string { func (*AgentRemoteConfig) ProtoMessage() {} func (x *AgentRemoteConfig) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[32] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[33] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3376,7 +3390,7 @@ func (x *AgentRemoteConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentRemoteConfig.ProtoReflect.Descriptor instead. func (*AgentRemoteConfig) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{32} + return file_opamp_proto_rawDescGZIP(), []int{33} } func (x *AgentRemoteConfig) GetConfig() *AgentConfigMap { @@ -3394,25 +3408,22 @@ func (x *AgentRemoteConfig) GetConfigHash() []byte { } type AgentConfigMap struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Map of configs. Keys are config file names or config section names. // The configuration is assumed to be a collection of one or more named config files // or sections. // For agents that use a single config file or section the map SHOULD contain a single // entry and the key may be an empty string. - ConfigMap map[string]*AgentConfigFile `protobuf:"bytes,1,rep,name=config_map,json=configMap,proto3" json:"config_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + ConfigMap map[string]*AgentConfigFile `protobuf:"bytes,1,rep,name=config_map,json=configMap,proto3" json:"config_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentConfigMap) Reset() { *x = AgentConfigMap{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentConfigMap) String() string { @@ -3422,8 +3433,8 @@ func (x *AgentConfigMap) String() string { func (*AgentConfigMap) ProtoMessage() {} func (x *AgentConfigMap) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[33] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[34] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3435,7 +3446,7 @@ func (x *AgentConfigMap) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentConfigMap.ProtoReflect.Descriptor instead. func (*AgentConfigMap) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{33} + return file_opamp_proto_rawDescGZIP(), []int{34} } func (x *AgentConfigMap) GetConfigMap() map[string]*AgentConfigFile { @@ -3446,25 +3457,22 @@ func (x *AgentConfigMap) GetConfigMap() map[string]*AgentConfigFile { } type AgentConfigFile struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Config file or section body. The content, format and encoding depends on the Agent // type. The content_type field may optionally describe the MIME type of the body. Body []byte `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` // Optional MIME Content-Type that describes what's in the body field, for // example "text/yaml". - ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` + ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentConfigFile) Reset() { *x = AgentConfigFile{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentConfigFile) String() string { @@ -3474,8 +3482,8 @@ func (x *AgentConfigFile) String() string { func (*AgentConfigFile) ProtoMessage() {} func (x *AgentConfigFile) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[34] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[35] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3487,7 +3495,7 @@ func (x *AgentConfigFile) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentConfigFile.ProtoReflect.Descriptor instead. func (*AgentConfigFile) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{34} + return file_opamp_proto_rawDescGZIP(), []int{35} } func (x *AgentConfigFile) GetBody() []byte { @@ -3505,24 +3513,21 @@ func (x *AgentConfigFile) GetContentType() string { } type CustomCapabilities struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A list of custom capabilities that are supported. Each capability is a reverse FQDN // with optional version information that uniquely identifies the custom capability // and should match a capability specified in a supported CustomMessage. // Status: [Development] - Capabilities []string `protobuf:"bytes,1,rep,name=capabilities,proto3" json:"capabilities,omitempty"` + Capabilities []string `protobuf:"bytes,1,rep,name=capabilities,proto3" json:"capabilities,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CustomCapabilities) Reset() { *x = CustomCapabilities{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CustomCapabilities) String() string { @@ -3532,8 +3537,8 @@ func (x *CustomCapabilities) String() string { func (*CustomCapabilities) ProtoMessage() {} func (x *CustomCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[35] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[36] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3545,7 +3550,7 @@ func (x *CustomCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomCapabilities.ProtoReflect.Descriptor instead. func (*CustomCapabilities) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{35} + return file_opamp_proto_rawDescGZIP(), []int{36} } func (x *CustomCapabilities) GetCapabilities() []string { @@ -3556,10 +3561,7 @@ func (x *CustomCapabilities) GetCapabilities() []string { } type CustomMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // A reverse FQDN that uniquely identifies the capability and matches one of the // capabilities in the CustomCapabilities message. // Status: [Development] @@ -3572,16 +3574,16 @@ type CustomMessage struct { // Binary data of the message. The capability must specify the format of the contents // of the data for each custom message type it defines. // Status: [Development] - Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CustomMessage) Reset() { *x = CustomMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_opamp_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_opamp_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CustomMessage) String() string { @@ -3591,8 +3593,8 @@ func (x *CustomMessage) String() string { func (*CustomMessage) ProtoMessage() {} func (x *CustomMessage) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[36] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_opamp_proto_msgTypes[37] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3604,7 +3606,7 @@ func (x *CustomMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomMessage.ProtoReflect.Descriptor instead. func (*CustomMessage) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{36} + return file_opamp_proto_rawDescGZIP(), []int{37} } func (x *CustomMessage) GetCapability() string { @@ -3628,676 +3630,341 @@ func (x *CustomMessage) GetData() []byte { return nil } -var File_opamp_proto protoreflect.FileDescriptor +type TrustChainResponse_Certificate struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The certificate in DER format. + DerData []byte `protobuf:"bytes,1,opt,name=der_data,json=derData,proto3" json:"der_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TrustChainResponse_Certificate) Reset() { + *x = TrustChainResponse_Certificate{} + mi := &file_opamp_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} -var file_opamp_proto_rawDesc = []byte{ - 0x0a, 0x0b, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x6f, - 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0e, 0x61, 0x6e, 0x79, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf6, 0x07, 0x0a, 0x0d, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x69, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, - 0x75, 0x6d, 0x12, 0x4a, 0x0a, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, - 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x12, 0x34, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x47, 0x0a, 0x10, 0x65, 0x66, 0x66, 0x65, - 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x51, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x63, - 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x52, 0x0f, 0x70, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x47, 0x0a, - 0x10, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x0f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x66, 0x0a, 0x1b, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x19, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, - 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x61, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, - 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x13, 0x61, 0x76, 0x61, 0x69, 0x6c, - 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x63, - 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x22, 0x5e, 0x0a, 0x19, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x05, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x4f, 0x70, 0x41, 0x4d, 0x50, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, - 0x05, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x22, 0x72, 0x0a, 0x1e, 0x4f, 0x70, 0x41, 0x4d, 0x50, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a, 0x13, 0x63, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x26, 0x0a, 0x12, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, - 0x73, 0x72, 0x22, 0xd9, 0x01, 0x0a, 0x13, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, - 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x1a, 0x5c, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x88, - 0x02, 0x0a, 0x10, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x5e, 0x0a, 0x11, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x32, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x2e, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x1a, 0x61, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x43, 0x6f, 0x6d, - 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xc8, 0x05, 0x0a, 0x0d, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x69, 0x64, 0x12, 0x47, - 0x0a, 0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, - 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x56, 0x0a, 0x13, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73, - 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4d, 0x0a, 0x12, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, - 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x52, 0x11, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, - 0x62, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x61, 0x70, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x53, 0x0a, - 0x14, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6f, 0x70, - 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, - 0x50, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, - 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x12, 0x63, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, - 0x73, 0x12, 0x41, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x22, 0xeb, 0x02, 0x0a, 0x17, 0x4f, 0x70, 0x41, 0x4d, 0x50, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, - 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, - 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, - 0x12, 0x34, 0x0a, 0x03, 0x74, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x52, 0x03, 0x74, 0x6c, 0x73, 0x12, 0x3a, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x05, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x22, 0xb1, 0x02, 0x0a, 0x1b, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x07, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x61, - 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x03, 0x74, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x03, 0x74, 0x6c, 0x73, 0x12, 0x3a, 0x0a, 0x05, 0x70, 0x72, - 0x6f, 0x78, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, - 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x22, 0xcf, 0x03, 0x0a, 0x17, 0x4f, 0x74, 0x68, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x07, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x61, - 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x12, 0x5e, 0x0a, 0x0e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x6f, - 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x03, 0x74, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x03, 0x74, 0x6c, 0x73, 0x12, 0x3a, 0x0a, 0x05, 0x70, 0x72, - 0x6f, 0x78, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, - 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x1a, 0x40, 0x0a, 0x12, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x53, - 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x98, 0x02, 0x0a, 0x15, 0x54, 0x4c, 0x53, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x61, 0x5f, 0x70, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x61, 0x50, - 0x65, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3e, 0x0a, 0x1c, 0x69, 0x6e, - 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x61, 0x5f, - 0x63, 0x65, 0x72, 0x74, 0x73, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x18, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, - 0x61, 0x43, 0x65, 0x72, 0x74, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, - 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, - 0x66, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x1f, 0x0a, 0x0b, - 0x6d, 0x69, 0x6e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, - 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, - 0x0a, 0x0d, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x73, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, - 0x74, 0x65, 0x73, 0x22, 0x6a, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x10, - 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, - 0x12, 0x3d, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, - 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, - 0x38, 0x0a, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6f, 0x70, - 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0x30, 0x0a, 0x06, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x5e, 0x0a, 0x0e, 0x54, - 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, - 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x61, 0x43, 0x65, 0x72, 0x74, 0x22, 0x98, 0x04, 0x0a, 0x18, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x3a, 0x0a, 0x05, - 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, - 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x70, 0x41, 0x4d, 0x50, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x52, 0x05, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x12, 0x49, 0x0a, 0x0b, 0x6f, 0x77, 0x6e, 0x5f, - 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, - 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x65, 0x6c, 0x65, - 0x6d, 0x65, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x0a, 0x6f, 0x77, 0x6e, 0x4d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x12, 0x47, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x52, 0x09, 0x6f, 0x77, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x08, - 0x6f, 0x77, 0x6e, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, - 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x65, 0x6c, - 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x4c, 0x6f, 0x67, - 0x73, 0x12, 0x68, 0x0a, 0x11, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x6f, - 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4f, 0x66, 0x66, - 0x65, 0x72, 0x73, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, 0x6f, 0x74, 0x68, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x69, 0x0a, 0x15, 0x4f, - 0x74, 0x68, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe5, 0x01, 0x0a, 0x11, 0x50, 0x61, 0x63, 0x6b, 0x61, - 0x67, 0x65, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x48, 0x0a, 0x08, - 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, - 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x63, - 0x6b, 0x61, 0x67, 0x65, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x50, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x70, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0f, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x48, 0x61, - 0x73, 0x68, 0x1a, 0x5a, 0x0a, 0x0d, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xa1, - 0x01, 0x0a, 0x10, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, - 0x62, 0x6c, 0x65, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x18, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x04, 0x66, - 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6f, 0x70, 0x61, 0x6d, - 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x61, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x22, 0xa6, 0x01, 0x0a, 0x10, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x61, - 0x62, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, - 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x68, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, - 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x73, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0xb8, 0x01, 0x0a, 0x13, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, - 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, - 0x52, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x09, 0x0a, 0x07, 0x44, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x43, 0x0a, 0x09, 0x52, 0x65, 0x74, 0x72, 0x79, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x61, 0x66, 0x74, - 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x72, 0x65, 0x74, 0x72, 0x79, 0x41, 0x66, 0x74, 0x65, 0x72, - 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x44, 0x0a, 0x14, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x18, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x22, 0xb5, 0x01, 0x0a, 0x10, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, 0x16, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x15, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x1a, 0x6e, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, - 0x18, 0x6e, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x41, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0x93, 0x03, 0x0a, 0x0f, 0x43, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x18, 0x0a, - 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x2f, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x06, 0x52, 0x11, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, - 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x61, - 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x31, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, - 0x6e, 0x69, 0x78, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x06, 0x52, 0x12, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, - 0x6e, 0x6f, 0x12, 0x66, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, - 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x34, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, - 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2e, 0x43, - 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x4d, 0x61, - 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x4d, 0x61, 0x70, 0x1a, 0x63, 0x0a, 0x17, 0x43, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x4d, 0x61, 0x70, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x48, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x4d, 0x0a, 0x0f, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x4d, 0x61, 0x70, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x22, 0xab, - 0x01, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x61, 0x73, 0x68, 0x12, 0x39, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6f, - 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xc3, 0x01, 0x0a, - 0x18, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x1a, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3f, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x6f, - 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x65, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, - 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x22, 0xa1, 0x02, 0x0a, 0x0f, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x46, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x48, - 0x0a, 0x21, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x64, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1d, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x63, 0x6b, - 0x61, 0x67, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x57, 0x0a, - 0x0d, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x88, 0x03, 0x0a, 0x0d, 0x50, 0x61, 0x63, 0x6b, 0x61, - 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x48, 0x61, - 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x34, - 0x0a, 0x16, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x65, 0x64, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x65, 0x64, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6f, - 0x66, 0x66, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x11, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x65, 0x64, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x36, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x4e, 0x0a, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x64, 0x65, - 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x70, - 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, - 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x52, 0x0f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x22, 0x7e, 0x0a, 0x16, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x44, 0x6f, 0x77, 0x6e, - 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, - 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, - 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, - 0x61, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, - 0x6f, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x16, 0x64, 0x6f, 0x77, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x22, 0x3f, 0x0a, 0x13, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x65, 0x77, 0x5f, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0e, 0x6e, 0x65, 0x77, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x55, - 0x69, 0x64, 0x22, 0x69, 0x0a, 0x11, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x4d, 0x61, 0x70, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x61, 0x73, 0x68, 0x22, 0xb7, 0x01, - 0x0a, 0x0e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, - 0x12, 0x49, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, - 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x1a, 0x5a, 0x0a, 0x0e, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x48, 0x0a, 0x0f, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, - 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x22, 0x38, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x70, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, - 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x57, 0x0a, 0x0d, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, - 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x2a, 0x63, 0x0a, 0x12, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x46, 0x6c, 0x61, 0x67, 0x73, - 0x5f, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x10, 0x00, 0x12, 0x29, - 0x0a, 0x25, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x46, - 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x55, 0x69, 0x64, 0x10, 0x01, 0x2a, 0x92, 0x01, 0x0a, 0x12, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x73, - 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, - 0x65, 0x64, 0x10, 0x00, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x10, 0x01, 0x12, 0x30, 0x0a, 0x2c, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x46, 0x6c, 0x61, - 0x67, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, - 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x10, 0x02, 0x2a, 0xf7, - 0x02, 0x0a, 0x12, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, - 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x55, 0x6e, 0x73, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x10, 0x00, 0x12, 0x24, 0x0a, 0x20, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0x01, 0x12, - 0x29, 0x0a, 0x25, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73, 0x52, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x10, 0x02, 0x12, 0x2d, 0x0a, 0x29, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, - 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x10, 0x08, - 0x12, 0x2c, 0x0a, 0x28, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x50, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0x10, 0x12, 0x2f, - 0x0a, 0x2b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, - 0x74, 0x69, 0x65, 0x73, 0x5f, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x10, 0x20, 0x12, - 0x37, 0x0a, 0x33, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x40, 0x2a, 0x3e, 0x0a, 0x0b, 0x50, 0x61, 0x63, 0x6b, - 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x61, 0x63, 0x6b, 0x61, - 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x6f, 0x70, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x10, - 0x00, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x5f, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x10, 0x01, 0x2a, 0x8f, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, - 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, - 0x01, 0x12, 0x27, 0x0a, 0x23, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x55, 0x6e, 0x61, - 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x10, 0x02, 0x2a, 0x26, 0x0a, 0x0b, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x10, 0x00, 0x2a, 0x85, 0x06, 0x0a, 0x11, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x1d, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x55, 0x6e, - 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0x01, - 0x12, 0x29, 0x0a, 0x25, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x52, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x10, 0x02, 0x12, 0x2c, 0x0a, 0x28, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x10, 0x08, - 0x12, 0x2c, 0x0a, 0x28, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x50, 0x61, 0x63, - 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x10, 0x10, 0x12, 0x26, - 0x0a, 0x22, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, - 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x4f, 0x77, 0x6e, 0x54, 0x72, - 0x61, 0x63, 0x65, 0x73, 0x10, 0x20, 0x12, 0x27, 0x0a, 0x23, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, - 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x4f, 0x77, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x10, 0x40, 0x12, - 0x25, 0x0a, 0x20, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, - 0x74, 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x4f, 0x77, 0x6e, 0x4c, - 0x6f, 0x67, 0x73, 0x10, 0x80, 0x01, 0x12, 0x35, 0x0a, 0x30, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, - 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x41, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x73, 0x4f, 0x70, 0x41, 0x4d, 0x50, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x10, 0x80, 0x02, 0x12, 0x35, 0x0a, - 0x30, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x10, 0x80, 0x04, 0x12, 0x2c, 0x0a, 0x27, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x10, - 0x80, 0x08, 0x12, 0x24, 0x0a, 0x1f, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x10, 0x80, 0x10, 0x12, 0x2a, 0x0a, 0x25, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x10, 0x80, 0x20, 0x12, 0x27, 0x0a, 0x22, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x10, 0x80, 0x40, 0x12, 0x32, 0x0a, - 0x2c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, - 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x10, 0x80, 0x80, - 0x01, 0x12, 0x37, 0x0a, 0x31, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0x80, 0x80, 0x02, 0x2a, 0xba, 0x01, 0x0a, 0x1a, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x20, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, - 0x26, 0x0a, 0x22, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x41, 0x50, - 0x50, 0x4c, 0x49, 0x45, 0x44, 0x10, 0x01, 0x12, 0x27, 0x0a, 0x23, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x41, 0x50, 0x50, 0x4c, 0x59, 0x49, 0x4e, 0x47, 0x10, 0x02, - 0x12, 0x25, 0x0a, 0x21, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x9c, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, - 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, - 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x41, 0x50, 0x50, 0x4c, 0x49, 0x45, 0x44, - 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x41, 0x50, 0x50, 0x4c, 0x59, - 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x5f, 0x46, 0x41, - 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x2a, 0xc4, 0x01, 0x0a, 0x11, 0x50, 0x61, 0x63, 0x6b, 0x61, - 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x1f, 0x0a, 0x1b, - 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, - 0x6d, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x10, 0x00, 0x12, 0x24, 0x0a, - 0x20, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, - 0x75, 0x6d, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, - 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x23, 0x0a, 0x1f, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6c, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x03, 0x12, 0x21, 0x0a, 0x1d, 0x50, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x5f, - 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x04, 0x42, 0x2e, 0x5a, - 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, - 0x2d, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, 0x6f, 0x70, 0x61, 0x6d, 0x70, - 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x73, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, +func (x *TrustChainResponse_Certificate) String() string { + return protoimpl.X.MessageStringOf(x) } +func (*TrustChainResponse_Certificate) ProtoMessage() {} + +func (x *TrustChainResponse_Certificate) ProtoReflect() protoreflect.Message { + mi := &file_opamp_proto_msgTypes[40] + 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 TrustChainResponse_Certificate.ProtoReflect.Descriptor instead. +func (*TrustChainResponse_Certificate) Descriptor() ([]byte, []int) { + return file_opamp_proto_rawDescGZIP(), []int{8, 0} +} + +func (x *TrustChainResponse_Certificate) GetDerData() []byte { + if x != nil { + return x.DerData + } + return nil +} + +var File_opamp_proto protoreflect.FileDescriptor + +const file_opamp_proto_rawDesc = "" + + "\n" + + "\vopamp.proto\x12\vopamp.proto\x1a\x0eanyvalue.proto\"\xf6\a\n" + + "\rAgentToServer\x12!\n" + + "\finstance_uid\x18\x01 \x01(\fR\vinstanceUid\x12!\n" + + "\fsequence_num\x18\x02 \x01(\x04R\vsequenceNum\x12J\n" + + "\x11agent_description\x18\x03 \x01(\v2\x1d.opamp.proto.AgentDescriptionR\x10agentDescription\x12\"\n" + + "\fcapabilities\x18\x04 \x01(\x04R\fcapabilities\x124\n" + + "\x06health\x18\x05 \x01(\v2\x1c.opamp.proto.ComponentHealthR\x06health\x12G\n" + + "\x10effective_config\x18\x06 \x01(\v2\x1c.opamp.proto.EffectiveConfigR\x0feffectiveConfig\x12Q\n" + + "\x14remote_config_status\x18\a \x01(\v2\x1f.opamp.proto.RemoteConfigStatusR\x12remoteConfigStatus\x12G\n" + + "\x10package_statuses\x18\b \x01(\v2\x1c.opamp.proto.PackageStatusesR\x0fpackageStatuses\x12G\n" + + "\x10agent_disconnect\x18\t \x01(\v2\x1c.opamp.proto.AgentDisconnectR\x0fagentDisconnect\x12\x14\n" + + "\x05flags\x18\n" + + " \x01(\x04R\x05flags\x12f\n" + + "\x1bconnection_settings_request\x18\v \x01(\v2&.opamp.proto.ConnectionSettingsRequestR\x19connectionSettingsRequest\x12P\n" + + "\x13custom_capabilities\x18\f \x01(\v2\x1f.opamp.proto.CustomCapabilitiesR\x12customCapabilities\x12A\n" + + "\x0ecustom_message\x18\r \x01(\v2\x1a.opamp.proto.CustomMessageR\rcustomMessage\x12S\n" + + "\x14available_components\x18\x0e \x01(\v2 .opamp.proto.AvailableComponentsR\x13availableComponents\x12c\n" + + "\x1aconnection_settings_status\x18\x0f \x01(\v2%.opamp.proto.ConnectionSettingsStatusR\x18connectionSettingsStatus\"\x11\n" + + "\x0fAgentDisconnect\"^\n" + + "\x19ConnectionSettingsRequest\x12A\n" + + "\x05opamp\x18\x01 \x01(\v2+.opamp.proto.OpAMPConnectionSettingsRequestR\x05opamp\"r\n" + + "\x1eOpAMPConnectionSettingsRequest\x12P\n" + + "\x13certificate_request\x18\x01 \x01(\v2\x1f.opamp.proto.CertificateRequestR\x12certificateRequest\"&\n" + + "\x12CertificateRequest\x12\x10\n" + + "\x03csr\x18\x01 \x01(\fR\x03csr\"\xd9\x01\n" + + "\x13AvailableComponents\x12P\n" + + "\n" + + "components\x18\x01 \x03(\v20.opamp.proto.AvailableComponents.ComponentsEntryR\n" + + "components\x12\x12\n" + + "\x04hash\x18\x02 \x01(\fR\x04hash\x1a\\\n" + + "\x0fComponentsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + + "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.ComponentDetailsR\x05value:\x028\x01\"\x88\x02\n" + + "\x10ComponentDetails\x121\n" + + "\bmetadata\x18\x01 \x03(\v2\x15.opamp.proto.KeyValueR\bmetadata\x12^\n" + + "\x11sub_component_map\x18\x02 \x03(\v22.opamp.proto.ComponentDetails.SubComponentMapEntryR\x0fsubComponentMap\x1aa\n" + + "\x14SubComponentMapEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + + "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.ComponentDetailsR\x05value:\x028\x01\"\xb9\x06\n" + + "\rServerToAgent\x12!\n" + + "\finstance_uid\x18\x01 \x01(\fR\vinstanceUid\x12G\n" + + "\x0eerror_response\x18\x02 \x01(\v2 .opamp.proto.ServerErrorResponseR\rerrorResponse\x12C\n" + + "\rremote_config\x18\x03 \x01(\v2\x1e.opamp.proto.AgentRemoteConfigR\fremoteConfig\x12V\n" + + "\x13connection_settings\x18\x04 \x01(\v2%.opamp.proto.ConnectionSettingsOffersR\x12connectionSettings\x12M\n" + + "\x12packages_available\x18\x05 \x01(\v2\x1e.opamp.proto.PackagesAvailableR\x11packagesAvailable\x12\x14\n" + + "\x05flags\x18\x06 \x01(\x04R\x05flags\x12\"\n" + + "\fcapabilities\x18\a \x01(\x04R\fcapabilities\x12S\n" + + "\x14agent_identification\x18\b \x01(\v2 .opamp.proto.AgentIdentificationR\x13agentIdentification\x12;\n" + + "\acommand\x18\t \x01(\v2!.opamp.proto.ServerToAgentCommandR\acommand\x12P\n" + + "\x13custom_capabilities\x18\n" + + " \x01(\v2\x1f.opamp.proto.CustomCapabilitiesR\x12customCapabilities\x12A\n" + + "\x0ecustom_message\x18\v \x01(\v2\x1a.opamp.proto.CustomMessageR\rcustomMessage\x12Q\n" + + "\x14trust_chain_response\x18\f \x01(\v2\x1f.opamp.proto.TrustChainResponseR\x12trustChainResponse\x12\x1c\n" + + "\tsignature\x18\r \x01(\fR\tsignature\"\xbd\x01\n" + + "\x12TrustChainResponse\x12X\n" + + "\x11certificate_chain\x18\x01 \x03(\v2+.opamp.proto.TrustChainResponse.CertificateR\x10certificateChain\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x1a(\n" + + "\vCertificate\x12\x19\n" + + "\bder_data\x18\x01 \x01(\fR\aderData\"\xeb\x02\n" + + "\x17OpAMPConnectionSettings\x121\n" + + "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x12.\n" + + "\aheaders\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\x12=\n" + + "\vcertificate\x18\x03 \x01(\v2\x1b.opamp.proto.TLSCertificateR\vcertificate\x12<\n" + + "\x1aheartbeat_interval_seconds\x18\x04 \x01(\x04R\x18heartbeatIntervalSeconds\x124\n" + + "\x03tls\x18\x05 \x01(\v2\".opamp.proto.TLSConnectionSettingsR\x03tls\x12:\n" + + "\x05proxy\x18\x06 \x01(\v2$.opamp.proto.ProxyConnectionSettingsR\x05proxy\"\xb1\x02\n" + + "\x1bTelemetryConnectionSettings\x121\n" + + "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x12.\n" + + "\aheaders\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\x12=\n" + + "\vcertificate\x18\x03 \x01(\v2\x1b.opamp.proto.TLSCertificateR\vcertificate\x124\n" + + "\x03tls\x18\x04 \x01(\v2\".opamp.proto.TLSConnectionSettingsR\x03tls\x12:\n" + + "\x05proxy\x18\x05 \x01(\v2$.opamp.proto.ProxyConnectionSettingsR\x05proxy\"\xcf\x03\n" + + "\x17OtherConnectionSettings\x121\n" + + "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x12.\n" + + "\aheaders\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\x12=\n" + + "\vcertificate\x18\x03 \x01(\v2\x1b.opamp.proto.TLSCertificateR\vcertificate\x12^\n" + + "\x0eother_settings\x18\x04 \x03(\v27.opamp.proto.OtherConnectionSettings.OtherSettingsEntryR\rotherSettings\x124\n" + + "\x03tls\x18\x05 \x01(\v2\".opamp.proto.TLSConnectionSettingsR\x03tls\x12:\n" + + "\x05proxy\x18\x06 \x01(\v2$.opamp.proto.ProxyConnectionSettingsR\x05proxy\x1a@\n" + + "\x12OtherSettingsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x98\x02\n" + + "\x15TLSConnectionSettings\x12&\n" + + "\x0fca_pem_contents\x18\x01 \x01(\tR\rcaPemContents\x12>\n" + + "\x1cinclude_system_ca_certs_pool\x18\x02 \x01(\bR\x18includeSystemCaCertsPool\x120\n" + + "\x14insecure_skip_verify\x18\x03 \x01(\bR\x12insecureSkipVerify\x12\x1f\n" + + "\vmin_version\x18\x04 \x01(\tR\n" + + "minVersion\x12\x1f\n" + + "\vmax_version\x18\x05 \x01(\tR\n" + + "maxVersion\x12#\n" + + "\rcipher_suites\x18\x06 \x03(\tR\fcipherSuites\"j\n" + + "\x17ProxyConnectionSettings\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12=\n" + + "\x0fconnect_headers\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\x0econnectHeaders\"8\n" + + "\aHeaders\x12-\n" + + "\aheaders\x18\x01 \x03(\v2\x13.opamp.proto.HeaderR\aheaders\"0\n" + + "\x06Header\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value\"^\n" + + "\x0eTLSCertificate\x12\x12\n" + + "\x04cert\x18\x01 \x01(\fR\x04cert\x12\x1f\n" + + "\vprivate_key\x18\x02 \x01(\fR\n" + + "privateKey\x12\x17\n" + + "\aca_cert\x18\x03 \x01(\fR\x06caCert\"\x98\x04\n" + + "\x18ConnectionSettingsOffers\x12\x12\n" + + "\x04hash\x18\x01 \x01(\fR\x04hash\x12:\n" + + "\x05opamp\x18\x02 \x01(\v2$.opamp.proto.OpAMPConnectionSettingsR\x05opamp\x12I\n" + + "\vown_metrics\x18\x03 \x01(\v2(.opamp.proto.TelemetryConnectionSettingsR\n" + + "ownMetrics\x12G\n" + + "\n" + + "own_traces\x18\x04 \x01(\v2(.opamp.proto.TelemetryConnectionSettingsR\townTraces\x12C\n" + + "\bown_logs\x18\x05 \x01(\v2(.opamp.proto.TelemetryConnectionSettingsR\aownLogs\x12h\n" + + "\x11other_connections\x18\x06 \x03(\v2;.opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntryR\x10otherConnections\x1ai\n" + + "\x15OtherConnectionsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12:\n" + + "\x05value\x18\x02 \x01(\v2$.opamp.proto.OtherConnectionSettingsR\x05value:\x028\x01\"\xe5\x01\n" + + "\x11PackagesAvailable\x12H\n" + + "\bpackages\x18\x01 \x03(\v2,.opamp.proto.PackagesAvailable.PackagesEntryR\bpackages\x12*\n" + + "\x11all_packages_hash\x18\x02 \x01(\fR\x0fallPackagesHash\x1aZ\n" + + "\rPackagesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + + "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.PackageAvailableR\x05value:\x028\x01\"\xa1\x01\n" + + "\x10PackageAvailable\x12,\n" + + "\x04type\x18\x01 \x01(\x0e2\x18.opamp.proto.PackageTypeR\x04type\x12\x18\n" + + "\aversion\x18\x02 \x01(\tR\aversion\x121\n" + + "\x04file\x18\x03 \x01(\v2\x1d.opamp.proto.DownloadableFileR\x04file\x12\x12\n" + + "\x04hash\x18\x04 \x01(\fR\x04hash\"\xa6\x01\n" + + "\x10DownloadableFile\x12!\n" + + "\fdownload_url\x18\x01 \x01(\tR\vdownloadUrl\x12!\n" + + "\fcontent_hash\x18\x02 \x01(\fR\vcontentHash\x12\x1c\n" + + "\tsignature\x18\x03 \x01(\fR\tsignature\x12.\n" + + "\aheaders\x18\x04 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\"\xb8\x01\n" + + "\x13ServerErrorResponse\x128\n" + + "\x04type\x18\x01 \x01(\x0e2$.opamp.proto.ServerErrorResponseTypeR\x04type\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x127\n" + + "\n" + + "retry_info\x18\x03 \x01(\v2\x16.opamp.proto.RetryInfoH\x00R\tretryInfoB\t\n" + + "\aDetails\"C\n" + + "\tRetryInfo\x126\n" + + "\x17retry_after_nanoseconds\x18\x01 \x01(\x04R\x15retryAfterNanoseconds\"D\n" + + "\x14ServerToAgentCommand\x12,\n" + + "\x04type\x18\x01 \x01(\x0e2\x18.opamp.proto.CommandTypeR\x04type\"\xb5\x01\n" + + "\x10AgentDescription\x12L\n" + + "\x16identifying_attributes\x18\x01 \x03(\v2\x15.opamp.proto.KeyValueR\x15identifyingAttributes\x12S\n" + + "\x1anon_identifying_attributes\x18\x02 \x03(\v2\x15.opamp.proto.KeyValueR\x18nonIdentifyingAttributes\"\x93\x03\n" + + "\x0fComponentHealth\x12\x18\n" + + "\ahealthy\x18\x01 \x01(\bR\ahealthy\x12/\n" + + "\x14start_time_unix_nano\x18\x02 \x01(\x06R\x11startTimeUnixNano\x12\x1d\n" + + "\n" + + "last_error\x18\x03 \x01(\tR\tlastError\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x121\n" + + "\x15status_time_unix_nano\x18\x05 \x01(\x06R\x12statusTimeUnixNano\x12f\n" + + "\x14component_health_map\x18\x06 \x03(\v24.opamp.proto.ComponentHealth.ComponentHealthMapEntryR\x12componentHealthMap\x1ac\n" + + "\x17ComponentHealthMapEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + + "\x05value\x18\x02 \x01(\v2\x1c.opamp.proto.ComponentHealthR\x05value:\x028\x01\"M\n" + + "\x0fEffectiveConfig\x12:\n" + + "\n" + + "config_map\x18\x01 \x01(\v2\x1b.opamp.proto.AgentConfigMapR\tconfigMap\"\xab\x01\n" + + "\x12RemoteConfigStatus\x125\n" + + "\x17last_remote_config_hash\x18\x01 \x01(\fR\x14lastRemoteConfigHash\x129\n" + + "\x06status\x18\x02 \x01(\x0e2!.opamp.proto.RemoteConfigStatusesR\x06status\x12#\n" + + "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\"\xc3\x01\n" + + "\x18ConnectionSettingsStatus\x12A\n" + + "\x1dlast_connection_settings_hash\x18\x01 \x01(\fR\x1alastConnectionSettingsHash\x12?\n" + + "\x06status\x18\x02 \x01(\x0e2'.opamp.proto.ConnectionSettingsStatusesR\x06status\x12#\n" + + "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\"\xa1\x02\n" + + "\x0fPackageStatuses\x12F\n" + + "\bpackages\x18\x01 \x03(\v2*.opamp.proto.PackageStatuses.PackagesEntryR\bpackages\x12H\n" + + "!server_provided_all_packages_hash\x18\x02 \x01(\fR\x1dserverProvidedAllPackagesHash\x12#\n" + + "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\x1aW\n" + + "\rPackagesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x120\n" + + "\x05value\x18\x02 \x01(\v2\x1a.opamp.proto.PackageStatusR\x05value:\x028\x01\"\x88\x03\n" + + "\rPackageStatus\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12*\n" + + "\x11agent_has_version\x18\x02 \x01(\tR\x0fagentHasVersion\x12$\n" + + "\x0eagent_has_hash\x18\x03 \x01(\fR\fagentHasHash\x124\n" + + "\x16server_offered_version\x18\x04 \x01(\tR\x14serverOfferedVersion\x12.\n" + + "\x13server_offered_hash\x18\x05 \x01(\fR\x11serverOfferedHash\x126\n" + + "\x06status\x18\x06 \x01(\x0e2\x1e.opamp.proto.PackageStatusEnumR\x06status\x12#\n" + + "\rerror_message\x18\a \x01(\tR\ferrorMessage\x12N\n" + + "\x10download_details\x18\b \x01(\v2#.opamp.proto.PackageDownloadDetailsR\x0fdownloadDetails\"~\n" + + "\x16PackageDownloadDetails\x12)\n" + + "\x10download_percent\x18\x01 \x01(\x01R\x0fdownloadPercent\x129\n" + + "\x19download_bytes_per_second\x18\x02 \x01(\x01R\x16downloadBytesPerSecond\"?\n" + + "\x13AgentIdentification\x12(\n" + + "\x10new_instance_uid\x18\x01 \x01(\fR\x0enewInstanceUid\"i\n" + + "\x11AgentRemoteConfig\x123\n" + + "\x06config\x18\x01 \x01(\v2\x1b.opamp.proto.AgentConfigMapR\x06config\x12\x1f\n" + + "\vconfig_hash\x18\x02 \x01(\fR\n" + + "configHash\"\xb7\x01\n" + + "\x0eAgentConfigMap\x12I\n" + + "\n" + + "config_map\x18\x01 \x03(\v2*.opamp.proto.AgentConfigMap.ConfigMapEntryR\tconfigMap\x1aZ\n" + + "\x0eConfigMapEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + + "\x05value\x18\x02 \x01(\v2\x1c.opamp.proto.AgentConfigFileR\x05value:\x028\x01\"H\n" + + "\x0fAgentConfigFile\x12\x12\n" + + "\x04body\x18\x01 \x01(\fR\x04body\x12!\n" + + "\fcontent_type\x18\x02 \x01(\tR\vcontentType\"8\n" + + "\x12CustomCapabilities\x12\"\n" + + "\fcapabilities\x18\x01 \x03(\tR\fcapabilities\"W\n" + + "\rCustomMessage\x12\x1e\n" + + "\n" + + "capability\x18\x01 \x01(\tR\n" + + "capability\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12\x12\n" + + "\x04data\x18\x03 \x01(\fR\x04data*c\n" + + "\x12AgentToServerFlags\x12\"\n" + + "\x1eAgentToServerFlags_Unspecified\x10\x00\x12)\n" + + "%AgentToServerFlags_RequestInstanceUid\x10\x01*\x92\x01\n" + + "\x12ServerToAgentFlags\x12\"\n" + + "\x1eServerToAgentFlags_Unspecified\x10\x00\x12&\n" + + "\"ServerToAgentFlags_ReportFullState\x10\x01\x120\n" + + ",ServerToAgentFlags_ReportAvailableComponents\x10\x02*\xaf\x03\n" + + "\x12ServerCapabilities\x12\"\n" + + "\x1eServerCapabilities_Unspecified\x10\x00\x12$\n" + + " ServerCapabilities_AcceptsStatus\x10\x01\x12)\n" + + "%ServerCapabilities_OffersRemoteConfig\x10\x02\x12-\n" + + ")ServerCapabilities_AcceptsEffectiveConfig\x10\x04\x12%\n" + + "!ServerCapabilities_OffersPackages\x10\b\x12,\n" + + "(ServerCapabilities_AcceptsPackagesStatus\x10\x10\x12/\n" + + "+ServerCapabilities_OffersConnectionSettings\x10 \x127\n" + + "3ServerCapabilities_AcceptsConnectionSettingsRequest\x10@\x126\n" + + "1ServerCapabilities_OffersPayloadTrustVerification\x10\x80\x01*>\n" + + "\vPackageType\x12\x18\n" + + "\x14PackageType_TopLevel\x10\x00\x12\x15\n" + + "\x11PackageType_Addon\x10\x01*\x8f\x01\n" + + "\x17ServerErrorResponseType\x12#\n" + + "\x1fServerErrorResponseType_Unknown\x10\x00\x12&\n" + + "\"ServerErrorResponseType_BadRequest\x10\x01\x12'\n" + + "#ServerErrorResponseType_Unavailable\x10\x02*&\n" + + "\vCommandType\x12\x17\n" + + "\x13CommandType_Restart\x10\x00*\xbf\x06\n" + + "\x11AgentCapabilities\x12!\n" + + "\x1dAgentCapabilities_Unspecified\x10\x00\x12#\n" + + "\x1fAgentCapabilities_ReportsStatus\x10\x01\x12)\n" + + "%AgentCapabilities_AcceptsRemoteConfig\x10\x02\x12,\n" + + "(AgentCapabilities_ReportsEffectiveConfig\x10\x04\x12%\n" + + "!AgentCapabilities_AcceptsPackages\x10\b\x12,\n" + + "(AgentCapabilities_ReportsPackageStatuses\x10\x10\x12&\n" + + "\"AgentCapabilities_ReportsOwnTraces\x10 \x12'\n" + + "#AgentCapabilities_ReportsOwnMetrics\x10@\x12%\n" + + " AgentCapabilities_ReportsOwnLogs\x10\x80\x01\x125\n" + + "0AgentCapabilities_AcceptsOpAMPConnectionSettings\x10\x80\x02\x125\n" + + "0AgentCapabilities_AcceptsOtherConnectionSettings\x10\x80\x04\x12,\n" + + "'AgentCapabilities_AcceptsRestartCommand\x10\x80\b\x12$\n" + + "\x1fAgentCapabilities_ReportsHealth\x10\x80\x10\x12*\n" + + "%AgentCapabilities_ReportsRemoteConfig\x10\x80 \x12'\n" + + "\"AgentCapabilities_ReportsHeartbeat\x10\x80@\x122\n" + + ",AgentCapabilities_ReportsAvailableComponents\x10\x80\x80\x01\x127\n" + + "1AgentCapabilities_ReportsConnectionSettingsStatus\x10\x80\x80\x02\x128\n" + + "2AgentCapabilities_RequiresPayloadTrustVerification\x10\x80\x80\x04*\xba\x01\n" + + "\x1aConnectionSettingsStatuses\x12$\n" + + " ConnectionSettingsStatuses_UNSET\x10\x00\x12&\n" + + "\"ConnectionSettingsStatuses_APPLIED\x10\x01\x12'\n" + + "#ConnectionSettingsStatuses_APPLYING\x10\x02\x12%\n" + + "!ConnectionSettingsStatuses_FAILED\x10\x03*\x9c\x01\n" + + "\x14RemoteConfigStatuses\x12\x1e\n" + + "\x1aRemoteConfigStatuses_UNSET\x10\x00\x12 \n" + + "\x1cRemoteConfigStatuses_APPLIED\x10\x01\x12!\n" + + "\x1dRemoteConfigStatuses_APPLYING\x10\x02\x12\x1f\n" + + "\x1bRemoteConfigStatuses_FAILED\x10\x03*\xc4\x01\n" + + "\x11PackageStatusEnum\x12\x1f\n" + + "\x1bPackageStatusEnum_Installed\x10\x00\x12$\n" + + " PackageStatusEnum_InstallPending\x10\x01\x12 \n" + + "\x1cPackageStatusEnum_Installing\x10\x02\x12#\n" + + "\x1fPackageStatusEnum_InstallFailed\x10\x03\x12!\n" + + "\x1dPackageStatusEnum_Downloading\x10\x04B.Z,github.com/open-telemetry/opamp-go/protobufsb\x06proto3" + var ( file_opamp_proto_rawDescOnce sync.Once - file_opamp_proto_rawDescData = file_opamp_proto_rawDesc + file_opamp_proto_rawDescData []byte ) func file_opamp_proto_rawDescGZIP() []byte { file_opamp_proto_rawDescOnce.Do(func() { - file_opamp_proto_rawDescData = protoimpl.X.CompressGZIP(file_opamp_proto_rawDescData) + file_opamp_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_opamp_proto_rawDesc), len(file_opamp_proto_rawDesc))) }) return file_opamp_proto_rawDescData } var file_opamp_proto_enumTypes = make([]protoimpl.EnumInfo, 10) -var file_opamp_proto_msgTypes = make([]protoimpl.MessageInfo, 45) -var file_opamp_proto_goTypes = []interface{}{ +var file_opamp_proto_msgTypes = make([]protoimpl.MessageInfo, 47) +var file_opamp_proto_goTypes = []any{ (AgentToServerFlags)(0), // 0: opamp.proto.AgentToServerFlags (ServerToAgentFlags)(0), // 1: opamp.proto.ServerToAgentFlags (ServerCapabilities)(0), // 2: opamp.proto.ServerCapabilities @@ -4316,120 +3983,124 @@ var file_opamp_proto_goTypes = []interface{}{ (*AvailableComponents)(nil), // 15: opamp.proto.AvailableComponents (*ComponentDetails)(nil), // 16: opamp.proto.ComponentDetails (*ServerToAgent)(nil), // 17: opamp.proto.ServerToAgent - (*OpAMPConnectionSettings)(nil), // 18: opamp.proto.OpAMPConnectionSettings - (*TelemetryConnectionSettings)(nil), // 19: opamp.proto.TelemetryConnectionSettings - (*OtherConnectionSettings)(nil), // 20: opamp.proto.OtherConnectionSettings - (*TLSConnectionSettings)(nil), // 21: opamp.proto.TLSConnectionSettings - (*ProxyConnectionSettings)(nil), // 22: opamp.proto.ProxyConnectionSettings - (*Headers)(nil), // 23: opamp.proto.Headers - (*Header)(nil), // 24: opamp.proto.Header - (*TLSCertificate)(nil), // 25: opamp.proto.TLSCertificate - (*ConnectionSettingsOffers)(nil), // 26: opamp.proto.ConnectionSettingsOffers - (*PackagesAvailable)(nil), // 27: opamp.proto.PackagesAvailable - (*PackageAvailable)(nil), // 28: opamp.proto.PackageAvailable - (*DownloadableFile)(nil), // 29: opamp.proto.DownloadableFile - (*ServerErrorResponse)(nil), // 30: opamp.proto.ServerErrorResponse - (*RetryInfo)(nil), // 31: opamp.proto.RetryInfo - (*ServerToAgentCommand)(nil), // 32: opamp.proto.ServerToAgentCommand - (*AgentDescription)(nil), // 33: opamp.proto.AgentDescription - (*ComponentHealth)(nil), // 34: opamp.proto.ComponentHealth - (*EffectiveConfig)(nil), // 35: opamp.proto.EffectiveConfig - (*RemoteConfigStatus)(nil), // 36: opamp.proto.RemoteConfigStatus - (*ConnectionSettingsStatus)(nil), // 37: opamp.proto.ConnectionSettingsStatus - (*PackageStatuses)(nil), // 38: opamp.proto.PackageStatuses - (*PackageStatus)(nil), // 39: opamp.proto.PackageStatus - (*PackageDownloadDetails)(nil), // 40: opamp.proto.PackageDownloadDetails - (*AgentIdentification)(nil), // 41: opamp.proto.AgentIdentification - (*AgentRemoteConfig)(nil), // 42: opamp.proto.AgentRemoteConfig - (*AgentConfigMap)(nil), // 43: opamp.proto.AgentConfigMap - (*AgentConfigFile)(nil), // 44: opamp.proto.AgentConfigFile - (*CustomCapabilities)(nil), // 45: opamp.proto.CustomCapabilities - (*CustomMessage)(nil), // 46: opamp.proto.CustomMessage - nil, // 47: opamp.proto.AvailableComponents.ComponentsEntry - nil, // 48: opamp.proto.ComponentDetails.SubComponentMapEntry - nil, // 49: opamp.proto.OtherConnectionSettings.OtherSettingsEntry - nil, // 50: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry - nil, // 51: opamp.proto.PackagesAvailable.PackagesEntry - nil, // 52: opamp.proto.ComponentHealth.ComponentHealthMapEntry - nil, // 53: opamp.proto.PackageStatuses.PackagesEntry - nil, // 54: opamp.proto.AgentConfigMap.ConfigMapEntry - (*KeyValue)(nil), // 55: opamp.proto.KeyValue + (*TrustChainResponse)(nil), // 18: opamp.proto.TrustChainResponse + (*OpAMPConnectionSettings)(nil), // 19: opamp.proto.OpAMPConnectionSettings + (*TelemetryConnectionSettings)(nil), // 20: opamp.proto.TelemetryConnectionSettings + (*OtherConnectionSettings)(nil), // 21: opamp.proto.OtherConnectionSettings + (*TLSConnectionSettings)(nil), // 22: opamp.proto.TLSConnectionSettings + (*ProxyConnectionSettings)(nil), // 23: opamp.proto.ProxyConnectionSettings + (*Headers)(nil), // 24: opamp.proto.Headers + (*Header)(nil), // 25: opamp.proto.Header + (*TLSCertificate)(nil), // 26: opamp.proto.TLSCertificate + (*ConnectionSettingsOffers)(nil), // 27: opamp.proto.ConnectionSettingsOffers + (*PackagesAvailable)(nil), // 28: opamp.proto.PackagesAvailable + (*PackageAvailable)(nil), // 29: opamp.proto.PackageAvailable + (*DownloadableFile)(nil), // 30: opamp.proto.DownloadableFile + (*ServerErrorResponse)(nil), // 31: opamp.proto.ServerErrorResponse + (*RetryInfo)(nil), // 32: opamp.proto.RetryInfo + (*ServerToAgentCommand)(nil), // 33: opamp.proto.ServerToAgentCommand + (*AgentDescription)(nil), // 34: opamp.proto.AgentDescription + (*ComponentHealth)(nil), // 35: opamp.proto.ComponentHealth + (*EffectiveConfig)(nil), // 36: opamp.proto.EffectiveConfig + (*RemoteConfigStatus)(nil), // 37: opamp.proto.RemoteConfigStatus + (*ConnectionSettingsStatus)(nil), // 38: opamp.proto.ConnectionSettingsStatus + (*PackageStatuses)(nil), // 39: opamp.proto.PackageStatuses + (*PackageStatus)(nil), // 40: opamp.proto.PackageStatus + (*PackageDownloadDetails)(nil), // 41: opamp.proto.PackageDownloadDetails + (*AgentIdentification)(nil), // 42: opamp.proto.AgentIdentification + (*AgentRemoteConfig)(nil), // 43: opamp.proto.AgentRemoteConfig + (*AgentConfigMap)(nil), // 44: opamp.proto.AgentConfigMap + (*AgentConfigFile)(nil), // 45: opamp.proto.AgentConfigFile + (*CustomCapabilities)(nil), // 46: opamp.proto.CustomCapabilities + (*CustomMessage)(nil), // 47: opamp.proto.CustomMessage + nil, // 48: opamp.proto.AvailableComponents.ComponentsEntry + nil, // 49: opamp.proto.ComponentDetails.SubComponentMapEntry + (*TrustChainResponse_Certificate)(nil), // 50: opamp.proto.TrustChainResponse.Certificate + nil, // 51: opamp.proto.OtherConnectionSettings.OtherSettingsEntry + nil, // 52: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry + nil, // 53: opamp.proto.PackagesAvailable.PackagesEntry + nil, // 54: opamp.proto.ComponentHealth.ComponentHealthMapEntry + nil, // 55: opamp.proto.PackageStatuses.PackagesEntry + nil, // 56: opamp.proto.AgentConfigMap.ConfigMapEntry + (*KeyValue)(nil), // 57: opamp.proto.KeyValue } var file_opamp_proto_depIdxs = []int32{ - 33, // 0: opamp.proto.AgentToServer.agent_description:type_name -> opamp.proto.AgentDescription - 34, // 1: opamp.proto.AgentToServer.health:type_name -> opamp.proto.ComponentHealth - 35, // 2: opamp.proto.AgentToServer.effective_config:type_name -> opamp.proto.EffectiveConfig - 36, // 3: opamp.proto.AgentToServer.remote_config_status:type_name -> opamp.proto.RemoteConfigStatus - 38, // 4: opamp.proto.AgentToServer.package_statuses:type_name -> opamp.proto.PackageStatuses + 34, // 0: opamp.proto.AgentToServer.agent_description:type_name -> opamp.proto.AgentDescription + 35, // 1: opamp.proto.AgentToServer.health:type_name -> opamp.proto.ComponentHealth + 36, // 2: opamp.proto.AgentToServer.effective_config:type_name -> opamp.proto.EffectiveConfig + 37, // 3: opamp.proto.AgentToServer.remote_config_status:type_name -> opamp.proto.RemoteConfigStatus + 39, // 4: opamp.proto.AgentToServer.package_statuses:type_name -> opamp.proto.PackageStatuses 11, // 5: opamp.proto.AgentToServer.agent_disconnect:type_name -> opamp.proto.AgentDisconnect 12, // 6: opamp.proto.AgentToServer.connection_settings_request:type_name -> opamp.proto.ConnectionSettingsRequest - 45, // 7: opamp.proto.AgentToServer.custom_capabilities:type_name -> opamp.proto.CustomCapabilities - 46, // 8: opamp.proto.AgentToServer.custom_message:type_name -> opamp.proto.CustomMessage + 46, // 7: opamp.proto.AgentToServer.custom_capabilities:type_name -> opamp.proto.CustomCapabilities + 47, // 8: opamp.proto.AgentToServer.custom_message:type_name -> opamp.proto.CustomMessage 15, // 9: opamp.proto.AgentToServer.available_components:type_name -> opamp.proto.AvailableComponents - 37, // 10: opamp.proto.AgentToServer.connection_settings_status:type_name -> opamp.proto.ConnectionSettingsStatus + 38, // 10: opamp.proto.AgentToServer.connection_settings_status:type_name -> opamp.proto.ConnectionSettingsStatus 13, // 11: opamp.proto.ConnectionSettingsRequest.opamp:type_name -> opamp.proto.OpAMPConnectionSettingsRequest 14, // 12: opamp.proto.OpAMPConnectionSettingsRequest.certificate_request:type_name -> opamp.proto.CertificateRequest - 47, // 13: opamp.proto.AvailableComponents.components:type_name -> opamp.proto.AvailableComponents.ComponentsEntry - 55, // 14: opamp.proto.ComponentDetails.metadata:type_name -> opamp.proto.KeyValue - 48, // 15: opamp.proto.ComponentDetails.sub_component_map:type_name -> opamp.proto.ComponentDetails.SubComponentMapEntry - 30, // 16: opamp.proto.ServerToAgent.error_response:type_name -> opamp.proto.ServerErrorResponse - 42, // 17: opamp.proto.ServerToAgent.remote_config:type_name -> opamp.proto.AgentRemoteConfig - 26, // 18: opamp.proto.ServerToAgent.connection_settings:type_name -> opamp.proto.ConnectionSettingsOffers - 27, // 19: opamp.proto.ServerToAgent.packages_available:type_name -> opamp.proto.PackagesAvailable - 41, // 20: opamp.proto.ServerToAgent.agent_identification:type_name -> opamp.proto.AgentIdentification - 32, // 21: opamp.proto.ServerToAgent.command:type_name -> opamp.proto.ServerToAgentCommand - 45, // 22: opamp.proto.ServerToAgent.custom_capabilities:type_name -> opamp.proto.CustomCapabilities - 46, // 23: opamp.proto.ServerToAgent.custom_message:type_name -> opamp.proto.CustomMessage - 23, // 24: opamp.proto.OpAMPConnectionSettings.headers:type_name -> opamp.proto.Headers - 25, // 25: opamp.proto.OpAMPConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 21, // 26: opamp.proto.OpAMPConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 22, // 27: opamp.proto.OpAMPConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 23, // 28: opamp.proto.TelemetryConnectionSettings.headers:type_name -> opamp.proto.Headers - 25, // 29: opamp.proto.TelemetryConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 21, // 30: opamp.proto.TelemetryConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 22, // 31: opamp.proto.TelemetryConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 23, // 32: opamp.proto.OtherConnectionSettings.headers:type_name -> opamp.proto.Headers - 25, // 33: opamp.proto.OtherConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 49, // 34: opamp.proto.OtherConnectionSettings.other_settings:type_name -> opamp.proto.OtherConnectionSettings.OtherSettingsEntry - 21, // 35: opamp.proto.OtherConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 22, // 36: opamp.proto.OtherConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 23, // 37: opamp.proto.ProxyConnectionSettings.connect_headers:type_name -> opamp.proto.Headers - 24, // 38: opamp.proto.Headers.headers:type_name -> opamp.proto.Header - 18, // 39: opamp.proto.ConnectionSettingsOffers.opamp:type_name -> opamp.proto.OpAMPConnectionSettings - 19, // 40: opamp.proto.ConnectionSettingsOffers.own_metrics:type_name -> opamp.proto.TelemetryConnectionSettings - 19, // 41: opamp.proto.ConnectionSettingsOffers.own_traces:type_name -> opamp.proto.TelemetryConnectionSettings - 19, // 42: opamp.proto.ConnectionSettingsOffers.own_logs:type_name -> opamp.proto.TelemetryConnectionSettings - 50, // 43: opamp.proto.ConnectionSettingsOffers.other_connections:type_name -> opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry - 51, // 44: opamp.proto.PackagesAvailable.packages:type_name -> opamp.proto.PackagesAvailable.PackagesEntry - 3, // 45: opamp.proto.PackageAvailable.type:type_name -> opamp.proto.PackageType - 29, // 46: opamp.proto.PackageAvailable.file:type_name -> opamp.proto.DownloadableFile - 23, // 47: opamp.proto.DownloadableFile.headers:type_name -> opamp.proto.Headers - 4, // 48: opamp.proto.ServerErrorResponse.type:type_name -> opamp.proto.ServerErrorResponseType - 31, // 49: opamp.proto.ServerErrorResponse.retry_info:type_name -> opamp.proto.RetryInfo - 5, // 50: opamp.proto.ServerToAgentCommand.type:type_name -> opamp.proto.CommandType - 55, // 51: opamp.proto.AgentDescription.identifying_attributes:type_name -> opamp.proto.KeyValue - 55, // 52: opamp.proto.AgentDescription.non_identifying_attributes:type_name -> opamp.proto.KeyValue - 52, // 53: opamp.proto.ComponentHealth.component_health_map:type_name -> opamp.proto.ComponentHealth.ComponentHealthMapEntry - 43, // 54: opamp.proto.EffectiveConfig.config_map:type_name -> opamp.proto.AgentConfigMap - 8, // 55: opamp.proto.RemoteConfigStatus.status:type_name -> opamp.proto.RemoteConfigStatuses - 7, // 56: opamp.proto.ConnectionSettingsStatus.status:type_name -> opamp.proto.ConnectionSettingsStatuses - 53, // 57: opamp.proto.PackageStatuses.packages:type_name -> opamp.proto.PackageStatuses.PackagesEntry - 9, // 58: opamp.proto.PackageStatus.status:type_name -> opamp.proto.PackageStatusEnum - 40, // 59: opamp.proto.PackageStatus.download_details:type_name -> opamp.proto.PackageDownloadDetails - 43, // 60: opamp.proto.AgentRemoteConfig.config:type_name -> opamp.proto.AgentConfigMap - 54, // 61: opamp.proto.AgentConfigMap.config_map:type_name -> opamp.proto.AgentConfigMap.ConfigMapEntry - 16, // 62: opamp.proto.AvailableComponents.ComponentsEntry.value:type_name -> opamp.proto.ComponentDetails - 16, // 63: opamp.proto.ComponentDetails.SubComponentMapEntry.value:type_name -> opamp.proto.ComponentDetails - 20, // 64: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry.value:type_name -> opamp.proto.OtherConnectionSettings - 28, // 65: opamp.proto.PackagesAvailable.PackagesEntry.value:type_name -> opamp.proto.PackageAvailable - 34, // 66: opamp.proto.ComponentHealth.ComponentHealthMapEntry.value:type_name -> opamp.proto.ComponentHealth - 39, // 67: opamp.proto.PackageStatuses.PackagesEntry.value:type_name -> opamp.proto.PackageStatus - 44, // 68: opamp.proto.AgentConfigMap.ConfigMapEntry.value:type_name -> opamp.proto.AgentConfigFile - 69, // [69:69] is the sub-list for method output_type - 69, // [69:69] is the sub-list for method input_type - 69, // [69:69] is the sub-list for extension type_name - 69, // [69:69] is the sub-list for extension extendee - 0, // [0:69] is the sub-list for field type_name + 48, // 13: opamp.proto.AvailableComponents.components:type_name -> opamp.proto.AvailableComponents.ComponentsEntry + 57, // 14: opamp.proto.ComponentDetails.metadata:type_name -> opamp.proto.KeyValue + 49, // 15: opamp.proto.ComponentDetails.sub_component_map:type_name -> opamp.proto.ComponentDetails.SubComponentMapEntry + 31, // 16: opamp.proto.ServerToAgent.error_response:type_name -> opamp.proto.ServerErrorResponse + 43, // 17: opamp.proto.ServerToAgent.remote_config:type_name -> opamp.proto.AgentRemoteConfig + 27, // 18: opamp.proto.ServerToAgent.connection_settings:type_name -> opamp.proto.ConnectionSettingsOffers + 28, // 19: opamp.proto.ServerToAgent.packages_available:type_name -> opamp.proto.PackagesAvailable + 42, // 20: opamp.proto.ServerToAgent.agent_identification:type_name -> opamp.proto.AgentIdentification + 33, // 21: opamp.proto.ServerToAgent.command:type_name -> opamp.proto.ServerToAgentCommand + 46, // 22: opamp.proto.ServerToAgent.custom_capabilities:type_name -> opamp.proto.CustomCapabilities + 47, // 23: opamp.proto.ServerToAgent.custom_message:type_name -> opamp.proto.CustomMessage + 18, // 24: opamp.proto.ServerToAgent.trust_chain_response:type_name -> opamp.proto.TrustChainResponse + 50, // 25: opamp.proto.TrustChainResponse.certificate_chain:type_name -> opamp.proto.TrustChainResponse.Certificate + 24, // 26: opamp.proto.OpAMPConnectionSettings.headers:type_name -> opamp.proto.Headers + 26, // 27: opamp.proto.OpAMPConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate + 22, // 28: opamp.proto.OpAMPConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings + 23, // 29: opamp.proto.OpAMPConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings + 24, // 30: opamp.proto.TelemetryConnectionSettings.headers:type_name -> opamp.proto.Headers + 26, // 31: opamp.proto.TelemetryConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate + 22, // 32: opamp.proto.TelemetryConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings + 23, // 33: opamp.proto.TelemetryConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings + 24, // 34: opamp.proto.OtherConnectionSettings.headers:type_name -> opamp.proto.Headers + 26, // 35: opamp.proto.OtherConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate + 51, // 36: opamp.proto.OtherConnectionSettings.other_settings:type_name -> opamp.proto.OtherConnectionSettings.OtherSettingsEntry + 22, // 37: opamp.proto.OtherConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings + 23, // 38: opamp.proto.OtherConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings + 24, // 39: opamp.proto.ProxyConnectionSettings.connect_headers:type_name -> opamp.proto.Headers + 25, // 40: opamp.proto.Headers.headers:type_name -> opamp.proto.Header + 19, // 41: opamp.proto.ConnectionSettingsOffers.opamp:type_name -> opamp.proto.OpAMPConnectionSettings + 20, // 42: opamp.proto.ConnectionSettingsOffers.own_metrics:type_name -> opamp.proto.TelemetryConnectionSettings + 20, // 43: opamp.proto.ConnectionSettingsOffers.own_traces:type_name -> opamp.proto.TelemetryConnectionSettings + 20, // 44: opamp.proto.ConnectionSettingsOffers.own_logs:type_name -> opamp.proto.TelemetryConnectionSettings + 52, // 45: opamp.proto.ConnectionSettingsOffers.other_connections:type_name -> opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry + 53, // 46: opamp.proto.PackagesAvailable.packages:type_name -> opamp.proto.PackagesAvailable.PackagesEntry + 3, // 47: opamp.proto.PackageAvailable.type:type_name -> opamp.proto.PackageType + 30, // 48: opamp.proto.PackageAvailable.file:type_name -> opamp.proto.DownloadableFile + 24, // 49: opamp.proto.DownloadableFile.headers:type_name -> opamp.proto.Headers + 4, // 50: opamp.proto.ServerErrorResponse.type:type_name -> opamp.proto.ServerErrorResponseType + 32, // 51: opamp.proto.ServerErrorResponse.retry_info:type_name -> opamp.proto.RetryInfo + 5, // 52: opamp.proto.ServerToAgentCommand.type:type_name -> opamp.proto.CommandType + 57, // 53: opamp.proto.AgentDescription.identifying_attributes:type_name -> opamp.proto.KeyValue + 57, // 54: opamp.proto.AgentDescription.non_identifying_attributes:type_name -> opamp.proto.KeyValue + 54, // 55: opamp.proto.ComponentHealth.component_health_map:type_name -> opamp.proto.ComponentHealth.ComponentHealthMapEntry + 44, // 56: opamp.proto.EffectiveConfig.config_map:type_name -> opamp.proto.AgentConfigMap + 8, // 57: opamp.proto.RemoteConfigStatus.status:type_name -> opamp.proto.RemoteConfigStatuses + 7, // 58: opamp.proto.ConnectionSettingsStatus.status:type_name -> opamp.proto.ConnectionSettingsStatuses + 55, // 59: opamp.proto.PackageStatuses.packages:type_name -> opamp.proto.PackageStatuses.PackagesEntry + 9, // 60: opamp.proto.PackageStatus.status:type_name -> opamp.proto.PackageStatusEnum + 41, // 61: opamp.proto.PackageStatus.download_details:type_name -> opamp.proto.PackageDownloadDetails + 44, // 62: opamp.proto.AgentRemoteConfig.config:type_name -> opamp.proto.AgentConfigMap + 56, // 63: opamp.proto.AgentConfigMap.config_map:type_name -> opamp.proto.AgentConfigMap.ConfigMapEntry + 16, // 64: opamp.proto.AvailableComponents.ComponentsEntry.value:type_name -> opamp.proto.ComponentDetails + 16, // 65: opamp.proto.ComponentDetails.SubComponentMapEntry.value:type_name -> opamp.proto.ComponentDetails + 21, // 66: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry.value:type_name -> opamp.proto.OtherConnectionSettings + 29, // 67: opamp.proto.PackagesAvailable.PackagesEntry.value:type_name -> opamp.proto.PackageAvailable + 35, // 68: opamp.proto.ComponentHealth.ComponentHealthMapEntry.value:type_name -> opamp.proto.ComponentHealth + 40, // 69: opamp.proto.PackageStatuses.PackagesEntry.value:type_name -> opamp.proto.PackageStatus + 45, // 70: opamp.proto.AgentConfigMap.ConfigMapEntry.value:type_name -> opamp.proto.AgentConfigFile + 71, // [71:71] is the sub-list for method output_type + 71, // [71:71] is the sub-list for method input_type + 71, // [71:71] is the sub-list for extension type_name + 71, // [71:71] is the sub-list for extension extendee + 0, // [0:71] is the sub-list for field type_name } func init() { file_opamp_proto_init() } @@ -4438,462 +4109,16 @@ func file_opamp_proto_init() { return } file_anyvalue_proto_init() - if !protoimpl.UnsafeEnabled { - file_opamp_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentToServer); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentDisconnect); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConnectionSettingsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpAMPConnectionSettingsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CertificateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AvailableComponents); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ComponentDetails); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerToAgent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpAMPConnectionSettings); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TelemetryConnectionSettings); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OtherConnectionSettings); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TLSConnectionSettings); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProxyConnectionSettings); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Headers); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Header); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TLSCertificate); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConnectionSettingsOffers); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PackagesAvailable); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PackageAvailable); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DownloadableFile); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerErrorResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RetryInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServerToAgentCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentDescription); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ComponentHealth); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EffectiveConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoteConfigStatus); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConnectionSettingsStatus); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PackageStatuses); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PackageStatus); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PackageDownloadDetails); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentIdentification); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentRemoteConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentConfigMap); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentConfigFile); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CustomCapabilities); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_opamp_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CustomMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_opamp_proto_msgTypes[20].OneofWrappers = []interface{}{ + file_opamp_proto_msgTypes[21].OneofWrappers = []any{ (*ServerErrorResponse_RetryInfo)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_opamp_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_opamp_proto_rawDesc), len(file_opamp_proto_rawDesc)), NumEnums: 10, - NumMessages: 45, + NumMessages: 47, NumExtensions: 0, NumServices: 0, }, @@ -4903,7 +4128,6 @@ func file_opamp_proto_init() { MessageInfos: file_opamp_proto_msgTypes, }.Build() File_opamp_proto = out.File - file_opamp_proto_rawDesc = nil file_opamp_proto_goTypes = nil file_opamp_proto_depIdxs = nil } From cb7330bc4affdf1f512e6760e2090fb08c68ef49 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 01:23:12 +0200 Subject: [PATCH 02/22] Regenerate protobufs for SignedServerToAgent envelope Bump internal/opamp-spec submodule to commit 1f75ca1 (the Phase A spec pivot to detached signing via SignedServerToAgent envelope) and regenerate protobufs/opamp.pb.go. Wire-level changes vs the previous regen (e21eeda): * ServerToAgent.TrustChainResponse (field 12) removed * ServerToAgent.Signature (field 13) removed * Both field numbers reserved on ServerToAgent (cannot be reused) * New SignedServerToAgent message added: Payload []byte (field 1) Signature []byte (field 2) TrustChainResponse *TrustChainResponse (field 3) ServerToAgent is now byte-identical to upstream OpAMP. The SignedServerToAgent envelope appears on the wire only when both peers have negotiated payload trust verification. Generated locally with protoc 34.1 + protoc-gen-go (native arm64) to work around a SIGSEGV in the otel/build-protobuf Docker image under amd64 emulation on Apple Silicon. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/opamp-spec | 2 +- protobufs/opamp.pb.go | 509 ++++++++++++++++++++++++------------------ 2 files changed, 287 insertions(+), 224 deletions(-) diff --git a/internal/opamp-spec b/internal/opamp-spec index 65b74b88..1f75ca1f 160000 --- a/internal/opamp-spec +++ b/internal/opamp-spec @@ -1 +1 @@ -Subproject commit 65b74b886aadfc6610198a2a42a80ff7ce14df52 +Subproject commit 1f75ca1f63a0006e627bc8f23c13a9279a1232ac diff --git a/protobufs/opamp.pb.go b/protobufs/opamp.pb.go index 3f946b71..a202c073 100644 --- a/protobufs/opamp.pb.go +++ b/protobufs/opamp.pb.go @@ -1271,21 +1271,6 @@ type ServerToAgent struct { // A custom message sent from the Server to an Agent. // Status: [Development] CustomMessage *CustomMessage `protobuf:"bytes,11,opt,name=custom_message,json=customMessage,proto3" json:"custom_message,omitempty"` - // Sent by the Server in its first ServerToAgent message in response to an - // Agent that has set the RequiresPayloadTrustVerification capability. - // Carries the signing certificate chain the Agent will use to verify - // subsequent ServerToAgent messages. If the Agent set - // RequiresPayloadTrustVerification but the first ServerToAgent does not - // include trust_chain_response, the Agent MUST terminate the connection. - // See the Message Attestation section of the specification. - // Status: [Development] - TrustChainResponse *TrustChainResponse `protobuf:"bytes,12,opt,name=trust_chain_response,json=trustChainResponse,proto3" json:"trust_chain_response,omitempty"` - // The signature of this ServerToAgent message. The exact bytes that are - // signed and the verification procedure are defined in the - // Message Attestation section of the specification. Set only after the - // payload trust verification handshake has completed successfully. - // Status: [Development] - Signature []byte `protobuf:"bytes,13,opt,name=signature,proto3" json:"signature,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1397,20 +1382,6 @@ func (x *ServerToAgent) GetCustomMessage() *CustomMessage { return nil } -func (x *ServerToAgent) GetTrustChainResponse() *TrustChainResponse { - if x != nil { - return x.TrustChainResponse - } - return nil -} - -func (x *ServerToAgent) GetSignature() []byte { - if x != nil { - return x.Signature - } - return nil -} - // TrustChainResponse carries the signing certificate chain used by the Server // to sign subsequent ServerToAgent messages, as part of the payload trust // verification handshake. See the Message Attestation section of the @@ -1475,6 +1446,95 @@ func (x *TrustChainResponse) GetErrorMessage() string { return "" } +// SignedServerToAgent wraps a ServerToAgent message when the payload trust +// verification handshake has been negotiated between Server and Agent. When +// both AgentCapabilities_RequiresPayloadTrustVerification (set by the Agent) +// and ServerCapabilities_OffersPayloadTrustVerification (set by the Server) +// are advertised, every Server-to-Agent message on the connection is wrapped +// in SignedServerToAgent. +// +// The signature is computed and verified over the bytes of the payload field +// exactly as they appear on the wire (a "detached" signature). This avoids +// any dependency on canonical protobuf encoding, which is not guaranteed +// across protobuf library versions, schema changes, or build flags. +// +// See the Message Attestation section of the specification. +// Status: [Development] +type SignedServerToAgent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Serialised bytes of a ServerToAgent message. The Agent verifies the + // detached signature over these exact bytes, without re-marshalling, + // and then unmarshals them into a ServerToAgent for normal processing. + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // Detached signature over the bytes of the payload field. MAY be empty + // on the first SignedServerToAgent of a connection: trust on the first + // message is established by validating the certificate chain carried in + // trust_chain_response against the Agent's pre-configured payload trust + // anchor. MUST be present and verifiable on every subsequent + // SignedServerToAgent. + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + // Sent only in the first SignedServerToAgent on a connection. Carries + // the signing certificate chain the Agent will use to verify signatures + // on subsequent messages. If the Agent set + // RequiresPayloadTrustVerification but the first SignedServerToAgent + // does not include a usable trust_chain_response, the Agent MUST + // terminate the connection. + TrustChainResponse *TrustChainResponse `protobuf:"bytes,3,opt,name=trust_chain_response,json=trustChainResponse,proto3" json:"trust_chain_response,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SignedServerToAgent) Reset() { + *x = SignedServerToAgent{} + mi := &file_opamp_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignedServerToAgent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignedServerToAgent) ProtoMessage() {} + +func (x *SignedServerToAgent) ProtoReflect() protoreflect.Message { + mi := &file_opamp_proto_msgTypes[9] + 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 SignedServerToAgent.ProtoReflect.Descriptor instead. +func (*SignedServerToAgent) Descriptor() ([]byte, []int) { + return file_opamp_proto_rawDescGZIP(), []int{9} +} + +func (x *SignedServerToAgent) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *SignedServerToAgent) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *SignedServerToAgent) GetTrustChainResponse() *TrustChainResponse { + if x != nil { + return x.TrustChainResponse + } + return nil +} + // The OpAMPConnectionSettings message is a collection of fields which comprise an // offer from the Server to the Agent to use the specified settings for OpAMP // connection. @@ -1521,7 +1581,7 @@ type OpAMPConnectionSettings struct { func (x *OpAMPConnectionSettings) Reset() { *x = OpAMPConnectionSettings{} - mi := &file_opamp_proto_msgTypes[9] + mi := &file_opamp_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1533,7 +1593,7 @@ func (x *OpAMPConnectionSettings) String() string { func (*OpAMPConnectionSettings) ProtoMessage() {} func (x *OpAMPConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[9] + mi := &file_opamp_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1546,7 +1606,7 @@ func (x *OpAMPConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use OpAMPConnectionSettings.ProtoReflect.Descriptor instead. func (*OpAMPConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{9} + return file_opamp_proto_rawDescGZIP(), []int{10} } func (x *OpAMPConnectionSettings) GetDestinationEndpoint() string { @@ -1626,7 +1686,7 @@ type TelemetryConnectionSettings struct { func (x *TelemetryConnectionSettings) Reset() { *x = TelemetryConnectionSettings{} - mi := &file_opamp_proto_msgTypes[10] + mi := &file_opamp_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1638,7 +1698,7 @@ func (x *TelemetryConnectionSettings) String() string { func (*TelemetryConnectionSettings) ProtoMessage() {} func (x *TelemetryConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[10] + mi := &file_opamp_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1651,7 +1711,7 @@ func (x *TelemetryConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use TelemetryConnectionSettings.ProtoReflect.Descriptor instead. func (*TelemetryConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{10} + return file_opamp_proto_rawDescGZIP(), []int{11} } func (x *TelemetryConnectionSettings) GetDestinationEndpoint() string { @@ -1742,7 +1802,7 @@ type OtherConnectionSettings struct { func (x *OtherConnectionSettings) Reset() { *x = OtherConnectionSettings{} - mi := &file_opamp_proto_msgTypes[11] + mi := &file_opamp_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1754,7 +1814,7 @@ func (x *OtherConnectionSettings) String() string { func (*OtherConnectionSettings) ProtoMessage() {} func (x *OtherConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[11] + mi := &file_opamp_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1767,7 +1827,7 @@ func (x *OtherConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use OtherConnectionSettings.ProtoReflect.Descriptor instead. func (*OtherConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{11} + return file_opamp_proto_rawDescGZIP(), []int{12} } func (x *OtherConnectionSettings) GetDestinationEndpoint() string { @@ -1835,7 +1895,7 @@ type TLSConnectionSettings struct { func (x *TLSConnectionSettings) Reset() { *x = TLSConnectionSettings{} - mi := &file_opamp_proto_msgTypes[12] + mi := &file_opamp_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1847,7 +1907,7 @@ func (x *TLSConnectionSettings) String() string { func (*TLSConnectionSettings) ProtoMessage() {} func (x *TLSConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[12] + mi := &file_opamp_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1860,7 +1920,7 @@ func (x *TLSConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use TLSConnectionSettings.ProtoReflect.Descriptor instead. func (*TLSConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{12} + return file_opamp_proto_rawDescGZIP(), []int{13} } func (x *TLSConnectionSettings) GetCaPemContents() string { @@ -1921,7 +1981,7 @@ type ProxyConnectionSettings struct { func (x *ProxyConnectionSettings) Reset() { *x = ProxyConnectionSettings{} - mi := &file_opamp_proto_msgTypes[13] + mi := &file_opamp_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1933,7 +1993,7 @@ func (x *ProxyConnectionSettings) String() string { func (*ProxyConnectionSettings) ProtoMessage() {} func (x *ProxyConnectionSettings) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[13] + mi := &file_opamp_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1946,7 +2006,7 @@ func (x *ProxyConnectionSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use ProxyConnectionSettings.ProtoReflect.Descriptor instead. func (*ProxyConnectionSettings) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{13} + return file_opamp_proto_rawDescGZIP(), []int{14} } func (x *ProxyConnectionSettings) GetUrl() string { @@ -1973,7 +2033,7 @@ type Headers struct { func (x *Headers) Reset() { *x = Headers{} - mi := &file_opamp_proto_msgTypes[14] + mi := &file_opamp_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1985,7 +2045,7 @@ func (x *Headers) String() string { func (*Headers) ProtoMessage() {} func (x *Headers) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[14] + mi := &file_opamp_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1998,7 +2058,7 @@ func (x *Headers) ProtoReflect() protoreflect.Message { // Deprecated: Use Headers.ProtoReflect.Descriptor instead. func (*Headers) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{14} + return file_opamp_proto_rawDescGZIP(), []int{15} } func (x *Headers) GetHeaders() []*Header { @@ -2019,7 +2079,7 @@ type Header struct { func (x *Header) Reset() { *x = Header{} - mi := &file_opamp_proto_msgTypes[15] + mi := &file_opamp_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2031,7 +2091,7 @@ func (x *Header) String() string { func (*Header) ProtoMessage() {} func (x *Header) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[15] + mi := &file_opamp_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2044,7 +2104,7 @@ func (x *Header) ProtoReflect() protoreflect.Message { // Deprecated: Use Header.ProtoReflect.Descriptor instead. func (*Header) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{15} + return file_opamp_proto_rawDescGZIP(), []int{16} } func (x *Header) GetKey() string { @@ -2081,7 +2141,7 @@ type TLSCertificate struct { func (x *TLSCertificate) Reset() { *x = TLSCertificate{} - mi := &file_opamp_proto_msgTypes[16] + mi := &file_opamp_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2093,7 +2153,7 @@ func (x *TLSCertificate) String() string { func (*TLSCertificate) ProtoMessage() {} func (x *TLSCertificate) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[16] + mi := &file_opamp_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2106,7 +2166,7 @@ func (x *TLSCertificate) ProtoReflect() protoreflect.Message { // Deprecated: Use TLSCertificate.ProtoReflect.Descriptor instead. func (*TLSCertificate) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{16} + return file_opamp_proto_rawDescGZIP(), []int{17} } func (x *TLSCertificate) GetCert() []byte { @@ -2177,7 +2237,7 @@ type ConnectionSettingsOffers struct { func (x *ConnectionSettingsOffers) Reset() { *x = ConnectionSettingsOffers{} - mi := &file_opamp_proto_msgTypes[17] + mi := &file_opamp_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2189,7 +2249,7 @@ func (x *ConnectionSettingsOffers) String() string { func (*ConnectionSettingsOffers) ProtoMessage() {} func (x *ConnectionSettingsOffers) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[17] + mi := &file_opamp_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2202,7 +2262,7 @@ func (x *ConnectionSettingsOffers) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectionSettingsOffers.ProtoReflect.Descriptor instead. func (*ConnectionSettingsOffers) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{17} + return file_opamp_proto_rawDescGZIP(), []int{18} } func (x *ConnectionSettingsOffers) GetHash() []byte { @@ -2269,7 +2329,7 @@ type PackagesAvailable struct { func (x *PackagesAvailable) Reset() { *x = PackagesAvailable{} - mi := &file_opamp_proto_msgTypes[18] + mi := &file_opamp_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2281,7 +2341,7 @@ func (x *PackagesAvailable) String() string { func (*PackagesAvailable) ProtoMessage() {} func (x *PackagesAvailable) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[18] + mi := &file_opamp_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2294,7 +2354,7 @@ func (x *PackagesAvailable) ProtoReflect() protoreflect.Message { // Deprecated: Use PackagesAvailable.ProtoReflect.Descriptor instead. func (*PackagesAvailable) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{18} + return file_opamp_proto_rawDescGZIP(), []int{19} } func (x *PackagesAvailable) GetPackages() map[string]*PackageAvailable { @@ -2347,7 +2407,7 @@ type PackageAvailable struct { func (x *PackageAvailable) Reset() { *x = PackageAvailable{} - mi := &file_opamp_proto_msgTypes[19] + mi := &file_opamp_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2359,7 +2419,7 @@ func (x *PackageAvailable) String() string { func (*PackageAvailable) ProtoMessage() {} func (x *PackageAvailable) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[19] + mi := &file_opamp_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2372,7 +2432,7 @@ func (x *PackageAvailable) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageAvailable.ProtoReflect.Descriptor instead. func (*PackageAvailable) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{19} + return file_opamp_proto_rawDescGZIP(), []int{20} } func (x *PackageAvailable) GetType() PackageType { @@ -2433,7 +2493,7 @@ type DownloadableFile struct { func (x *DownloadableFile) Reset() { *x = DownloadableFile{} - mi := &file_opamp_proto_msgTypes[20] + mi := &file_opamp_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2445,7 +2505,7 @@ func (x *DownloadableFile) String() string { func (*DownloadableFile) ProtoMessage() {} func (x *DownloadableFile) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[20] + mi := &file_opamp_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2458,7 +2518,7 @@ func (x *DownloadableFile) ProtoReflect() protoreflect.Message { // Deprecated: Use DownloadableFile.ProtoReflect.Descriptor instead. func (*DownloadableFile) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{20} + return file_opamp_proto_rawDescGZIP(), []int{21} } func (x *DownloadableFile) GetDownloadUrl() string { @@ -2504,7 +2564,7 @@ type ServerErrorResponse struct { func (x *ServerErrorResponse) Reset() { *x = ServerErrorResponse{} - mi := &file_opamp_proto_msgTypes[21] + mi := &file_opamp_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2516,7 +2576,7 @@ func (x *ServerErrorResponse) String() string { func (*ServerErrorResponse) ProtoMessage() {} func (x *ServerErrorResponse) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[21] + mi := &file_opamp_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2529,7 +2589,7 @@ func (x *ServerErrorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerErrorResponse.ProtoReflect.Descriptor instead. func (*ServerErrorResponse) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{21} + return file_opamp_proto_rawDescGZIP(), []int{22} } func (x *ServerErrorResponse) GetType() ServerErrorResponseType { @@ -2582,7 +2642,7 @@ type RetryInfo struct { func (x *RetryInfo) Reset() { *x = RetryInfo{} - mi := &file_opamp_proto_msgTypes[22] + mi := &file_opamp_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2594,7 +2654,7 @@ func (x *RetryInfo) String() string { func (*RetryInfo) ProtoMessage() {} func (x *RetryInfo) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[22] + mi := &file_opamp_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2607,7 +2667,7 @@ func (x *RetryInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RetryInfo.ProtoReflect.Descriptor instead. func (*RetryInfo) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{22} + return file_opamp_proto_rawDescGZIP(), []int{23} } func (x *RetryInfo) GetRetryAfterNanoseconds() uint64 { @@ -2629,7 +2689,7 @@ type ServerToAgentCommand struct { func (x *ServerToAgentCommand) Reset() { *x = ServerToAgentCommand{} - mi := &file_opamp_proto_msgTypes[23] + mi := &file_opamp_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2641,7 +2701,7 @@ func (x *ServerToAgentCommand) String() string { func (*ServerToAgentCommand) ProtoMessage() {} func (x *ServerToAgentCommand) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[23] + mi := &file_opamp_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2654,7 +2714,7 @@ func (x *ServerToAgentCommand) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerToAgentCommand.ProtoReflect.Descriptor instead. func (*ServerToAgentCommand) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{23} + return file_opamp_proto_rawDescGZIP(), []int{24} } func (x *ServerToAgentCommand) GetType() CommandType { @@ -2704,7 +2764,7 @@ type AgentDescription struct { func (x *AgentDescription) Reset() { *x = AgentDescription{} - mi := &file_opamp_proto_msgTypes[24] + mi := &file_opamp_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2716,7 +2776,7 @@ func (x *AgentDescription) String() string { func (*AgentDescription) ProtoMessage() {} func (x *AgentDescription) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[24] + mi := &file_opamp_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2729,7 +2789,7 @@ func (x *AgentDescription) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentDescription.ProtoReflect.Descriptor instead. func (*AgentDescription) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{24} + return file_opamp_proto_rawDescGZIP(), []int{25} } func (x *AgentDescription) GetIdentifyingAttributes() []*KeyValue { @@ -2774,7 +2834,7 @@ type ComponentHealth struct { func (x *ComponentHealth) Reset() { *x = ComponentHealth{} - mi := &file_opamp_proto_msgTypes[25] + mi := &file_opamp_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2786,7 +2846,7 @@ func (x *ComponentHealth) String() string { func (*ComponentHealth) ProtoMessage() {} func (x *ComponentHealth) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[25] + mi := &file_opamp_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2799,7 +2859,7 @@ func (x *ComponentHealth) ProtoReflect() protoreflect.Message { // Deprecated: Use ComponentHealth.ProtoReflect.Descriptor instead. func (*ComponentHealth) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{25} + return file_opamp_proto_rawDescGZIP(), []int{26} } func (x *ComponentHealth) GetHealthy() bool { @@ -2854,7 +2914,7 @@ type EffectiveConfig struct { func (x *EffectiveConfig) Reset() { *x = EffectiveConfig{} - mi := &file_opamp_proto_msgTypes[26] + mi := &file_opamp_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2866,7 +2926,7 @@ func (x *EffectiveConfig) String() string { func (*EffectiveConfig) ProtoMessage() {} func (x *EffectiveConfig) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[26] + mi := &file_opamp_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2879,7 +2939,7 @@ func (x *EffectiveConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use EffectiveConfig.ProtoReflect.Descriptor instead. func (*EffectiveConfig) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{26} + return file_opamp_proto_rawDescGZIP(), []int{27} } func (x *EffectiveConfig) GetConfigMap() *AgentConfigMap { @@ -2906,7 +2966,7 @@ type RemoteConfigStatus struct { func (x *RemoteConfigStatus) Reset() { *x = RemoteConfigStatus{} - mi := &file_opamp_proto_msgTypes[27] + mi := &file_opamp_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2918,7 +2978,7 @@ func (x *RemoteConfigStatus) String() string { func (*RemoteConfigStatus) ProtoMessage() {} func (x *RemoteConfigStatus) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[27] + mi := &file_opamp_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2931,7 +2991,7 @@ func (x *RemoteConfigStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoteConfigStatus.ProtoReflect.Descriptor instead. func (*RemoteConfigStatus) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{27} + return file_opamp_proto_rawDescGZIP(), []int{28} } func (x *RemoteConfigStatus) GetLastRemoteConfigHash() []byte { @@ -2973,7 +3033,7 @@ type ConnectionSettingsStatus struct { func (x *ConnectionSettingsStatus) Reset() { *x = ConnectionSettingsStatus{} - mi := &file_opamp_proto_msgTypes[28] + mi := &file_opamp_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2985,7 +3045,7 @@ func (x *ConnectionSettingsStatus) String() string { func (*ConnectionSettingsStatus) ProtoMessage() {} func (x *ConnectionSettingsStatus) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[28] + mi := &file_opamp_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2998,7 +3058,7 @@ func (x *ConnectionSettingsStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectionSettingsStatus.ProtoReflect.Descriptor instead. func (*ConnectionSettingsStatus) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{28} + return file_opamp_proto_rawDescGZIP(), []int{29} } func (x *ConnectionSettingsStatus) GetLastConnectionSettingsHash() []byte { @@ -3048,7 +3108,7 @@ type PackageStatuses struct { func (x *PackageStatuses) Reset() { *x = PackageStatuses{} - mi := &file_opamp_proto_msgTypes[29] + mi := &file_opamp_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3060,7 +3120,7 @@ func (x *PackageStatuses) String() string { func (*PackageStatuses) ProtoMessage() {} func (x *PackageStatuses) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[29] + mi := &file_opamp_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3073,7 +3133,7 @@ func (x *PackageStatuses) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageStatuses.ProtoReflect.Descriptor instead. func (*PackageStatuses) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{29} + return file_opamp_proto_rawDescGZIP(), []int{30} } func (x *PackageStatuses) GetPackages() map[string]*PackageStatus { @@ -3153,7 +3213,7 @@ type PackageStatus struct { func (x *PackageStatus) Reset() { *x = PackageStatus{} - mi := &file_opamp_proto_msgTypes[30] + mi := &file_opamp_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3165,7 +3225,7 @@ func (x *PackageStatus) String() string { func (*PackageStatus) ProtoMessage() {} func (x *PackageStatus) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[30] + mi := &file_opamp_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3178,7 +3238,7 @@ func (x *PackageStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageStatus.ProtoReflect.Descriptor instead. func (*PackageStatus) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{30} + return file_opamp_proto_rawDescGZIP(), []int{31} } func (x *PackageStatus) GetName() string { @@ -3251,7 +3311,7 @@ type PackageDownloadDetails struct { func (x *PackageDownloadDetails) Reset() { *x = PackageDownloadDetails{} - mi := &file_opamp_proto_msgTypes[31] + mi := &file_opamp_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3263,7 +3323,7 @@ func (x *PackageDownloadDetails) String() string { func (*PackageDownloadDetails) ProtoMessage() {} func (x *PackageDownloadDetails) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[31] + mi := &file_opamp_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3276,7 +3336,7 @@ func (x *PackageDownloadDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use PackageDownloadDetails.ProtoReflect.Descriptor instead. func (*PackageDownloadDetails) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{31} + return file_opamp_proto_rawDescGZIP(), []int{32} } func (x *PackageDownloadDetails) GetDownloadPercent() float64 { @@ -3307,7 +3367,7 @@ type AgentIdentification struct { func (x *AgentIdentification) Reset() { *x = AgentIdentification{} - mi := &file_opamp_proto_msgTypes[32] + mi := &file_opamp_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3319,7 +3379,7 @@ func (x *AgentIdentification) String() string { func (*AgentIdentification) ProtoMessage() {} func (x *AgentIdentification) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[32] + mi := &file_opamp_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3332,7 +3392,7 @@ func (x *AgentIdentification) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentIdentification.ProtoReflect.Descriptor instead. func (*AgentIdentification) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{32} + return file_opamp_proto_rawDescGZIP(), []int{33} } func (x *AgentIdentification) GetNewInstanceUid() []byte { @@ -3365,7 +3425,7 @@ type AgentRemoteConfig struct { func (x *AgentRemoteConfig) Reset() { *x = AgentRemoteConfig{} - mi := &file_opamp_proto_msgTypes[33] + mi := &file_opamp_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3377,7 +3437,7 @@ func (x *AgentRemoteConfig) String() string { func (*AgentRemoteConfig) ProtoMessage() {} func (x *AgentRemoteConfig) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[33] + mi := &file_opamp_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3390,7 +3450,7 @@ func (x *AgentRemoteConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentRemoteConfig.ProtoReflect.Descriptor instead. func (*AgentRemoteConfig) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{33} + return file_opamp_proto_rawDescGZIP(), []int{34} } func (x *AgentRemoteConfig) GetConfig() *AgentConfigMap { @@ -3421,7 +3481,7 @@ type AgentConfigMap struct { func (x *AgentConfigMap) Reset() { *x = AgentConfigMap{} - mi := &file_opamp_proto_msgTypes[34] + mi := &file_opamp_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3433,7 +3493,7 @@ func (x *AgentConfigMap) String() string { func (*AgentConfigMap) ProtoMessage() {} func (x *AgentConfigMap) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[34] + mi := &file_opamp_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3446,7 +3506,7 @@ func (x *AgentConfigMap) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentConfigMap.ProtoReflect.Descriptor instead. func (*AgentConfigMap) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{34} + return file_opamp_proto_rawDescGZIP(), []int{35} } func (x *AgentConfigMap) GetConfigMap() map[string]*AgentConfigFile { @@ -3470,7 +3530,7 @@ type AgentConfigFile struct { func (x *AgentConfigFile) Reset() { *x = AgentConfigFile{} - mi := &file_opamp_proto_msgTypes[35] + mi := &file_opamp_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3482,7 +3542,7 @@ func (x *AgentConfigFile) String() string { func (*AgentConfigFile) ProtoMessage() {} func (x *AgentConfigFile) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[35] + mi := &file_opamp_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3495,7 +3555,7 @@ func (x *AgentConfigFile) ProtoReflect() protoreflect.Message { // Deprecated: Use AgentConfigFile.ProtoReflect.Descriptor instead. func (*AgentConfigFile) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{35} + return file_opamp_proto_rawDescGZIP(), []int{36} } func (x *AgentConfigFile) GetBody() []byte { @@ -3525,7 +3585,7 @@ type CustomCapabilities struct { func (x *CustomCapabilities) Reset() { *x = CustomCapabilities{} - mi := &file_opamp_proto_msgTypes[36] + mi := &file_opamp_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3537,7 +3597,7 @@ func (x *CustomCapabilities) String() string { func (*CustomCapabilities) ProtoMessage() {} func (x *CustomCapabilities) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[36] + mi := &file_opamp_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3550,7 +3610,7 @@ func (x *CustomCapabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomCapabilities.ProtoReflect.Descriptor instead. func (*CustomCapabilities) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{36} + return file_opamp_proto_rawDescGZIP(), []int{37} } func (x *CustomCapabilities) GetCapabilities() []string { @@ -3581,7 +3641,7 @@ type CustomMessage struct { func (x *CustomMessage) Reset() { *x = CustomMessage{} - mi := &file_opamp_proto_msgTypes[37] + mi := &file_opamp_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3593,7 +3653,7 @@ func (x *CustomMessage) String() string { func (*CustomMessage) ProtoMessage() {} func (x *CustomMessage) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[37] + mi := &file_opamp_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3606,7 +3666,7 @@ func (x *CustomMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomMessage.ProtoReflect.Descriptor instead. func (*CustomMessage) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{37} + return file_opamp_proto_rawDescGZIP(), []int{38} } func (x *CustomMessage) GetCapability() string { @@ -3640,7 +3700,7 @@ type TrustChainResponse_Certificate struct { func (x *TrustChainResponse_Certificate) Reset() { *x = TrustChainResponse_Certificate{} - mi := &file_opamp_proto_msgTypes[40] + mi := &file_opamp_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3652,7 +3712,7 @@ func (x *TrustChainResponse_Certificate) String() string { func (*TrustChainResponse_Certificate) ProtoMessage() {} func (x *TrustChainResponse_Certificate) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[40] + mi := &file_opamp_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3717,7 +3777,7 @@ const file_opamp_proto_rawDesc = "" + "\x11sub_component_map\x18\x02 \x03(\v22.opamp.proto.ComponentDetails.SubComponentMapEntryR\x0fsubComponentMap\x1aa\n" + "\x14SubComponentMapEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + - "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.ComponentDetailsR\x05value:\x028\x01\"\xb9\x06\n" + + "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.ComponentDetailsR\x05value:\x028\x01\"\xf5\x05\n" + "\rServerToAgent\x12!\n" + "\finstance_uid\x18\x01 \x01(\fR\vinstanceUid\x12G\n" + "\x0eerror_response\x18\x02 \x01(\v2 .opamp.proto.ServerErrorResponseR\rerrorResponse\x12C\n" + @@ -3730,14 +3790,16 @@ const file_opamp_proto_rawDesc = "" + "\acommand\x18\t \x01(\v2!.opamp.proto.ServerToAgentCommandR\acommand\x12P\n" + "\x13custom_capabilities\x18\n" + " \x01(\v2\x1f.opamp.proto.CustomCapabilitiesR\x12customCapabilities\x12A\n" + - "\x0ecustom_message\x18\v \x01(\v2\x1a.opamp.proto.CustomMessageR\rcustomMessage\x12Q\n" + - "\x14trust_chain_response\x18\f \x01(\v2\x1f.opamp.proto.TrustChainResponseR\x12trustChainResponse\x12\x1c\n" + - "\tsignature\x18\r \x01(\fR\tsignature\"\xbd\x01\n" + + "\x0ecustom_message\x18\v \x01(\v2\x1a.opamp.proto.CustomMessageR\rcustomMessageJ\x04\b\f\x10\rJ\x04\b\r\x10\x0eR\x14trust_chain_responseR\tsignature\"\xbd\x01\n" + "\x12TrustChainResponse\x12X\n" + "\x11certificate_chain\x18\x01 \x03(\v2+.opamp.proto.TrustChainResponse.CertificateR\x10certificateChain\x12#\n" + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x1a(\n" + "\vCertificate\x12\x19\n" + - "\bder_data\x18\x01 \x01(\fR\aderData\"\xeb\x02\n" + + "\bder_data\x18\x01 \x01(\fR\aderData\"\xa0\x01\n" + + "\x13SignedServerToAgent\x12\x18\n" + + "\apayload\x18\x01 \x01(\fR\apayload\x12\x1c\n" + + "\tsignature\x18\x02 \x01(\fR\tsignature\x12Q\n" + + "\x14trust_chain_response\x18\x03 \x01(\v2\x1f.opamp.proto.TrustChainResponseR\x12trustChainResponse\"\xeb\x02\n" + "\x17OpAMPConnectionSettings\x121\n" + "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x12.\n" + "\aheaders\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\x12=\n" + @@ -3963,7 +4025,7 @@ func file_opamp_proto_rawDescGZIP() []byte { } var file_opamp_proto_enumTypes = make([]protoimpl.EnumInfo, 10) -var file_opamp_proto_msgTypes = make([]protoimpl.MessageInfo, 47) +var file_opamp_proto_msgTypes = make([]protoimpl.MessageInfo, 48) var file_opamp_proto_goTypes = []any{ (AgentToServerFlags)(0), // 0: opamp.proto.AgentToServerFlags (ServerToAgentFlags)(0), // 1: opamp.proto.ServerToAgentFlags @@ -3984,118 +4046,119 @@ var file_opamp_proto_goTypes = []any{ (*ComponentDetails)(nil), // 16: opamp.proto.ComponentDetails (*ServerToAgent)(nil), // 17: opamp.proto.ServerToAgent (*TrustChainResponse)(nil), // 18: opamp.proto.TrustChainResponse - (*OpAMPConnectionSettings)(nil), // 19: opamp.proto.OpAMPConnectionSettings - (*TelemetryConnectionSettings)(nil), // 20: opamp.proto.TelemetryConnectionSettings - (*OtherConnectionSettings)(nil), // 21: opamp.proto.OtherConnectionSettings - (*TLSConnectionSettings)(nil), // 22: opamp.proto.TLSConnectionSettings - (*ProxyConnectionSettings)(nil), // 23: opamp.proto.ProxyConnectionSettings - (*Headers)(nil), // 24: opamp.proto.Headers - (*Header)(nil), // 25: opamp.proto.Header - (*TLSCertificate)(nil), // 26: opamp.proto.TLSCertificate - (*ConnectionSettingsOffers)(nil), // 27: opamp.proto.ConnectionSettingsOffers - (*PackagesAvailable)(nil), // 28: opamp.proto.PackagesAvailable - (*PackageAvailable)(nil), // 29: opamp.proto.PackageAvailable - (*DownloadableFile)(nil), // 30: opamp.proto.DownloadableFile - (*ServerErrorResponse)(nil), // 31: opamp.proto.ServerErrorResponse - (*RetryInfo)(nil), // 32: opamp.proto.RetryInfo - (*ServerToAgentCommand)(nil), // 33: opamp.proto.ServerToAgentCommand - (*AgentDescription)(nil), // 34: opamp.proto.AgentDescription - (*ComponentHealth)(nil), // 35: opamp.proto.ComponentHealth - (*EffectiveConfig)(nil), // 36: opamp.proto.EffectiveConfig - (*RemoteConfigStatus)(nil), // 37: opamp.proto.RemoteConfigStatus - (*ConnectionSettingsStatus)(nil), // 38: opamp.proto.ConnectionSettingsStatus - (*PackageStatuses)(nil), // 39: opamp.proto.PackageStatuses - (*PackageStatus)(nil), // 40: opamp.proto.PackageStatus - (*PackageDownloadDetails)(nil), // 41: opamp.proto.PackageDownloadDetails - (*AgentIdentification)(nil), // 42: opamp.proto.AgentIdentification - (*AgentRemoteConfig)(nil), // 43: opamp.proto.AgentRemoteConfig - (*AgentConfigMap)(nil), // 44: opamp.proto.AgentConfigMap - (*AgentConfigFile)(nil), // 45: opamp.proto.AgentConfigFile - (*CustomCapabilities)(nil), // 46: opamp.proto.CustomCapabilities - (*CustomMessage)(nil), // 47: opamp.proto.CustomMessage - nil, // 48: opamp.proto.AvailableComponents.ComponentsEntry - nil, // 49: opamp.proto.ComponentDetails.SubComponentMapEntry - (*TrustChainResponse_Certificate)(nil), // 50: opamp.proto.TrustChainResponse.Certificate - nil, // 51: opamp.proto.OtherConnectionSettings.OtherSettingsEntry - nil, // 52: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry - nil, // 53: opamp.proto.PackagesAvailable.PackagesEntry - nil, // 54: opamp.proto.ComponentHealth.ComponentHealthMapEntry - nil, // 55: opamp.proto.PackageStatuses.PackagesEntry - nil, // 56: opamp.proto.AgentConfigMap.ConfigMapEntry - (*KeyValue)(nil), // 57: opamp.proto.KeyValue + (*SignedServerToAgent)(nil), // 19: opamp.proto.SignedServerToAgent + (*OpAMPConnectionSettings)(nil), // 20: opamp.proto.OpAMPConnectionSettings + (*TelemetryConnectionSettings)(nil), // 21: opamp.proto.TelemetryConnectionSettings + (*OtherConnectionSettings)(nil), // 22: opamp.proto.OtherConnectionSettings + (*TLSConnectionSettings)(nil), // 23: opamp.proto.TLSConnectionSettings + (*ProxyConnectionSettings)(nil), // 24: opamp.proto.ProxyConnectionSettings + (*Headers)(nil), // 25: opamp.proto.Headers + (*Header)(nil), // 26: opamp.proto.Header + (*TLSCertificate)(nil), // 27: opamp.proto.TLSCertificate + (*ConnectionSettingsOffers)(nil), // 28: opamp.proto.ConnectionSettingsOffers + (*PackagesAvailable)(nil), // 29: opamp.proto.PackagesAvailable + (*PackageAvailable)(nil), // 30: opamp.proto.PackageAvailable + (*DownloadableFile)(nil), // 31: opamp.proto.DownloadableFile + (*ServerErrorResponse)(nil), // 32: opamp.proto.ServerErrorResponse + (*RetryInfo)(nil), // 33: opamp.proto.RetryInfo + (*ServerToAgentCommand)(nil), // 34: opamp.proto.ServerToAgentCommand + (*AgentDescription)(nil), // 35: opamp.proto.AgentDescription + (*ComponentHealth)(nil), // 36: opamp.proto.ComponentHealth + (*EffectiveConfig)(nil), // 37: opamp.proto.EffectiveConfig + (*RemoteConfigStatus)(nil), // 38: opamp.proto.RemoteConfigStatus + (*ConnectionSettingsStatus)(nil), // 39: opamp.proto.ConnectionSettingsStatus + (*PackageStatuses)(nil), // 40: opamp.proto.PackageStatuses + (*PackageStatus)(nil), // 41: opamp.proto.PackageStatus + (*PackageDownloadDetails)(nil), // 42: opamp.proto.PackageDownloadDetails + (*AgentIdentification)(nil), // 43: opamp.proto.AgentIdentification + (*AgentRemoteConfig)(nil), // 44: opamp.proto.AgentRemoteConfig + (*AgentConfigMap)(nil), // 45: opamp.proto.AgentConfigMap + (*AgentConfigFile)(nil), // 46: opamp.proto.AgentConfigFile + (*CustomCapabilities)(nil), // 47: opamp.proto.CustomCapabilities + (*CustomMessage)(nil), // 48: opamp.proto.CustomMessage + nil, // 49: opamp.proto.AvailableComponents.ComponentsEntry + nil, // 50: opamp.proto.ComponentDetails.SubComponentMapEntry + (*TrustChainResponse_Certificate)(nil), // 51: opamp.proto.TrustChainResponse.Certificate + nil, // 52: opamp.proto.OtherConnectionSettings.OtherSettingsEntry + nil, // 53: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry + nil, // 54: opamp.proto.PackagesAvailable.PackagesEntry + nil, // 55: opamp.proto.ComponentHealth.ComponentHealthMapEntry + nil, // 56: opamp.proto.PackageStatuses.PackagesEntry + nil, // 57: opamp.proto.AgentConfigMap.ConfigMapEntry + (*KeyValue)(nil), // 58: opamp.proto.KeyValue } var file_opamp_proto_depIdxs = []int32{ - 34, // 0: opamp.proto.AgentToServer.agent_description:type_name -> opamp.proto.AgentDescription - 35, // 1: opamp.proto.AgentToServer.health:type_name -> opamp.proto.ComponentHealth - 36, // 2: opamp.proto.AgentToServer.effective_config:type_name -> opamp.proto.EffectiveConfig - 37, // 3: opamp.proto.AgentToServer.remote_config_status:type_name -> opamp.proto.RemoteConfigStatus - 39, // 4: opamp.proto.AgentToServer.package_statuses:type_name -> opamp.proto.PackageStatuses + 35, // 0: opamp.proto.AgentToServer.agent_description:type_name -> opamp.proto.AgentDescription + 36, // 1: opamp.proto.AgentToServer.health:type_name -> opamp.proto.ComponentHealth + 37, // 2: opamp.proto.AgentToServer.effective_config:type_name -> opamp.proto.EffectiveConfig + 38, // 3: opamp.proto.AgentToServer.remote_config_status:type_name -> opamp.proto.RemoteConfigStatus + 40, // 4: opamp.proto.AgentToServer.package_statuses:type_name -> opamp.proto.PackageStatuses 11, // 5: opamp.proto.AgentToServer.agent_disconnect:type_name -> opamp.proto.AgentDisconnect 12, // 6: opamp.proto.AgentToServer.connection_settings_request:type_name -> opamp.proto.ConnectionSettingsRequest - 46, // 7: opamp.proto.AgentToServer.custom_capabilities:type_name -> opamp.proto.CustomCapabilities - 47, // 8: opamp.proto.AgentToServer.custom_message:type_name -> opamp.proto.CustomMessage + 47, // 7: opamp.proto.AgentToServer.custom_capabilities:type_name -> opamp.proto.CustomCapabilities + 48, // 8: opamp.proto.AgentToServer.custom_message:type_name -> opamp.proto.CustomMessage 15, // 9: opamp.proto.AgentToServer.available_components:type_name -> opamp.proto.AvailableComponents - 38, // 10: opamp.proto.AgentToServer.connection_settings_status:type_name -> opamp.proto.ConnectionSettingsStatus + 39, // 10: opamp.proto.AgentToServer.connection_settings_status:type_name -> opamp.proto.ConnectionSettingsStatus 13, // 11: opamp.proto.ConnectionSettingsRequest.opamp:type_name -> opamp.proto.OpAMPConnectionSettingsRequest 14, // 12: opamp.proto.OpAMPConnectionSettingsRequest.certificate_request:type_name -> opamp.proto.CertificateRequest - 48, // 13: opamp.proto.AvailableComponents.components:type_name -> opamp.proto.AvailableComponents.ComponentsEntry - 57, // 14: opamp.proto.ComponentDetails.metadata:type_name -> opamp.proto.KeyValue - 49, // 15: opamp.proto.ComponentDetails.sub_component_map:type_name -> opamp.proto.ComponentDetails.SubComponentMapEntry - 31, // 16: opamp.proto.ServerToAgent.error_response:type_name -> opamp.proto.ServerErrorResponse - 43, // 17: opamp.proto.ServerToAgent.remote_config:type_name -> opamp.proto.AgentRemoteConfig - 27, // 18: opamp.proto.ServerToAgent.connection_settings:type_name -> opamp.proto.ConnectionSettingsOffers - 28, // 19: opamp.proto.ServerToAgent.packages_available:type_name -> opamp.proto.PackagesAvailable - 42, // 20: opamp.proto.ServerToAgent.agent_identification:type_name -> opamp.proto.AgentIdentification - 33, // 21: opamp.proto.ServerToAgent.command:type_name -> opamp.proto.ServerToAgentCommand - 46, // 22: opamp.proto.ServerToAgent.custom_capabilities:type_name -> opamp.proto.CustomCapabilities - 47, // 23: opamp.proto.ServerToAgent.custom_message:type_name -> opamp.proto.CustomMessage - 18, // 24: opamp.proto.ServerToAgent.trust_chain_response:type_name -> opamp.proto.TrustChainResponse - 50, // 25: opamp.proto.TrustChainResponse.certificate_chain:type_name -> opamp.proto.TrustChainResponse.Certificate - 24, // 26: opamp.proto.OpAMPConnectionSettings.headers:type_name -> opamp.proto.Headers - 26, // 27: opamp.proto.OpAMPConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 22, // 28: opamp.proto.OpAMPConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 23, // 29: opamp.proto.OpAMPConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 24, // 30: opamp.proto.TelemetryConnectionSettings.headers:type_name -> opamp.proto.Headers - 26, // 31: opamp.proto.TelemetryConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 22, // 32: opamp.proto.TelemetryConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 23, // 33: opamp.proto.TelemetryConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 24, // 34: opamp.proto.OtherConnectionSettings.headers:type_name -> opamp.proto.Headers - 26, // 35: opamp.proto.OtherConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 51, // 36: opamp.proto.OtherConnectionSettings.other_settings:type_name -> opamp.proto.OtherConnectionSettings.OtherSettingsEntry - 22, // 37: opamp.proto.OtherConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 23, // 38: opamp.proto.OtherConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 24, // 39: opamp.proto.ProxyConnectionSettings.connect_headers:type_name -> opamp.proto.Headers - 25, // 40: opamp.proto.Headers.headers:type_name -> opamp.proto.Header - 19, // 41: opamp.proto.ConnectionSettingsOffers.opamp:type_name -> opamp.proto.OpAMPConnectionSettings - 20, // 42: opamp.proto.ConnectionSettingsOffers.own_metrics:type_name -> opamp.proto.TelemetryConnectionSettings - 20, // 43: opamp.proto.ConnectionSettingsOffers.own_traces:type_name -> opamp.proto.TelemetryConnectionSettings - 20, // 44: opamp.proto.ConnectionSettingsOffers.own_logs:type_name -> opamp.proto.TelemetryConnectionSettings - 52, // 45: opamp.proto.ConnectionSettingsOffers.other_connections:type_name -> opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry - 53, // 46: opamp.proto.PackagesAvailable.packages:type_name -> opamp.proto.PackagesAvailable.PackagesEntry + 49, // 13: opamp.proto.AvailableComponents.components:type_name -> opamp.proto.AvailableComponents.ComponentsEntry + 58, // 14: opamp.proto.ComponentDetails.metadata:type_name -> opamp.proto.KeyValue + 50, // 15: opamp.proto.ComponentDetails.sub_component_map:type_name -> opamp.proto.ComponentDetails.SubComponentMapEntry + 32, // 16: opamp.proto.ServerToAgent.error_response:type_name -> opamp.proto.ServerErrorResponse + 44, // 17: opamp.proto.ServerToAgent.remote_config:type_name -> opamp.proto.AgentRemoteConfig + 28, // 18: opamp.proto.ServerToAgent.connection_settings:type_name -> opamp.proto.ConnectionSettingsOffers + 29, // 19: opamp.proto.ServerToAgent.packages_available:type_name -> opamp.proto.PackagesAvailable + 43, // 20: opamp.proto.ServerToAgent.agent_identification:type_name -> opamp.proto.AgentIdentification + 34, // 21: opamp.proto.ServerToAgent.command:type_name -> opamp.proto.ServerToAgentCommand + 47, // 22: opamp.proto.ServerToAgent.custom_capabilities:type_name -> opamp.proto.CustomCapabilities + 48, // 23: opamp.proto.ServerToAgent.custom_message:type_name -> opamp.proto.CustomMessage + 51, // 24: opamp.proto.TrustChainResponse.certificate_chain:type_name -> opamp.proto.TrustChainResponse.Certificate + 18, // 25: opamp.proto.SignedServerToAgent.trust_chain_response:type_name -> opamp.proto.TrustChainResponse + 25, // 26: opamp.proto.OpAMPConnectionSettings.headers:type_name -> opamp.proto.Headers + 27, // 27: opamp.proto.OpAMPConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate + 23, // 28: opamp.proto.OpAMPConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings + 24, // 29: opamp.proto.OpAMPConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings + 25, // 30: opamp.proto.TelemetryConnectionSettings.headers:type_name -> opamp.proto.Headers + 27, // 31: opamp.proto.TelemetryConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate + 23, // 32: opamp.proto.TelemetryConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings + 24, // 33: opamp.proto.TelemetryConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings + 25, // 34: opamp.proto.OtherConnectionSettings.headers:type_name -> opamp.proto.Headers + 27, // 35: opamp.proto.OtherConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate + 52, // 36: opamp.proto.OtherConnectionSettings.other_settings:type_name -> opamp.proto.OtherConnectionSettings.OtherSettingsEntry + 23, // 37: opamp.proto.OtherConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings + 24, // 38: opamp.proto.OtherConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings + 25, // 39: opamp.proto.ProxyConnectionSettings.connect_headers:type_name -> opamp.proto.Headers + 26, // 40: opamp.proto.Headers.headers:type_name -> opamp.proto.Header + 20, // 41: opamp.proto.ConnectionSettingsOffers.opamp:type_name -> opamp.proto.OpAMPConnectionSettings + 21, // 42: opamp.proto.ConnectionSettingsOffers.own_metrics:type_name -> opamp.proto.TelemetryConnectionSettings + 21, // 43: opamp.proto.ConnectionSettingsOffers.own_traces:type_name -> opamp.proto.TelemetryConnectionSettings + 21, // 44: opamp.proto.ConnectionSettingsOffers.own_logs:type_name -> opamp.proto.TelemetryConnectionSettings + 53, // 45: opamp.proto.ConnectionSettingsOffers.other_connections:type_name -> opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry + 54, // 46: opamp.proto.PackagesAvailable.packages:type_name -> opamp.proto.PackagesAvailable.PackagesEntry 3, // 47: opamp.proto.PackageAvailable.type:type_name -> opamp.proto.PackageType - 30, // 48: opamp.proto.PackageAvailable.file:type_name -> opamp.proto.DownloadableFile - 24, // 49: opamp.proto.DownloadableFile.headers:type_name -> opamp.proto.Headers + 31, // 48: opamp.proto.PackageAvailable.file:type_name -> opamp.proto.DownloadableFile + 25, // 49: opamp.proto.DownloadableFile.headers:type_name -> opamp.proto.Headers 4, // 50: opamp.proto.ServerErrorResponse.type:type_name -> opamp.proto.ServerErrorResponseType - 32, // 51: opamp.proto.ServerErrorResponse.retry_info:type_name -> opamp.proto.RetryInfo + 33, // 51: opamp.proto.ServerErrorResponse.retry_info:type_name -> opamp.proto.RetryInfo 5, // 52: opamp.proto.ServerToAgentCommand.type:type_name -> opamp.proto.CommandType - 57, // 53: opamp.proto.AgentDescription.identifying_attributes:type_name -> opamp.proto.KeyValue - 57, // 54: opamp.proto.AgentDescription.non_identifying_attributes:type_name -> opamp.proto.KeyValue - 54, // 55: opamp.proto.ComponentHealth.component_health_map:type_name -> opamp.proto.ComponentHealth.ComponentHealthMapEntry - 44, // 56: opamp.proto.EffectiveConfig.config_map:type_name -> opamp.proto.AgentConfigMap + 58, // 53: opamp.proto.AgentDescription.identifying_attributes:type_name -> opamp.proto.KeyValue + 58, // 54: opamp.proto.AgentDescription.non_identifying_attributes:type_name -> opamp.proto.KeyValue + 55, // 55: opamp.proto.ComponentHealth.component_health_map:type_name -> opamp.proto.ComponentHealth.ComponentHealthMapEntry + 45, // 56: opamp.proto.EffectiveConfig.config_map:type_name -> opamp.proto.AgentConfigMap 8, // 57: opamp.proto.RemoteConfigStatus.status:type_name -> opamp.proto.RemoteConfigStatuses 7, // 58: opamp.proto.ConnectionSettingsStatus.status:type_name -> opamp.proto.ConnectionSettingsStatuses - 55, // 59: opamp.proto.PackageStatuses.packages:type_name -> opamp.proto.PackageStatuses.PackagesEntry + 56, // 59: opamp.proto.PackageStatuses.packages:type_name -> opamp.proto.PackageStatuses.PackagesEntry 9, // 60: opamp.proto.PackageStatus.status:type_name -> opamp.proto.PackageStatusEnum - 41, // 61: opamp.proto.PackageStatus.download_details:type_name -> opamp.proto.PackageDownloadDetails - 44, // 62: opamp.proto.AgentRemoteConfig.config:type_name -> opamp.proto.AgentConfigMap - 56, // 63: opamp.proto.AgentConfigMap.config_map:type_name -> opamp.proto.AgentConfigMap.ConfigMapEntry + 42, // 61: opamp.proto.PackageStatus.download_details:type_name -> opamp.proto.PackageDownloadDetails + 45, // 62: opamp.proto.AgentRemoteConfig.config:type_name -> opamp.proto.AgentConfigMap + 57, // 63: opamp.proto.AgentConfigMap.config_map:type_name -> opamp.proto.AgentConfigMap.ConfigMapEntry 16, // 64: opamp.proto.AvailableComponents.ComponentsEntry.value:type_name -> opamp.proto.ComponentDetails 16, // 65: opamp.proto.ComponentDetails.SubComponentMapEntry.value:type_name -> opamp.proto.ComponentDetails - 21, // 66: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry.value:type_name -> opamp.proto.OtherConnectionSettings - 29, // 67: opamp.proto.PackagesAvailable.PackagesEntry.value:type_name -> opamp.proto.PackageAvailable - 35, // 68: opamp.proto.ComponentHealth.ComponentHealthMapEntry.value:type_name -> opamp.proto.ComponentHealth - 40, // 69: opamp.proto.PackageStatuses.PackagesEntry.value:type_name -> opamp.proto.PackageStatus - 45, // 70: opamp.proto.AgentConfigMap.ConfigMapEntry.value:type_name -> opamp.proto.AgentConfigFile + 22, // 66: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry.value:type_name -> opamp.proto.OtherConnectionSettings + 30, // 67: opamp.proto.PackagesAvailable.PackagesEntry.value:type_name -> opamp.proto.PackageAvailable + 36, // 68: opamp.proto.ComponentHealth.ComponentHealthMapEntry.value:type_name -> opamp.proto.ComponentHealth + 41, // 69: opamp.proto.PackageStatuses.PackagesEntry.value:type_name -> opamp.proto.PackageStatus + 46, // 70: opamp.proto.AgentConfigMap.ConfigMapEntry.value:type_name -> opamp.proto.AgentConfigFile 71, // [71:71] is the sub-list for method output_type 71, // [71:71] is the sub-list for method input_type 71, // [71:71] is the sub-list for extension type_name @@ -4109,7 +4172,7 @@ func file_opamp_proto_init() { return } file_anyvalue_proto_init() - file_opamp_proto_msgTypes[21].OneofWrappers = []any{ + file_opamp_proto_msgTypes[22].OneofWrappers = []any{ (*ServerErrorResponse_RetryInfo)(nil), } type x struct{} @@ -4118,7 +4181,7 @@ func file_opamp_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_opamp_proto_rawDesc), len(file_opamp_proto_rawDesc)), NumEnums: 10, - NumMessages: 47, + NumMessages: 48, NumExtensions: 0, NumServices: 0, }, From 26d91b49c2665b1582465ef612ef3c3c651fa332 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 01:43:46 +0200 Subject: [PATCH 03/22] Build signing package for detached message attestation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the opamp-go signing/ package, the foundation for Message Attestation in the client and server wire layers (Phases 3-4). Public API: * Signer interface — Sign(ctx, payload []byte) → ([]byte, error) ChainDER(ctx) → ([][]byte, error) * Verifier interface — ValidateChain(ctx, chainDER, now) → (*x509.Certificate, error) Verify(ctx, payload, signature, leaf) → error * LocalSigner — in-process reference Signer, holds a crypto.Signer private key and a DER cert chain. * LocalVerifier — in-process reference Verifier, wraps an x509.CertPool trust anchor pool. * VerifierFromFile — convenience constructor reading a PEM CA bundle. * LocalSignerFromFiles — constructor reading PEM-encoded leaf key + chain. * GenerateCA / GenerateLeaf — multi-algorithm cert generation for tests and example servers (CertOptions for clock skew + custom validity windows). Algorithm dispatch covers the spec baseline: * ECDSA P-256 + SHA-256 (DER-encoded (r,s)) * ECDSA P-384 + SHA-384 * RSA-2048+ with PKCS#1 v1.5 + SHA-256 * Ed25519 Path validation uses x509.Certificate.Verify with the ExtKeyUsageCodeSigning EKU enforced. crypto/x509 handles signature, validity window, basicConstraints, pathLenConstraint, and critical extension checks. The Signer interface is deliberately minimal so RPC-backed signers (e.g. Datadog rc-x509-api) can be plugged in without touching the wire-level opamp-go code. The signer takes raw payload bytes; the opamp-go server marshals the inner ServerToAgent and hands those bytes to Sign. No proto knowledge in this package. Tests cover: * Per-algorithm sign/verify round-trip (4 algorithms). * Tampered signature → ErrSignatureMismatch. * Tampered payload → ErrSignatureMismatch. * Expired leaf, not-yet-valid leaf, unknown trust anchor, garbage cert bytes, leaf with wrong EKU (TLS server auth instead of code signing). * PEM file round-trips for both the verifier and the signer, in all four algorithms. * Context cancellation propagation through every method. * LocalSigner.ChainDER defensive-copy semantics. go test ./signing/... -race -count=1 passes; go vet clean; gofumpt clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- signing/algorithm.go | 132 ++++++++++++++++++++ signing/algorithm_test.go | 197 +++++++++++++++++++++++++++++ signing/certs.go | 174 ++++++++++++++++++++++++++ signing/chain.go | 92 ++++++++++++++ signing/chain_test.go | 222 +++++++++++++++++++++++++++++++++ signing/loader.go | 134 ++++++++++++++++++++ signing/loader_test.go | 180 ++++++++++++++++++++++++++ signing/local_signer.go | 104 +++++++++++++++ signing/local_signer_test.go | 68 ++++++++++ signing/local_verifier.go | 64 ++++++++++ signing/local_verifier_test.go | 42 +++++++ signing/types.go | 124 ++++++++++++++++++ 12 files changed, 1533 insertions(+) create mode 100644 signing/algorithm.go create mode 100644 signing/algorithm_test.go create mode 100644 signing/certs.go create mode 100644 signing/chain.go create mode 100644 signing/chain_test.go create mode 100644 signing/loader.go create mode 100644 signing/loader_test.go create mode 100644 signing/local_signer.go create mode 100644 signing/local_signer_test.go create mode 100644 signing/local_verifier.go create mode 100644 signing/local_verifier_test.go create mode 100644 signing/types.go diff --git a/signing/algorithm.go b/signing/algorithm.go new file mode 100644 index 00000000..19493c8c --- /dev/null +++ b/signing/algorithm.go @@ -0,0 +1,132 @@ +package signing + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "errors" + "fmt" +) + +// ErrUnsupportedAlgorithm indicates that a certificate's +// SignatureAlgorithm is not in the supported set, or that the +// public/private key type does not match the requested algorithm. +var ErrUnsupportedAlgorithm = errors.New("signing: unsupported signature algorithm") + +// algorithmFromCert maps x509.Certificate.SignatureAlgorithm to the +// package's Algorithm enum, returning ErrUnsupportedAlgorithm for any +// value outside the supported baseline. +func algorithmFromCert(cert *x509.Certificate) (Algorithm, error) { + switch cert.SignatureAlgorithm { + case x509.ECDSAWithSHA256: + return AlgorithmECDSAP256SHA256, nil + case x509.ECDSAWithSHA384: + return AlgorithmECDSAP384SHA384, nil + case x509.SHA256WithRSA: + return AlgorithmRSAPKCS1v15SHA256, nil + case x509.PureEd25519: + return AlgorithmEd25519, nil + default: + return AlgorithmUnspecified, fmt.Errorf("%w: %s", ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) + } +} + +// signWithKey produces a detached signature over payload using key, +// dispatching on alg. The caller is responsible for matching alg to +// the type of key (private key types are not switchable at runtime). +func signWithKey(key crypto.Signer, alg Algorithm, payload []byte) ([]byte, error) { + switch alg { + case AlgorithmECDSAP256SHA256: + k, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("%w: ECDSA-P256 requires *ecdsa.PrivateKey, got %T", ErrUnsupportedAlgorithm, key) + } + h := sha256.Sum256(payload) + return ecdsa.SignASN1(rand.Reader, k, h[:]) + + case AlgorithmECDSAP384SHA384: + k, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("%w: ECDSA-P384 requires *ecdsa.PrivateKey, got %T", ErrUnsupportedAlgorithm, key) + } + h := sha512.Sum384(payload) + return ecdsa.SignASN1(rand.Reader, k, h[:]) + + case AlgorithmRSAPKCS1v15SHA256: + k, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("%w: RSA-PKCS1v15-SHA256 requires *rsa.PrivateKey, got %T", ErrUnsupportedAlgorithm, key) + } + h := sha256.Sum256(payload) + return rsa.SignPKCS1v15(rand.Reader, k, crypto.SHA256, h[:]) + + case AlgorithmEd25519: + k, ok := key.(ed25519.PrivateKey) + if !ok { + return nil, fmt.Errorf("%w: Ed25519 requires ed25519.PrivateKey, got %T", ErrUnsupportedAlgorithm, key) + } + return ed25519.Sign(k, payload), nil + + default: + return nil, fmt.Errorf("%w: %d", ErrUnsupportedAlgorithm, alg) + } +} + +// verifyWithPub verifies signature over payload using the supplied +// public key under alg. Returns ErrSignatureMismatch when the +// signature does not verify, or ErrUnsupportedAlgorithm if alg or pub +// is unsupported. +func verifyWithPub(pub crypto.PublicKey, alg Algorithm, payload, signature []byte) error { + switch alg { + case AlgorithmECDSAP256SHA256: + p, ok := pub.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("%w: ECDSA-P256 requires *ecdsa.PublicKey, got %T", ErrUnsupportedAlgorithm, pub) + } + h := sha256.Sum256(payload) + if !ecdsa.VerifyASN1(p, h[:], signature) { + return ErrSignatureMismatch + } + return nil + + case AlgorithmECDSAP384SHA384: + p, ok := pub.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("%w: ECDSA-P384 requires *ecdsa.PublicKey, got %T", ErrUnsupportedAlgorithm, pub) + } + h := sha512.Sum384(payload) + if !ecdsa.VerifyASN1(p, h[:], signature) { + return ErrSignatureMismatch + } + return nil + + case AlgorithmRSAPKCS1v15SHA256: + p, ok := pub.(*rsa.PublicKey) + if !ok { + return fmt.Errorf("%w: RSA-PKCS1v15-SHA256 requires *rsa.PublicKey, got %T", ErrUnsupportedAlgorithm, pub) + } + h := sha256.Sum256(payload) + if err := rsa.VerifyPKCS1v15(p, crypto.SHA256, h[:], signature); err != nil { + return fmt.Errorf("%w: %v", ErrSignatureMismatch, err) + } + return nil + + case AlgorithmEd25519: + p, ok := pub.(ed25519.PublicKey) + if !ok { + return fmt.Errorf("%w: Ed25519 requires ed25519.PublicKey, got %T", ErrUnsupportedAlgorithm, pub) + } + if !ed25519.Verify(p, payload, signature) { + return ErrSignatureMismatch + } + return nil + + default: + return fmt.Errorf("%w: %d", ErrUnsupportedAlgorithm, alg) + } +} diff --git a/signing/algorithm_test.go b/signing/algorithm_test.go new file mode 100644 index 00000000..b374d6c9 --- /dev/null +++ b/signing/algorithm_test.go @@ -0,0 +1,197 @@ +package signing + +import ( + "context" + "crypto/x509" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// allAlgorithms is the set of algorithms the package is required to +// support. Tests that exercise the algorithm-dispatch path table-drive +// across these. +var allAlgorithms = []Algorithm{ + AlgorithmECDSAP256SHA256, + AlgorithmECDSAP384SHA384, + AlgorithmRSAPKCS1v15SHA256, + AlgorithmEd25519, +} + +// testKeyPair generates a fresh CA + leaf pair for the supplied +// algorithm, returning the leaf, leaf's signing key, and a trust anchor +// pool containing the CA. It's the workhorse for round-trip tests. +func testKeyPair(t *testing.T, alg Algorithm) (*x509.Certificate, *LocalSigner, *LocalVerifier) { + t.Helper() + ca, caKey, err := GenerateCA(alg, CertOptions{}) + require.NoError(t, err) + leaf, leafKey, err := GenerateLeaf(alg, ca, caKey, CertOptions{}) + require.NoError(t, err) + + signer, err := NewLocalSigner(leafKey, []*x509.Certificate{leaf}) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(ca) + verifier, err := NewLocalVerifier(roots) + require.NoError(t, err) + + return leaf, signer, verifier +} + +// TestRoundTrip_AllAlgorithms exercises the happy path across every +// supported algorithm: sign a payload via LocalSigner, validate the +// chain and verify the signature via LocalVerifier. +func TestRoundTrip_AllAlgorithms(t *testing.T) { + ctx := context.Background() + payload := []byte("OpAMP Message Attestation round-trip payload") + + for _, alg := range allAlgorithms { + t.Run(alg.String(), func(t *testing.T) { + _, signer, verifier := testKeyPair(t, alg) + assert.Equal(t, alg, signer.Algorithm()) + + sig, err := signer.Sign(ctx, payload) + require.NoError(t, err) + require.NotEmpty(t, sig) + + chainDER, err := signer.ChainDER(ctx) + require.NoError(t, err) + require.Len(t, chainDER, 1, "chain should be just the leaf (no intermediates in the simple case)") + + leaf, err := verifier.ValidateChain(ctx, chainDER, time.Now()) + require.NoError(t, err) + require.NotNil(t, leaf) + + require.NoError(t, verifier.Verify(ctx, payload, sig, leaf)) + }) + } +} + +// TestTamperedSignature_AllAlgorithms confirms that flipping a single +// byte in the signature is detected as ErrSignatureMismatch (or for +// RSA, wrapped by ErrSignatureMismatch since the stdlib returns a +// distinct internal error). +func TestTamperedSignature_AllAlgorithms(t *testing.T) { + ctx := context.Background() + payload := []byte("payload to sign") + + for _, alg := range allAlgorithms { + t.Run(alg.String(), func(t *testing.T) { + _, signer, verifier := testKeyPair(t, alg) + sig, err := signer.Sign(ctx, payload) + require.NoError(t, err) + require.NotEmpty(t, sig) + + tampered := append([]byte(nil), sig...) + tampered[len(tampered)-1] ^= 0x01 + + chainDER, err := signer.ChainDER(ctx) + require.NoError(t, err) + leaf, err := verifier.ValidateChain(ctx, chainDER, time.Now()) + require.NoError(t, err) + + err = verifier.Verify(ctx, payload, tampered, leaf) + require.Error(t, err) + require.ErrorIs(t, err, ErrSignatureMismatch) + }) + } +} + +// TestTamperedPayload_AllAlgorithms confirms that flipping a single +// byte of the payload makes the original signature invalid. +func TestTamperedPayload_AllAlgorithms(t *testing.T) { + ctx := context.Background() + payload := []byte("payload to sign") + + for _, alg := range allAlgorithms { + t.Run(alg.String(), func(t *testing.T) { + _, signer, verifier := testKeyPair(t, alg) + sig, err := signer.Sign(ctx, payload) + require.NoError(t, err) + + tampered := append([]byte(nil), payload...) + tampered[0] ^= 0x01 + + chainDER, err := signer.ChainDER(ctx) + require.NoError(t, err) + leaf, err := verifier.ValidateChain(ctx, chainDER, time.Now()) + require.NoError(t, err) + + err = verifier.Verify(ctx, tampered, sig, leaf) + require.Error(t, err) + require.ErrorIs(t, err, ErrSignatureMismatch) + }) + } +} + +// TestEmptySignatureRejected confirms that the Verifier refuses to +// even attempt verification when the signature is empty — the wire +// spec requires signature to be present and non-empty on every +// message after the handshake. +func TestEmptySignatureRejected(t *testing.T) { + ctx := context.Background() + _, _, verifier := testKeyPair(t, AlgorithmECDSAP256SHA256) + // Need a valid leaf to pass the early nil check. + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + err = verifier.Verify(ctx, []byte("payload"), nil, leaf) + require.Error(t, err) +} + +// TestContextCancellationPropagates confirms that a cancelled context +// is honoured by Sign, ChainDER, ValidateChain, and Verify. +func TestContextCancellationPropagates(t *testing.T) { + _, signer, verifier := testKeyPair(t, AlgorithmECDSAP256SHA256) + chainDER, err := signer.ChainDER(context.Background()) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + _, err = signer.Sign(ctx, []byte("x")) + require.ErrorIs(t, err, context.Canceled) + + _, err = signer.ChainDER(ctx) + require.ErrorIs(t, err, context.Canceled) + + _, err = verifier.ValidateChain(ctx, chainDER, time.Now()) + require.ErrorIs(t, err, context.Canceled) + + err = verifier.Verify(ctx, []byte("x"), []byte("y"), nil) + require.ErrorIs(t, err, context.Canceled) +} + +// TestUnsupportedAlgorithmFromCert confirms that algorithmFromCert +// rejects unsupported algorithms. +func TestUnsupportedAlgorithmFromCert(t *testing.T) { + // Forge a cert with an unsupported SignatureAlgorithm. + cert := &x509.Certificate{SignatureAlgorithm: x509.MD5WithRSA} + _, err := algorithmFromCert(cert) + require.Error(t, err) + require.True(t, errors.Is(err, ErrUnsupportedAlgorithm), "expected ErrUnsupportedAlgorithm, got %v", err) +} + +// TestAlgorithmString covers Algorithm.String for diagnostic output. +func TestAlgorithmString(t *testing.T) { + cases := []struct { + alg Algorithm + want string + }{ + {AlgorithmECDSAP256SHA256, "ECDSA-P256-SHA256"}, + {AlgorithmECDSAP384SHA384, "ECDSA-P384-SHA384"}, + {AlgorithmRSAPKCS1v15SHA256, "RSA-PKCS1v15-SHA256"}, + {AlgorithmEd25519, "Ed25519"}, + {AlgorithmUnspecified, "unspecified"}, + {Algorithm(99), "unspecified"}, + } + for _, tc := range cases { + assert.Equal(t, tc.want, tc.alg.String()) + } +} diff --git a/signing/certs.go b/signing/certs.go new file mode 100644 index 00000000..abd2359d --- /dev/null +++ b/signing/certs.go @@ -0,0 +1,174 @@ +package signing + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "time" +) + +// CertOptions configures certificate generation in [GenerateCA] and +// [GenerateLeaf]. The zero value yields a 24-hour validity window +// starting one hour in the past (to absorb minor clock skew). +type CertOptions struct { + // NotBefore overrides the validity start. Zero means + // time.Now().Add(-1 * time.Hour). + NotBefore time.Time + // NotAfter overrides the validity end. Zero means + // time.Now().Add(24 * time.Hour). + NotAfter time.Time + // CommonName overrides the certificate's Subject CommonName. + CommonName string +} + +func (o CertOptions) notBefore() time.Time { + if !o.NotBefore.IsZero() { + return o.NotBefore + } + return time.Now().Add(-1 * time.Hour) +} + +func (o CertOptions) notAfter() time.Time { + if !o.NotAfter.IsZero() { + return o.NotAfter + } + return time.Now().Add(24 * time.Hour) +} + +// GenerateCA produces a self-signed CA certificate and its +// corresponding private key for the supplied algorithm. The CA has +// KeyUsageCertSign + KeyUsageDigitalSignature and is marked CA:TRUE +// with a critical basicConstraints extension. +// +// Intended primarily for tests and for the opamp-go example server. +// Production deployments will use externally-managed CA infrastructure. +func GenerateCA(alg Algorithm, opts CertOptions) (*x509.Certificate, crypto.Signer, error) { + key, sigAlg, pub, err := newKey(alg) + if err != nil { + return nil, nil, err + } + + serial, err := randomSerial() + if err != nil { + return nil, nil, err + } + + cn := opts.CommonName + if cn == "" { + cn = fmt.Sprintf("opamp-go test CA (%s)", alg) + } + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: cn}, + NotBefore: opts.notBefore(), + NotAfter: opts.notAfter(), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, + IsCA: true, + BasicConstraintsValid: true, + SignatureAlgorithm: sigAlg, + } + + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, key) + if err != nil { + return nil, nil, fmt.Errorf("signing: create CA cert: %w", err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, nil, fmt.Errorf("signing: parse CA cert: %w", err) + } + return cert, key, nil +} + +// GenerateLeaf produces a leaf signing certificate signed by ca with +// caKey, using alg. The leaf carries ExtKeyUsageCodeSigning (the EKU +// required by the OpAMP Message Attestation spec) and +// KeyUsageDigitalSignature. +// +// Intended primarily for tests and example servers. +func GenerateLeaf(alg Algorithm, ca *x509.Certificate, caKey crypto.Signer, opts CertOptions) (*x509.Certificate, crypto.Signer, error) { + key, sigAlg, pub, err := newKey(alg) + if err != nil { + return nil, nil, err + } + + serial, err := randomSerial() + if err != nil { + return nil, nil, err + } + + cn := opts.CommonName + if cn == "" { + cn = fmt.Sprintf("opamp-go test leaf (%s)", alg) + } + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: cn}, + NotBefore: opts.notBefore(), + NotAfter: opts.notAfter(), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + SignatureAlgorithm: sigAlg, + } + + der, err := x509.CreateCertificate(rand.Reader, tmpl, ca, pub, caKey) + if err != nil { + return nil, nil, fmt.Errorf("signing: create leaf cert: %w", err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, nil, fmt.Errorf("signing: parse leaf cert: %w", err) + } + return cert, key, nil +} + +// newKey creates a private key for alg and returns the corresponding +// x509.SignatureAlgorithm to record in certificates, along with the +// public-key form needed by x509.CreateCertificate. +func newKey(alg Algorithm) (crypto.Signer, x509.SignatureAlgorithm, crypto.PublicKey, error) { + switch alg { + case AlgorithmECDSAP256SHA256: + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, 0, nil, fmt.Errorf("signing: generate ECDSA-P256 key: %w", err) + } + return k, x509.ECDSAWithSHA256, &k.PublicKey, nil + case AlgorithmECDSAP384SHA384: + k, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, 0, nil, fmt.Errorf("signing: generate ECDSA-P384 key: %w", err) + } + return k, x509.ECDSAWithSHA384, &k.PublicKey, nil + case AlgorithmRSAPKCS1v15SHA256: + k, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, 0, nil, fmt.Errorf("signing: generate RSA-2048 key: %w", err) + } + return k, x509.SHA256WithRSA, &k.PublicKey, nil + case AlgorithmEd25519: + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, 0, nil, fmt.Errorf("signing: generate Ed25519 key: %w", err) + } + return priv, x509.PureEd25519, pub, nil + default: + return nil, 0, nil, fmt.Errorf("%w: %d", ErrUnsupportedAlgorithm, alg) + } +} + +func randomSerial() (*big.Int, error) { + limit := new(big.Int).Lsh(big.NewInt(1), 128) + n, err := rand.Int(rand.Reader, limit) + if err != nil { + return nil, fmt.Errorf("signing: generate serial: %w", err) + } + return n, nil +} diff --git a/signing/chain.go b/signing/chain.go new file mode 100644 index 00000000..d7a62ef3 --- /dev/null +++ b/signing/chain.go @@ -0,0 +1,92 @@ +package signing + +import ( + "context" + "crypto/x509" + "errors" + "fmt" + "time" +) + +// Sentinel errors for chain validation. Callers can use errors.Is to +// distinguish failure modes (for example, to log a structured reason +// for terminating a connection). +var ( + // ErrEmptyChain is returned when ValidateChain is called with an + // empty chainDER slice. The OpAMP spec requires at least the leaf + // signing certificate to be present. + ErrEmptyChain = errors.New("signing: empty certificate chain") + + // ErrParseCertificate wraps an inner x509 parse failure on one of + // the chain entries. + ErrParseCertificate = errors.New("signing: parse certificate") + + // ErrChainValidation wraps an inner x509 path-validation failure + // (expired cert, unknown issuer, missing EKU, etc.). The wrapped + // error preserves the original x509-level reason. + ErrChainValidation = errors.New("signing: chain validation") + + // ErrSignatureMismatch is returned when a detached signature does + // not verify against the supplied public key and payload bytes. + ErrSignatureMismatch = errors.New("signing: signature does not verify") +) + +// ValidateChain performs RFC 5280 §6 X.509 path validation of the +// supplied DER certificate chain against the trust anchor pool in +// roots. +// +// The chain MUST be ordered intermediates first, leaf last, matching +// the on-wire ordering of SignedServerToAgent.trust_chain_response. +// The root certificate (the Agent's pre-configured payload trust +// anchor) is supplied via roots and MUST NOT appear in chainDER. +// +// The leaf certificate MUST carry the id-kp-codeSigning Extended Key +// Usage (OID 1.3.6.1.5.5.7.3.3). This prevents certificates issued +// for TLS server authentication from being repurposed to sign OpAMP +// messages. +// +// Other RFC 5280 checks — per-certificate signature, validity window, +// basicConstraints, pathLenConstraint, critical extensions — are +// enforced by the underlying crypto/x509 implementation. +// +// Revocation checking via CRL/OCSP is RECOMMENDED by the OpAMP spec +// but not performed here in the current implementation; that is a +// follow-up. Operators MAY rely on short-lived signing certificates +// as a complementary mitigation. +func ValidateChain(_ context.Context, chainDER [][]byte, roots *x509.CertPool, now time.Time) (*x509.Certificate, error) { + if len(chainDER) == 0 { + return nil, ErrEmptyChain + } + if roots == nil { + return nil, fmt.Errorf("%w: nil trust anchor pool", ErrChainValidation) + } + + certs := make([]*x509.Certificate, 0, len(chainDER)) + for i, der := range chainDER { + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, fmt.Errorf("%w: chain[%d]: %v", ErrParseCertificate, i, err) + } + certs = append(certs, cert) + } + + leaf := certs[len(certs)-1] + + intermediates := x509.NewCertPool() + for i := 0; i < len(certs)-1; i++ { + intermediates.AddCert(certs[i]) + } + + opts := x509.VerifyOptions{ + Roots: roots, + Intermediates: intermediates, + CurrentTime: now, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + } + + if _, err := leaf.Verify(opts); err != nil { + return nil, fmt.Errorf("%w: %v", ErrChainValidation, err) + } + + return leaf, nil +} diff --git a/signing/chain_test.go b/signing/chain_test.go new file mode 100644 index 00000000..b98d5ff9 --- /dev/null +++ b/signing/chain_test.go @@ -0,0 +1,222 @@ +package signing + +import ( + "context" + "crypto" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TestValidateChain_HappyPath_NoIntermediates exercises the simplest +// chain: leaf signed directly by the trust anchor. +func TestValidateChain_HappyPath_NoIntermediates(t *testing.T) { + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(ca) + + got, err := ValidateChain(context.Background(), [][]byte{leaf.Raw}, roots, time.Now()) + require.NoError(t, err) + require.Equal(t, leaf.SerialNumber, got.SerialNumber) +} + +// TestValidateChain_HappyPath_WithIntermediate exercises a chain with +// one intermediate CA between the root and the leaf. +func TestValidateChain_HappyPath_WithIntermediate(t *testing.T) { + rootCA, rootKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{CommonName: "Test Root"}) + require.NoError(t, err) + intermediate, intermediateKey, err := generateIntermediate(t, AlgorithmECDSAP256SHA256, rootCA, rootKey) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, intermediate, intermediateKey, CertOptions{}) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(rootCA) + + got, err := ValidateChain(context.Background(), [][]byte{intermediate.Raw, leaf.Raw}, roots, time.Now()) + require.NoError(t, err) + require.Equal(t, leaf.SerialNumber, got.SerialNumber) +} + +// TestValidateChain_EmptyChain confirms the empty-chain sentinel +// error. +func TestValidateChain_EmptyChain(t *testing.T) { + roots := x509.NewCertPool() + _, err := ValidateChain(context.Background(), nil, roots, time.Now()) + require.ErrorIs(t, err, ErrEmptyChain) +} + +// TestValidateChain_NilRoots returns ErrChainValidation rather than +// panicking when the trust anchor pool is missing. +func TestValidateChain_NilRoots(t *testing.T) { + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + _, err = ValidateChain(context.Background(), [][]byte{leaf.Raw}, nil, time.Now()) + require.ErrorIs(t, err, ErrChainValidation) +} + +// TestValidateChain_GarbageBytes confirms ErrParseCertificate when a +// chain entry isn't a valid DER certificate. +func TestValidateChain_GarbageBytes(t *testing.T) { + roots := x509.NewCertPool() + _, err := ValidateChain(context.Background(), [][]byte{{0xde, 0xad, 0xbe, 0xef}}, roots, time.Now()) + require.ErrorIs(t, err, ErrParseCertificate) +} + +// TestValidateChain_UnknownRoot confirms that a chain whose root is +// not in the agent's trust pool is rejected. +func TestValidateChain_UnknownRoot(t *testing.T) { + serverCA, serverKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, serverCA, serverKey, CertOptions{}) + require.NoError(t, err) + + // Agent's trust pool — a different CA entirely. + agentCA, _, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + roots := x509.NewCertPool() + roots.AddCert(agentCA) + + _, err = ValidateChain(context.Background(), [][]byte{leaf.Raw}, roots, time.Now()) + require.ErrorIs(t, err, ErrChainValidation) +} + +// TestValidateChain_ExpiredLeaf confirms that expired leaves are +// rejected. +func TestValidateChain_ExpiredLeaf(t *testing.T) { + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{ + NotBefore: time.Now().Add(-48 * time.Hour), + NotAfter: time.Now().Add(-24 * time.Hour), + }) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(ca) + + _, err = ValidateChain(context.Background(), [][]byte{leaf.Raw}, roots, time.Now()) + require.ErrorIs(t, err, ErrChainValidation) +} + +// TestValidateChain_NotYetValidLeaf confirms that leaves whose +// NotBefore is in the future are rejected. +func TestValidateChain_NotYetValidLeaf(t *testing.T) { + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{ + NotBefore: time.Now().Add(24 * time.Hour), + NotAfter: time.Now().Add(48 * time.Hour), + }) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(ca) + + _, err = ValidateChain(context.Background(), [][]byte{leaf.Raw}, roots, time.Now()) + require.ErrorIs(t, err, ErrChainValidation) +} + +// TestValidateChain_LeafMissingEKU confirms that a cert without +// id-kp-codeSigning is rejected. This is the load-bearing check that +// prevents a TLS server certificate from being repurposed to sign +// OpAMP messages. +func TestValidateChain_LeafMissingEKU(t *testing.T) { + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := generateLeafWithEKU(t, AlgorithmECDSAP256SHA256, ca, caKey, + []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(ca) + + _, err = ValidateChain(context.Background(), [][]byte{leaf.Raw}, roots, time.Now()) + require.ErrorIs(t, err, ErrChainValidation) +} + +// generateIntermediate produces a CA-capable intermediate certificate +// signed by the supplied root, using the requested algorithm for the +// intermediate's own key. The public GenerateLeaf in certs.go is for +// non-CA leaves, so the test file needs its own helper. +func generateIntermediate(t *testing.T, alg Algorithm, root *x509.Certificate, rootKey crypto.Signer) (*x509.Certificate, crypto.Signer, error) { + t.Helper() + intermediateKey, sigAlg, intermediatePub, err := newKey(alg) + if err != nil { + return nil, nil, err + } + + serial, err := randomSerial() + if err != nil { + return nil, nil, err + } + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: "opamp-go test intermediate"}, + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, + IsCA: true, + BasicConstraintsValid: true, + SignatureAlgorithm: sigAlg, + } + + der, err := x509.CreateCertificate(rand.Reader, tmpl, root, intermediatePub, rootKey) + if err != nil { + return nil, nil, err + } + parsed, err := x509.ParseCertificate(der) + if err != nil { + return nil, nil, err + } + return parsed, intermediateKey, nil +} + +// generateLeafWithEKU produces a leaf certificate with the supplied +// ExtKeyUsage set (and only that — code signing is intentionally +// absent for tests that want to verify EKU enforcement). Otherwise +// it mirrors GenerateLeaf. +func generateLeafWithEKU(t *testing.T, alg Algorithm, ca *x509.Certificate, caKey crypto.Signer, ekus []x509.ExtKeyUsage) (*x509.Certificate, crypto.Signer, error) { + t.Helper() + leafKey, sigAlg, leafPub, err := newKey(alg) + if err != nil { + return nil, nil, err + } + + serial, err := randomSerial() + if err != nil { + return nil, nil, err + } + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: "opamp-go test wrong-EKU leaf"}, + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: ekus, + SignatureAlgorithm: sigAlg, + } + + der, err := x509.CreateCertificate(rand.Reader, tmpl, ca, leafPub, caKey) + if err != nil { + return nil, nil, err + } + parsed, err := x509.ParseCertificate(der) + if err != nil { + return nil, nil, err + } + return parsed, leafKey, nil +} diff --git a/signing/loader.go b/signing/loader.go new file mode 100644 index 00000000..aa8b3abb --- /dev/null +++ b/signing/loader.go @@ -0,0 +1,134 @@ +package signing + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "os" +) + +// ErrLoadCAFile wraps failures to read or parse the operator-supplied +// trust anchor PEM file. +var ErrLoadCAFile = errors.New("signing: load CA file") + +// ErrParsePrivateKey wraps failures to decode a PEM-encoded private +// key. Multiple PKCS encodings are tried in turn (PKCS#8, EC, PKCS#1). +var ErrParsePrivateKey = errors.New("signing: parse private key") + +// VerifierFromFile constructs a LocalVerifier whose trust anchor pool +// is populated from a PEM file at caPath. The file MUST contain one or +// more PEM-encoded X.509 certificates; any non-CERTIFICATE PEM blocks +// (for example RSA PRIVATE KEY blocks accidentally left in the file) +// are ignored. +// +// Typical use: the opamp-go client supervisor or extension calls this +// at startup with the operator-supplied payload_ca path. +func VerifierFromFile(caPath string) (*LocalVerifier, error) { + if caPath == "" { + return nil, fmt.Errorf("%w: empty path", ErrLoadCAFile) + } + data, err := os.ReadFile(caPath) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrLoadCAFile, err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(data) { + return nil, fmt.Errorf("%w: no valid PEM certificates in %s", ErrLoadCAFile, caPath) + } + return NewLocalVerifier(pool) +} + +// LocalSignerFromFiles constructs a LocalSigner from PEM-encoded files: +// +// - keyPath: path to a PEM file containing the leaf signing private +// key. PKCS#8, EC, and PKCS#1 encodings are accepted. +// - chainPath: path to a PEM file containing the certificate chain. +// The chain MUST be ordered intermediates first, leaf last, and the +// leaf cert MUST correspond to the private key. The root MUST NOT +// be included. +// +// Intended for example servers, smoke tests, and any deployment that +// stores signing material as PEM files on disk. +func LocalSignerFromFiles(keyPath, chainPath string) (*LocalSigner, error) { + if keyPath == "" { + return nil, errors.New("signing: empty key path") + } + if chainPath == "" { + return nil, errors.New("signing: empty chain path") + } + + keyBytes, err := os.ReadFile(keyPath) + if err != nil { + return nil, fmt.Errorf("signing: read key: %w", err) + } + chainBytes, err := os.ReadFile(chainPath) + if err != nil { + return nil, fmt.Errorf("signing: read chain: %w", err) + } + + key, err := parsePrivateKeyPEM(keyBytes) + if err != nil { + return nil, err + } + + chain, err := parseCertChainPEM(chainBytes) + if err != nil { + return nil, err + } + if len(chain) == 0 { + return nil, ErrEmptyChain + } + + return NewLocalSigner(key, chain) +} + +func parsePrivateKeyPEM(data []byte) (crypto.Signer, error) { + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("%w: no PEM block found", ErrParsePrivateKey) + } + // Try PKCS#8 first (covers RSA, ECDSA, Ed25519). + if k, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { + switch t := k.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: + return t.(crypto.Signer), nil + default: + return nil, fmt.Errorf("%w: PKCS#8 key type %T does not implement crypto.Signer", ErrParsePrivateKey, t) + } + } + // Then EC-specific (PKCS#1-like) for ECDSA keys. + if k, err := x509.ParseECPrivateKey(block.Bytes); err == nil { + return k, nil + } + // Then PKCS#1 for RSA. + if k, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + return k, nil + } + return nil, fmt.Errorf("%w: tried PKCS#8, EC, PKCS#1 — none matched", ErrParsePrivateKey) +} + +func parseCertChainPEM(data []byte) ([]*x509.Certificate, error) { + var chain []*x509.Certificate + rest := data + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if block.Type != "CERTIFICATE" { + continue + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("signing: parse certificate in chain: %w", err) + } + chain = append(chain, cert) + } + return chain, nil +} diff --git a/signing/loader_test.go b/signing/loader_test.go new file mode 100644 index 00000000..443b48f9 --- /dev/null +++ b/signing/loader_test.go @@ -0,0 +1,180 @@ +package signing + +import ( + "context" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TestVerifierFromFile_RoundTrip writes a CA to disk, loads it via +// VerifierFromFile, and confirms a chain signed by that CA validates. +func TestVerifierFromFile_RoundTrip(t *testing.T) { + dir := t.TempDir() + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + caPath := filepath.Join(dir, "ca.pem") + writePEM(t, caPath, "CERTIFICATE", ca.Raw) + + v, err := VerifierFromFile(caPath) + require.NoError(t, err) + require.NotNil(t, v) + + got, err := v.ValidateChain(context.Background(), [][]byte{leaf.Raw}, time.Now()) + require.NoError(t, err) + require.Equal(t, leaf.SerialNumber, got.SerialNumber) +} + +// TestVerifierFromFile_EmptyPath rejects an empty path. +func TestVerifierFromFile_EmptyPath(t *testing.T) { + _, err := VerifierFromFile("") + require.ErrorIs(t, err, ErrLoadCAFile) +} + +// TestVerifierFromFile_MissingFile rejects a path that doesn't exist. +func TestVerifierFromFile_MissingFile(t *testing.T) { + _, err := VerifierFromFile(filepath.Join(t.TempDir(), "nonexistent.pem")) + require.ErrorIs(t, err, ErrLoadCAFile) +} + +// TestVerifierFromFile_NoCerts rejects a file with no CERTIFICATE PEM +// blocks. +func TestVerifierFromFile_NoCerts(t *testing.T) { + dir := t.TempDir() + caPath := filepath.Join(dir, "empty.pem") + require.NoError(t, os.WriteFile(caPath, []byte("not a PEM"), 0o600)) + _, err := VerifierFromFile(caPath) + require.ErrorIs(t, err, ErrLoadCAFile) +} + +// TestLocalSignerFromFiles_RoundTrip exercises the file-based loader +// for the LocalSigner across each algorithm. We write the leaf's +// private key (PKCS#8) and the chain (just the leaf in this case) to +// disk and then load. +func TestLocalSignerFromFiles_RoundTrip(t *testing.T) { + for _, alg := range allAlgorithms { + t.Run(alg.String(), func(t *testing.T) { + dir := t.TempDir() + ca, caKey, err := GenerateCA(alg, CertOptions{}) + require.NoError(t, err) + leaf, leafKey, err := GenerateLeaf(alg, ca, caKey, CertOptions{}) + require.NoError(t, err) + + // Write key in PKCS#8 encoding (covers all four algorithms). + keyDER, err := x509.MarshalPKCS8PrivateKey(leafKey) + require.NoError(t, err) + keyPath := filepath.Join(dir, "leaf.key.pem") + writePEM(t, keyPath, "PRIVATE KEY", keyDER) + + chainPath := filepath.Join(dir, "chain.pem") + writePEM(t, chainPath, "CERTIFICATE", leaf.Raw) + + signer, err := LocalSignerFromFiles(keyPath, chainPath) + require.NoError(t, err) + require.Equal(t, alg, signer.Algorithm()) + + // Round-trip sign + verify to confirm the loaded key + // matches the loaded chain. + roots := x509.NewCertPool() + roots.AddCert(ca) + verifier, err := NewLocalVerifier(roots) + require.NoError(t, err) + + payload := []byte("loader round-trip payload") + sig, err := signer.Sign(context.Background(), payload) + require.NoError(t, err) + validatedLeaf, err := verifier.ValidateChain(context.Background(), [][]byte{leaf.Raw}, time.Now()) + require.NoError(t, err) + require.NoError(t, verifier.Verify(context.Background(), payload, sig, validatedLeaf)) + + // Sanity-check that what came back from PEM is the same + // type of key the algorithm expects. + switch alg { + case AlgorithmECDSAP256SHA256, AlgorithmECDSAP384SHA384: + _, ok := leafKey.(*ecdsa.PrivateKey) + require.True(t, ok) + case AlgorithmRSAPKCS1v15SHA256: + _, ok := leafKey.(*rsa.PrivateKey) + require.True(t, ok) + case AlgorithmEd25519: + _, ok := leafKey.(ed25519.PrivateKey) + require.True(t, ok) + } + }) + } +} + +// TestLocalSignerFromFiles_EmptyPaths rejects empty paths. +func TestLocalSignerFromFiles_EmptyPaths(t *testing.T) { + _, err := LocalSignerFromFiles("", "chain.pem") + require.Error(t, err) + _, err = LocalSignerFromFiles("key.pem", "") + require.Error(t, err) +} + +// TestLocalSignerFromFiles_BadKey rejects a key file with no parseable +// PEM key block. +func TestLocalSignerFromFiles_BadKey(t *testing.T) { + dir := t.TempDir() + keyPath := filepath.Join(dir, "key.pem") + require.NoError(t, os.WriteFile(keyPath, []byte("not a key"), 0o600)) + chainPath := filepath.Join(dir, "chain.pem") + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + writePEM(t, chainPath, "CERTIFICATE", leaf.Raw) + + _, err = LocalSignerFromFiles(keyPath, chainPath) + require.ErrorIs(t, err, ErrParsePrivateKey) +} + +// TestLocalSignerFromFiles_EmptyChainFile rejects a chain file with no +// CERTIFICATE blocks. +func TestLocalSignerFromFiles_EmptyChainFile(t *testing.T) { + dir := t.TempDir() + chainPath := filepath.Join(dir, "chain.pem") + require.NoError(t, os.WriteFile(chainPath, []byte("not a chain"), 0o600)) + + // Need a valid key file to get past the key-load step. + key, _ := generateValidKey(t) + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + require.NoError(t, err) + keyPath := filepath.Join(dir, "key.pem") + writePEM(t, keyPath, "PRIVATE KEY", keyDER) + + _, err = LocalSignerFromFiles(keyPath, chainPath) + require.ErrorIs(t, err, ErrEmptyChain) +} + +// writePEM is a test helper that writes a PEM-encoded block of the +// given type to path. Permissions are 0600 since these tests handle +// private keys. +func writePEM(t *testing.T, path, blockType string, bytes []byte) { + t.Helper() + block := &pem.Block{Type: blockType, Bytes: bytes} + encoded := pem.EncodeToMemory(block) + require.NoError(t, os.WriteFile(path, encoded, 0o600)) +} + +// generateValidKey returns a freshly-generated ECDSA-P256 key, used by +// tests that only care about getting past the "valid key" check. +func generateValidKey(t *testing.T) (*ecdsa.PrivateKey, error) { + t.Helper() + k, _, _, err := newKey(AlgorithmECDSAP256SHA256) + if err != nil { + return nil, err + } + return k.(*ecdsa.PrivateKey), nil +} diff --git a/signing/local_signer.go b/signing/local_signer.go new file mode 100644 index 00000000..b532f26d --- /dev/null +++ b/signing/local_signer.go @@ -0,0 +1,104 @@ +package signing + +import ( + "context" + "crypto" + "crypto/x509" + "errors" +) + +// ErrNilKey is returned by NewLocalSigner when key is nil. +var ErrNilKey = errors.New("signing: nil private key") + +// LocalSigner is the in-process reference implementation of [Signer]. +// It holds a private key and certificate chain in memory and signs +// requests synchronously without any network IO. +// +// LocalSigner is suitable for tests, the opamp-go example server, and +// any deployment where the signing private key is colocated with the +// OpAMP server process. Deployments that delegate signing to a hosted +// platform (HSM-backed RPC, central signing service) should provide +// their own Signer implementation; the wire-level opamp-go code is +// agnostic to which Signer is in use. +// +// LocalSigner is safe for concurrent use: the underlying crypto.Signer +// implementations in the Go standard library are themselves +// concurrency-safe. +type LocalSigner struct { + key crypto.Signer + alg Algorithm + chainDER [][]byte +} + +// NewLocalSigner constructs a LocalSigner from the supplied private +// key (typically a crypto.Signer implementation from the standard +// library) and certificate chain. +// +// The chain MUST be ordered intermediates first, leaf last; the leaf +// is the certificate whose private key signs payloads. The root MUST +// NOT be included — the Agent supplies the root via its pre-configured +// trust anchor pool. +// +// The algorithm is determined by the leaf certificate's +// SignatureAlgorithm; ErrUnsupportedAlgorithm is returned for any +// value outside the supported baseline. +func NewLocalSigner(key crypto.Signer, chain []*x509.Certificate) (*LocalSigner, error) { + if key == nil { + return nil, ErrNilKey + } + if len(chain) == 0 { + return nil, ErrEmptyChain + } + leaf := chain[len(chain)-1] + alg, err := algorithmFromCert(leaf) + if err != nil { + return nil, err + } + + chainDER := make([][]byte, len(chain)) + for i, cert := range chain { + // cert.Raw is the DER bytes the certificate was parsed from + // (or that x509.CreateCertificate produced). Copy to defend + // against later mutation of cert.Raw by callers, even though + // it's expected to be immutable in practice. + raw := make([]byte, len(cert.Raw)) + copy(raw, cert.Raw) + chainDER[i] = raw + } + + return &LocalSigner{ + key: key, + alg: alg, + chainDER: chainDER, + }, nil +} + +// Sign implements [Signer]. The context is honoured only for +// cancellation; the in-process signing operation itself does not block. +func (s *LocalSigner) Sign(ctx context.Context, payload []byte) ([]byte, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + return signWithKey(s.key, s.alg, payload) +} + +// ChainDER implements [Signer]. Returns a defensive copy so callers +// cannot mutate the signer's internal state. +func (s *LocalSigner) ChainDER(ctx context.Context) ([][]byte, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + out := make([][]byte, len(s.chainDER)) + for i, der := range s.chainDER { + clone := make([]byte, len(der)) + copy(clone, der) + out[i] = clone + } + return out, nil +} + +// Algorithm reports the algorithm dispatched by this signer (derived +// from the leaf certificate). Exposed for diagnostics and tests. +func (s *LocalSigner) Algorithm() Algorithm { + return s.alg +} diff --git a/signing/local_signer_test.go b/signing/local_signer_test.go new file mode 100644 index 00000000..25399970 --- /dev/null +++ b/signing/local_signer_test.go @@ -0,0 +1,68 @@ +package signing + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestNewLocalSigner_NilKey rejects a nil private key. +func TestNewLocalSigner_NilKey(t *testing.T) { + ca, _, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + _, err = NewLocalSigner(nil, []*x509.Certificate{ca}) + require.ErrorIs(t, err, ErrNilKey) +} + +// TestNewLocalSigner_EmptyChain rejects an empty chain. +func TestNewLocalSigner_EmptyChain(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + _, err = NewLocalSigner(key, nil) + require.ErrorIs(t, err, ErrEmptyChain) +} + +// TestNewLocalSigner_UnsupportedLeafAlgorithm rejects a leaf whose +// SignatureAlgorithm is outside the supported set. +func TestNewLocalSigner_UnsupportedLeafAlgorithm(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + bogusLeaf := &x509.Certificate{SignatureAlgorithm: x509.MD5WithRSA, Raw: []byte{0x01}} + _, err = NewLocalSigner(key, []*x509.Certificate{bogusLeaf}) + require.ErrorIs(t, err, ErrUnsupportedAlgorithm) +} + +// TestLocalSigner_ChainDERDefensiveCopy confirms the signer returns a +// fresh copy that callers can mutate without disturbing the signer's +// internal state. +func TestLocalSigner_ChainDERDefensiveCopy(t *testing.T) { + _, signer, _ := testKeyPair(t, AlgorithmECDSAP256SHA256) + + c1, err := signer.ChainDER(context.Background()) + require.NoError(t, err) + require.Len(t, c1, 1) + + // Mutate the returned slice. + c1[0][0] ^= 0xff + + // A second call must still return the original bytes. + c2, err := signer.ChainDER(context.Background()) + require.NoError(t, err) + require.Len(t, c2, 1) + require.NotEqual(t, c1[0][0], c2[0][0], "second ChainDER call must not see caller mutations to first") +} + +// TestLocalSigner_AlgorithmAccessor confirms the diagnostic accessor. +func TestLocalSigner_AlgorithmAccessor(t *testing.T) { + for _, alg := range allAlgorithms { + t.Run(alg.String(), func(t *testing.T) { + _, signer, _ := testKeyPair(t, alg) + require.Equal(t, alg, signer.Algorithm()) + }) + } +} diff --git a/signing/local_verifier.go b/signing/local_verifier.go new file mode 100644 index 00000000..cfad4096 --- /dev/null +++ b/signing/local_verifier.go @@ -0,0 +1,64 @@ +package signing + +import ( + "context" + "crypto/x509" + "errors" + "time" +) + +// ErrNilRoots is returned by NewLocalVerifier when roots is nil. +var ErrNilRoots = errors.New("signing: nil trust anchor pool") + +// LocalVerifier is the in-process reference implementation of +// [Verifier]. It wraps a trust anchor pool and uses [ValidateChain] +// for path validation plus the algorithm-dispatch table in +// algorithm.go for signature verification. +// +// LocalVerifier is safe for concurrent use. +type LocalVerifier struct { + roots *x509.CertPool +} + +// NewLocalVerifier constructs a LocalVerifier that will validate +// delivered certificate chains against the supplied trust anchor pool. +// +// The trust anchor pool MUST be operator-managed and supplied +// out-of-band (typically a PEM file path read at startup); it MUST NOT +// be installed or modified by any OpAMP message. +func NewLocalVerifier(roots *x509.CertPool) (*LocalVerifier, error) { + if roots == nil { + return nil, ErrNilRoots + } + return &LocalVerifier{roots: roots}, nil +} + +// ValidateChain implements [Verifier], delegating to the package-level +// [ValidateChain] function with the verifier's trust anchor pool. +func (v *LocalVerifier) ValidateChain(ctx context.Context, chainDER [][]byte, now time.Time) (*x509.Certificate, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + return ValidateChain(ctx, chainDER, v.roots, now) +} + +// Verify implements [Verifier]. The algorithm is derived from +// leaf.SignatureAlgorithm; ErrUnsupportedAlgorithm is returned for any +// algorithm outside the supported baseline. ErrSignatureMismatch is +// returned when the signature does not verify. +func (v *LocalVerifier) Verify(ctx context.Context, payload, signature []byte, leaf *x509.Certificate) error { + if err := ctx.Err(); err != nil { + return err + } + if leaf == nil { + return errors.New("signing: nil leaf certificate") + } + if len(signature) == 0 { + return errors.New("signing: empty signature") + } + alg, err := algorithmFromCert(leaf) + if err != nil { + return err + } + return verifyWithPub(leaf.PublicKey, alg, payload, signature) +} diff --git a/signing/local_verifier_test.go b/signing/local_verifier_test.go new file mode 100644 index 00000000..4e276eb1 --- /dev/null +++ b/signing/local_verifier_test.go @@ -0,0 +1,42 @@ +package signing + +import ( + "context" + "crypto/x509" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestNewLocalVerifier_NilRoots rejects a nil trust anchor pool. +func TestNewLocalVerifier_NilRoots(t *testing.T) { + _, err := NewLocalVerifier(nil) + require.ErrorIs(t, err, ErrNilRoots) +} + +// TestLocalVerifier_VerifyNilLeaf rejects a nil leaf, since +// signature verification fundamentally needs a public key. +func TestLocalVerifier_VerifyNilLeaf(t *testing.T) { + roots := x509.NewCertPool() + v, err := NewLocalVerifier(roots) + require.NoError(t, err) + err = v.Verify(context.Background(), []byte("payload"), []byte("sig"), nil) + require.Error(t, err) +} + +// TestLocalVerifier_VerifyEmptySignature rejects empty signatures — +// the OpAMP spec mandates signature be present and non-empty on every +// message after the handshake. +func TestLocalVerifier_VerifyEmptySignature(t *testing.T) { + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(ca) + v, err := NewLocalVerifier(roots) + require.NoError(t, err) + err = v.Verify(context.Background(), []byte("payload"), nil, leaf) + require.Error(t, err) +} diff --git a/signing/types.go b/signing/types.go new file mode 100644 index 00000000..21210df0 --- /dev/null +++ b/signing/types.go @@ -0,0 +1,124 @@ +// Package signing implements payload trust verification (Message +// Attestation) for the OpAMP protocol. +// +// The package exposes two interfaces — [Signer] and [Verifier] — together +// with in-process reference implementations [LocalSigner] and +// [LocalVerifier]. The split between interface and implementation lets +// downstream consumers plug in alternative signers backed by remote +// signing services (for example HSM-backed RPC endpoints or hosted +// signing platforms with policy gating) without touching the wire-level +// code in the opamp-go client or server. +// +// Signing is performed over the raw bytes of a marshalled +// [protobufs.ServerToAgent] (the bytes carried in +// SignedServerToAgent.payload on the wire), producing a detached +// signature placed in SignedServerToAgent.signature. The receiver +// verifies the signature over the bytes exactly as they arrive on the +// wire — no re-marshalling is required, sidestepping protobuf's +// non-canonical-encoding caveat. See the Message Attestation section +// of the OpAMP specification for the wire protocol. +// +// The signing algorithm for a given connection is determined by the +// signing certificate's SignatureAlgorithm field; the OpAMP protocol +// does not negotiate algorithms. +package signing + +import ( + "context" + "crypto/x509" + "time" +) + +// Algorithm identifies the signature algorithm used by a signing +// certificate. The OpAMP protocol does not negotiate algorithms; the +// algorithm in use is determined by the certificate's SignatureAlgorithm +// field. This enum exists so that test helpers, cert generators, and +// internal dispatch tables can refer to a specific algorithm by name. +type Algorithm uint8 + +const ( + // AlgorithmUnspecified is the zero value and is never a valid + // algorithm in production. + AlgorithmUnspecified Algorithm = iota + // AlgorithmECDSAP256SHA256 — ECDSA over the P-256 curve with + // SHA-256, DER-encoded (r,s) signatures. + AlgorithmECDSAP256SHA256 + // AlgorithmECDSAP384SHA384 — ECDSA over the P-384 curve with + // SHA-384, DER-encoded (r,s) signatures. + AlgorithmECDSAP384SHA384 + // AlgorithmRSAPKCS1v15SHA256 — RSA with PKCS#1 v1.5 padding and + // SHA-256. Minimum 2048-bit modulus recommended. + AlgorithmRSAPKCS1v15SHA256 + // AlgorithmEd25519 — Ed25519 (signs the payload directly; no + // pre-hash). + AlgorithmEd25519 +) + +// String returns the canonical name of the algorithm. +func (a Algorithm) String() string { + switch a { + case AlgorithmECDSAP256SHA256: + return "ECDSA-P256-SHA256" + case AlgorithmECDSAP384SHA384: + return "ECDSA-P384-SHA384" + case AlgorithmRSAPKCS1v15SHA256: + return "RSA-PKCS1v15-SHA256" + case AlgorithmEd25519: + return "Ed25519" + default: + return "unspecified" + } +} + +// Signer produces detached signatures over arbitrary payload bytes and +// supplies the signing certificate chain. +// +// Implementations may sign locally with an in-process key (see +// [LocalSigner]) or delegate to an external signing service (HSM, +// remote signing RPC, hosted platforms with policy gating). Sign and +// ChainDER both accept a context so RPC-backed implementations can +// cancel, set deadlines, and propagate trace IDs. +type Signer interface { + // Sign computes a signature over payload. The OpAMP server places + // the returned bytes into SignedServerToAgent.signature on the + // wire. The signing algorithm is determined by the signing + // certificate; the caller does not pass it explicitly. + Sign(ctx context.Context, payload []byte) ([]byte, error) + + // ChainDER returns the signing certificate chain in DER form, + // ordered from the first intermediate down to the signing leaf. + // The root certificate (which the Agent already possesses as its + // pre-configured payload trust anchor) is excluded. + // + // The OpAMP server snapshots this once per new client connection + // and reuses the result for the connection's lifetime so that + // mid-session rotation on the signer side does not change the + // chain mid-stream. + ChainDER(ctx context.Context) ([][]byte, error) +} + +// Verifier validates a delivered trust chain and verifies detached +// signatures against the resulting leaf certificate. +// +// Implementations are expected to perform RFC 5280 §6 X.509 path +// validation in ValidateChain. The Verify method performs the +// signature-only check against the leaf returned by a successful +// ValidateChain call. +type Verifier interface { + // ValidateChain performs RFC 5280 §6 path validation of the + // supplied DER certificate chain against the verifier's + // pre-configured trust anchor pool. The chain MUST be ordered + // intermediates first, leaf last; the root is supplied via the + // verifier's configuration and MUST NOT appear in chainDER. + // + // Returns the validated leaf certificate on success. The Agent + // stores the leaf for the duration of the connection and passes + // it to Verify on every subsequent message. + ValidateChain(ctx context.Context, chainDER [][]byte, now time.Time) (*x509.Certificate, error) + + // Verify validates signature over payload using the public key of + // leaf and the algorithm declared by leaf.SignatureAlgorithm. The + // payload bytes are the wire bytes of SignedServerToAgent.payload + // — the receiver does not re-marshal anything. + Verify(ctx context.Context, payload, signature []byte, leaf *x509.Certificate) error +} From 94c621e6dd6eb4a920c7fba19ccaf5b031378a61 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 11:49:53 +0200 Subject: [PATCH 04/22] Address code-review fixes in signing/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four issues surfaced by review of the signing/ package: 1. algorithmFromCert dispatched from cert.SignatureAlgorithm (the *issuer's* signing algorithm) rather than from the leaf's actual public key type and curve. Within-family mismatches (e.g. a P-384 CA issuing a P-256 leaf with SignatureAlgorithm=ECDSAWithSHA384) could slip past type assertions because both keys are *ecdsa.PrivateKey/PublicKey. Fix: dispatch on leaf.PublicKey type and ECDSA curve; cross-check that cert.SignatureAlgorithm is consistent with the pubkey. 2. No minimum RSA modulus enforced. The docstring claimed "2048-bit minimum recommended" but nothing checked it. Add a constant rsaMinModulusBits = 2048 and reject smaller keys in algorithmFromCert. 3. parsePrivateKeyPEM had a dead default branch: if PKCS#8 parsed but produced a non-Signer type, it returned an error instead of falling through to PKCS#1/EC. Simplified to a crypto.Signer interface assertion (all stdlib private-key types satisfy it). Drop the now-unused crypto/ecdsa, crypto/ed25519, crypto/rsa imports. 4. New adversarial test file (signing/adversarial_test.go) covers the high-risk paths the review flagged: - TestVerify_LeafPublicKeyMismatch: sig from key A vs leaf with pubkey B → ErrSignatureMismatch - TestVerify_AlgorithmFamilyMismatch: ECDSA sig vs RSA leaf - TestWrongChainOrder_DetectedAtSignatureVerify: documents that X.509 path validation accepts a chain with [leaf, intermediate] ordering (Go's x509 is permissive about EKU absence on the intermediate), but the per-message signature step catches it. This is the load-bearing protection. - TestValidateChain_IntermediateNotSignedByRoot: attacker-rooted chain rejected - TestNewLocalSigner_LeafPublicKeyMismatchDetected: documents the deliberate non-validation at construction time, asserts the observable symptom (Sign + Verify round-trip fails) and provides a regression hook if we choose to validate at construction later - TestParseCertChainPEM_IgnoresNonCertificateBlocks: covers the "skip non-CERTIFICATE block" branch in parseCertChainPEM - TestAlgorithmFromCert_RSAKeyTooSmall: 1024-bit RSA rejected - TestAlgorithmFromCert_AlgorithmDeclarationMismatch: P-256 key with declared SHA-384 signature algorithm rejected go test ./signing/... -race -count=1 passes. go vet + gofumpt clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- signing/adversarial_test.go | 320 ++++++++++++++++++++++++++++++++++++ signing/algorithm.go | 83 ++++++++-- signing/loader.go | 27 ++- 3 files changed, 402 insertions(+), 28 deletions(-) create mode 100644 signing/adversarial_test.go diff --git a/signing/adversarial_test.go b/signing/adversarial_test.go new file mode 100644 index 00000000..441c3510 --- /dev/null +++ b/signing/adversarial_test.go @@ -0,0 +1,320 @@ +package signing + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "math/big" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TestVerify_LeafPublicKeyMismatch is the load-bearing security test: +// a signature produced by private key A must not verify when the leaf +// certificate carries the public key of a different key B. This is +// the spoofing scenario the verifier is designed to defeat — bugs in +// algorithm dispatch (e.g., reading cert.SignatureAlgorithm instead of +// dispatching on the leaf's actual pubkey) would let it succeed. +func TestVerify_LeafPublicKeyMismatch(t *testing.T) { + ctx := context.Background() + payload := []byte("payload") + + // Build two independent CA + leaf pairs with the same algorithm. + caA, caAKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{CommonName: "CA-A"}) + require.NoError(t, err) + leafA, leafAKey, err := GenerateLeaf(AlgorithmECDSAP256SHA256, caA, caAKey, CertOptions{CommonName: "leaf-A"}) + require.NoError(t, err) + + caB, caBKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{CommonName: "CA-B"}) + require.NoError(t, err) + leafB, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, caB, caBKey, CertOptions{CommonName: "leaf-B"}) + require.NoError(t, err) + + signerA, err := NewLocalSigner(leafAKey, []*x509.Certificate{leafA}) + require.NoError(t, err) + sig, err := signerA.Sign(ctx, payload) + require.NoError(t, err) + + // Verifier with B's CA pool, asked to verify A's signature against + // leaf B's public key. Must reject. + rootsB := x509.NewCertPool() + rootsB.AddCert(caB) + verifierB, err := NewLocalVerifier(rootsB) + require.NoError(t, err) + + err = verifierB.Verify(ctx, payload, sig, leafB) + require.Error(t, err) + require.ErrorIs(t, err, ErrSignatureMismatch) +} + +// TestVerify_AlgorithmFamilyMismatch confirms the verifier rejects an +// ECDSA signature presented against an RSA leaf and vice versa — even +// though both algorithms are in the supported set, mixing them must +// fail. +func TestVerify_AlgorithmFamilyMismatch(t *testing.T) { + ctx := context.Background() + payload := []byte("payload") + + // ECDSA signer. + caEc, caEcKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leafEc, leafEcKey, err := GenerateLeaf(AlgorithmECDSAP256SHA256, caEc, caEcKey, CertOptions{}) + require.NoError(t, err) + ecSigner, err := NewLocalSigner(leafEcKey, []*x509.Certificate{leafEc}) + require.NoError(t, err) + ecSig, err := ecSigner.Sign(ctx, payload) + require.NoError(t, err) + + // RSA leaf to present to the verifier. + caRsa, caRsaKey, err := GenerateCA(AlgorithmRSAPKCS1v15SHA256, CertOptions{}) + require.NoError(t, err) + leafRsa, _, err := GenerateLeaf(AlgorithmRSAPKCS1v15SHA256, caRsa, caRsaKey, CertOptions{}) + require.NoError(t, err) + + rootsRsa := x509.NewCertPool() + rootsRsa.AddCert(caRsa) + verifier, err := NewLocalVerifier(rootsRsa) + require.NoError(t, err) + + // ECDSA signature, RSA leaf → must reject. Verifier dispatches on + // the RSA leaf's pubkey type, so we end up in the RSA branch trying + // to verify ECDSA-DER bytes as a PKCS#1 v1.5 signature. + err = verifier.Verify(ctx, payload, ecSig, leafRsa) + require.Error(t, err) + // Either ErrSignatureMismatch (verify failed) or + // ErrUnsupportedAlgorithm — anything but success. + require.True(t, + errors.Is(err, ErrSignatureMismatch) || errors.Is(err, ErrUnsupportedAlgorithm), + "expected mismatch or unsupported, got %v", err) +} + +// TestWrongChainOrder_DetectedAtSignatureVerify documents how the +// package handles a chain delivered in the wrong order +// ([leaf, intermediate] instead of the spec-mandated +// [intermediate, leaf]). ValidateChain takes the last entry as the +// leaf, so a wrong-order chain returns the intermediate's certificate +// as "leaf". Standard X.509 verification accepts that because the +// intermediate is also a CA-capable cert signed by the root, and +// Go's x509 library does not require an explicit EKU when none is +// declared on the cert. +// +// The actual defence against wrong-ordered chains is the per-message +// signature step: the server signs with the *real* leaf's private +// key, but the wrong-order chain delivers the intermediate's public +// key to the verifier. Subsequent signature verifications therefore +// fail. This test exercises exactly that path. +// +// The lesson for downstream signer implementations (LocalSigner and +// any RPC-backed equivalent): the order of bytes in ChainDER is +// load-bearing for signature verification, even when X.509 path +// validation succeeds in spite of it. +func TestWrongChainOrder_DetectedAtSignatureVerify(t *testing.T) { + ctx := context.Background() + rootCA, rootKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{CommonName: "Root"}) + require.NoError(t, err) + intermediate, intermediateKey, err := generateIntermediate(t, AlgorithmECDSAP256SHA256, rootCA, rootKey) + require.NoError(t, err) + leaf, leafKey, err := GenerateLeaf(AlgorithmECDSAP256SHA256, intermediate, intermediateKey, CertOptions{}) + require.NoError(t, err) + + // The signer holds the REAL leaf's private key and produces a + // signature with it. The wrong-order chain delivery is purely a + // receive-side framing issue. + signer, err := NewLocalSigner(leafKey, []*x509.Certificate{intermediate, leaf}) + require.NoError(t, err) + payload := []byte("payload") + sig, err := signer.Sign(ctx, payload) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(rootCA) + verifier, err := NewLocalVerifier(roots) + require.NoError(t, err) + + // Correct order: validate succeeds, verify succeeds. + correctLeaf, err := verifier.ValidateChain(ctx, [][]byte{intermediate.Raw, leaf.Raw}, time.Now()) + require.NoError(t, err) + require.NoError(t, verifier.Verify(ctx, payload, sig, correctLeaf)) + + // Wrong order: ValidateChain returns the *intermediate* as the + // "leaf" (Go's x509 doesn't reject it for EKU absence), but the + // signature was produced by the real leaf's private key, so verify + // against the intermediate's pubkey fails. + wrongLeaf, err := verifier.ValidateChain(ctx, [][]byte{leaf.Raw, intermediate.Raw}, time.Now()) + require.NoError(t, err, "chain validation is structurally permissive about leaf identity; the spec-level protection lives at signature verify time") + require.NotEqual(t, leaf.SerialNumber, wrongLeaf.SerialNumber, "validate returned the intermediate as leaf, not the real leaf") + + err = verifier.Verify(ctx, payload, sig, wrongLeaf) + require.Error(t, err) + require.ErrorIs(t, err, ErrSignatureMismatch, "wrong-ordered chain produces a signature-verification failure") +} + +// TestValidateChain_IntermediateNotSignedByRoot confirms a chain where +// the intermediate's issuer does not match any anchor in the trust +// pool is rejected. +func TestValidateChain_IntermediateNotSignedByRoot(t *testing.T) { + // Agent's trusted root. + trustedRoot, _, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{CommonName: "Trusted Root"}) + require.NoError(t, err) + + // Attacker's parallel root, intermediate, and leaf — entirely + // outside the agent's trust pool. + attackerRoot, attackerRootKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{CommonName: "Attacker Root"}) + require.NoError(t, err) + attackerIntermediate, attackerIntermediateKey, err := generateIntermediate(t, AlgorithmECDSAP256SHA256, attackerRoot, attackerRootKey) + require.NoError(t, err) + attackerLeaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, attackerIntermediate, attackerIntermediateKey, CertOptions{}) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(trustedRoot) + + // Attacker presents chain rooted in their own CA. + _, err = ValidateChain(context.Background(), [][]byte{attackerIntermediate.Raw, attackerLeaf.Raw}, roots, time.Now()) + require.Error(t, err) + require.ErrorIs(t, err, ErrChainValidation) +} + +// TestNewLocalSigner_LeafPublicKeyMismatchDetected confirms that +// constructing a LocalSigner where the supplied private key does not +// correspond to the leaf cert's public key produces signatures that +// fail to verify — protecting operators from an accidental +// configuration where keys and certs are mismatched (the kind of +// thing that would only surface at first-message time and look like +// a Verifier bug). +func TestNewLocalSigner_LeafPublicKeyMismatchDetected(t *testing.T) { + // NOTE: LocalSigner doesn't validate key↔leaf consistency at + // construction (a deliberate trade-off — see signing/local_signer.go). + // This test instead asserts the observable symptom: a Sign + Verify + // round-trip fails when the key doesn't match the leaf's pubkey. + ctx := context.Background() + + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + // Wrong key — fresh ECDSA-P256 not bound to leaf. + wrongKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + signer, err := NewLocalSigner(wrongKey, []*x509.Certificate{leaf}) + require.NoError(t, err) // construction is permissive + + sig, err := signer.Sign(ctx, []byte("payload")) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AddCert(ca) + verifier, err := NewLocalVerifier(roots) + require.NoError(t, err) + validatedLeaf, err := verifier.ValidateChain(ctx, [][]byte{leaf.Raw}, time.Now()) + require.NoError(t, err) + err = verifier.Verify(ctx, []byte("payload"), sig, validatedLeaf) + require.Error(t, err) + require.ErrorIs(t, err, ErrSignatureMismatch) +} + +// TestParseCertChainPEM_IgnoresNonCertificateBlocks confirms that a +// chain file with stray PEM blocks (e.g. a private key accidentally +// left in the chain bundle) is parsed correctly — non-CERTIFICATE +// blocks are skipped, and the certificates that ARE present load. +func TestParseCertChainPEM_IgnoresNonCertificateBlocks(t *testing.T) { + ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + // Build a "chain" file with an arbitrary non-CERTIFICATE block + // between the (only) leaf certificate and the end. + leafPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leaf.Raw}) + junkPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: []byte{0x00, 0x01, 0x02}}) + combined := append(append([]byte{}, leafPEM...), junkPEM...) + + dir := t.TempDir() + chainPath := filepath.Join(dir, "chain.pem") + require.NoError(t, os.WriteFile(chainPath, combined, 0o600)) + + keyDER, err := x509.MarshalPKCS8PrivateKey(caKey) + require.NoError(t, err) + _ = keyDER + + chain, err := parseCertChainPEM(combined) + require.NoError(t, err) + require.Len(t, chain, 1, "non-CERTIFICATE block should be skipped, only leaf remains") + require.Equal(t, leaf.SerialNumber, chain[0].SerialNumber) +} + +// TestAlgorithmFromCert_RSAKeyTooSmall confirms the minimum RSA +// modulus enforcement: a 1024-bit RSA key is rejected even if the +// declared SignatureAlgorithm matches. +func TestAlgorithmFromCert_RSAKeyTooSmall(t *testing.T) { + // Generate a 1024-bit RSA leaf directly (bypassing GenerateLeaf + // which would refuse — or rather, succeed since GenerateLeaf + // uses 2048; we forge a 1024-bit one here). + ca, caKey, err := GenerateCA(AlgorithmRSAPKCS1v15SHA256, CertOptions{}) + require.NoError(t, err) + + smallKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + + serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: "small-rsa-leaf"}, + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + SignatureAlgorithm: x509.SHA256WithRSA, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, ca, &smallKey.PublicKey, caKey) + require.NoError(t, err) + smallCert, err := x509.ParseCertificate(der) + require.NoError(t, err) + + _, err = algorithmFromCert(smallCert) + require.Error(t, err) + require.ErrorIs(t, err, ErrUnsupportedAlgorithm) +} + +// TestAlgorithmFromCert_AlgorithmDeclarationMismatch confirms that a +// cert whose declared SignatureAlgorithm doesn't match its actual +// public-key type is rejected. +func TestAlgorithmFromCert_AlgorithmDeclarationMismatch(t *testing.T) { + // Forge a cert with an ECDSA P-256 pubkey but SignatureAlgorithm + // declared as ECDSAWithSHA384 (which is for P-384). + caRoot, caRootKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) + require.NoError(t, err) + wrongAlgKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: "mismatched-alg-leaf"}, + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + SignatureAlgorithm: x509.ECDSAWithSHA384, // intentionally wrong: P-256 key paired with SHA-384 + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, caRoot, &wrongAlgKey.PublicKey, caRootKey) + require.NoError(t, err) + cert, err := x509.ParseCertificate(der) + require.NoError(t, err) + + _, err = algorithmFromCert(cert) + require.Error(t, err) + require.ErrorIs(t, err, ErrUnsupportedAlgorithm) +} diff --git a/signing/algorithm.go b/signing/algorithm.go index 19493c8c..4b2b166a 100644 --- a/signing/algorithm.go +++ b/signing/algorithm.go @@ -4,6 +4,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" @@ -13,26 +14,80 @@ import ( "fmt" ) -// ErrUnsupportedAlgorithm indicates that a certificate's -// SignatureAlgorithm is not in the supported set, or that the -// public/private key type does not match the requested algorithm. +// rsaMinModulusBits is the minimum acceptable RSA modulus size. Keys +// below this size are rejected even if the rest of the chain validates. +const rsaMinModulusBits = 2048 + +// ErrUnsupportedAlgorithm indicates that a certificate's public key +// (or the algorithm declared by the issuer's signature on the cert) +// is not in the supported set: it is the wrong key type, an +// unsupported ECDSA curve, an RSA key below rsaMinModulusBits, or the +// declared SignatureAlgorithm does not match the leaf's actual key +// type/curve. var ErrUnsupportedAlgorithm = errors.New("signing: unsupported signature algorithm") -// algorithmFromCert maps x509.Certificate.SignatureAlgorithm to the -// package's Algorithm enum, returning ErrUnsupportedAlgorithm for any -// value outside the supported baseline. +// algorithmFromCert derives the Algorithm to use for signature +// operations involving cert. Dispatching on the leaf's own public key +// type (rather than on cert.SignatureAlgorithm, which describes the +// issuer's signing of the cert itself) is the correct authority: the +// Algorithm controls how a payload is signed/verified, and that has to +// match the leaf key's algorithm and curve, not the issuer's. +// +// The function additionally cross-checks cert.SignatureAlgorithm +// against the leaf key so that a certificate whose declared algorithm +// is inconsistent with its pubkey is rejected up front. This prevents +// a within-family mismatch (e.g., a P-384 CA issuing a P-256 leaf with +// SignatureAlgorithm=ECDSAWithSHA384) from silently accepting the +// wrong hash size at sign/verify time. +// +// Minimum RSA modulus is rsaMinModulusBits. func algorithmFromCert(cert *x509.Certificate) (Algorithm, error) { - switch cert.SignatureAlgorithm { - case x509.ECDSAWithSHA256: - return AlgorithmECDSAP256SHA256, nil - case x509.ECDSAWithSHA384: - return AlgorithmECDSAP384SHA384, nil - case x509.SHA256WithRSA: + switch pub := cert.PublicKey.(type) { + case *ecdsa.PublicKey: + switch pub.Curve { + case elliptic.P256(): + if cert.SignatureAlgorithm != x509.ECDSAWithSHA256 { + return AlgorithmUnspecified, fmt.Errorf("%w: P-256 leaf with mismatched declared algorithm %s", + ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) + } + return AlgorithmECDSAP256SHA256, nil + case elliptic.P384(): + if cert.SignatureAlgorithm != x509.ECDSAWithSHA384 { + return AlgorithmUnspecified, fmt.Errorf("%w: P-384 leaf with mismatched declared algorithm %s", + ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) + } + return AlgorithmECDSAP384SHA384, nil + default: + curveName := "unknown" + if pub.Curve != nil && pub.Curve.Params() != nil { + curveName = pub.Curve.Params().Name + } + return AlgorithmUnspecified, fmt.Errorf("%w: unsupported ECDSA curve %s", + ErrUnsupportedAlgorithm, curveName) + } + case *rsa.PublicKey: + if pub.N == nil || pub.N.BitLen() < rsaMinModulusBits { + bits := 0 + if pub.N != nil { + bits = pub.N.BitLen() + } + return AlgorithmUnspecified, fmt.Errorf("%w: RSA key %d bits < %d", + ErrUnsupportedAlgorithm, bits, rsaMinModulusBits) + } + if cert.SignatureAlgorithm != x509.SHA256WithRSA { + return AlgorithmUnspecified, fmt.Errorf("%w: RSA leaf with mismatched declared algorithm %s", + ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) + } return AlgorithmRSAPKCS1v15SHA256, nil - case x509.PureEd25519: + case ed25519.PublicKey: + if cert.SignatureAlgorithm != x509.PureEd25519 { + return AlgorithmUnspecified, fmt.Errorf("%w: Ed25519 leaf with mismatched declared algorithm %s", + ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) + } return AlgorithmEd25519, nil default: - return AlgorithmUnspecified, fmt.Errorf("%w: %s", ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) + return AlgorithmUnspecified, fmt.Errorf("%w: unsupported public key type %T", + ErrUnsupportedAlgorithm, pub) } } diff --git a/signing/loader.go b/signing/loader.go index aa8b3abb..08123d7f 100644 --- a/signing/loader.go +++ b/signing/loader.go @@ -2,9 +2,6 @@ package signing import ( "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" "crypto/x509" "encoding/pem" "errors" @@ -92,24 +89,26 @@ func parsePrivateKeyPEM(data []byte) (crypto.Signer, error) { if block == nil { return nil, fmt.Errorf("%w: no PEM block found", ErrParsePrivateKey) } - // Try PKCS#8 first (covers RSA, ECDSA, Ed25519). + // PKCS#8 first — covers RSA, ECDSA, and Ed25519 in one call. If it + // succeeds, we accept any key type that implements crypto.Signer + // (which all current and likely-future stdlib private-key types + // do). if k, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { - switch t := k.(type) { - case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: - return t.(crypto.Signer), nil - default: - return nil, fmt.Errorf("%w: PKCS#8 key type %T does not implement crypto.Signer", ErrParsePrivateKey, t) + s, ok := k.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("%w: PKCS#8 key type %T does not implement crypto.Signer", ErrParsePrivateKey, k) } + return s, nil } - // Then EC-specific (PKCS#1-like) for ECDSA keys. - if k, err := x509.ParseECPrivateKey(block.Bytes); err == nil { + // PKCS#1 for legacy RSA private keys. + if k, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { return k, nil } - // Then PKCS#1 for RSA. - if k, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + // EC for legacy ECDSA private keys. + if k, err := x509.ParseECPrivateKey(block.Bytes); err == nil { return k, nil } - return nil, fmt.Errorf("%w: tried PKCS#8, EC, PKCS#1 — none matched", ErrParsePrivateKey) + return nil, fmt.Errorf("%w: tried PKCS#8, PKCS#1, EC — none matched", ErrParsePrivateKey) } func parseCertChainPEM(data []byte) ([]*x509.Certificate, error) { From 37d7e2cf098bebcfe05a743307ea931d0adb9d50 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 12:05:19 +0200 Subject: [PATCH 05/22] Polish: docstrings + test assertions Three small follow-ups from the second code-review pass: 1. Stale docstrings in local_signer.go, local_verifier.go, and types.go still described the algorithm as "derived from leaf.SignatureAlgorithm". The 94c621e refactor changed the authority to leaf.PublicKey with SignatureAlgorithm cross-checked; update the docs so future readers (and any RPC-backed Signer implementer) don't re-introduce the original bug. 2. TestVerify_AlgorithmFamilyMismatch used a permissive `errors.Is(ErrSignatureMismatch) || errors.Is(ErrUnsupportedAlgorithm)` assertion. The outcome is actually deterministic: the verifier reaches verifyWithPub's RSA branch with ECDSA-DER bytes, which always returns ErrSignatureMismatch. Tighten to a single ErrorIs. 3. TestParseCertChainPEM_IgnoresNonCertificateBlocks had dead setup (writing a chain file to disk that was never read). Drop the file I/O. Also strengthen the test by using a well-formed PRIVATE KEY PEM block as the "junk" entry (the realistic mis-bundling case) instead of a PUBLIC KEY block with 3 garbage bytes. Drop unused imports (errors, os, path/filepath) from adversarial_test.go. go test ./signing/... -race -count=1 passes; go vet clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- signing/adversarial_test.go | 33 +++++++++++---------------------- signing/local_signer.go | 9 ++++++--- signing/local_verifier.go | 11 +++++++---- signing/types.go | 8 +++++--- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/signing/adversarial_test.go b/signing/adversarial_test.go index 441c3510..376f2382 100644 --- a/signing/adversarial_test.go +++ b/signing/adversarial_test.go @@ -9,10 +9,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" - "errors" "math/big" - "os" - "path/filepath" "testing" "time" @@ -88,14 +85,10 @@ func TestVerify_AlgorithmFamilyMismatch(t *testing.T) { // ECDSA signature, RSA leaf → must reject. Verifier dispatches on // the RSA leaf's pubkey type, so we end up in the RSA branch trying - // to verify ECDSA-DER bytes as a PKCS#1 v1.5 signature. + // to verify ECDSA-DER bytes as a PKCS#1 v1.5 signature, which + // always returns ErrSignatureMismatch. err = verifier.Verify(ctx, payload, ecSig, leafRsa) - require.Error(t, err) - // Either ErrSignatureMismatch (verify failed) or - // ErrUnsupportedAlgorithm — anything but success. - require.True(t, - errors.Is(err, ErrSignatureMismatch) || errors.Is(err, ErrUnsupportedAlgorithm), - "expected mismatch or unsupported, got %v", err) + require.ErrorIs(t, err, ErrSignatureMismatch) } // TestWrongChainOrder_DetectedAtSignatureVerify documents how the @@ -226,8 +219,8 @@ func TestNewLocalSigner_LeafPublicKeyMismatchDetected(t *testing.T) { } // TestParseCertChainPEM_IgnoresNonCertificateBlocks confirms that a -// chain file with stray PEM blocks (e.g. a private key accidentally -// left in the chain bundle) is parsed correctly — non-CERTIFICATE +// chain bundle with stray PEM blocks (e.g. a private key accidentally +// left in the chain file) is parsed correctly — non-CERTIFICATE // blocks are skipped, and the certificates that ARE present load. func TestParseCertChainPEM_IgnoresNonCertificateBlocks(t *testing.T) { ca, caKey, err := GenerateCA(AlgorithmECDSAP256SHA256, CertOptions{}) @@ -235,19 +228,15 @@ func TestParseCertChainPEM_IgnoresNonCertificateBlocks(t *testing.T) { leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) require.NoError(t, err) - // Build a "chain" file with an arbitrary non-CERTIFICATE block - // between the (only) leaf certificate and the end. + // Build a chain bundle with an arbitrary non-CERTIFICATE block + // (the realistic mis-bundling: an operator pastes a PRIVATE KEY + // block into the chain file). Use the CA's actual PKCS#8 key + // bytes so the junk block is well-formed PEM, not random bytes. leafPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leaf.Raw}) - junkPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: []byte{0x00, 0x01, 0x02}}) - combined := append(append([]byte{}, leafPEM...), junkPEM...) - - dir := t.TempDir() - chainPath := filepath.Join(dir, "chain.pem") - require.NoError(t, os.WriteFile(chainPath, combined, 0o600)) - keyDER, err := x509.MarshalPKCS8PrivateKey(caKey) require.NoError(t, err) - _ = keyDER + junkPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + combined := append(append([]byte{}, leafPEM...), junkPEM...) chain, err := parseCertChainPEM(combined) require.NoError(t, err) diff --git a/signing/local_signer.go b/signing/local_signer.go index b532f26d..4e08fcbc 100644 --- a/signing/local_signer.go +++ b/signing/local_signer.go @@ -39,9 +39,12 @@ type LocalSigner struct { // NOT be included — the Agent supplies the root via its pre-configured // trust anchor pool. // -// The algorithm is determined by the leaf certificate's -// SignatureAlgorithm; ErrUnsupportedAlgorithm is returned for any -// value outside the supported baseline. +// The signing algorithm is determined by the leaf certificate's public +// key type and (for ECDSA) curve, cross-checked against the cert's +// SignatureAlgorithm field. ErrUnsupportedAlgorithm is returned for +// any pubkey type/curve outside the supported baseline, for RSA keys +// below the minimum modulus (rsaMinModulusBits), or when +// SignatureAlgorithm does not match the leaf's actual key. func NewLocalSigner(key crypto.Signer, chain []*x509.Certificate) (*LocalSigner, error) { if key == nil { return nil, ErrNilKey diff --git a/signing/local_verifier.go b/signing/local_verifier.go index cfad4096..92468c52 100644 --- a/signing/local_verifier.go +++ b/signing/local_verifier.go @@ -42,10 +42,13 @@ func (v *LocalVerifier) ValidateChain(ctx context.Context, chainDER [][]byte, no return ValidateChain(ctx, chainDER, v.roots, now) } -// Verify implements [Verifier]. The algorithm is derived from -// leaf.SignatureAlgorithm; ErrUnsupportedAlgorithm is returned for any -// algorithm outside the supported baseline. ErrSignatureMismatch is -// returned when the signature does not verify. +// Verify implements [Verifier]. The signature algorithm is derived +// from the leaf certificate's public-key type and (for ECDSA) curve, +// cross-checked against leaf.SignatureAlgorithm. +// ErrUnsupportedAlgorithm is returned for any pubkey type/curve +// outside the supported baseline (or when leaf.SignatureAlgorithm +// disagrees with the actual key). ErrSignatureMismatch is returned +// when the signature does not verify. func (v *LocalVerifier) Verify(ctx context.Context, payload, signature []byte, leaf *x509.Certificate) error { if err := ctx.Err(); err != nil { return err diff --git a/signing/types.go b/signing/types.go index 21210df0..593b3867 100644 --- a/signing/types.go +++ b/signing/types.go @@ -117,8 +117,10 @@ type Verifier interface { ValidateChain(ctx context.Context, chainDER [][]byte, now time.Time) (*x509.Certificate, error) // Verify validates signature over payload using the public key of - // leaf and the algorithm declared by leaf.SignatureAlgorithm. The - // payload bytes are the wire bytes of SignedServerToAgent.payload - // — the receiver does not re-marshal anything. + // leaf. The signature algorithm is derived from leaf's public-key + // type and (for ECDSA) curve, cross-checked against + // leaf.SignatureAlgorithm. The payload bytes are the wire bytes of + // SignedServerToAgent.payload — the receiver does not re-marshal + // anything. Verify(ctx context.Context, payload, signature []byte, leaf *x509.Certificate) error } From c019428a94a4e1c3f6093c5dce66e0a6153aa38c Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 12:40:57 +0200 Subject: [PATCH 06/22] Wire client to verify SignedServerToAgent envelopes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 of the OpAMP Message Attestation implementation: client-side wiring for the negotiated payload-trust verification path. When the Agent opts in by configuring StartSettings.PayloadVerifier and setting AgentCapabilities_RequiresPayloadTrustVerification, every inbound server-to-agent message is treated as a SignedServerToAgent envelope: - First message carries trust_chain_response. The agent validates the delivered chain against its pre-configured trust anchor pool and stores the resulting leaf for the connection's lifetime. The signature MAY be empty on this first message; if present, it's verified as defence-in-depth. - Subsequent messages MUST carry a detached signature over the payload bytes. The agent verifies against the stored leaf. - Any failure (missing chain, bad chain, missing/invalid signature) returns an error from the receive path, terminating the WebSocket receive loop (and therefore the connection). HTTP polling logs and skips the response; the next poll re-handshakes. When PayloadVerifier is nil (the default), the wire format is byte-identical to upstream OpAMP — strict opt-in at the wire level. Architecture: - client/internal/attestation.go (new): per-connection attestationState handles the handshake + per-message verification. unwrapServerToAgent acts as either a pass-through (nil state) or an envelope unwrap. - client/types/startsettings.go: new PayloadVerifier signing.Verifier field. - client/internal/clientcommon.go: stores verifier on ClientCommon before validateCapabilities runs; new consistency check ensures the Requires bit ↔ verifier presence. - client/internal/wsreceiver.go: NewWSReceiver gains a payloadVerifier signing.Verifier parameter; receiveMessage now threads context and strips the WS framing first, then dispatches to unwrapServerToAgent. - client/internal/httpsender.go: HTTPSender.Run gains a payloadVerifier parameter; receiveResponse uses unwrapServerToAgent. - internal/wsmessage.go: factor StripWSMessageHeader out of DecodeWSMessage so callers can strip the framing without committing to a proto message type at that moment. Tests (17 new tests, all passing under -race): - attestation_test.go: ProcessEnvelope happy path + 8 reject paths (missing chain, error_message reported, unknown CA, missing signature on subsequent, tampered signature, empty payload, tampered first signature). unwrapServerToAgent pass-through + envelope. - clientcommon_attestation_test.go: capability ↔ verifier consistency (four quadrants). Pre-existing TestRedirectHTTP failures and three pre-existing vet warnings on the client/internal/packagessyncer.go path remain unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/httpclient.go | 1 + client/internal/attestation.go | 157 +++++++++ client/internal/attestation_test.go | 297 ++++++++++++++++++ client/internal/clientcommon.go | 23 ++ .../internal/clientcommon_attestation_test.go | 47 +++ client/internal/httpsender.go | 20 +- client/internal/wsreceiver.go | 30 +- client/internal/wsreceiver_test.go | 12 +- client/types/startsettings.go | 16 + client/wsclient.go | 1 + internal/wsmessage.go | 34 +- 11 files changed, 612 insertions(+), 26 deletions(-) create mode 100644 client/internal/attestation.go create mode 100644 client/internal/attestation_test.go create mode 100644 client/internal/clientcommon_attestation_test.go diff --git a/client/httpclient.go b/client/httpclient.go index 62e59b00..a7157cbe 100644 --- a/client/httpclient.go +++ b/client/httpclient.go @@ -155,6 +155,7 @@ func (c *httpClient) runUntilStopped(ctx context.Context) { c.common.PackagesStateProvider, &c.common.PackageSyncMutex, c.common.DownloadReporterInterval, + c.common.PayloadVerifier, ) } diff --git a/client/internal/attestation.go b/client/internal/attestation.go new file mode 100644 index 00000000..96d33566 --- /dev/null +++ b/client/internal/attestation.go @@ -0,0 +1,157 @@ +package internal + +import ( + "context" + "crypto/x509" + "errors" + "fmt" + "sync" + "time" + + "google.golang.org/protobuf/proto" + + "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" +) + +// Sentinel errors returned by attestationState. Callers can use +// errors.Is to distinguish failure modes when terminating the +// connection. +var ( + // ErrMissingTrustChain is returned when the first + // SignedServerToAgent received on a connection does not carry a + // trust_chain_response field. Per the spec this is a fatal + // handshake error. + ErrMissingTrustChain = errors.New("client: first SignedServerToAgent missing trust_chain_response") + + // ErrTrustChainErrorReported is returned when the Server populates + // trust_chain_response.error_message, signalling that it cannot + // satisfy the handshake. + ErrTrustChainErrorReported = errors.New("client: server reported trust chain error") + + // ErrMissingSignature is returned when a SignedServerToAgent after + // the first is missing its signature field. Subsequent messages + // MUST be signed. + ErrMissingSignature = errors.New("client: SignedServerToAgent missing signature on non-first message") + + // ErrMissingPayload is returned when SignedServerToAgent.payload + // is empty. The payload carries the inner ServerToAgent; an empty + // payload would unmarshal into an empty ServerToAgent and is + // rejected eagerly. + ErrMissingPayload = errors.New("client: SignedServerToAgent missing payload") +) + +// attestationState holds per-connection state for payload trust +// verification on the Agent (client) side. Construct one per OpAMP +// connection via newAttestationState and call ProcessEnvelope on each +// inbound SignedServerToAgent. +// +// When Verifier is nil (the operator did not opt in), the OpAMP wire +// format is byte-identical to upstream and no attestationState is +// created at all; payload trust is simply not negotiated. +type attestationState struct { + verifier signing.Verifier + + mu sync.Mutex + firstSeen bool + leaf *x509.Certificate +} + +// newAttestationState constructs a per-connection attestation state. +// verifier MUST be non-nil; callers without a configured verifier +// should not construct an attestationState at all. +func newAttestationState(verifier signing.Verifier) *attestationState { + return &attestationState{verifier: verifier} +} + +// ProcessEnvelope handles an incoming SignedServerToAgent received on +// this connection. On the first call, the envelope's certificate +// chain is validated against the verifier's pre-configured trust +// anchor pool and the resulting leaf is cached on the state. On +// subsequent calls, the envelope's signature is verified against the +// cached leaf. +// +// On success it returns the inner ServerToAgent payload bytes, which +// the caller unmarshals into a *protobufs.ServerToAgent for normal +// dispatch. +// +// On any failure — missing trust chain, chain validation failure, +// missing/invalid signature — it returns a non-nil error. Per the +// spec the caller MUST then terminate the OpAMP connection. +func (s *attestationState) ProcessEnvelope(ctx context.Context, envelope *protobufs.SignedServerToAgent) ([]byte, error) { + if envelope == nil { + return nil, errors.New("client: nil SignedServerToAgent envelope") + } + if len(envelope.Payload) == 0 { + return nil, ErrMissingPayload + } + + s.mu.Lock() + defer s.mu.Unlock() + + if !s.firstSeen { + chainResp := envelope.TrustChainResponse + if chainResp == nil { + return nil, ErrMissingTrustChain + } + if chainResp.ErrorMessage != "" { + return nil, fmt.Errorf("%w: %s", ErrTrustChainErrorReported, chainResp.ErrorMessage) + } + chainDER := make([][]byte, len(chainResp.CertificateChain)) + for i, cert := range chainResp.CertificateChain { + chainDER[i] = cert.GetDerData() + } + leaf, err := s.verifier.ValidateChain(ctx, chainDER, time.Now()) + if err != nil { + return nil, fmt.Errorf("client: validate trust chain: %w", err) + } + s.leaf = leaf + s.firstSeen = true + + // First message MAY be unsigned per the spec (chain validation + // establishes trust at this point). If a signature is present, + // verify it as defence in depth — a server that supplies a + // signature alongside the chain handshake should produce a + // valid one. + if len(envelope.Signature) > 0 { + if err := s.verifier.Verify(ctx, envelope.Payload, envelope.Signature, leaf); err != nil { + return nil, fmt.Errorf("client: verify first message signature: %w", err) + } + } + return envelope.Payload, nil + } + + // Subsequent messages: signature MUST be present and verifiable. + if len(envelope.Signature) == 0 { + return nil, ErrMissingSignature + } + if err := s.verifier.Verify(ctx, envelope.Payload, envelope.Signature, s.leaf); err != nil { + return nil, fmt.Errorf("client: verify signature: %w", err) + } + return envelope.Payload, nil +} + +// unwrapServerToAgent is a convenience that combines ProcessEnvelope +// with proto.Unmarshal of the resulting payload bytes into msg. If +// state is nil, the input bytes are unmarshalled directly as a +// ServerToAgent (the standard non-attestation path). +// +// rawProto is the protobuf bytes after any transport-level framing +// has been stripped (for WebSocket, after the wsMsgHeader varint). +func unwrapServerToAgent(ctx context.Context, state *attestationState, rawProto []byte, msg *protobufs.ServerToAgent) error { + if state == nil { + return proto.Unmarshal(rawProto, msg) + } + var envelope protobufs.SignedServerToAgent + if err := proto.Unmarshal(rawProto, &envelope); err != nil { + return fmt.Errorf("client: decode SignedServerToAgent envelope: %w", err) + } + payload, err := state.ProcessEnvelope(ctx, &envelope) + if err != nil { + return err + } + if err := proto.Unmarshal(payload, msg); err != nil { + return fmt.Errorf("client: decode inner ServerToAgent: %w", err) + } + return nil +} diff --git a/client/internal/attestation_test.go b/client/internal/attestation_test.go new file mode 100644 index 00000000..faa30541 --- /dev/null +++ b/client/internal/attestation_test.go @@ -0,0 +1,297 @@ +package internal + +import ( + "context" + "crypto/x509" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" +) + +// attestationFixture bundles a signer + matching verifier so tests can +// produce wire-realistic SignedServerToAgent envelopes and validate +// them end-to-end without re-implementing the cert plumbing. +type attestationFixture struct { + signer *signing.LocalSigner + verifier *signing.LocalVerifier + leafCert *x509.Certificate +} + +func newAttestationFixture(t *testing.T) attestationFixture { + t.Helper() + ca, caKey, err := signing.GenerateCA(signing.AlgorithmECDSAP256SHA256, signing.CertOptions{}) + require.NoError(t, err) + leaf, leafKey, err := signing.GenerateLeaf(signing.AlgorithmECDSAP256SHA256, ca, caKey, signing.CertOptions{}) + require.NoError(t, err) + signer, err := signing.NewLocalSigner(leafKey, []*x509.Certificate{leaf}) + require.NoError(t, err) + + pool := x509.NewCertPool() + pool.AddCert(ca) + verifier, err := signing.NewLocalVerifier(pool) + require.NoError(t, err) + return attestationFixture{signer: signer, verifier: verifier, leafCert: leaf} +} + +// buildFirstEnvelope produces the on-the-wire bytes for the FIRST +// SignedServerToAgent on a connection — carries trust_chain_response +// and an optional signature. +func (f attestationFixture) buildFirstEnvelope(t *testing.T, inner *protobufs.ServerToAgent, signFirst bool) *protobufs.SignedServerToAgent { + t.Helper() + payload, err := proto.Marshal(inner) + require.NoError(t, err) + + chainDER, err := f.signer.ChainDER(context.Background()) + require.NoError(t, err) + chain := make([]*protobufs.TrustChainResponse_Certificate, len(chainDER)) + for i, c := range chainDER { + chain[i] = &protobufs.TrustChainResponse_Certificate{DerData: c} + } + + env := &protobufs.SignedServerToAgent{ + Payload: payload, + TrustChainResponse: &protobufs.TrustChainResponse{ + CertificateChain: chain, + }, + } + if signFirst { + sig, err := f.signer.Sign(context.Background(), payload) + require.NoError(t, err) + env.Signature = sig + } + return env +} + +// buildSignedEnvelope produces an envelope for a non-first message: +// no trust_chain_response, signature MUST be present. +func (f attestationFixture) buildSignedEnvelope(t *testing.T, inner *protobufs.ServerToAgent) *protobufs.SignedServerToAgent { + t.Helper() + payload, err := proto.Marshal(inner) + require.NoError(t, err) + sig, err := f.signer.Sign(context.Background(), payload) + require.NoError(t, err) + return &protobufs.SignedServerToAgent{Payload: payload, Signature: sig} +} + +// TestAttestationState_FirstAndSubsequent exercises the happy path +// through both the handshake (first envelope) and the per-message +// signature verification (subsequent envelopes). +func TestAttestationState_FirstAndSubsequent(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + first := &protobufs.ServerToAgent{InstanceUid: []byte("first-msg-uid000")} + firstEnv := f.buildFirstEnvelope(t, first, false /* signFirst */) + + payload, err := state.ProcessEnvelope(ctx, firstEnv) + require.NoError(t, err) + var firstParsed protobufs.ServerToAgent + require.NoError(t, proto.Unmarshal(payload, &firstParsed)) + require.Equal(t, first.InstanceUid, firstParsed.InstanceUid) + + // State should now have a leaf cached. + require.NotNil(t, state.leaf) + require.True(t, state.firstSeen) + + // Subsequent message: must include a signature. + second := &protobufs.ServerToAgent{InstanceUid: []byte("second-msg-uid00")} + secondEnv := f.buildSignedEnvelope(t, second) + + payload, err = state.ProcessEnvelope(ctx, secondEnv) + require.NoError(t, err) + var secondParsed protobufs.ServerToAgent + require.NoError(t, proto.Unmarshal(payload, &secondParsed)) + require.Equal(t, second.InstanceUid, secondParsed.InstanceUid) +} + +// TestAttestationState_FirstMessageMayBeSigned confirms that when a +// server chooses to sign the first envelope (defence-in-depth), the +// signature is verified. +func TestAttestationState_FirstMessageMayBeSigned(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("signed-first-uid")} + env := f.buildFirstEnvelope(t, inner, true /* signFirst */) + + _, err := state.ProcessEnvelope(ctx, env) + require.NoError(t, err) +} + +// TestAttestationState_FirstMessageSignedButTampered confirms that +// when the first message carries a signature but it's invalid, the +// state rejects it. +func TestAttestationState_FirstMessageSignedButTampered(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("tampered-first0")} + env := f.buildFirstEnvelope(t, inner, true) + env.Signature[0] ^= 0xff + + _, err := state.ProcessEnvelope(ctx, env) + require.Error(t, err) + require.ErrorIs(t, err, signing.ErrSignatureMismatch) +} + +// TestAttestationState_MissingTrustChain confirms ErrMissingTrustChain +// when the first envelope lacks trust_chain_response. +func TestAttestationState_MissingTrustChain(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("no-chain-uid000")} + payload, err := proto.Marshal(inner) + require.NoError(t, err) + env := &protobufs.SignedServerToAgent{Payload: payload} + + _, err = state.ProcessEnvelope(ctx, env) + require.ErrorIs(t, err, ErrMissingTrustChain) +} + +// TestAttestationState_TrustChainErrorReported confirms that a +// non-empty error_message on the first envelope produces +// ErrTrustChainErrorReported. +func TestAttestationState_TrustChainErrorReported(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("err-msg-uid00000")} + payload, err := proto.Marshal(inner) + require.NoError(t, err) + env := &protobufs.SignedServerToAgent{ + Payload: payload, + TrustChainResponse: &protobufs.TrustChainResponse{ + ErrorMessage: "server cannot sign right now", + }, + } + + _, err = state.ProcessEnvelope(ctx, env) + require.ErrorIs(t, err, ErrTrustChainErrorReported) +} + +// TestAttestationState_UnknownCA confirms an envelope whose chain +// does not validate against the verifier's trust pool is rejected. +func TestAttestationState_UnknownCA(t *testing.T) { + f := newAttestationFixture(t) + // Build a SECOND fixture with a different CA — its envelope won't + // validate against f.verifier. + other := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("unknown-ca-uid00")} + env := other.buildFirstEnvelope(t, inner, false) + + _, err := state.ProcessEnvelope(ctx, env) + require.Error(t, err) + require.ErrorIs(t, err, signing.ErrChainValidation) +} + +// TestAttestationState_MissingSignatureOnSubsequent confirms that +// after a successful handshake, an envelope without a signature is +// rejected. +func TestAttestationState_MissingSignatureOnSubsequent(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, false)) + require.NoError(t, err) + + second := &protobufs.ServerToAgent{InstanceUid: []byte("second0000000000")} + payload, err := proto.Marshal(second) + require.NoError(t, err) + env := &protobufs.SignedServerToAgent{Payload: payload} // no signature + + _, err = state.ProcessEnvelope(ctx, env) + require.ErrorIs(t, err, ErrMissingSignature) +} + +// TestAttestationState_TamperedSignatureOnSubsequent confirms that a +// flipped byte in the signature is rejected. +func TestAttestationState_TamperedSignatureOnSubsequent(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, false)) + require.NoError(t, err) + + second := &protobufs.ServerToAgent{InstanceUid: []byte("second0000000000")} + env := f.buildSignedEnvelope(t, second) + env.Signature[len(env.Signature)-1] ^= 0xff + + _, err = state.ProcessEnvelope(ctx, env) + require.ErrorIs(t, err, signing.ErrSignatureMismatch) +} + +// TestAttestationState_EmptyPayload confirms an envelope with no +// payload bytes is rejected up front — an empty inner ServerToAgent +// would unmarshal to a no-op, which is not a useful message and may +// hide signaling issues. +func TestAttestationState_EmptyPayload(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + env := &protobufs.SignedServerToAgent{} + _, err := state.ProcessEnvelope(ctx, env) + require.ErrorIs(t, err, ErrMissingPayload) +} + +// TestUnwrapServerToAgent_NilState_PassThrough confirms that when no +// attestation state is configured, unwrapServerToAgent acts as a +// plain proto.Unmarshal of the wire bytes into a ServerToAgent. +func TestUnwrapServerToAgent_NilState_PassThrough(t *testing.T) { + inner := &protobufs.ServerToAgent{InstanceUid: []byte("plain-uid0000000")} + bytes, err := proto.Marshal(inner) + require.NoError(t, err) + + var msg protobufs.ServerToAgent + require.NoError(t, unwrapServerToAgent(context.Background(), nil, bytes, &msg)) + require.Equal(t, inner.InstanceUid, msg.InstanceUid) +} + +// TestUnwrapServerToAgent_WithState_HappyPath confirms that with an +// attestation state, wire bytes are unmarshalled as a +// SignedServerToAgent envelope and the inner ServerToAgent is +// returned. +func TestUnwrapServerToAgent_WithState_HappyPath(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("envelope-uid0000")} + env := f.buildFirstEnvelope(t, inner, false) + envBytes, err := proto.Marshal(env) + require.NoError(t, err) + + var msg protobufs.ServerToAgent + require.NoError(t, unwrapServerToAgent(context.Background(), state, envBytes, &msg)) + require.Equal(t, inner.InstanceUid, msg.InstanceUid) +} + +// TestUnwrapServerToAgent_WithState_GarbageBytes confirms that +// non-SignedServerToAgent bytes returned over the wire when signing +// is negotiated are rejected. +func TestUnwrapServerToAgent_WithState_GarbageBytes(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + + var msg protobufs.ServerToAgent + // Bytes that don't decode as SignedServerToAgent — proto3 is + // forgiving but completely random bytes typically still fail. + err := unwrapServerToAgent(context.Background(), state, []byte{0xff, 0xfe, 0xfd, 0xfc}, &msg) + require.Error(t, err) +} diff --git a/client/internal/clientcommon.go b/client/internal/clientcommon.go index 50afa587..dcf0ba27 100644 --- a/client/internal/clientcommon.go +++ b/client/internal/clientcommon.go @@ -11,6 +11,7 @@ import ( "github.com/open-telemetry/opamp-go/client/types" "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" ) var ( @@ -24,6 +25,8 @@ var ( ErrAcceptsPackagesNotSet = errors.New("AcceptsPackages and ReportsPackageStatuses must be set") ErrAvailableComponentsMissing = errors.New("AvailableComponents is nil") ErrReportsConnectionSettingsStatusNotSet = errors.New("ReportsConnectionSettingsStatus capability is not set") + ErrPayloadVerifierMissing = errors.New("PayloadVerifier must be set when RequiresPayloadTrustVerification capability is enabled") + ErrPayloadVerifierWithoutCapability = errors.New("PayloadVerifier set but RequiresPayloadTrustVerification capability is not enabled") errAlreadyStarted = errors.New("already started") errCannotStopNotStarted = errors.New("cannot stop because not started") @@ -46,6 +49,16 @@ type ClientCommon struct { // PackageSyncMutex makes sure only one package syncing operation happens at a time. PackageSyncMutex sync.Mutex + // PayloadVerifier validates the trust chain delivered in + // SignedServerToAgent.trust_chain_response on the first message of + // a connection, and verifies the per-message signature on every + // subsequent ServerToAgent. nil when the Agent has not opted in to + // payload trust verification (the standard OpAMP wire path stays + // active). MUST be non-nil when + // AgentCapabilities_RequiresPayloadTrustVerification is in the + // declared capability set. + PayloadVerifier signing.Verifier + // The transport-specific sender. sender Sender @@ -94,6 +107,13 @@ func (c *ClientCommon) validateCapabilities(capabilities protobufs.AgentCapabili return ErrPackagesStateProviderNotSet } } + requiresAttestation := capabilities&protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification != 0 + switch { + case requiresAttestation && c.PayloadVerifier == nil: + return ErrPayloadVerifierMissing + case !requiresAttestation && c.PayloadVerifier != nil: + return ErrPayloadVerifierWithoutCapability + } return nil } @@ -132,6 +152,9 @@ func (c *ClientCommon) PrepareStart( // Prepare package statuses. c.PackagesStateProvider = settings.PackagesStateProvider + // Store the payload trust verifier before capability validation so + // the capability ↔ verifier consistency check has both values. + c.PayloadVerifier = settings.PayloadVerifier if err := c.validateCapabilities(c.ClientSyncedState.Capabilities()); err != nil { return err } diff --git a/client/internal/clientcommon_attestation_test.go b/client/internal/clientcommon_attestation_test.go new file mode 100644 index 00000000..fcaa8bde --- /dev/null +++ b/client/internal/clientcommon_attestation_test.go @@ -0,0 +1,47 @@ +package internal + +import ( + "crypto/x509" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" +) + +// TestValidateCapabilities_AttestationCapabilityRequiresVerifier +// covers the new consistency check between the +// AgentCapabilities_RequiresPayloadTrustVerification bit and +// ClientCommon.PayloadVerifier. +func TestValidateCapabilities_AttestationCapabilityRequiresVerifier(t *testing.T) { + requiresBit := protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification + + t.Run("capability set + verifier nil → ErrPayloadVerifierMissing", func(t *testing.T) { + var c ClientCommon + err := c.validateCapabilities(requiresBit) + require.ErrorIs(t, err, ErrPayloadVerifierMissing) + }) + + t.Run("capability not set + verifier non-nil → ErrPayloadVerifierWithoutCapability", func(t *testing.T) { + verifier, err := signing.NewLocalVerifier(x509.NewCertPool()) + require.NoError(t, err) + c := ClientCommon{PayloadVerifier: verifier} + + err = c.validateCapabilities(0) + require.ErrorIs(t, err, ErrPayloadVerifierWithoutCapability) + }) + + t.Run("capability set + verifier non-nil → ok", func(t *testing.T) { + verifier, err := signing.NewLocalVerifier(x509.NewCertPool()) + require.NoError(t, err) + c := ClientCommon{PayloadVerifier: verifier} + + require.NoError(t, c.validateCapabilities(requiresBit)) + }) + + t.Run("neither set → ok (default opt-out)", func(t *testing.T) { + var c ClientCommon + require.NoError(t, c.validateCapabilities(0)) + }) +} diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go index 42a9fdde..0b4fc043 100644 --- a/client/internal/httpsender.go +++ b/client/internal/httpsender.go @@ -22,6 +22,7 @@ import ( "github.com/open-telemetry/opamp-go/client/types" "github.com/open-telemetry/opamp-go/internal" "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" ) const ( @@ -70,6 +71,12 @@ type HTTPSender struct { // Processor to handle received messages. receiveProcessor receivedProcessor + + // attestation, when non-nil, decodes inbound responses as + // SignedServerToAgent envelopes — validates the trust chain on the + // first response and verifies the signature on every subsequent + // one. Set by Run when the StartSettings supplied a PayloadVerifier. + attestation *attestationState } // NewHTTPSender creates a new Sender that uses HTTP to send messages @@ -129,10 +136,14 @@ func (h *HTTPSender) Run( packagesStateProvider types.PackagesStateProvider, packageSyncMutex *sync.Mutex, reporterInterval time.Duration, + payloadVerifier signing.Verifier, ) { h.url = url h.callbacks = callbacks h.receiveProcessor = newReceivedProcessor(h.logger, callbacks, h, clientSyncedState, packagesStateProvider, packageSyncMutex, reporterInterval) + if payloadVerifier != nil { + h.attestation = newAttestationState(payloadVerifier) + } // we need to detect if the redirect was ever set, if not, we want default behaviour if callbacks.CheckRedirect != nil { @@ -373,7 +384,14 @@ func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) { _ = resp.Body.Close() var response protobufs.ServerToAgent - if err := proto.Unmarshal(msgBytes, &response); err != nil { + if err := unwrapServerToAgent(ctx, h.attestation, msgBytes, &response); err != nil { + // When payload trust verification is enabled, a failure here + // means the response cannot be trusted; the spec says the + // connection MUST be terminated. For HTTP polling the agent + // has no persistent connection to drop — surface the error + // via the logger and skip processing this response. The next + // poll will re-establish state with the server, including a + // fresh trust-chain handshake. h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) return } diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go index 834036ee..b0c65898 100644 --- a/client/internal/wsreceiver.go +++ b/client/internal/wsreceiver.go @@ -10,6 +10,7 @@ import ( "github.com/open-telemetry/opamp-go/client/types" "github.com/open-telemetry/opamp-go/internal" "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" ) // wsReceiver implements the WebSocket client's receiving portion of OpAMP protocol. @@ -20,12 +21,24 @@ type wsReceiver struct { callbacks types.Callbacks processor receivedProcessor + // attestation, when non-nil, decodes inbound messages as + // SignedServerToAgent envelopes, validates the trust chain on the + // first message, verifies the signature on subsequent ones, and + // surfaces the inner ServerToAgent for normal processing. + attestation *attestationState + // Indicates that the receiver has fully stopped. stopped chan struct{} } // NewWSReceiver creates a new Receiver that uses WebSocket to receive -// messages from the server. +// messages from the server. If payloadVerifier is non-nil, every +// inbound message is treated as a SignedServerToAgent envelope: the +// trust chain is validated on the first message, signatures are +// verified on every subsequent one, and any failure terminates the +// receive loop (and, by extension, the connection). When +// payloadVerifier is nil, the receiver uses the standard ServerToAgent +// wire format (identical to upstream OpAMP). func NewWSReceiver( logger types.Logger, callbacks types.Callbacks, @@ -35,6 +48,7 @@ func NewWSReceiver( packagesStateProvider types.PackagesStateProvider, packageSyncMutex *sync.Mutex, reporterInterval time.Duration, + payloadVerifier signing.Verifier, ) *wsReceiver { w := &wsReceiver{ conn: conn, @@ -44,6 +58,9 @@ func NewWSReceiver( processor: newReceivedProcessor(logger, callbacks, sender, clientSyncedState, packagesStateProvider, packageSyncMutex, reporterInterval), stopped: make(chan struct{}), } + if payloadVerifier != nil { + w.attestation = newAttestationState(payloadVerifier) + } return w } @@ -78,7 +95,7 @@ func (r *wsReceiver) ReceiverLoop(ctx context.Context) { // To stop this goroutine, close the websocket connection go func() { var message protobufs.ServerToAgent - err := r.receiveMessage(&message) + err := r.receiveMessage(ctx, &message) result <- receivedMessage{&message, err} }() @@ -98,7 +115,7 @@ func (r *wsReceiver) ReceiverLoop(ctx context.Context) { } } -func (r *wsReceiver) receiveMessage(msg *protobufs.ServerToAgent) error { +func (r *wsReceiver) receiveMessage(ctx context.Context, msg *protobufs.ServerToAgent) error { mt, bytes, err := r.conn.ReadMessage() if err != nil { return err @@ -106,9 +123,12 @@ func (r *wsReceiver) receiveMessage(msg *protobufs.ServerToAgent) error { if mt != websocket.BinaryMessage { return fmt.Errorf("unsupported message type: %v", mt) } - err = internal.DecodeWSMessage(bytes, msg) + protoBytes, err := internal.StripWSMessageHeader(bytes) if err != nil { return fmt.Errorf("cannot decode received message: %w", err) } - return err + if err := unwrapServerToAgent(ctx, r.attestation, protoBytes, msg); err != nil { + return fmt.Errorf("cannot decode received message: %w", err) + } + return nil } diff --git a/client/internal/wsreceiver_test.go b/client/internal/wsreceiver_test.go index aefd0fb5..6315416e 100644 --- a/client/internal/wsreceiver_test.go +++ b/client/internal/wsreceiver_test.go @@ -93,7 +93,7 @@ func TestServerToAgentCommand(t *testing.T) { sender := WSSender{} capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsRestartCommand clientSyncedState.SetCapabilities(&capabilities) - receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second) + receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil) receiver.processor.ProcessReceivedMessage(context.Background(), &protobufs.ServerToAgent{ Command: test.command, }) @@ -148,7 +148,7 @@ func TestServerToAgentCommandExclusive(t *testing.T) { } clientSyncedState := ClientSyncedState{} clientSyncedState.SetCapabilities(&test.capabilities) - receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, nil, &clientSyncedState, nil, new(sync.Mutex), time.Second) + receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, nil, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil) receiver.processor.ProcessReceivedMessage(context.Background(), &protobufs.ServerToAgent{ Command: &protobufs.ServerToAgentCommand{ Type: protobufs.CommandType_CommandType_Restart, @@ -211,7 +211,7 @@ func TestReceiverLoopStop(t *testing.T) { sender := WSSender{} capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsRestartCommand clientSyncedState.SetCapabilities(&capabilities) - receiver := NewWSReceiver(TestLogger{t}, callbacks, conn, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second) + receiver := NewWSReceiver(TestLogger{t}, callbacks, conn, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil) ctx, cancel := context.WithCancel(context.Background()) go func() { @@ -254,7 +254,7 @@ func TestWSPackageUpdatesInParallel(t *testing.T) { capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsPackages sender := NewSender(&internal.NopLogger{}) clientSyncedState.SetCapabilities(&capabilities) - receiver := NewWSReceiver(&internal.NopLogger{}, callbacks, nil, sender, clientSyncedState, localPackageState, &mux, time.Second) + receiver := NewWSReceiver(&internal.NopLogger{}, callbacks, nil, sender, clientSyncedState, localPackageState, &mux, time.Second, nil) receiver.processor.ProcessReceivedMessage(ctx, &protobufs.ServerToAgent{ @@ -369,9 +369,9 @@ func TestRecieveMessage(t *testing.T) { state := &ClientSyncedState{} capabilities := protobufs.AgentCapabilities_AgentCapabilities_ReportsStatus state.SetCapabilities(&capabilities) - rec := NewWSReceiver(&internal.NopLogger{}, callbacks, conn, NewSender(&internal.NopLogger{}), state, nil, new(sync.Mutex), time.Second) + rec := NewWSReceiver(&internal.NopLogger{}, callbacks, conn, NewSender(&internal.NopLogger{}), state, nil, new(sync.Mutex), time.Second, nil) - err = rec.receiveMessage(&protobufs.ServerToAgent{}) + err = rec.receiveMessage(context.Background(), &protobufs.ServerToAgent{}) if tc.hasError { assert.Error(t, err) } else { diff --git a/client/types/startsettings.go b/client/types/startsettings.go index 7faa62d1..97297a7e 100644 --- a/client/types/startsettings.go +++ b/client/types/startsettings.go @@ -6,6 +6,7 @@ import ( "time" "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" ) // StartSettings defines the parameters for starting the OpAMP Client. @@ -53,6 +54,21 @@ type StartSettings struct { // i.e. package status reporting and syncing from the Server will be disabled. PackagesStateProvider PackagesStateProvider + // PayloadVerifier validates the X.509 trust chain delivered in the + // initial SignedServerToAgent.trust_chain_response of a connection + // and verifies the detached signature on every subsequent + // ServerToAgent message. MUST be set when the Agent's capability + // set includes + // AgentCapabilities_RequiresPayloadTrustVerification. When nil + // (the default), payload trust verification is disabled and the + // Server-to-Agent wire format is the standard ServerToAgent + // protobuf — identical to upstream OpAMP. + // + // See the signing package for the in-process LocalVerifier + // implementation and the VerifierFromFile helper that constructs + // one from a PEM-encoded CA bundle. + PayloadVerifier signing.Verifier + // Defines the capabilities of the Agent. AgentCapabilities_ReportsStatus bit does not need to // be set in this field, it will be set automatically since it is required by OpAMP protocol. // Deprecated: Use client.SetCapabilities() instead. diff --git a/client/wsclient.go b/client/wsclient.go index 2ad90f0b..7ed5618d 100644 --- a/client/wsclient.go +++ b/client/wsclient.go @@ -401,6 +401,7 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) { c.common.PackagesStateProvider, &c.common.PackageSyncMutex, c.common.DownloadReporterInterval, + c.common.PayloadVerifier, ) // When the wsclient is closed, the context passed to runOneCycle will be canceled. diff --git a/internal/wsmessage.go b/internal/wsmessage.go index 772fc293..e9a49111 100644 --- a/internal/wsmessage.go +++ b/internal/wsmessage.go @@ -12,29 +12,35 @@ import ( // Message header is currently uint64 zero value. const wsMsgHeader = uint64(0) -// DecodeWSMessage decodes a websocket message as bytes into a proto.Message. -func DecodeWSMessage(bytes []byte, msg proto.Message) error { - // Message header is optional until the end of grace period that ends Feb 1, 2023. - // Check if the header is present. +// StripWSMessageHeader removes the optional varint header from a +// WebSocket message and returns the protobuf payload bytes ready for +// proto.Unmarshal. The header is currently always uint64(0). When the +// header is absent (pre-2023 grace-period message format), the input +// is returned unchanged. +// +// This helper exists so that callers that need to choose a proto type +// at runtime (for example, the OpAMP client unwrapping a +// SignedServerToAgent envelope vs. a plain ServerToAgent) can strip +// the framing first and then unmarshal into the appropriate message +// type. +func StripWSMessageHeader(bytes []byte) ([]byte, error) { if len(bytes) > 0 && bytes[0] == 0 { - // New message format. The Protobuf message is preceded by a zero byte header. - // Decode the header. header, n := binary.Uvarint(bytes) if header != wsMsgHeader { - return errors.New("unexpected non-zero header") + return nil, errors.New("unexpected non-zero header") } - // Skip the header. It really is just a single zero byte for now. - bytes = bytes[n:] + return bytes[n:], nil } - // If no header was present (the "if" check above), then this is the old - // message format. No header is present. + return bytes, nil +} - // Decode WebSocket message as a Protobuf message. - err := proto.Unmarshal(bytes, msg) +// DecodeWSMessage decodes a websocket message as bytes into a proto.Message. +func DecodeWSMessage(bytes []byte, msg proto.Message) error { + protoBytes, err := StripWSMessageHeader(bytes) if err != nil { return err } - return nil + return proto.Unmarshal(protoBytes, msg) } func WriteWSMessage(conn *websocket.Conn, msg proto.Message) error { From ae83bfb2f02055a5948cd31044f94d2a724094f2 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 13:05:49 +0200 Subject: [PATCH 07/22] Tighten Phase 3 receive-path attestation failure handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups from the Phase 3 code review: 1. WebSocket: eagerly close the connection on attestation failure (wsreceiver.go). The receive loop already returned on attestation-related errors but never invoked r.conn.Close(), leaving a small window where the sender goroutine could keep writing AgentToServer messages to an untrusted server before the wsclient owner observed the stopped signal and tore the connection down. Per the Message Attestation spec the Agent MUST terminate the connection on any payload-trust verification failure; this commit closes the conn eagerly when isAttestationFailure(err) classifies the error as attestation-related. New helper isAttestationFailure() in attestation.go centralises the errors.Is checks against the local sentinels (ErrMissingTrust Chain, ErrTrustChainErrorReported, ErrMissingSignature, ErrMissing Payload) plus the signing-package sentinels propagated up (ErrChain Validation, ErrSignatureMismatch, ErrEmptyChain, ErrParseCertificate, ErrUnsupportedAlgorithm). 2. HTTP: reset per-connection attestation state on verification failure (httpsender.go::receiveResponse). The previous behaviour created the attestationState once in Run() and never reset it; an attestation failure after the initial successful handshake (e.g. following server-side key rotation) would keep firstSeen=true and the cached leaf, causing every subsequent poll to fail because the server's new chain handshake would be treated as a normal signed message and rejected for missing signature. New attestationState.Reset() method clears firstSeen + leaf under the mutex. HTTP receiveResponse now calls it on any unwrap failure so the next poll can re-attempt the handshake. WebSocket callers do not need Reset — failure terminates the connection and the next reconnect attempt constructs a fresh attestationState. Tests: * TestAttestationState_Reset: drives a successful handshake, calls Reset, asserts firstSeen/leaf cleared and that the next envelope without trust_chain_response now produces ErrMissingTrustChain rather than ErrMissingSignature, then verifies a fresh first envelope succeeds. * TestIsAttestationFailure_ClassifiesSentinels: covers the classification helper across all sentinels (local + signing package), confirms wrapped errors still classify via errors.Is, and confirms generic transport errors do NOT classify. go test ./client/internal/... -race -count=1 passes; pre-existing TestRedirectHTTP failure on upstream/main and pre-existing vet warnings on the client/internal/packagessyncer.go family unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/attestation.go | 38 ++++++++++++++++ client/internal/attestation_test.go | 67 +++++++++++++++++++++++++++++ client/internal/httpsender.go | 15 +++++-- client/internal/wsreceiver.go | 18 ++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) diff --git a/client/internal/attestation.go b/client/internal/attestation.go index 96d33566..d963c78d 100644 --- a/client/internal/attestation.go +++ b/client/internal/attestation.go @@ -64,6 +64,44 @@ func newAttestationState(verifier signing.Verifier) *attestationState { return &attestationState{verifier: verifier} } +// Reset clears the per-connection handshake state. After Reset, the +// next call to ProcessEnvelope is treated as if it were the first +// message on the connection — requiring trust_chain_response and +// performing a fresh chain validation. +// +// Used by transports that lack a persistent connection (the HTTP +// polling transport) to recover from server-side key rotation or +// other mid-stream handshake faults. WebSocket callers do not need +// to call Reset because a failure terminates the connection and the +// next reconnect attempt constructs a new attestationState. +func (s *attestationState) Reset() { + s.mu.Lock() + defer s.mu.Unlock() + s.firstSeen = false + s.leaf = nil +} + +// isAttestationFailure reports whether err originated from a payload +// trust verification problem (envelope malformed, chain validation +// failed, signature missing or invalid, etc.). Used by the WebSocket +// receive loop to distinguish attestation failures — which require +// explicit connection termination per the spec — from generic +// transport-level errors. +func isAttestationFailure(err error) bool { + if err == nil { + return false + } + return errors.Is(err, ErrMissingTrustChain) || + errors.Is(err, ErrTrustChainErrorReported) || + errors.Is(err, ErrMissingSignature) || + errors.Is(err, ErrMissingPayload) || + errors.Is(err, signing.ErrChainValidation) || + errors.Is(err, signing.ErrSignatureMismatch) || + errors.Is(err, signing.ErrEmptyChain) || + errors.Is(err, signing.ErrParseCertificate) || + errors.Is(err, signing.ErrUnsupportedAlgorithm) +} + // ProcessEnvelope handles an incoming SignedServerToAgent received on // this connection. On the first call, the envelope's certificate // chain is validated against the verifier's pre-configured trust diff --git a/client/internal/attestation_test.go b/client/internal/attestation_test.go index faa30541..b551d220 100644 --- a/client/internal/attestation_test.go +++ b/client/internal/attestation_test.go @@ -3,6 +3,8 @@ package internal import ( "context" "crypto/x509" + "errors" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -295,3 +297,68 @@ func TestUnwrapServerToAgent_WithState_GarbageBytes(t *testing.T) { err := unwrapServerToAgent(context.Background(), state, []byte{0xff, 0xfe, 0xfd, 0xfc}, &msg) require.Error(t, err) } + +// TestAttestationState_Reset confirms that Reset() returns the state +// to its initial (handshake-pending) form. The use case is the HTTP +// polling transport, which lacks a persistent connection to drop on +// attestation failure and must be able to re-handshake on the next +// poll. +func TestAttestationState_Reset(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + // Drive the state through a successful handshake. + first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, false)) + require.NoError(t, err) + require.True(t, state.firstSeen) + require.NotNil(t, state.leaf) + + // Reset and confirm the state is handshake-pending again. + state.Reset() + require.False(t, state.firstSeen) + require.Nil(t, state.leaf) + + // A "next" envelope without trust_chain_response now produces + // ErrMissingTrustChain (rather than ErrMissingSignature), proving + // the state is back to first-message semantics. + second := &protobufs.ServerToAgent{InstanceUid: []byte("second0000000000")} + envWithoutChain := f.buildSignedEnvelope(t, second) + envWithoutChain.TrustChainResponse = nil + + _, err = state.ProcessEnvelope(ctx, envWithoutChain) + require.ErrorIs(t, err, ErrMissingTrustChain) + + // A fresh first-message envelope succeeds after Reset. + thirdEnv := f.buildFirstEnvelope(t, second, false) + _, err = state.ProcessEnvelope(ctx, thirdEnv) + require.NoError(t, err) +} + +// TestIsAttestationFailure_ClassifiesSentinels confirms the +// classification helper used by the WS receive loop to decide when to +// close the connection. +func TestIsAttestationFailure_ClassifiesSentinels(t *testing.T) { + require.False(t, isAttestationFailure(nil)) + + // Local attestation sentinels. + require.True(t, isAttestationFailure(ErrMissingTrustChain)) + require.True(t, isAttestationFailure(ErrTrustChainErrorReported)) + require.True(t, isAttestationFailure(ErrMissingSignature)) + require.True(t, isAttestationFailure(ErrMissingPayload)) + + // Signing-package sentinels propagated up. + require.True(t, isAttestationFailure(signing.ErrChainValidation)) + require.True(t, isAttestationFailure(signing.ErrSignatureMismatch)) + require.True(t, isAttestationFailure(signing.ErrEmptyChain)) + require.True(t, isAttestationFailure(signing.ErrParseCertificate)) + require.True(t, isAttestationFailure(signing.ErrUnsupportedAlgorithm)) + + // Wrapped errors still classify correctly (errors.Is chain). + wrapped := fmt.Errorf("client: validate trust chain: %w", signing.ErrChainValidation) + require.True(t, isAttestationFailure(wrapped)) + + // Generic transport errors do NOT classify as attestation failures. + require.False(t, isAttestationFailure(errors.New("read: connection reset"))) +} diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go index 0b4fc043..4f2cec79 100644 --- a/client/internal/httpsender.go +++ b/client/internal/httpsender.go @@ -388,11 +388,18 @@ func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) { // When payload trust verification is enabled, a failure here // means the response cannot be trusted; the spec says the // connection MUST be terminated. For HTTP polling the agent - // has no persistent connection to drop — surface the error - // via the logger and skip processing this response. The next - // poll will re-establish state with the server, including a - // fresh trust-chain handshake. + // has no persistent connection to drop, so we skip processing + // this response and Reset the per-connection attestation + // state. The next poll will re-attempt the trust-chain + // handshake, allowing the Agent to recover from mid-stream + // faults such as server-side key rotation. Without the Reset, + // the cached firstSeen flag would keep us in the "verify + // signature" branch and the Agent could be stuck rejecting + // every subsequent response. h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) + if h.attestation != nil { + h.attestation.Reset() + } return } diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go index b0c65898..65f5992a 100644 --- a/client/internal/wsreceiver.go +++ b/client/internal/wsreceiver.go @@ -104,6 +104,24 @@ func (r *wsReceiver) ReceiverLoop(ctx context.Context) { return case res := <-result: if res.err != nil { + if isAttestationFailure(res.err) { + // Per the Message Attestation spec, the Agent + // MUST terminate the connection on any + // payload-trust verification failure. + // Returning here ends the receive loop, but + // the sender goroutine might still write + // pending AgentToServer messages on the same + // conn until the wsclient owner observes the + // stopped signal and closes; eagerly closing + // the conn here prevents that small leak + // window of agent messages to an untrusted + // server. + r.logger.Errorf(ctx, "Payload trust verification failed; terminating connection: %v", res.err) + if r.conn != nil { + _ = r.conn.Close() + } + return + } if !websocket.IsCloseError(res.err, websocket.CloseNormalClosure) { r.logger.Errorf(ctx, "Unexpected error while receiving: %v", res.err) } From 84298d61fcd7ff8d8fe85a10ac4346ce78f4cbcd Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 14:04:51 +0200 Subject: [PATCH 08/22] Wire server to wrap outbound in SignedServerToAgent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 of the OpAMP Message Attestation implementation: server-side wiring for the payload-trust verification path. When the operator configures Settings.PayloadSigner and a connecting Agent declares AgentCapabilities_RequiresPayloadTrustVerification on its first AgentToServer, the Server wraps every outbound ServerToAgent in a SignedServerToAgent envelope per the spec: - The first envelope on a WebSocket connection carries trust_chain_response (the cert chain delivered by Signer.ChainDER at accept time) and a detached signature over the payload bytes. - Subsequent envelopes on the same connection carry the signature only. The chain is snapshotted at connection accept so mid-stream signer-side rotation does not affect a live connection (the agent re-handshakes on reconnect). - For HTTP polling (no persistent connection), every response carries both the chain and a signature. The client's per-HTTPSender attestation state ignores the chain after the first. ServerCapabilities_OffersPayloadTrustVerification is auto-set on outgoing capabilities when PayloadSigner is configured for the connection. Operators don't need to remember to OR it in. When PayloadSigner is nil (the default) or the Agent did not declare Requires, the wire format is byte-identical to upstream OpAMP. Architecture: - server/server.go: Settings.PayloadSigner signing.Signer field. - server/attestation.go (new): connectionSigningState with signOutgoing(ctx, *ServerToAgent) → *SignedServerToAgent; first call includes trust_chain_response, subsequent calls don't. Helpers agentRequiresAttestation() and addOffersAttestationBit(). - server/wsconnection.go: wsConnection gains a *connectionSigningState field + enableSigning() method. Send() wraps if signing is enabled. - server/serverimpl.go::handleWSConnection: after reading the first AgentToServer, if the Agent declares Requires AND PayloadSigner is configured, snapshot the chain and enable signing on the wsConnection. Auto-OR the Offers bit on every outbound response. - server/serverimpl.go::handlePlainHTTPRequest: per-response chain snapshot + sign + wrap when the Agent declares Requires. Tests (8 new, all passing under -race): - connectionSigningState_FirstAndSubsequent: first envelope carries chain + signature, subsequent only signature; both verify against paired LocalVerifier. - connectionSigningState_ConcurrentFirstSend: exactly one of two concurrent signOutgoing calls carries the chain (firstSent guarded). - connectionSigningState_NilSigner / _SignerError: error paths from newConnectionSigningState. - agentRequiresAttestation / addOffersAttestationBit: helpers. - ServerWraps_WhenAgentRequires_WS: WS integration — agent sets Requires on first AgentToServer; server response decodes as SignedServerToAgent; signature verifies; OffersPayloadTrustVerification is auto-set on the inner ServerToAgent's capabilities. - ServerDoesNotWrap_WhenAgentDoesNotRequire_WS: agent omits Requires; server's response carries no signature, no chain. Strict opt-in preserved. Co-Authored-By: Claude Opus 4.7 (1M context) --- server/attestation.go | 96 +++++++++++ server/attestation_test.go | 325 +++++++++++++++++++++++++++++++++++++ server/server.go | 15 ++ server/serverimpl.go | 58 ++++++- server/wsconnection.go | 26 ++- 5 files changed, 516 insertions(+), 4 deletions(-) create mode 100644 server/attestation.go create mode 100644 server/attestation_test.go diff --git a/server/attestation.go b/server/attestation.go new file mode 100644 index 00000000..a4a3d8ce --- /dev/null +++ b/server/attestation.go @@ -0,0 +1,96 @@ +package server + +import ( + "context" + "fmt" + "sync" + + "google.golang.org/protobuf/proto" + + "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" +) + +// connectionSigningState holds the per-connection state needed to wrap +// outbound ServerToAgent messages in SignedServerToAgent envelopes +// when payload trust verification has been negotiated. +// +// The signer is held by reference; the certificate chain is +// snapshotted at construction time so that operator-side cert +// rotation does not affect a live connection (the agent only revalidates +// the chain on reconnect). The mutex is held briefly per outbound +// message to flip firstSent. +type connectionSigningState struct { + signer signing.Signer + chainDER [][]byte // snapshot + + mu sync.Mutex + firstSent bool +} + +// newConnectionSigningState constructs the per-connection state by +// asking the signer for its current chain. Errors here propagate to +// the server and prevent the connection from being established with +// signing enabled. +func newConnectionSigningState(ctx context.Context, signer signing.Signer) (*connectionSigningState, error) { + if signer == nil { + return nil, fmt.Errorf("server: nil signer") + } + chain, err := signer.ChainDER(ctx) + if err != nil { + return nil, fmt.Errorf("server: fetch signing chain: %w", err) + } + return &connectionSigningState{ + signer: signer, + chainDER: chain, + }, nil +} + +// signOutgoing produces a SignedServerToAgent envelope wrapping msg. +// The first call on a given state additionally populates +// trust_chain_response with the snapshotted chain; subsequent calls +// carry only payload + signature. +func (s *connectionSigningState) signOutgoing(ctx context.Context, msg *protobufs.ServerToAgent) (*protobufs.SignedServerToAgent, error) { + payload, err := proto.Marshal(msg) + if err != nil { + return nil, fmt.Errorf("server: marshal inner ServerToAgent: %w", err) + } + sig, err := s.signer.Sign(ctx, payload) + if err != nil { + return nil, fmt.Errorf("server: sign payload: %w", err) + } + env := &protobufs.SignedServerToAgent{ + Payload: payload, + Signature: sig, + } + + s.mu.Lock() + includeChain := !s.firstSent + if includeChain { + s.firstSent = true + } + s.mu.Unlock() + + if includeChain { + chain := make([]*protobufs.TrustChainResponse_Certificate, len(s.chainDER)) + for i, der := range s.chainDER { + chain[i] = &protobufs.TrustChainResponse_Certificate{DerData: der} + } + env.TrustChainResponse = &protobufs.TrustChainResponse{CertificateChain: chain} + } + return env, nil +} + +// agentRequiresAttestation reports whether the supplied +// AgentToServer.capabilities bitmask requests payload trust +// verification. +func agentRequiresAttestation(capabilities uint64) bool { + return capabilities&uint64(protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification) != 0 +} + +// addOffersAttestationBit returns capabilities with the +// ServerCapabilities_OffersPayloadTrustVerification bit set. It is a +// no-op if the bit is already set. +func addOffersAttestationBit(capabilities uint64) uint64 { + return capabilities | uint64(protobufs.ServerCapabilities_ServerCapabilities_OffersPayloadTrustVerification) +} diff --git a/server/attestation_test.go b/server/attestation_test.go new file mode 100644 index 00000000..655bc1bd --- /dev/null +++ b/server/attestation_test.go @@ -0,0 +1,325 @@ +package server + +import ( + "context" + "crypto/x509" + "errors" + "net/http" + "sync/atomic" + "testing" + "time" + + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + sharedinternal "github.com/open-telemetry/opamp-go/internal" + "github.com/open-telemetry/opamp-go/protobufs" + serverTypes "github.com/open-telemetry/opamp-go/server/types" + "github.com/open-telemetry/opamp-go/signing" +) + +// serverSigningFixture pairs a server-side Signer with a matching +// client-side Verifier so tests can drive a complete round trip +// without re-implementing cert plumbing. +type serverSigningFixture struct { + signer signing.Signer + verifier signing.Verifier +} + +func newServerSigningFixture(t *testing.T) serverSigningFixture { + t.Helper() + ca, caKey, err := signing.GenerateCA(signing.AlgorithmECDSAP256SHA256, signing.CertOptions{}) + require.NoError(t, err) + leaf, leafKey, err := signing.GenerateLeaf(signing.AlgorithmECDSAP256SHA256, ca, caKey, signing.CertOptions{}) + require.NoError(t, err) + signer, err := signing.NewLocalSigner(leafKey, []*x509.Certificate{leaf}) + require.NoError(t, err) + + pool := x509.NewCertPool() + pool.AddCert(ca) + verifier, err := signing.NewLocalVerifier(pool) + require.NoError(t, err) + + return serverSigningFixture{signer: signer, verifier: verifier} +} + +// TestConnectionSigningState_FirstAndSubsequent confirms the envelope +// produced for the first outbound message carries the trust chain +// and a signature; subsequent envelopes carry the signature only. +func TestConnectionSigningState_FirstAndSubsequent(t *testing.T) { + ctx := context.Background() + f := newServerSigningFixture(t) + + state, err := newConnectionSigningState(ctx, f.signer) + require.NoError(t, err) + + first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} + env1, err := state.signOutgoing(ctx, first) + require.NoError(t, err) + require.NotNil(t, env1.TrustChainResponse, "first envelope MUST carry the chain") + require.NotEmpty(t, env1.Signature, "server-side signer signs every message including the first") + + // Verify the inner payload round-trips and the signature + // verifies under the paired verifier. + leaf, err := f.verifier.ValidateChain(ctx, derChainFromResponse(env1.TrustChainResponse), time.Now()) + require.NoError(t, err) + require.NoError(t, f.verifier.Verify(ctx, env1.Payload, env1.Signature, leaf)) + var firstInner protobufs.ServerToAgent + require.NoError(t, proto.Unmarshal(env1.Payload, &firstInner)) + require.Equal(t, first.InstanceUid, firstInner.InstanceUid) + + // Second outbound — no chain. + second := &protobufs.ServerToAgent{InstanceUid: []byte("second0000000000")} + env2, err := state.signOutgoing(ctx, second) + require.NoError(t, err) + require.Nil(t, env2.TrustChainResponse, "subsequent envelopes do NOT re-send the chain") + require.NotEmpty(t, env2.Signature) + require.NoError(t, f.verifier.Verify(ctx, env2.Payload, env2.Signature, leaf)) +} + +// TestConnectionSigningState_ConcurrentFirstSend confirms that two +// concurrent signOutgoing calls result in exactly one envelope +// carrying the chain (firstSent flag is properly serialised). +func TestConnectionSigningState_ConcurrentFirstSend(t *testing.T) { + ctx := context.Background() + f := newServerSigningFixture(t) + state, err := newConnectionSigningState(ctx, f.signer) + require.NoError(t, err) + + // Drive two concurrent calls; exactly one should carry the chain. + type result struct { + env *protobufs.SignedServerToAgent + err error + } + out := make(chan result, 2) + for i := 0; i < 2; i++ { + go func() { + env, err := state.signOutgoing(ctx, &protobufs.ServerToAgent{InstanceUid: []byte("concurrent000000")}) + out <- result{env: env, err: err} + }() + } + withChain := 0 + withoutChain := 0 + for i := 0; i < 2; i++ { + r := <-out + require.NoError(t, r.err) + if r.env.TrustChainResponse != nil { + withChain++ + } else { + withoutChain++ + } + } + require.Equal(t, 1, withChain, "exactly one envelope should carry the chain") + require.Equal(t, 1, withoutChain, "the other envelope should not") +} + +// TestConnectionSigningState_NilSigner rejects a nil signer at +// construction. +func TestConnectionSigningState_NilSigner(t *testing.T) { + _, err := newConnectionSigningState(context.Background(), nil) + require.Error(t, err) +} + +// TestConnectionSigningState_SignerError propagates errors from the +// signer's ChainDER call. +func TestConnectionSigningState_SignerError(t *testing.T) { + bad := &failingSigner{chainErr: errors.New("chain unavailable")} + _, err := newConnectionSigningState(context.Background(), bad) + require.Error(t, err) +} + +// TestAgentRequiresAttestation_BitDetection covers the helper that +// inspects AgentToServer.Capabilities for the +// RequiresPayloadTrustVerification bit. +func TestAgentRequiresAttestation_BitDetection(t *testing.T) { + requiresBit := uint64(protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification) + otherBit := uint64(protobufs.AgentCapabilities_AgentCapabilities_ReportsStatus) + + require.False(t, agentRequiresAttestation(0)) + require.False(t, agentRequiresAttestation(otherBit)) + require.True(t, agentRequiresAttestation(requiresBit)) + require.True(t, agentRequiresAttestation(requiresBit|otherBit)) +} + +// TestAddOffersAttestationBit_Idempotent confirms the helper sets the +// OffersPayloadTrustVerification bit without disturbing other bits. +func TestAddOffersAttestationBit_Idempotent(t *testing.T) { + offersBit := uint64(protobufs.ServerCapabilities_ServerCapabilities_OffersPayloadTrustVerification) + otherBit := uint64(protobufs.ServerCapabilities_ServerCapabilities_OffersRemoteConfig) + + require.Equal(t, offersBit, addOffersAttestationBit(0)) + require.Equal(t, offersBit|otherBit, addOffersAttestationBit(otherBit)) + require.Equal(t, offersBit, addOffersAttestationBit(offersBit), "no-op when already set") +} + +// derChainFromResponse extracts the DER bytes from a +// TrustChainResponse for the paired verifier's ValidateChain call. +func derChainFromResponse(resp *protobufs.TrustChainResponse) [][]byte { + out := make([][]byte, len(resp.CertificateChain)) + for i, c := range resp.CertificateChain { + out[i] = c.GetDerData() + } + return out +} + +// failingSigner is a signing.Signer that returns the supplied errors +// from Sign and/or ChainDER. Used to test error-propagation paths. +type failingSigner struct { + signErr error + chainErr error +} + +func (s *failingSigner) Sign(_ context.Context, _ []byte) ([]byte, error) { + if s.signErr != nil { + return nil, s.signErr + } + return []byte("fake-sig"), nil +} + +func (s *failingSigner) ChainDER(_ context.Context) ([][]byte, error) { + if s.chainErr != nil { + return nil, s.chainErr + } + return [][]byte{[]byte("fake-cert")}, nil +} + +// TestServerWraps_WhenAgentRequires_WS exercises the WS path +// end-to-end: an Agent declares RequiresPayloadTrustVerification on +// its first AgentToServer; the Server (configured with a +// PayloadSigner) wraps the response in a SignedServerToAgent envelope +// carrying the trust chain and a valid signature. The OffersPayloadTrust +// Verification bit is auto-set on outgoing capabilities. +func TestServerWraps_WhenAgentRequires_WS(t *testing.T) { + var rcvMsg atomic.Value + f := newServerSigningFixture(t) + + settings := &StartSettings{Settings: Settings{ + PayloadSigner: f.signer, + Callbacks: serverTypes.Callbacks{ + OnConnecting: func(_ *http.Request) serverTypes.ConnectionResponse { + return serverTypes.ConnectionResponse{ + Accept: true, + ConnectionCallbacks: serverTypes.ConnectionCallbacks{ + OnMessage: func(_ context.Context, _ serverTypes.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent { + rcvMsg.Store(message) + return &protobufs.ServerToAgent{ + InstanceUid: message.InstanceUid, + Capabilities: uint64(protobufs.ServerCapabilities_ServerCapabilities_AcceptsStatus), + } + }, + }, + } + }, + }, + }} + + srv := startServer(t, settings) + defer srv.Stop(context.Background()) + + conn, _, err := dialClient(settings) + require.NoError(t, err) + require.NotNil(t, conn) + defer conn.Close() + + // Send an Agent message that DECLARES the Requires capability — + // this is what triggers the server-side wrap. + sendMsg := protobufs.AgentToServer{ + InstanceUid: testInstanceUid, + Capabilities: uint64(protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification), + } + bodyBytes, err := proto.Marshal(&sendMsg) + require.NoError(t, err) + err = conn.WriteMessage(websocket.BinaryMessage, bodyBytes) + require.NoError(t, err) + + eventually(t, func() bool { return rcvMsg.Load() != nil }) + + mt, frame, err := conn.ReadMessage() + require.NoError(t, err) + require.Equal(t, websocket.BinaryMessage, mt) + + // The server's response should be a SignedServerToAgent envelope, + // NOT a plain ServerToAgent. Decode and verify. + var envelope protobufs.SignedServerToAgent + require.NoError(t, sharedinternal.DecodeWSMessage(frame, &envelope)) + require.NotEmpty(t, envelope.Payload, "payload bytes carry the inner ServerToAgent") + require.NotEmpty(t, envelope.Signature, "first envelope is signed") + require.NotNil(t, envelope.TrustChainResponse, "first envelope carries the trust chain") + require.NotEmpty(t, envelope.TrustChainResponse.CertificateChain) + + // Validate the chain against our paired verifier, verify the + // signature, and confirm the inner payload decodes to the + // expected ServerToAgent. + leaf, err := f.verifier.ValidateChain(context.Background(), + derChainFromResponse(envelope.TrustChainResponse), time.Now()) + require.NoError(t, err) + require.NoError(t, f.verifier.Verify(context.Background(), envelope.Payload, envelope.Signature, leaf)) + + var inner protobufs.ServerToAgent + require.NoError(t, proto.Unmarshal(envelope.Payload, &inner)) + assert.Equal(t, sendMsg.InstanceUid, inner.InstanceUid) + // Server auto-set OffersPayloadTrustVerification on the response. + offersBit := uint64(protobufs.ServerCapabilities_ServerCapabilities_OffersPayloadTrustVerification) + assert.NotZero(t, inner.Capabilities&offersBit, "OffersPayloadTrustVerification should be auto-set on outgoing capabilities") +} + +// TestServerDoesNotWrap_WhenAgentDoesNotRequire_WS confirms that when +// the Agent doesn't set the Requires bit, the Server's outbound +// messages are plain ServerToAgent (wire-identical to upstream), +// even with PayloadSigner configured. +func TestServerDoesNotWrap_WhenAgentDoesNotRequire_WS(t *testing.T) { + f := newServerSigningFixture(t) + + settings := &StartSettings{Settings: Settings{ + PayloadSigner: f.signer, + Callbacks: serverTypes.Callbacks{ + OnConnecting: func(_ *http.Request) serverTypes.ConnectionResponse { + return serverTypes.ConnectionResponse{ + Accept: true, + ConnectionCallbacks: serverTypes.ConnectionCallbacks{ + OnMessage: func(_ context.Context, _ serverTypes.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent { + return &protobufs.ServerToAgent{InstanceUid: message.InstanceUid} + }, + }, + } + }, + }, + }} + + srv := startServer(t, settings) + defer srv.Stop(context.Background()) + + conn, _, err := dialClient(settings) + require.NoError(t, err) + defer conn.Close() + + // Agent does NOT set the Requires bit. + sendMsg := protobufs.AgentToServer{InstanceUid: testInstanceUid} + bodyBytes, err := proto.Marshal(&sendMsg) + require.NoError(t, err) + require.NoError(t, conn.WriteMessage(websocket.BinaryMessage, bodyBytes)) + + _, frame, err := conn.ReadMessage() + require.NoError(t, err) + + // Confirm the response is a plain ServerToAgent (not an + // envelope). NB: proto3 field-1-as-bytes makes both ServerToAgent + // and SignedServerToAgent partially decodable from the same wire + // bytes (InstanceUid vs Payload), so we can't disprove the + // envelope shape by attempting to decode as one. Instead, decode + // as the expected ServerToAgent and confirm the InstanceUid + // round-trips and the Signature/TrustChainResponse fields are + // absent from the wire by checking the envelope decode produces + // an empty Signature (which is field 2 — distinct from anything + // ServerToAgent carries). + var response protobufs.ServerToAgent + require.NoError(t, sharedinternal.DecodeWSMessage(frame, &response)) + assert.Equal(t, sendMsg.InstanceUid, response.InstanceUid) + + var envelope protobufs.SignedServerToAgent + require.NoError(t, sharedinternal.DecodeWSMessage(frame, &envelope)) + require.Empty(t, envelope.Signature, "no signature emitted when Agent didn't opt in") + require.Nil(t, envelope.TrustChainResponse, "no chain emitted when Agent didn't opt in") +} diff --git a/server/server.go b/server/server.go index abcf5389..d7846de1 100644 --- a/server/server.go +++ b/server/server.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/open-telemetry/opamp-go/server/types" + "github.com/open-telemetry/opamp-go/signing" ) // Settings contains the settings for attaching an OpAMP Server. @@ -27,6 +28,20 @@ type Settings struct { // https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#customcapabilities // for more details. CustomCapabilities []string + + // PayloadSigner produces detached signatures over outbound + // ServerToAgent messages and supplies the certificate chain that + // authenticates the Server to the Agent. When non-nil and the + // connecting Agent declares + // AgentCapabilities_RequiresPayloadTrustVerification, every + // ServerToAgent the Server sends on that connection is wrapped in + // a SignedServerToAgent envelope per the Message Attestation + // section of the OpAMP specification. The + // ServerCapabilities_OffersPayloadTrustVerification bit is + // automatically set on outgoing capabilities when this field is + // non-nil. nil disables payload trust signing — wire-identical to + // upstream OpAMP. + PayloadSigner signing.Signer } // StartSettings contains the settings for starting an OpAMP Server. diff --git a/server/serverimpl.go b/server/serverimpl.go index 72e33ddd..30138196 100644 --- a/server/serverimpl.go +++ b/server/serverimpl.go @@ -259,6 +259,11 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co connectionCallbacks.OnConnected(reqCtx, agentConn) sentCustomCapabilities := false + // signingNegotiated tracks whether we've already inspected the + // Agent's capabilities for this connection. The handshake is + // driven by the first AgentToServer's capabilities; subsequent + // messages don't re-negotiate. + signingNegotiated := false // Loop until fail to read from the WebSocket connection. for { @@ -301,6 +306,25 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co continue } + // On the first AgentToServer of this connection, decide + // whether payload trust verification is negotiated. The Agent + // declares its requirement via capabilities; the Server has + // to have a configured PayloadSigner. If both line up, snapshot + // the chain and attach a signing state to the connection so + // subsequent Sends wrap their messages in a SignedServerToAgent + // envelope. + if !signingNegotiated { + signingNegotiated = true + if s.settings.PayloadSigner != nil && agentRequiresAttestation(request.Capabilities) { + state, err := newConnectionSigningState(msgContext, s.settings.PayloadSigner) + if err != nil { + s.logger.Errorf(msgContext, "Cannot fetch signing certificate chain: %v", err) + break + } + agentConn.enableSigning(state) + } + } + response := connectionCallbacks.OnMessage(msgContext, agentConn, &request) if response == nil { // No send message when 'response' is empty continue @@ -315,6 +339,12 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co } sentCustomCapabilities = true } + // Auto-advertise OffersPayloadTrustVerification when this + // connection has signing enabled. The Agent inspects this bit + // on the first ServerToAgent. + if agentConn.signing != nil { + response.Capabilities = addOffersAttestationBit(response.Capabilities) + } err = agentConn.Send(msgContext, response) if err != nil { @@ -415,8 +445,32 @@ func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter Capabilities: s.settings.CustomCapabilities, } - // Marshal the response. - bodyBytes, err = proto.Marshal(response) + // Payload trust verification (HTTP path). HTTP is request-response + // with no persistent connection, so the trust handshake happens + // per-response: every signed response carries the chain alongside + // the signature. The Agent's HTTP receive path is stateful across + // polls (see client/internal/attestation.go) but tolerates the + // chain being re-sent — it just ignores it after the first. + var responseMessage proto.Message = response + if s.settings.PayloadSigner != nil && agentRequiresAttestation(request.Capabilities) { + response.Capabilities = addOffersAttestationBit(response.Capabilities) + state, sigErr := newConnectionSigningState(req.Context(), s.settings.PayloadSigner) + if sigErr != nil { + s.logger.Errorf(req.Context(), "Cannot fetch signing certificate chain: %v", sigErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + envelope, sigErr := state.signOutgoing(req.Context(), response) + if sigErr != nil { + s.logger.Errorf(req.Context(), "Cannot sign HTTP response: %v", sigErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + responseMessage = envelope + } + + // Marshal the response (or its envelope). + bodyBytes, err = proto.Marshal(responseMessage) if err != nil { w.WriteHeader(http.StatusInternalServerError) return diff --git a/server/wsconnection.go b/server/wsconnection.go index c721a043..7fae1049 100644 --- a/server/wsconnection.go +++ b/server/wsconnection.go @@ -21,22 +21,44 @@ type wsConnection struct { connMutex sync.Mutex wsConn *websocket.Conn closed atomic.Bool + + // signing, when non-nil, indicates that this connection has + // negotiated payload trust verification with the Agent. Outbound + // ServerToAgent messages are wrapped in a SignedServerToAgent + // envelope and the first send carries the trust chain. + signing *connectionSigningState } var _ types.Connection = (*wsConnection)(nil) -func newWSConnection(wsConn *websocket.Conn) types.Connection { +func newWSConnection(wsConn *websocket.Conn) *wsConnection { return &wsConnection{wsConn: wsConn} } +// enableSigning marks this connection as one that has negotiated +// payload trust verification. Outbound Send calls will wrap their +// ServerToAgent argument in a SignedServerToAgent envelope using the +// supplied state. Must be called before the first Send. +func (c *wsConnection) enableSigning(state *connectionSigningState) { + c.signing = state +} + func (c *wsConnection) Connection() net.Conn { return c.wsConn.UnderlyingConn() } -func (c *wsConnection) Send(_ context.Context, message *protobufs.ServerToAgent) error { +func (c *wsConnection) Send(ctx context.Context, message *protobufs.ServerToAgent) error { c.connMutex.Lock() defer c.connMutex.Unlock() + if c.signing != nil { + env, err := c.signing.signOutgoing(ctx, message) + if err != nil { + return err + } + return internal.WriteWSMessage(c.wsConn, env) + } + return internal.WriteWSMessage(c.wsConn, message) } From 65584948098c544953caa783aae0ea88c556b6d7 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Wed, 20 May 2026 17:54:39 +0200 Subject: [PATCH 09/22] Add end-to-end Message Attestation integration tests Covers the full client+server signing round trip across all four supported algorithms (ECDSA P-256/P-384, RSA-PKCS#1v1.5 SHA-256, Ed25519) on the WebSocket transport, plus an HTTP transport sanity check. Reject scenarios exercised end-to-end: server has no signer, expired leaf, unknown CA, tampered subsequent signature, HTTP under unknown CA. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../integrationtest/attestation_e2e_test.go | 551 ++++++++++++++++++ internal/integrationtest/doc.go | 6 + 2 files changed, 557 insertions(+) create mode 100644 internal/integrationtest/attestation_e2e_test.go create mode 100644 internal/integrationtest/doc.go diff --git a/internal/integrationtest/attestation_e2e_test.go b/internal/integrationtest/attestation_e2e_test.go new file mode 100644 index 00000000..001c3fe4 --- /dev/null +++ b/internal/integrationtest/attestation_e2e_test.go @@ -0,0 +1,551 @@ +package integrationtest + +import ( + "context" + "crypto/x509" + "fmt" + "net/http" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + opampclient "github.com/open-telemetry/opamp-go/client" + clienttypes "github.com/open-telemetry/opamp-go/client/types" + sharedinternal "github.com/open-telemetry/opamp-go/internal" + "github.com/open-telemetry/opamp-go/internal/testhelpers" + "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/server" + servertypes "github.com/open-telemetry/opamp-go/server/types" + "github.com/open-telemetry/opamp-go/signing" +) + +const ( + // e2eDeadline bounds how long a happy-path observation may take. + // Generous so RSA key generation on slow CI doesn't flake. + e2eDeadline = 5 * time.Second + // e2eNonOccurrenceDeadline bounds the wait for a "this should NOT + // happen" assertion (reject scenarios assert OnMessage never + // fires). Tradeoff: too short hides slow paths; too long slows the + // suite. 750ms catches anything that would have happened on a + // healthy local box. + e2eNonOccurrenceDeadline = 750 * time.Millisecond + // listenPath is shared by all e2e tests; ws/http URL building + // concatenates it to the dialed endpoint. + listenPath = "/v1/opamp" +) + +// e2eFixture pairs a server-side Signer with a matching client-side +// Verifier so the integration tests can drive a full round trip +// without re-implementing cert plumbing per test. +type e2eFixture struct { + algorithm signing.Algorithm + signer signing.Signer + verifier *instrumentedVerifier +} + +func newFixture(t *testing.T, alg signing.Algorithm) e2eFixture { + t.Helper() + return newFixtureWithLeafOpts(t, alg, signing.CertOptions{}) +} + +// newFixtureWithLeafOpts is the explicit-options variant — tests that +// need an expired leaf, a custom CN, etc. use this directly. +func newFixtureWithLeafOpts(t *testing.T, alg signing.Algorithm, leafOpts signing.CertOptions) e2eFixture { + t.Helper() + ca, caKey, err := signing.GenerateCA(alg, signing.CertOptions{}) + require.NoError(t, err) + leaf, leafKey, err := signing.GenerateLeaf(alg, ca, caKey, leafOpts) + require.NoError(t, err) + signer, err := signing.NewLocalSigner(leafKey, []*x509.Certificate{leaf}) + require.NoError(t, err) + pool := x509.NewCertPool() + pool.AddCert(ca) + inner, err := signing.NewLocalVerifier(pool) + require.NoError(t, err) + return e2eFixture{ + algorithm: alg, + signer: signer, + verifier: &instrumentedVerifier{inner: inner}, + } +} + +// instrumentedVerifier wraps a signing.Verifier with atomic counters +// for ValidateChain and Verify calls. Tests assert against the +// counters to confirm the on-wire envelope actually reached the +// verification path (and was not silently bypassed). +type instrumentedVerifier struct { + inner signing.Verifier + validateChainN atomic.Int32 + verifyN atomic.Int32 +} + +var _ signing.Verifier = (*instrumentedVerifier)(nil) + +func (v *instrumentedVerifier) ValidateChain(ctx context.Context, chainDER [][]byte, now time.Time) (*x509.Certificate, error) { + v.validateChainN.Add(1) + return v.inner.ValidateChain(ctx, chainDER, now) +} + +func (v *instrumentedVerifier) Verify(ctx context.Context, payload, signature []byte, leaf *x509.Certificate) error { + v.verifyN.Add(1) + return v.inner.Verify(ctx, payload, signature, leaf) +} + +// captureLogger records Errorf format strings so reject-scenario +// tests can assert that the client's attestation-failure path +// actually ran (and not e.g. a network error). +type captureLogger struct { + mu sync.Mutex + errLines []string +} + +func (l *captureLogger) Debugf(_ context.Context, _ string, _ ...interface{}) {} + +func (l *captureLogger) Errorf(_ context.Context, format string, v ...interface{}) { + l.mu.Lock() + defer l.mu.Unlock() + l.errLines = append(l.errLines, fmt.Sprintf(format, v...)) +} + +func (l *captureLogger) hasErrorContaining(s string) bool { + l.mu.Lock() + defer l.mu.Unlock() + for _, line := range l.errLines { + if strings.Contains(line, s) { + return true + } + } + return false +} + +// tamperingSigner wraps another Signer and corrupts the signature +// bytes starting from the Nth call (1-indexed). Used to exercise the +// "subsequent-message tampered signature" reject path while still +// letting the first envelope's handshake succeed. +type tamperingSigner struct { + inner signing.Signer + callN atomic.Int32 + tamperFromCall int32 +} + +func (t *tamperingSigner) Sign(ctx context.Context, payload []byte) ([]byte, error) { + n := t.callN.Add(1) + sig, err := t.inner.Sign(ctx, payload) + if err != nil { + return nil, err + } + if n >= t.tamperFromCall && len(sig) > 0 { + sig[0] ^= 0xff + } + return sig, nil +} + +func (t *tamperingSigner) ChainDER(ctx context.Context) ([][]byte, error) { + return t.inner.ChainDER(ctx) +} + +// runServer spins up an in-process OpAMP server. signer may be nil +// (no attestation). onMessage may be nil — when omitted, the server +// echoes the agent's InstanceUid in an empty ServerToAgent. +func runServer( + t *testing.T, + signer signing.Signer, + onConnected func(ctx context.Context, conn servertypes.Connection), + onMessage func(ctx context.Context, conn servertypes.Connection, msg *protobufs.AgentToServer) *protobufs.ServerToAgent, +) (server.OpAMPServer, string) { + t.Helper() + if onMessage == nil { + onMessage = func(_ context.Context, _ servertypes.Connection, m *protobufs.AgentToServer) *protobufs.ServerToAgent { + return &protobufs.ServerToAgent{InstanceUid: m.InstanceUid} + } + } + endpoint := testhelpers.GetAvailableLocalAddress() + settings := server.StartSettings{ + Settings: server.Settings{ + PayloadSigner: signer, + Callbacks: servertypes.Callbacks{ + OnConnecting: func(_ *http.Request) servertypes.ConnectionResponse { + return servertypes.ConnectionResponse{ + Accept: true, + ConnectionCallbacks: servertypes.ConnectionCallbacks{ + OnConnected: onConnected, + OnMessage: onMessage, + }, + } + }, + }, + }, + ListenEndpoint: endpoint, + ListenPath: listenPath, + } + srv := server.New(&sharedinternal.NopLogger{}) + require.NoError(t, srv.Start(settings)) + return srv, endpoint +} + +// newInstanceUid generates a UUIDv7 for use as the client's +// InstanceUid. A v7 (time-ordered) UUID matches what real Agents +// generate. +func newInstanceUid(t *testing.T) clienttypes.InstanceUid { + t.Helper() + uid, err := uuid.NewV7() + require.NoError(t, err) + b, err := uid.MarshalBinary() + require.NoError(t, err) + return clienttypes.InstanceUid(b) +} + +// minAgentDescr returns the smallest AgentDescription that satisfies +// the client's "non-empty identifying attributes" precondition. +func minAgentDescr() *protobufs.AgentDescription { + return &protobufs.AgentDescription{ + IdentifyingAttributes: []*protobufs.KeyValue{ + { + Key: "service.name", + Value: &protobufs.AnyValue{Value: &protobufs.AnyValue_StringValue{StringValue: "e2e-test-agent"}}, + }, + }, + } +} + +// startClient configures and starts an OpAMP client (WS or HTTP) with +// the supplied verifier and callbacks. When verifier is non-nil, the +// RequiresPayloadTrustVerification capability bit is OR'd into the +// caps so the server knows to wrap. +func startClient( + t *testing.T, + transport string, + endpoint string, + verifier signing.Verifier, + callbacks clienttypes.Callbacks, + logger clienttypes.Logger, +) opampclient.OpAMPClient { + t.Helper() + if logger == nil { + logger = &sharedinternal.NopLogger{} + } + + var c opampclient.OpAMPClient + var scheme string + switch transport { + case "ws": + c = opampclient.NewWebSocket(logger) + scheme = "ws" + case "http": + c = opampclient.NewHTTP(logger) + scheme = "http" + default: + t.Fatalf("unsupported transport: %s", transport) + } + + caps := protobufs.AgentCapabilities_AgentCapabilities_ReportsStatus + if verifier != nil { + caps |= protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification + } + + // HTTP polling needs a non-zero interval; pick something short so + // reject scenarios exercise multiple polls within their deadlines. + heartbeat := 100 * time.Millisecond + + settings := clienttypes.StartSettings{ + OpAMPServerURL: scheme + "://" + endpoint + listenPath, + InstanceUid: newInstanceUid(t), + PayloadVerifier: verifier, + Callbacks: callbacks, + HeartbeatInterval: &heartbeat, + } + require.NoError(t, c.SetAgentDescription(minAgentDescr())) + require.NoError(t, c.SetCapabilities(&caps)) + require.NoError(t, c.Start(context.Background(), settings)) + return c +} + +// TestE2E_HappyPath_AllAlgorithms_WS exercises the full WS round trip +// for each supported signature algorithm. The client requires +// attestation; the server signs every outbound. We assert OnMessage +// fires on the client and that the instrumented verifier was invoked +// for both chain validation and signature verification. +func TestE2E_HappyPath_AllAlgorithms_WS(t *testing.T) { + algorithms := []signing.Algorithm{ + signing.AlgorithmECDSAP256SHA256, + signing.AlgorithmECDSAP384SHA384, + signing.AlgorithmRSAPKCS1v15SHA256, + signing.AlgorithmEd25519, + } + for _, alg := range algorithms { + t.Run(alg.String(), func(t *testing.T) { + f := newFixture(t, alg) + + srv, endpoint := runServer(t, f.signer, nil, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, nil) + defer c.Stop(context.Background()) + + assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "client never received a ServerToAgent") + + // Chain validated once on the first envelope; signature + // verified at least once since our server signs every + // outbound message (including the first). + assert.GreaterOrEqual(t, f.verifier.validateChainN.Load(), int32(1), "ValidateChain should run on first envelope") + assert.GreaterOrEqual(t, f.verifier.verifyN.Load(), int32(1), "Verify should run since server signs first message too") + }) + } +} + +// TestE2E_FirstAndSubsequent_WS confirms that across two +// server-originated messages, the chain is validated exactly once and +// the signature is verified on each. Uses Connection.Send from +// OnConnected to push the second message. +func TestE2E_FirstAndSubsequent_WS(t *testing.T) { + f := newFixture(t, signing.AlgorithmECDSAP256SHA256) + + var savedConn atomic.Pointer[servertypes.Connection] + onConnected := func(_ context.Context, conn servertypes.Connection) { + savedConn.Store(&conn) + } + + srv, endpoint := runServer(t, f.signer, onConnected, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, nil) + defer c.Stop(context.Background()) + + // First message — server's OnMessage response. + assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "client never received the first ServerToAgent") + require.NotNil(t, savedConn.Load(), "server never observed OnConnected") + + // Second message — server-pushed via Connection.Send. + conn := *savedConn.Load() + require.NoError(t, conn.Send(context.Background(), &protobufs.ServerToAgent{ + InstanceUid: []byte("subsequent-uid00"), + })) + + assert.Eventually(t, func() bool { return msgN.Load() >= 2 }, e2eDeadline, 10*time.Millisecond, + "client never received the second ServerToAgent") + + assert.Equal(t, int32(1), f.verifier.validateChainN.Load(), "chain validated only on first envelope") + assert.GreaterOrEqual(t, f.verifier.verifyN.Load(), int32(2), "both envelopes signed") +} + +// TestE2E_NoAttestation_WS confirms the default wire format is +// untouched when neither side configures signing — i.e. attestation +// is purely opt-in. +func TestE2E_NoAttestation_WS(t *testing.T) { + srv, endpoint := runServer(t, nil, nil, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, nil /* verifier */, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, nil) + defer c.Stop(context.Background()) + + assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "plain OpAMP path should still deliver a ServerToAgent") +} + +// TestE2E_Reject_ServerHasNoSigner_WS — Agent declares Requires but +// the Server has no signer configured. The Server returns a plain +// ServerToAgent which the Agent parses as a SignedServerToAgent +// envelope (proto3 is permissive); the envelope fails the +// missing-trust-chain check on the first message and the Agent +// terminates the connection. +func TestE2E_Reject_ServerHasNoSigner_WS(t *testing.T) { + f := newFixture(t, signing.AlgorithmECDSAP256SHA256) + logger := &captureLogger{} + + srv, endpoint := runServer(t, nil /* no signer */, nil, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, logger) + defer c.Stop(context.Background()) + + // Wait long enough for the Agent to receive at least one bad + // envelope and tear the connection down. + assert.Eventually(t, func() bool { + return logger.hasErrorContaining("Payload trust verification failed") + }, e2eDeadline, 10*time.Millisecond, "client never logged an attestation failure") + + // The Agent must NOT deliver any message that failed verification. + time.Sleep(e2eNonOccurrenceDeadline) + assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire when attestation fails") +} + +// TestE2E_Reject_ExpiredLeaf_WS — Server's leaf is expired. The +// Agent's chain validation rejects on the first envelope. +func TestE2E_Reject_ExpiredLeaf_WS(t *testing.T) { + past := time.Now().Add(-2 * time.Hour) + expired := signing.CertOptions{ + NotBefore: past.Add(-1 * time.Hour), + NotAfter: past, + } + f := newFixtureWithLeafOpts(t, signing.AlgorithmECDSAP256SHA256, expired) + + logger := &captureLogger{} + srv, endpoint := runServer(t, f.signer, nil, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, logger) + defer c.Stop(context.Background()) + + assert.Eventually(t, func() bool { + return logger.hasErrorContaining("Payload trust verification failed") + }, e2eDeadline, 10*time.Millisecond, "client should reject expired leaf") + time.Sleep(e2eNonOccurrenceDeadline) + assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire for expired leaf") + assert.GreaterOrEqual(t, f.verifier.validateChainN.Load(), int32(1), "ValidateChain should have been called") +} + +// TestE2E_Reject_WrongCA_WS — Server signs with CA1; Agent trusts +// CA2. Chain validation fails on first envelope. +func TestE2E_Reject_WrongCA_WS(t *testing.T) { + server1 := newFixture(t, signing.AlgorithmECDSAP256SHA256) + client2 := newFixture(t, signing.AlgorithmECDSAP256SHA256) // independent CA + + logger := &captureLogger{} + srv, endpoint := runServer(t, server1.signer, nil, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, client2.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, logger) + defer c.Stop(context.Background()) + + assert.Eventually(t, func() bool { + return logger.hasErrorContaining("Payload trust verification failed") + }, e2eDeadline, 10*time.Millisecond, "client should reject chain from an unknown CA") + time.Sleep(e2eNonOccurrenceDeadline) + assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire for unknown CA") + assert.GreaterOrEqual(t, client2.verifier.validateChainN.Load(), int32(1), "ValidateChain should have been called") +} + +// TestE2E_Reject_TamperedSubsequentSignature_WS — handshake succeeds +// (first envelope is signed with a valid signature); the second +// server-pushed envelope arrives with a corrupted signature. The +// Agent rejects and terminates the connection. +func TestE2E_Reject_TamperedSubsequentSignature_WS(t *testing.T) { + f := newFixture(t, signing.AlgorithmECDSAP256SHA256) + // Wrap the signer so the SECOND signature it produces is corrupted. + bad := &tamperingSigner{inner: f.signer, tamperFromCall: 2} + + var savedConn atomic.Pointer[servertypes.Connection] + onConnected := func(_ context.Context, conn servertypes.Connection) { + savedConn.Store(&conn) + } + + logger := &captureLogger{} + srv, endpoint := runServer(t, bad, onConnected, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, logger) + defer c.Stop(context.Background()) + + // First message must succeed. + assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "first envelope should pass since its signature is well-formed") + + // Push the second — its signature will be corrupted by the + // tamperingSigner wrapper. + conn := *savedConn.Load() + require.NoError(t, conn.Send(context.Background(), &protobufs.ServerToAgent{ + InstanceUid: []byte("tampered-uid0000"), + })) + + assert.Eventually(t, func() bool { + return logger.hasErrorContaining("Payload trust verification failed") + }, e2eDeadline, 10*time.Millisecond, "client should reject the tampered subsequent envelope") + + // The second message must NOT have been delivered. + got := msgN.Load() + time.Sleep(e2eNonOccurrenceDeadline) + assert.Equal(t, got, msgN.Load(), "OnMessage should not fire after the tampered envelope") +} + +// TestE2E_HappyPath_HTTP — sanity-check the HTTP transport. We use +// the ECDSA P-256 algorithm to keep the test cheap; the per-algorithm +// matrix is already covered by the WS variant since the signing path +// is transport-independent. +func TestE2E_HappyPath_HTTP(t *testing.T) { + f := newFixture(t, signing.AlgorithmECDSAP256SHA256) + + srv, endpoint := runServer(t, f.signer, nil, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "http", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, nil) + defer c.Stop(context.Background()) + + assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "HTTP client never received a ServerToAgent") + assert.GreaterOrEqual(t, f.verifier.validateChainN.Load(), int32(1), "ValidateChain should run on first envelope") +} + +// TestE2E_HTTP_Reject_WrongCA — same as the WS variant but on the +// HTTP polling transport. The Agent keeps polling but never delivers +// a verified message. +func TestE2E_HTTP_Reject_WrongCA(t *testing.T) { + server1 := newFixture(t, signing.AlgorithmECDSAP256SHA256) + client2 := newFixture(t, signing.AlgorithmECDSAP256SHA256) // independent CA + + logger := &captureLogger{} + srv, endpoint := runServer(t, server1.signer, nil, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "http", endpoint, client2.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, logger) + defer c.Stop(context.Background()) + + assert.Eventually(t, func() bool { + return logger.hasErrorContaining("cannot unmarshal response") + }, e2eDeadline, 10*time.Millisecond, "HTTP client should reject chain from an unknown CA") + time.Sleep(e2eNonOccurrenceDeadline) + assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire when HTTP attestation fails") + assert.GreaterOrEqual(t, client2.verifier.validateChainN.Load(), int32(1), "ValidateChain should have been called") +} diff --git a/internal/integrationtest/doc.go b/internal/integrationtest/doc.go new file mode 100644 index 00000000..cdb0c513 --- /dev/null +++ b/internal/integrationtest/doc.go @@ -0,0 +1,6 @@ +// Package integrationtest contains end-to-end tests that exercise the +// OpAMP client and server together with the signing package. The tests +// live in a dedicated package outside both subtrees so they can import +// client and server without forcing a build-time edge between the two +// otherwise-independent packages. +package integrationtest From 81aa82b5edad3f71ada76c04581950587f3729f9 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Thu, 21 May 2026 02:06:40 +0200 Subject: [PATCH 10/22] Tighten server-side Message Attestation per code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OffersPayloadTrustVerification is now advertised whenever the server has a PayloadSigner configured, independent of any individual agent's Requires bit. This matches the spec's negotiation matrix (No/Yes quadrant: server is capable, agent didn't opt in) and the Settings.PayloadSigner docstring. - wsConnection.signing is now an atomic.Pointer so Send is safe to call from a goroutine other than the receive loop (matches the public Connection callback contract). - Send before the first AgentToServer is rejected with ErrSendBeforeNegotiated when PayloadSigner is configured, closing the silent-bypass window where OnConnected callbacks could emit unsigned bytes before the agent's capability bits were seen. - connectionSigningState.firstSent is now an atomic.Bool + CompareAndSwap; drops the per-state mutex. - handleWSConnection's signingNegotiated local is replaced by the connection's negotiated atomic.Bool, accessed via isNegotiated / markNegotiated accessors. - HTTP path notes a TODO for v2 ChainDER caching (deferred — affects RPC-backed signers only). Co-Authored-By: Claude Opus 4.7 (1M context) --- server/attestation.go | 29 ++++++--------- server/serverimpl.go | 63 +++++++++++++++++++------------- server/serverimpl_test.go | 2 +- server/wsconnection.go | 77 +++++++++++++++++++++++++++++++++------ 4 files changed, 116 insertions(+), 55 deletions(-) diff --git a/server/attestation.go b/server/attestation.go index a4a3d8ce..a9830d9f 100644 --- a/server/attestation.go +++ b/server/attestation.go @@ -3,7 +3,7 @@ package server import ( "context" "fmt" - "sync" + "sync/atomic" "google.golang.org/protobuf/proto" @@ -17,15 +17,14 @@ import ( // // The signer is held by reference; the certificate chain is // snapshotted at construction time so that operator-side cert -// rotation does not affect a live connection (the agent only revalidates -// the chain on reconnect). The mutex is held briefly per outbound -// message to flip firstSent. +// rotation does not affect a live connection (the agent only +// revalidates the chain on reconnect). firstSent atomically tracks +// whether the chain has already been delivered on this connection so +// that exactly one outbound envelope carries it. type connectionSigningState struct { - signer signing.Signer - chainDER [][]byte // snapshot - - mu sync.Mutex - firstSent bool + signer signing.Signer + chainDER [][]byte // snapshot + firstSent atomic.Bool } // newConnectionSigningState constructs the per-connection state by @@ -64,14 +63,10 @@ func (s *connectionSigningState) signOutgoing(ctx context.Context, msg *protobuf Signature: sig, } - s.mu.Lock() - includeChain := !s.firstSent - if includeChain { - s.firstSent = true - } - s.mu.Unlock() - - if includeChain { + // CompareAndSwap returns true iff we were the goroutine that + // transitioned firstSent from false to true — guaranteeing exactly + // one envelope carries the trust chain across concurrent callers. + if s.firstSent.CompareAndSwap(false, true) { chain := make([]*protobufs.TrustChainResponse_Certificate, len(s.chainDER)) for i, der := range s.chainDER { chain[i] = &protobufs.TrustChainResponse_Certificate{DerData: der} diff --git a/server/serverimpl.go b/server/serverimpl.go index 30138196..da5c96b1 100644 --- a/server/serverimpl.go +++ b/server/serverimpl.go @@ -238,7 +238,7 @@ func (s *server) httpHandler(w http.ResponseWriter, req *http.Request) { } func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Conn, connectionCallbacks *serverTypes.ConnectionCallbacks) { - agentConn := newWSConnection(wsConn) + agentConn := newWSConnection(wsConn, s.settings.PayloadSigner != nil) defer func() { // Close the connection when all is done. @@ -259,11 +259,6 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co connectionCallbacks.OnConnected(reqCtx, agentConn) sentCustomCapabilities := false - // signingNegotiated tracks whether we've already inspected the - // Agent's capabilities for this connection. The handshake is - // driven by the first AgentToServer's capabilities; subsequent - // messages don't re-negotiate. - signingNegotiated := false // Loop until fail to read from the WebSocket connection. for { @@ -312,9 +307,9 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co // to have a configured PayloadSigner. If both line up, snapshot // the chain and attach a signing state to the connection so // subsequent Sends wrap their messages in a SignedServerToAgent - // envelope. - if !signingNegotiated { - signingNegotiated = true + // envelope. markNegotiated also unblocks Send for callers that + // were rejected pre-negotiation (see ErrSendBeforeNegotiated). + if !agentConn.isNegotiated() { if s.settings.PayloadSigner != nil && agentRequiresAttestation(request.Capabilities) { state, err := newConnectionSigningState(msgContext, s.settings.PayloadSigner) if err != nil { @@ -323,6 +318,7 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co } agentConn.enableSigning(state) } + agentConn.markNegotiated() } response := connectionCallbacks.OnMessage(msgContext, agentConn, &request) @@ -339,10 +335,13 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co } sentCustomCapabilities = true } - // Auto-advertise OffersPayloadTrustVerification when this - // connection has signing enabled. The Agent inspects this bit - // on the first ServerToAgent. - if agentConn.signing != nil { + // Auto-advertise OffersPayloadTrustVerification whenever the + // server has a PayloadSigner configured — independent of + // whether THIS agent declared the Requires bit. Per the spec's + // negotiation matrix, the bit signals server capability. + // Agents that don't require attestation still see the bit and + // can choose to opt in on reconnect. + if s.settings.PayloadSigner != nil { response.Capabilities = addOffersAttestationBit(response.Capabilities) } @@ -451,22 +450,34 @@ func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter // the signature. The Agent's HTTP receive path is stateful across // polls (see client/internal/attestation.go) but tolerates the // chain being re-sent — it just ignores it after the first. + // + // TODO(perf): for RPC-backed signers, newConnectionSigningState + // re-fetches the chain on every request — at 10⁶ agents polling + // every 30s that's ~33k RPS just for ChainDER. A server-level + // cache (or a TTL-aware Signer wrapper) would amortise the cost. + // Defer until LocalSigner is no longer the only impl in use. var responseMessage proto.Message = response - if s.settings.PayloadSigner != nil && agentRequiresAttestation(request.Capabilities) { + if s.settings.PayloadSigner != nil { + // Always advertise Offers when the server is capable, even + // if THIS agent didn't declare Requires (per spec's + // negotiation matrix). response.Capabilities = addOffersAttestationBit(response.Capabilities) - state, sigErr := newConnectionSigningState(req.Context(), s.settings.PayloadSigner) - if sigErr != nil { - s.logger.Errorf(req.Context(), "Cannot fetch signing certificate chain: %v", sigErr) - w.WriteHeader(http.StatusInternalServerError) - return - } - envelope, sigErr := state.signOutgoing(req.Context(), response) - if sigErr != nil { - s.logger.Errorf(req.Context(), "Cannot sign HTTP response: %v", sigErr) - w.WriteHeader(http.StatusInternalServerError) - return + + if agentRequiresAttestation(request.Capabilities) { + state, sigErr := newConnectionSigningState(req.Context(), s.settings.PayloadSigner) + if sigErr != nil { + s.logger.Errorf(req.Context(), "Cannot fetch signing certificate chain: %v", sigErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + envelope, sigErr := state.signOutgoing(req.Context(), response) + if sigErr != nil { + s.logger.Errorf(req.Context(), "Cannot sign HTTP response: %v", sigErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + responseMessage = envelope } - responseMessage = envelope } // Marshal the response (or its envelope). diff --git a/server/serverimpl_test.go b/server/serverimpl_test.go index b988d412..fec45f64 100644 --- a/server/serverimpl_test.go +++ b/server/serverimpl_test.go @@ -273,7 +273,7 @@ func TestDisconnectClientWSConnection(t *testing.T) { assert.True(t, atomic.LoadInt32(&connectionCloseCalled) == 0) // Close connection from client side - clientConn := newWSConnection(conn) + clientConn := newWSConnection(conn, false /* no negotiation required */) err = clientConn.Disconnect() assert.NoError(t, err) diff --git a/server/wsconnection.go b/server/wsconnection.go index 7fae1049..a633e655 100644 --- a/server/wsconnection.go +++ b/server/wsconnection.go @@ -2,6 +2,7 @@ package server import ( "context" + "errors" "net" "sync" "sync/atomic" @@ -13,6 +14,18 @@ import ( "github.com/open-telemetry/opamp-go/server/types" ) +// ErrSendBeforeNegotiated is returned from Send when the server has +// a PayloadSigner configured but no AgentToServer has been processed +// yet on this connection. In that window the server cannot know +// whether the agent will declare RequiresPayloadTrustVerification, so +// emitting a message risks bypassing attestation. Push outbound +// messages from OnMessage (or any callback that runs after the first +// agent message), not from OnConnected. +var ErrSendBeforeNegotiated = errors.New( + "server: Send called before Message Attestation negotiation completed; " + + "push outbound messages from OnMessage rather than OnConnected", +) + // wsConnection represents a persistent OpAMP connection over a WebSocket. type wsConnection struct { // The websocket library does not allow multiple concurrent write operations, @@ -22,25 +35,63 @@ type wsConnection struct { wsConn *websocket.Conn closed atomic.Bool - // signing, when non-nil, indicates that this connection has - // negotiated payload trust verification with the Agent. Outbound - // ServerToAgent messages are wrapped in a SignedServerToAgent - // envelope and the first send carries the trust chain. - signing *connectionSigningState + // requiresNegotiation is fixed at construction. When true the + // server has a PayloadSigner configured and Send is rejected until + // negotiated flips to true. When false (no server-side signer), + // Send is always permitted — wire-identical to upstream OpAMP. + requiresNegotiation bool + + // negotiated flips to true after the connection's first + // AgentToServer has been processed by handleWSConnection. After + // that point the server has had its chance to decide whether to + // enable signing based on the agent's capability bits, so Send is + // safe to call. + negotiated atomic.Bool + + // signing, when loaded as non-nil, indicates that this connection + // has negotiated payload trust verification with the Agent. + // Outbound ServerToAgent messages are wrapped in a + // SignedServerToAgent envelope and the first send carries the + // trust chain. atomic.Pointer because enableSigning and Send may + // be called from different goroutines (Send is part of the public + // Connection callback API and may be invoked by user code). + signing atomic.Pointer[connectionSigningState] } var _ types.Connection = (*wsConnection)(nil) -func newWSConnection(wsConn *websocket.Conn) *wsConnection { - return &wsConnection{wsConn: wsConn} +func newWSConnection(wsConn *websocket.Conn, requiresNegotiation bool) *wsConnection { + return &wsConnection{ + wsConn: wsConn, + requiresNegotiation: requiresNegotiation, + } } // enableSigning marks this connection as one that has negotiated // payload trust verification. Outbound Send calls will wrap their // ServerToAgent argument in a SignedServerToAgent envelope using the -// supplied state. Must be called before the first Send. +// supplied state. func (c *wsConnection) enableSigning(state *connectionSigningState) { - c.signing = state + c.signing.Store(state) +} + +// signingEnabled reports whether this connection has negotiated +// payload trust verification. +func (c *wsConnection) signingEnabled() bool { + return c.signing.Load() != nil +} + +// markNegotiated records that the connection has processed its first +// AgentToServer message. After this point Send is no longer blocked +// by the pre-negotiation guard. +func (c *wsConnection) markNegotiated() { + c.negotiated.Store(true) +} + +// isNegotiated reports whether the connection has processed its +// first AgentToServer message. +func (c *wsConnection) isNegotiated() bool { + return c.negotiated.Load() } func (c *wsConnection) Connection() net.Conn { @@ -48,11 +99,15 @@ func (c *wsConnection) Connection() net.Conn { } func (c *wsConnection) Send(ctx context.Context, message *protobufs.ServerToAgent) error { + if c.requiresNegotiation && !c.negotiated.Load() { + return ErrSendBeforeNegotiated + } + c.connMutex.Lock() defer c.connMutex.Unlock() - if c.signing != nil { - env, err := c.signing.signOutgoing(ctx, message) + if state := c.signing.Load(); state != nil { + env, err := state.signOutgoing(ctx, message) if err != nil { return err } From 4a93753a3649428b51ea7cdc3a8dd3cd775dadbb Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Thu, 21 May 2026 02:52:29 +0200 Subject: [PATCH 11/22] Strengthen attestation tests per code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 5 e2e suite: - Raise e2eDeadline (5s → 15s) and e2eNonOccurrenceDeadline (750ms → 1.5s) so tests don't flake on -race-loaded CI. - Replace assert.Eventually with require.Eventually on positive awaits that gate subsequent dereferences (eliminates the nil-pointer SIGSEGV the agent flagged in the tampered-signature test on timeout). - Extract assertRejected helper that all four reject scenarios now share — drops ~100 lines of duplication. - Rename tamperingSigner → controlledSigner and add failFromCall mode so it can drive both tampered-signature and mid-stream Sign failure tests. Tampering now copies the slice before mutating (no inner signer coupling). - Drop the brittle HTTP-specific "cannot unmarshal response" log assertion in favour of the canonical "Payload trust verification failed" sentinel — httpsender.go now emits that phrase whenever isAttestationFailure(err) is true, matching the WS receive path. New e2e coverage: - TestE2E_ConcurrentConnections_MixedSigningState — two agents on one server, one with Requires + verifier, one without; confirms per-connection signing state is isolated. - TestE2E_Reject_MidStreamSignFailure_WS — exercises the controlledSigner.failFromCall path end-to-end. - TestE2E_SendBeforeNegotiation_Errors_WS — confirms the new ErrSendBeforeNegotiated guard from wsConnection.Send when PayloadSigner is configured and OnConnected pushes prematurely. - TestE2E_SendBeforeNegotiation_NoSigner_AllowsSend_WS — control case confirming the guard is scoped to attestation-enabled servers. Phase 4 unit tests: - TestServerDoesNotWrap_WhenAgentDoesNotRequire_WS now asserts the Offers bit IS advertised in the response, matching the new spec semantics (server is capable; agent declined). - TestSignOutgoing_MidStreamSignFailure_PropagatesError wires up the previously-dead failingSigner.signErr field. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/httpsender.go | 10 +- .../integrationtest/attestation_e2e_test.go | 313 ++++++++++++++---- server/attestation_test.go | 39 ++- 3 files changed, 296 insertions(+), 66 deletions(-) diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go index 4f2cec79..b089a1b0 100644 --- a/client/internal/httpsender.go +++ b/client/internal/httpsender.go @@ -396,9 +396,15 @@ func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) { // the cached firstSeen flag would keep us in the "verify // signature" branch and the Agent could be stuck rejecting // every subsequent response. - h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) - if h.attestation != nil { + // + // Use the same sentinel string the WebSocket receive path + // emits ("Payload trust verification failed") so operators + // can grep for one canonical phrase across both transports. + if h.attestation != nil && isAttestationFailure(err) { + h.logger.Errorf(ctx, "Payload trust verification failed; resetting attestation state: %v", err) h.attestation.Reset() + } else { + h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) } return } diff --git a/internal/integrationtest/attestation_e2e_test.go b/internal/integrationtest/attestation_e2e_test.go index 001c3fe4..d1f0778a 100644 --- a/internal/integrationtest/attestation_e2e_test.go +++ b/internal/integrationtest/attestation_e2e_test.go @@ -27,17 +27,26 @@ import ( const ( // e2eDeadline bounds how long a happy-path observation may take. - // Generous so RSA key generation on slow CI doesn't flake. - e2eDeadline = 5 * time.Second + // 15s is generous — under -race × parallel CPU contention, the + // first-envelope round trip can run several seconds on slow CI, + // and RSA-2048 key generation in newFixture is on the same clock. + // We'd rather wait than flake. + e2eDeadline = 15 * time.Second // e2eNonOccurrenceDeadline bounds the wait for a "this should NOT // happen" assertion (reject scenarios assert OnMessage never - // fires). Tradeoff: too short hides slow paths; too long slows the - // suite. 750ms catches anything that would have happened on a - // healthy local box. - e2eNonOccurrenceDeadline = 750 * time.Millisecond + // fires). One GC pause under -race can eat several hundred + // milliseconds; 1.5s catches anything that would have happened on + // a healthy box without making the suite slow. + e2eNonOccurrenceDeadline = 1500 * time.Millisecond // listenPath is shared by all e2e tests; ws/http URL building // concatenates it to the dialed endpoint. listenPath = "/v1/opamp" + + // attestationFailureLogSubstring is the canonical phrase the + // client logs on any payload trust verification failure (both WS + // and HTTP transports). Tests grep for this exact substring; if + // you change it on the receive paths, update both ends. + attestationFailureLogSubstring = "Payload trust verification failed" ) // e2eFixture pairs a server-side Signer with a matching client-side @@ -124,30 +133,43 @@ func (l *captureLogger) hasErrorContaining(s string) bool { return false } -// tamperingSigner wraps another Signer and corrupts the signature -// bytes starting from the Nth call (1-indexed). Used to exercise the -// "subsequent-message tampered signature" reject path while still -// letting the first envelope's handshake succeed. -type tamperingSigner struct { +// controlledSigner wraps another Signer with optional failure-mode +// injection: it can tamper signatures starting from the Nth call +// (drives the "tampered subsequent signature" reject path) or return +// failErr starting from the Nth call (drives the "mid-stream Sign +// failure" reject path). Zero-valued tamperFromCall / failFromCall +// disables the corresponding mode. Used only for tests. +type controlledSigner struct { inner signing.Signer callN atomic.Int32 tamperFromCall int32 + failFromCall int32 + failErr error } -func (t *tamperingSigner) Sign(ctx context.Context, payload []byte) ([]byte, error) { - n := t.callN.Add(1) - sig, err := t.inner.Sign(ctx, payload) +func (s *controlledSigner) Sign(ctx context.Context, payload []byte) ([]byte, error) { + n := s.callN.Add(1) + if s.failFromCall > 0 && n >= s.failFromCall { + return nil, s.failErr + } + sig, err := s.inner.Sign(ctx, payload) if err != nil { return nil, err } - if n >= t.tamperFromCall && len(sig) > 0 { - sig[0] ^= 0xff + if s.tamperFromCall > 0 && n >= s.tamperFromCall && len(sig) > 0 { + // Copy before mutating: the inner signer's contract doesn't + // promise the returned slice is exclusively ours, and a future + // pooled signer would break under in-place mutation. + out := make([]byte, len(sig)) + copy(out, sig) + out[0] ^= 0xff + return out, nil } return sig, nil } -func (t *tamperingSigner) ChainDER(ctx context.Context) ([][]byte, error) { - return t.inner.ChainDER(ctx) +func (s *controlledSigner) ChainDER(ctx context.Context) ([][]byte, error) { + return s.inner.ChainDER(ctx) } // runServer spins up an in-process OpAMP server. signer may be nil @@ -266,6 +288,20 @@ func startClient( return c } +// assertRejected is the common reject-scenario tail: wait until the +// client logs the canonical attestation-failure substring (fail-fast +// via require.Eventually), then confirm no message was ever delivered +// to OnMessage during e2eNonOccurrenceDeadline. +func assertRejected(t *testing.T, logger *captureLogger, msgN *atomic.Int32) { + t.Helper() + require.Eventually(t, func() bool { + return logger.hasErrorContaining(attestationFailureLogSubstring) + }, e2eDeadline, 10*time.Millisecond, + "client never logged %q within e2eDeadline", attestationFailureLogSubstring) + time.Sleep(e2eNonOccurrenceDeadline) + assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire when attestation fails") +} + // TestE2E_HappyPath_AllAlgorithms_WS exercises the full WS round trip // for each supported signature algorithm. The client requires // attestation; the server signs every outbound. We assert OnMessage @@ -293,7 +329,7 @@ func TestE2E_HappyPath_AllAlgorithms_WS(t *testing.T) { }, nil) defer c.Stop(context.Background()) - assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, "client never received a ServerToAgent") // Chain validated once on the first envelope; signature @@ -328,18 +364,22 @@ func TestE2E_FirstAndSubsequent_WS(t *testing.T) { }, nil) defer c.Stop(context.Background()) - // First message — server's OnMessage response. - assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + // First message — server's OnMessage response. Use require so + // we fail fast (and don't deref a nil savedConn below). + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, "client never received the first ServerToAgent") require.NotNil(t, savedConn.Load(), "server never observed OnConnected") - // Second message — server-pushed via Connection.Send. + // Second message — server-pushed via Connection.Send. The + // connection's signing-negotiation is complete by now because + // OnMessage on the server has fired (signing is decided BEFORE + // OnMessage; see handleWSConnection). conn := *savedConn.Load() require.NoError(t, conn.Send(context.Background(), &protobufs.ServerToAgent{ InstanceUid: []byte("subsequent-uid00"), })) - assert.Eventually(t, func() bool { return msgN.Load() >= 2 }, e2eDeadline, 10*time.Millisecond, + require.Eventually(t, func() bool { return msgN.Load() >= 2 }, e2eDeadline, 10*time.Millisecond, "client never received the second ServerToAgent") assert.Equal(t, int32(1), f.verifier.validateChainN.Load(), "chain validated only on first envelope") @@ -361,7 +401,7 @@ func TestE2E_NoAttestation_WS(t *testing.T) { }, nil) defer c.Stop(context.Background()) - assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, "plain OpAMP path should still deliver a ServerToAgent") } @@ -386,15 +426,7 @@ func TestE2E_Reject_ServerHasNoSigner_WS(t *testing.T) { }, logger) defer c.Stop(context.Background()) - // Wait long enough for the Agent to receive at least one bad - // envelope and tear the connection down. - assert.Eventually(t, func() bool { - return logger.hasErrorContaining("Payload trust verification failed") - }, e2eDeadline, 10*time.Millisecond, "client never logged an attestation failure") - - // The Agent must NOT deliver any message that failed verification. - time.Sleep(e2eNonOccurrenceDeadline) - assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire when attestation fails") + assertRejected(t, logger, &msgN) } // TestE2E_Reject_ExpiredLeaf_WS — Server's leaf is expired. The @@ -419,11 +451,7 @@ func TestE2E_Reject_ExpiredLeaf_WS(t *testing.T) { }, logger) defer c.Stop(context.Background()) - assert.Eventually(t, func() bool { - return logger.hasErrorContaining("Payload trust verification failed") - }, e2eDeadline, 10*time.Millisecond, "client should reject expired leaf") - time.Sleep(e2eNonOccurrenceDeadline) - assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire for expired leaf") + assertRejected(t, logger, &msgN) assert.GreaterOrEqual(t, f.verifier.validateChainN.Load(), int32(1), "ValidateChain should have been called") } @@ -445,11 +473,7 @@ func TestE2E_Reject_WrongCA_WS(t *testing.T) { }, logger) defer c.Stop(context.Background()) - assert.Eventually(t, func() bool { - return logger.hasErrorContaining("Payload trust verification failed") - }, e2eDeadline, 10*time.Millisecond, "client should reject chain from an unknown CA") - time.Sleep(e2eNonOccurrenceDeadline) - assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire for unknown CA") + assertRejected(t, logger, &msgN) assert.GreaterOrEqual(t, client2.verifier.validateChainN.Load(), int32(1), "ValidateChain should have been called") } @@ -460,7 +484,7 @@ func TestE2E_Reject_WrongCA_WS(t *testing.T) { func TestE2E_Reject_TamperedSubsequentSignature_WS(t *testing.T) { f := newFixture(t, signing.AlgorithmECDSAP256SHA256) // Wrap the signer so the SECOND signature it produces is corrupted. - bad := &tamperingSigner{inner: f.signer, tamperFromCall: 2} + bad := &controlledSigner{inner: f.signer, tamperFromCall: 2} var savedConn atomic.Pointer[servertypes.Connection] onConnected := func(_ context.Context, conn servertypes.Connection) { @@ -479,23 +503,25 @@ func TestE2E_Reject_TamperedSubsequentSignature_WS(t *testing.T) { }, logger) defer c.Stop(context.Background()) - // First message must succeed. - assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + // First message must succeed. Use require so we fail fast and + // don't deref a nil savedConn below. + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, "first envelope should pass since its signature is well-formed") + require.NotNil(t, savedConn.Load(), "server never observed OnConnected") // Push the second — its signature will be corrupted by the - // tamperingSigner wrapper. + // controlledSigner wrapper. conn := *savedConn.Load() require.NoError(t, conn.Send(context.Background(), &protobufs.ServerToAgent{ InstanceUid: []byte("tampered-uid0000"), })) - assert.Eventually(t, func() bool { - return logger.hasErrorContaining("Payload trust verification failed") + got := msgN.Load() + require.Eventually(t, func() bool { + return logger.hasErrorContaining(attestationFailureLogSubstring) }, e2eDeadline, 10*time.Millisecond, "client should reject the tampered subsequent envelope") // The second message must NOT have been delivered. - got := msgN.Load() time.Sleep(e2eNonOccurrenceDeadline) assert.Equal(t, got, msgN.Load(), "OnMessage should not fire after the tampered envelope") } @@ -518,14 +544,16 @@ func TestE2E_HappyPath_HTTP(t *testing.T) { }, nil) defer c.Stop(context.Background()) - assert.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, "HTTP client never received a ServerToAgent") assert.GreaterOrEqual(t, f.verifier.validateChainN.Load(), int32(1), "ValidateChain should run on first envelope") } // TestE2E_HTTP_Reject_WrongCA — same as the WS variant but on the // HTTP polling transport. The Agent keeps polling but never delivers -// a verified message. +// a verified message. The HTTP receive path now emits the same +// canonical "Payload trust verification failed" sentinel as WS, so +// assertRejected works uniformly. func TestE2E_HTTP_Reject_WrongCA(t *testing.T) { server1 := newFixture(t, signing.AlgorithmECDSAP256SHA256) client2 := newFixture(t, signing.AlgorithmECDSAP256SHA256) // independent CA @@ -542,10 +570,181 @@ func TestE2E_HTTP_Reject_WrongCA(t *testing.T) { }, logger) defer c.Stop(context.Background()) - assert.Eventually(t, func() bool { - return logger.hasErrorContaining("cannot unmarshal response") - }, e2eDeadline, 10*time.Millisecond, "HTTP client should reject chain from an unknown CA") + assertRejected(t, logger, &msgN) + assert.GreaterOrEqual(t, client2.verifier.validateChainN.Load(), int32(1), + "ValidateChain on HTTP should have been called at least once") +} + +// TestE2E_ConcurrentConnections_MixedSigningState — two agents share +// the same server: one requires attestation (and gets wrapped +// responses), the other doesn't (and gets plain wire). Confirms the +// server's per-connection signing state is isolated, and that one +// agent's handshake doesn't leak into the other's wire format. +func TestE2E_ConcurrentConnections_MixedSigningState(t *testing.T) { + f := newFixture(t, signing.AlgorithmECDSAP256SHA256) + + srv, endpoint := runServer(t, f.signer, nil, nil) + defer srv.Stop(context.Background()) + + // Agent A — requires attestation. Its verifier should fire. + var aMsgN atomic.Int32 + cA := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + aMsgN.Add(1) + }, + }, nil) + defer cA.Stop(context.Background()) + + // Agent B — same server, no verifier (does NOT require attestation). + // Server's PayloadSigner is configured, but B didn't opt in, so B + // must see plain ServerToAgent wire bytes. + var bMsgN atomic.Int32 + cB := startClient(t, "ws", endpoint, nil /* no verifier */, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + bMsgN.Add(1) + }, + }, nil) + defer cB.Stop(context.Background()) + + require.Eventually(t, func() bool { return aMsgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "agent A (requires) never received a message") + require.Eventually(t, func() bool { return bMsgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "agent B (no verifier) never received a message") + + // A's verifier ran; B's wire format never went through any verifier + // (B doesn't have one). The fact that B got a message at all is + // proof the server didn't accidentally wrap B's responses. + assert.GreaterOrEqual(t, f.verifier.validateChainN.Load(), int32(1), + "A's verifier should have validated the chain") +} + +// errSignFailure is the sentinel returned by the failing controlledSigner +// in TestE2E_Reject_MidStreamSignFailure_WS. +var errSignFailure = fmt.Errorf("e2e test: synthetic signer failure") + +// TestE2E_Reject_MidStreamSignFailure_WS — handshake succeeds; the +// server's signer fails on the second outbound Sign call. The +// server-side OnMessageResponseError callback fires; the agent never +// delivers a corrupt message; Send returns the signer's error. +func TestE2E_Reject_MidStreamSignFailure_WS(t *testing.T) { + f := newFixture(t, signing.AlgorithmECDSAP256SHA256) + bad := &controlledSigner{ + inner: f.signer, + failFromCall: 2, + failErr: errSignFailure, + } + + var savedConn atomic.Pointer[servertypes.Connection] + onConnected := func(_ context.Context, conn servertypes.Connection) { + savedConn.Store(&conn) + } + + srv, endpoint := runServer(t, bad, onConnected, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, nil) + defer c.Stop(context.Background()) + + // First message succeeds (callN starts at 0; failFromCall is 2). + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "first envelope should pass since its signature is well-formed") + require.NotNil(t, savedConn.Load(), "server never observed OnConnected") + + // Push the second outbound — signer will fail this time. The + // signer's error propagates back through wsConnection.Send. + conn := *savedConn.Load() + err := conn.Send(context.Background(), &protobufs.ServerToAgent{ + InstanceUid: []byte("mid-stream-uid00"), + }) + require.ErrorIs(t, err, errSignFailure, "Send should propagate the signer's error") + + // Confirm the agent never sees the corrupt message — neither + // payload (since signing failed before wire write) nor a tampered + // envelope (since we error out before WriteWSMessage runs). time.Sleep(e2eNonOccurrenceDeadline) - assert.Equal(t, int32(0), msgN.Load(), "OnMessage should not fire when HTTP attestation fails") - assert.GreaterOrEqual(t, client2.verifier.validateChainN.Load(), int32(1), "ValidateChain should have been called") + assert.Equal(t, int32(1), msgN.Load(), "agent should still have only the first message") +} + +// TestE2E_SendBeforeNegotiation_Errors_WS — when the server has a +// PayloadSigner configured and the user's OnConnected callback calls +// Connection.Send BEFORE the first AgentToServer has been processed, +// Send must return ErrSendBeforeNegotiated rather than emitting +// unsigned wire bytes. Closes the silent-bypass gap the code review +// flagged. +func TestE2E_SendBeforeNegotiation_Errors_WS(t *testing.T) { + f := newFixture(t, signing.AlgorithmECDSAP256SHA256) + + var sendErr atomic.Value // error + onConnected := func(_ context.Context, conn servertypes.Connection) { + err := conn.Send(context.Background(), &protobufs.ServerToAgent{ + InstanceUid: []byte("premature-uid000"), + }) + sendErr.Store(err) + } + + srv, endpoint := runServer(t, f.signer, onConnected, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, f.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, nil) + defer c.Stop(context.Background()) + + require.Eventually(t, func() bool { return sendErr.Load() != nil }, e2eDeadline, 10*time.Millisecond, + "server's OnConnected never fired") + got, ok := sendErr.Load().(error) + require.True(t, ok, "sendErr should hold an error") + require.ErrorIs(t, got, server.ErrSendBeforeNegotiated, + "Send before the first AgentToServer should error when PayloadSigner is configured") + + // After the pre-handshake Send error, the agent should still + // negotiate normally on its next AgentToServer and receive a + // valid signed response. + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "negotiation should still succeed despite the pre-handshake Send error") +} + +// TestE2E_SendBeforeNegotiation_NoSigner_AllowsSend_WS — control case +// for the test above: when PayloadSigner is NOT configured, Send from +// OnConnected works as before (no negotiation gate). Confirms the +// gate is strictly scoped to attestation-enabled servers. +func TestE2E_SendBeforeNegotiation_NoSigner_AllowsSend_WS(t *testing.T) { + var sendCalled atomic.Bool + var sendOK atomic.Bool + onConnected := func(_ context.Context, conn servertypes.Connection) { + err := conn.Send(context.Background(), &protobufs.ServerToAgent{ + InstanceUid: []byte("preflight-uid000"), + }) + sendCalled.Store(true) + if err == nil { + sendOK.Store(true) + } + } + + srv, endpoint := runServer(t, nil /* no signer */, onConnected, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + c := startClient(t, "ws", endpoint, nil /* no verifier */, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, nil) + defer c.Stop(context.Background()) + + require.Eventually(t, sendCalled.Load, e2eDeadline, 10*time.Millisecond, + "server's OnConnected never fired") + assert.True(t, sendOK.Load(), + "Send before negotiation should succeed when PayloadSigner is nil") + + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "client should still receive messages") } diff --git a/server/attestation_test.go b/server/attestation_test.go index 655bc1bd..8372b744 100644 --- a/server/attestation_test.go +++ b/server/attestation_test.go @@ -267,8 +267,12 @@ func TestServerWraps_WhenAgentRequires_WS(t *testing.T) { // TestServerDoesNotWrap_WhenAgentDoesNotRequire_WS confirms that when // the Agent doesn't set the Requires bit, the Server's outbound -// messages are plain ServerToAgent (wire-identical to upstream), -// even with PayloadSigner configured. +// messages are plain ServerToAgent — NOT wrapped in an envelope — +// even with PayloadSigner configured. The server still advertises +// OffersPayloadTrustVerification in response.Capabilities so the +// Agent learns it could opt in on a future reconnect (per the spec's +// negotiation matrix: No/Yes quadrant — server capable, agent +// declined). func TestServerDoesNotWrap_WhenAgentDoesNotRequire_WS(t *testing.T) { f := newServerSigningFixture(t) @@ -309,17 +313,38 @@ func TestServerDoesNotWrap_WhenAgentDoesNotRequire_WS(t *testing.T) { // and SignedServerToAgent partially decodable from the same wire // bytes (InstanceUid vs Payload), so we can't disprove the // envelope shape by attempting to decode as one. Instead, decode - // as the expected ServerToAgent and confirm the InstanceUid - // round-trips and the Signature/TrustChainResponse fields are - // absent from the wire by checking the envelope decode produces - // an empty Signature (which is field 2 — distinct from anything - // ServerToAgent carries). + // the wire as ServerToAgent and assert InstanceUid round-trips; + // then decode as SignedServerToAgent and check the envelope-only + // fields (Signature field 2, TrustChainResponse field 3) are + // absent — neither of which exists on ServerToAgent. var response protobufs.ServerToAgent require.NoError(t, sharedinternal.DecodeWSMessage(frame, &response)) assert.Equal(t, sendMsg.InstanceUid, response.InstanceUid) + // Offers bit MUST be advertised because PayloadSigner is + // configured — spec No/Yes quadrant. + offersBit := uint64(protobufs.ServerCapabilities_ServerCapabilities_OffersPayloadTrustVerification) + assert.NotZero(t, response.Capabilities&offersBit, + "OffersPayloadTrustVerification should be advertised whenever PayloadSigner is configured") var envelope protobufs.SignedServerToAgent require.NoError(t, sharedinternal.DecodeWSMessage(frame, &envelope)) require.Empty(t, envelope.Signature, "no signature emitted when Agent didn't opt in") require.Nil(t, envelope.TrustChainResponse, "no chain emitted when Agent didn't opt in") } + +// TestSignOutgoing_MidStreamSignFailure_PropagatesError exercises the +// failingSigner.signErr path: a signer that produces a valid chain +// but fails when asked to Sign. signOutgoing must propagate the +// signer's error wrapped with context. +func TestSignOutgoing_MidStreamSignFailure_PropagatesError(t *testing.T) { + ctx := context.Background() + signErr := errors.New("e2e test: synthetic signer failure") + bad := &failingSigner{signErr: signErr} + + state, err := newConnectionSigningState(ctx, bad) + require.NoError(t, err, "ChainDER should succeed; the failure is on Sign") + + _, err = state.signOutgoing(ctx, &protobufs.ServerToAgent{InstanceUid: testInstanceUid}) + require.Error(t, err) + require.ErrorIs(t, err, signErr, "signOutgoing should propagate the signer's error") +} From eb885134b7167f5b0e3f2dc5e0ee21d6d5174694 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Thu, 21 May 2026 12:28:19 +0200 Subject: [PATCH 12/22] Fix pre-existing TestRedirectHTTP failure on master MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestRedirectHTTP/{simple_redirect,check_redirect} have been failing on upstream master since #269 introduced them — this is a pre-existing bug unrelated to Message Attestation work; we are fixing it here so the client/ suite goes green. Two bugs combined to silently break the test: 1) `redirectServer` (client/wsclient_test.go) accepted a status int argument but hardcoded http.StatusSeeOther (303), so the test intent of "redirect with 302" actually emitted 303 on the wire. 2) Both 302 and 303 cause net/http's client to convert POST → GET when following the redirect (RFC 7231 §6.4.4), stripping Content-Type along the way. The OpAMP mock server dispatches on Content-Type: application/x-protobuf and falls through to a WebSocket upgrade for anything else — the upgrader writes 400 on a GET without WS headers, which the HTTP client treats as a non-retryable error and silently drops without firing OnConnect/OnConnectFailed. The test's eventually(...) wait then times out. Fix: - Honour the status arg in redirectServer (one-line cleanup). - Switch TestRedirectHTTP to http.StatusTemporaryRedirect (307), which preserves both method and body across the redirect, so the redirected request stays a POST with the protobuf payload and the mock server's plain-HTTP path handles it normally. - Set req.GetBody in HTTPSender.prepareRequest so net/http can replay the request body on method-preserving redirects (307/308) without the "http: can't replay request body" error. This is also a small real fix for OpAMP HTTP clients pointing at servers behind redirecting load balancers. TestRedirectWS continues to use 302 — WebSocket uses GET, so the POST→GET conversion is a no-op and the redirect chain works either way. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/httpclient_test.go | 12 +++++++++--- client/internal/httpsender.go | 9 +++++++++ client/wsclient_test.go | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/client/httpclient_test.go b/client/httpclient_test.go index 522d29d4..7c6ed1c4 100644 --- a/client/httpclient_test.go +++ b/client/httpclient_test.go @@ -249,17 +249,23 @@ func TestRedirectHTTP(t *testing.T) { MockRedirect *checkRedirectMock }{ { + // 307 Temporary Redirect preserves both method and body, so + // the redirected POST still reaches redirectee as a POST with + // the OpAMP protobuf payload — the mock server's plain-HTTP + // dispatch path handles it. (302/303 would convert POST→GET + // in net/http, stripping Content-Type, so the mock server + // would fall through to a WS upgrade and fail with 400.) Name: "simple redirect", - Redirector: redirectServer("http://"+redirectee.Endpoint, 302), + Redirector: redirectServer("http://"+redirectee.Endpoint, http.StatusTemporaryRedirect), }, { Name: "check redirect", - Redirector: redirectServer("http://"+redirectee.Endpoint, 302), + Redirector: redirectServer("http://"+redirectee.Endpoint, http.StatusTemporaryRedirect), MockRedirect: mockRedirectHTTP(t, 1, nil), }, { Name: "check redirect returns error", - Redirector: redirectServer("http://"+redirectee.Endpoint, 302), + Redirector: redirectServer("http://"+redirectee.Endpoint, http.StatusTemporaryRedirect), MockRedirect: mockRedirectHTTP(t, 1, errors.New("hello")), ExpError: true, }, diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go index b089a1b0..5507fda3 100644 --- a/client/internal/httpsender.go +++ b/client/internal/httpsender.go @@ -361,6 +361,15 @@ func (h *HTTPSender) prepareRequest(ctx context.Context) (*requestWrapper, error req.bodyReader = bodyReader(data) } + // Provide GetBody so net/http can replay the request body when + // following a method-preserving redirect (307/308). Without this, + // the http.Client returns "http: can't replay request body" before + // re-issuing the redirected POST. + br := req.bodyReader + r.GetBody = func() (io.ReadCloser, error) { + return br(), nil + } + req.Header = h.getHeader() if msgToSend.InstanceUid != nil { diff --git a/client/wsclient_test.go b/client/wsclient_test.go index 36314730..b5ec73af 100644 --- a/client/wsclient_test.go +++ b/client/wsclient_test.go @@ -328,7 +328,7 @@ func TestVerifyWSCompress(t *testing.T) { func redirectServer(to string, status int) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - http.Redirect(w, req, to, http.StatusSeeOther) + http.Redirect(w, req, to, status) })) } From 8c3de6962fd532c0d4f3eef26d292f4be82e5516 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Mon, 25 May 2026 17:04:50 +0200 Subject: [PATCH 13/22] Tighten signing/ package conventions and add downgrade defense-in-depth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit signing/doc.go (new) - Moved the 23-line package-comment block out of types.go into its own doc.go file, matching the Go convention for package documentation. Added a paragraph noting that GenerateCA / GenerateLeaf are exported test helpers (kept in this package rather than a sibling signingtest subpackage because in-package signing tests need them without creating an import cycle). - signing/types.go now opens directly with `package signing`. Defense-in-depth on the client receive path: - client/internal/attestation.go's unwrapServerToAgent now rejects inner ServerToAgent payloads that decode to all-default values (proto.Equal to the zero ServerToAgent). New sentinel ErrEmptyInnerServerToAgent + entry in isAttestationFailure. Rationale: proto3 field-1 wire-type collision means a malicious server that downgrades by sending a plain ServerToAgent (no envelope) has its InstanceUid bytes misinterpreted as SignedServerToAgent.payload. The inner decode of those random UUID bytes either errors (proto3 tag validation) or produces a default-valued ServerToAgent. ProcessEnvelope's chain/signature checks already terminate the connection on the first message of this attack — this check is belt-and-suspenders that pins the contract: every legitimate ServerToAgent the agent processes has at least one non-default field (typically InstanceUid, auto-filled by serverimpl.go). Verified clean under -race × 2 across signing/, client/internal/, internal/integrationtest/. Co-Authored-By: Claude Opus 4.7 (1M context) --- client/internal/attestation.go | 22 ++++++++++++++++++++++ signing/doc.go | 30 ++++++++++++++++++++++++++++++ signing/types.go | 23 ----------------------- 3 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 signing/doc.go diff --git a/client/internal/attestation.go b/client/internal/attestation.go index d963c78d..0be4bb1b 100644 --- a/client/internal/attestation.go +++ b/client/internal/attestation.go @@ -39,6 +39,18 @@ var ( // payload would unmarshal into an empty ServerToAgent and is // rejected eagerly. ErrMissingPayload = errors.New("client: SignedServerToAgent missing payload") + + // ErrEmptyInnerServerToAgent is returned when the inner payload + // decodes to a ServerToAgent with all default values. Defends + // against the proto3 field-1 wire-type collision: a malicious + // server that downgrades by responding with a plain ServerToAgent + // has its InstanceUid bytes misinterpreted as + // SignedServerToAgent.payload; the inner decode of those random + // UUID bytes either errors or produces a default-valued message. + // Legitimate server responses always carry at least InstanceUid + // because handleWSConnection auto-fills it (see + // server/serverimpl.go). + ErrEmptyInnerServerToAgent = errors.New("client: inner ServerToAgent decoded to all default values; likely downgrade attempt") ) // attestationState holds per-connection state for payload trust @@ -95,6 +107,7 @@ func isAttestationFailure(err error) bool { errors.Is(err, ErrTrustChainErrorReported) || errors.Is(err, ErrMissingSignature) || errors.Is(err, ErrMissingPayload) || + errors.Is(err, ErrEmptyInnerServerToAgent) || errors.Is(err, signing.ErrChainValidation) || errors.Is(err, signing.ErrSignatureMismatch) || errors.Is(err, signing.ErrEmptyChain) || @@ -191,5 +204,14 @@ func unwrapServerToAgent(ctx context.Context, state *attestationState, rawProto if err := proto.Unmarshal(payload, msg); err != nil { return fmt.Errorf("client: decode inner ServerToAgent: %w", err) } + // Defense in depth against proto3 field-1 wire-type collision. + // ProcessEnvelope's chain/signature checks already terminate the + // connection on the downgrade path that produces this state, but + // this check pins the contract: every legitimate ServerToAgent + // the agent processes has at least one non-default field + // (typically InstanceUid). + if proto.Equal(msg, &protobufs.ServerToAgent{}) { + return ErrEmptyInnerServerToAgent + } return nil } diff --git a/signing/doc.go b/signing/doc.go new file mode 100644 index 00000000..92ed5d45 --- /dev/null +++ b/signing/doc.go @@ -0,0 +1,30 @@ +// Package signing implements payload trust verification (Message +// Attestation) for the OpAMP protocol. +// +// The package exposes two interfaces — [Signer] and [Verifier] — together +// with in-process reference implementations [LocalSigner] and +// [LocalVerifier]. The split between interface and implementation lets +// downstream consumers plug in alternative signers backed by remote +// signing services (for example HSM-backed RPC endpoints or hosted +// signing platforms with policy gating) without touching the wire-level +// code in the opamp-go client or server. +// +// Signing is performed over the raw bytes of a marshalled +// [protobufs.ServerToAgent] (the bytes carried in +// SignedServerToAgent.payload on the wire), producing a detached +// signature placed in SignedServerToAgent.signature. The receiver +// verifies the signature over the bytes exactly as they arrive on the +// wire — no re-marshalling is required, sidestepping protobuf's +// non-canonical-encoding caveat. See the Message Attestation section +// of the OpAMP specification for the wire protocol. +// +// The signing algorithm for a given connection is determined by the +// signing certificate's SignatureAlgorithm field; the OpAMP protocol +// does not negotiate algorithms. +// +// [GenerateCA] and [GenerateLeaf] are exported test helpers; they +// also serve smoke tests and the example server. Production +// deployments use externally-managed PKI and only need the +// [LocalSigner] / [LocalVerifier] constructors, the loader helpers, +// or a custom [Signer] / [Verifier] implementation. +package signing diff --git a/signing/types.go b/signing/types.go index 593b3867..03287855 100644 --- a/signing/types.go +++ b/signing/types.go @@ -1,26 +1,3 @@ -// Package signing implements payload trust verification (Message -// Attestation) for the OpAMP protocol. -// -// The package exposes two interfaces — [Signer] and [Verifier] — together -// with in-process reference implementations [LocalSigner] and -// [LocalVerifier]. The split between interface and implementation lets -// downstream consumers plug in alternative signers backed by remote -// signing services (for example HSM-backed RPC endpoints or hosted -// signing platforms with policy gating) without touching the wire-level -// code in the opamp-go client or server. -// -// Signing is performed over the raw bytes of a marshalled -// [protobufs.ServerToAgent] (the bytes carried in -// SignedServerToAgent.payload on the wire), producing a detached -// signature placed in SignedServerToAgent.signature. The receiver -// verifies the signature over the bytes exactly as they arrive on the -// wire — no re-marshalling is required, sidestepping protobuf's -// non-canonical-encoding caveat. See the Message Attestation section -// of the OpAMP specification for the wire protocol. -// -// The signing algorithm for a given connection is determined by the -// signing certificate's SignatureAlgorithm field; the OpAMP protocol -// does not negotiate algorithms. package signing import ( From 02d83f5d17d147f3d293454592e28f546e85ba3a Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Mon, 25 May 2026 17:47:20 +0200 Subject: [PATCH 14/22] Add BenchmarkSign and BenchmarkVerify for the four supported algorithms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gives operators a number when they ask "what's the per-message overhead of OpAMP Message Attestation?" — and gives us a regression signal against the algorithm dispatch code. Measurements on Apple M1 Max (10 iterations each): BenchmarkSign/ECDSA-P256-SHA256 25 µs / 6.1 KB / 59 allocs BenchmarkSign/ECDSA-P384-SHA384 174 µs / 6.2 KB / 61 allocs BenchmarkSign/RSA-PKCS1v15-SHA256 1264 µs / 512 B / 2 allocs BenchmarkSign/Ed25519 31 µs / 88 B / 1 alloc BenchmarkVerify/ECDSA-P256-SHA256 87 µs / 576 B / 10 allocs BenchmarkVerify/ECDSA-P384-SHA384 606 µs / 808 B / 17 allocs BenchmarkVerify/RSA-PKCS1v15-SHA256 30 µs / 1376 B / 9 allocs BenchmarkVerify/Ed25519 45 µs / 0 B / 0 allocs (Asymmetric Sign/Verify costs are expected — RSA verify is cheap but sign is expensive; ECDSA is the opposite. Ed25519 is fast and allocation-free on both sides, making it the right default for both small and large fleets.) Run with: go test -bench=. -benchmem ./signing/ Co-Authored-By: Claude Opus 4.7 (1M context) --- signing/benchmark_test.go | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 signing/benchmark_test.go diff --git a/signing/benchmark_test.go b/signing/benchmark_test.go new file mode 100644 index 00000000..519a053a --- /dev/null +++ b/signing/benchmark_test.go @@ -0,0 +1,93 @@ +package signing + +import ( + "context" + "crypto/x509" + "testing" +) + +// benchPayload approximates the size of a typical ServerToAgent — +// not so small that fixed costs dominate, not so large that the +// hash function (rather than the asymmetric op) becomes the bottleneck. +// 1 KiB is roughly an OpAMP RemoteConfig with a handful of receivers. +var benchPayload = func() []byte { + b := make([]byte, 1024) + for i := range b { + b[i] = byte(i) + } + return b +}() + +// BenchmarkSign reports the per-message Sign cost for each supported +// algorithm. Useful for operators sizing OpAMP servers: at 10⁶ agents +// each receiving a heartbeat every 30s, the server's signing budget +// is ~33k ops/s — these numbers tell you whether that fits one CPU +// core. +// +// Run with: +// +// go test -bench=BenchmarkSign -benchmem ./signing/ +func BenchmarkSign(b *testing.B) { + for _, alg := range allAlgorithms { + b.Run(alg.String(), func(b *testing.B) { + _, signer, _ := benchKeyPair(b, alg) + ctx := context.Background() + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := signer.Sign(ctx, benchPayload); err != nil { + b.Fatal(err) + } + } + }) + } +} + +// BenchmarkVerify reports the per-message Verify cost for each +// supported algorithm. This is the agent's per-envelope overhead in +// steady state (chain validation runs only on the first envelope of +// each connection). +func BenchmarkVerify(b *testing.B) { + for _, alg := range allAlgorithms { + b.Run(alg.String(), func(b *testing.B) { + leaf, signer, verifier := benchKeyPair(b, alg) + ctx := context.Background() + sig, err := signer.Sign(ctx, benchPayload) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if err := verifier.Verify(ctx, benchPayload, sig, leaf); err != nil { + b.Fatal(err) + } + } + }) + } +} + +// benchKeyPair mirrors testKeyPair but takes *testing.B. Kept separate +// to avoid coupling benchmark setup to test-only assertions. +func benchKeyPair(b *testing.B, alg Algorithm) (*x509.Certificate, *LocalSigner, *LocalVerifier) { + b.Helper() + ca, caKey, err := GenerateCA(alg, CertOptions{}) + if err != nil { + b.Fatal(err) + } + leaf, leafKey, err := GenerateLeaf(alg, ca, caKey, CertOptions{}) + if err != nil { + b.Fatal(err) + } + signer, err := NewLocalSigner(leafKey, []*x509.Certificate{leaf}) + if err != nil { + b.Fatal(err) + } + roots := x509.NewCertPool() + roots.AddCert(ca) + verifier, err := NewLocalVerifier(roots) + if err != nil { + b.Fatal(err) + } + return leaf, signer, verifier +} From 19153f5391a7a07d01df316441280f921eab1975 Mon Sep 17 00:00:00 2001 From: Jaime Fullaondo Date: Mon, 25 May 2026 17:55:03 +0200 Subject: [PATCH 15/22] Add e2e test for mid-stream server key rotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pins a contract that was documented only in code comments before: rotating the server's underlying signing key mid-stream MUST NOT compromise the agent's currently-live connection — signatures produced by the rotated key fail verification against the snapshotted leaf the agent locked in on handshake, the agent terminates the connection, and on the next reconnect it re-fetches the new chain. Two locations in opamp-go already promise this behaviour in comments: - server/attestation.go's connectionSigningState: "the certificate chain is snapshotted at construction time so that operator-side cert rotation does not affect a live connection" - client/internal/httpsender.go's Reset(): "recover from mid-stream faults such as server-side key rotation" Neither claim had a test until now. The new TestE2E_MidStreamKeyRotation_DoesNotAffectLiveConnection uses a rotatableSigner (atomic.Pointer to *LocalSigner) so the test can swap the underlying signer at any point without re-running the server's chain-snapshot path. After the swap, signatures use the new key but the agent's cached leaf is still the old one — verifying fails and the connection terminates. Verified clean under -race × 3. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../integrationtest/attestation_e2e_test.go | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/internal/integrationtest/attestation_e2e_test.go b/internal/integrationtest/attestation_e2e_test.go index d1f0778a..7e00c3b5 100644 --- a/internal/integrationtest/attestation_e2e_test.go +++ b/internal/integrationtest/attestation_e2e_test.go @@ -748,3 +748,102 @@ func TestE2E_SendBeforeNegotiation_NoSigner_AllowsSend_WS(t *testing.T) { require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, "client should still receive messages") } + +// rotatableSigner wraps a signing.Signer indirectly: it holds an +// atomic.Pointer that the test can swap to a different inner signer +// at any moment. Used by the mid-stream key rotation test below. +// +// The contract being tested: opamp-go's server snapshots the chain +// at connection accept time (newConnectionSigningState calls +// ChainDER once). Subsequent Sign calls go to whatever the current +// inner signer is — but the agent's verifier locked in the FIRST +// chain's leaf via firstSeen, so signatures from a rotated inner +// signer (different key) MUST fail verification on the live +// connection. Rotation only takes effect on RECONNECT. +type rotatableSigner struct { + inner atomic.Pointer[signing.LocalSigner] +} + +func (r *rotatableSigner) Sign(ctx context.Context, payload []byte) ([]byte, error) { + return r.inner.Load().Sign(ctx, payload) +} + +func (r *rotatableSigner) ChainDER(ctx context.Context) ([][]byte, error) { + return r.inner.Load().ChainDER(ctx) +} + +// TestE2E_MidStreamKeyRotation_DoesNotAffectLiveConnection pins the +// per-connection chain snapshot semantic: rotating the server's +// underlying signing key mid-stream MUST NOT compromise the agent's +// current connection — signatures from the rotated key fail +// verification against the snapshotted leaf, the agent terminates +// the connection, and on reconnect it picks up the new chain. +// +// This is documented in two code comments today +// (server/attestation.go's connectionSigningState — "the chain is +// snapshotted at construction time so that operator-side cert +// rotation does not affect a live connection" — and +// client/internal/httpsender.go's Reset() — "recover from mid-stream +// faults such as server-side key rotation") but had no test until +// now. +func TestE2E_MidStreamKeyRotation_DoesNotAffectLiveConnection(t *testing.T) { + // First key pair: server signs the handshake with this, agent's + // verifier locks in the leaf via firstSeen. + first := newFixture(t, signing.AlgorithmECDSAP256SHA256) + rot := &rotatableSigner{} + rot.inner.Store(first.signer.(*signing.LocalSigner)) + + var savedConn atomic.Pointer[servertypes.Connection] + srv, endpoint := runServer(t, rot, func(_ context.Context, conn servertypes.Connection) { + savedConn.Store(&conn) + }, nil) + defer srv.Stop(context.Background()) + + var msgN atomic.Int32 + logger := &captureLogger{} + c := startClient(t, "ws", endpoint, first.verifier, clienttypes.Callbacks{ + OnMessage: func(_ context.Context, _ *clienttypes.MessageData) { + msgN.Add(1) + }, + }, logger) + defer c.Stop(context.Background()) + + // Handshake: first envelope arrives signed by the FIRST key, + // carrying the FIRST chain. Agent validates chain, caches leaf. + require.Eventually(t, func() bool { return msgN.Load() >= 1 }, e2eDeadline, 10*time.Millisecond, + "client never received the first signed envelope") + require.NotNil(t, savedConn.Load(), "server never observed OnConnected") + + // Rotate: swap the underlying signer to a fresh key pair. The + // server's per-connection state still holds the FIRST chain + // snapshot — but Sign() will now produce signatures from the + // SECOND key. + second := newFixture(t, signing.AlgorithmECDSAP256SHA256) + rot.inner.Store(second.signer.(*signing.LocalSigner)) + + // Snapshot the message count so we can assert the rotated-key + // push doesn't slip through. + msgsBeforeRotation := msgN.Load() + + // Push a second server-initiated message. The wrapper signs with + // the second key; the agent's cached leaf is the first key's + // public key; verification fails; the WS receive loop terminates + // the connection. The agent will reconnect and re-handshake against + // the new chain — but that's not exercised here. + conn := *savedConn.Load() + require.NoError(t, conn.Send(context.Background(), &protobufs.ServerToAgent{ + InstanceUid: []byte("post-rotate-uid0"), + })) + + // Agent must log the attestation failure within e2eDeadline. + require.Eventually(t, func() bool { + return logger.hasErrorContaining(attestationFailureLogSubstring) + }, e2eDeadline, 10*time.Millisecond, + "client did not log %q for the rotated-key envelope", attestationFailureLogSubstring) + + // The rotated envelope must NOT have been delivered to OnMessage. + // msgN may still equal msgsBeforeRotation, but it must not grow. + time.Sleep(e2eNonOccurrenceDeadline) + assert.Equal(t, msgsBeforeRotation, msgN.Load(), + "rotated-key envelope must not be delivered to OnMessage") +} From 5627d935025b73ae105922535192e6aade96545e Mon Sep 17 00:00:00 2001 From: Stanley Liu Date: Mon, 15 Jun 2026 14:55:23 -0400 Subject: [PATCH 16/22] Fix exponential backoff for message attestation retries --- client/internal/wsreceiver.go | 14 +++++++++++++ client/wsclient.go | 38 +++++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go index 65f5992a..0c9d5df3 100644 --- a/client/internal/wsreceiver.go +++ b/client/internal/wsreceiver.go @@ -29,6 +29,11 @@ type wsReceiver struct { // Indicates that the receiver has fully stopped. stopped chan struct{} + + // Set to true (before stopped is closed) when the loop exits because + // of a payload trust verification failure. Safe to read only after + // <-IsStopped() returns. + attestationFailure bool } // NewWSReceiver creates a new Receiver that uses WebSocket to receive @@ -75,6 +80,12 @@ func (r *wsReceiver) IsStopped() <-chan struct{} { return r.stopped } +// WasAttestationFailure reports whether the receiver stopped because of a +// payload trust verification failure. Only valid after <-IsStopped() returns. +func (r *wsReceiver) WasAttestationFailure() bool { + return r.attestationFailure +} + // ReceiverLoop runs the receiver loop. // To stop the receiver cancel the context and close the websocket connection func (r *wsReceiver) ReceiverLoop(ctx context.Context) { @@ -120,6 +131,9 @@ func (r *wsReceiver) ReceiverLoop(ctx context.Context) { if r.conn != nil { _ = r.conn.Close() } + // Mark before returning so the caller can read + // WasAttestationFailure() after <-IsStopped(). + r.attestationFailure = true return } if !websocket.IsCloseError(res.err, websocket.CloseNormalClosure) { diff --git a/client/wsclient.go b/client/wsclient.go index 7ed5618d..feb44f2e 100644 --- a/client/wsclient.go +++ b/client/wsclient.go @@ -354,7 +354,11 @@ func (c *wsClient) ensureConnected(ctx context.Context) error { // When Stop() is called (ctx is cancelled, isStopping is set), wsClient will shutdown gracefully: // 1. sender will be cancelled by the ctx, send the close message to server and return the error via sender.Err(). // 2. runOneCycle will handle that error and wait for the close message from server until timeout. -func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) { +// +// Returns true if the cycle ended because of a payload trust verification +// failure (wrong CA, bad signature, etc.). The caller should apply exponential +// backoff before retrying in that case. +func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) (attestationFailed bool) { if err := c.ensureConnected(ctx); err != nil { // Can't connect, so can't move forward. This currently happens when we // are being stopped. @@ -437,18 +441,48 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) { stopSender() <-c.sender.IsStopped() + attestationFailed = r.WasAttestationFailure() } + return } func (c *wsClient) runUntilStopped(ctx context.Context) { // Iterates until we detect that the client is stopping. sendFirstMessage := true + + // Separate backoff for attestation failures. ensureConnected already + // backs off TCP-level failures within a single runOneCycle call, but + // when the transport connects and only the application-level + // attestation check fails the receiver stops, runOneCycle returns, + // and ensureConnected would immediately succeed again on the next + // call (TCP is fine). Without this outer backoff the client would + // spin in a tight reject-reconnect loop as fast as the network + // allows, which is contrary to the spec's SHOULD-exponential-backoff + // requirement for attestation failures. + attestBackoff := backoff.NewExponentialBackOff() + attestBackoff.MaxElapsedTime = 0 // retry forever + for { if c.common.IsStopping() { return } - c.runOneCycle(ctx, sendFirstMessage) + if attestationFailed := c.runOneCycle(ctx, sendFirstMessage); attestationFailed { + interval := attestBackoff.NextBackOff() + c.common.Logger.Errorf(ctx, "Payload trust verification failed, will retry in %v.", interval) + timer := time.NewTimer(interval) + select { + case <-timer.C: + case <-ctx.Done(): + timer.Stop() + return + } + } else { + // Non-attestation cycle: reset so the next attestation + // failure starts backoff from the initial interval again. + attestBackoff.Reset() + } + sendFirstMessage = false } } From 925a42608482e8f23413bf69d137071320ca630f Mon Sep 17 00:00:00 2001 From: Stanley Liu Date: Mon, 15 Jun 2026 15:59:16 -0400 Subject: [PATCH 17/22] Align implementation with spec: PEM trust chain and mandatory first-message signature Switch TrustChainResponse.certificate_chain from a repeated DER-encoded Certificate message to a single PEM blob, matching the format now specified in the spec branch (truthbk:jaime/x509-spec-full-protocol). The proto is regenerated from the updated spec proto file; the nested TrustChainResponse_Certificate type is removed. On the server side, the snapshotted DER chain is now encoded to a concatenated PEM blob before being placed in TrustChainResponse. On the client side, the PEM blob is decoded back to individual DER slices via a new parsePEMChain helper before being passed to Verifier.ValidateChain. Make the signature field mandatory on every SignedServerToAgent, including the first message. The spec removed the "MAY leave signature empty on the first envelope" exception; ProcessEnvelope now enforces signature presence and validity unconditionally after chain validation, regardless of whether it is the first or a subsequent message. Tests are updated throughout: PEM encoding replaces DER construction in test fixtures, all happy-path calls to buildFirstEnvelope pass signFirst=true, and a new TestAttestationState_MissingSignatureOnFirst test covers the first-message rejection path. --- client/internal/attestation.go | 52 ++- client/internal/attestation_test.go | 50 ++- protobufs/opamp.pb.go | 641 +++++++++++++--------------- server/attestation.go | 9 +- server/attestation_test.go | 19 +- 5 files changed, 379 insertions(+), 392 deletions(-) diff --git a/client/internal/attestation.go b/client/internal/attestation.go index 0be4bb1b..6e1fe732 100644 --- a/client/internal/attestation.go +++ b/client/internal/attestation.go @@ -3,6 +3,7 @@ package internal import ( "context" "crypto/x509" + "encoding/pem" "errors" "fmt" "sync" @@ -29,10 +30,10 @@ var ( // satisfy the handshake. ErrTrustChainErrorReported = errors.New("client: server reported trust chain error") - // ErrMissingSignature is returned when a SignedServerToAgent after - // the first is missing its signature field. Subsequent messages - // MUST be signed. - ErrMissingSignature = errors.New("client: SignedServerToAgent missing signature on non-first message") + // ErrMissingSignature is returned when a SignedServerToAgent is + // missing its signature field. Every message MUST be signed, + // including the first. + ErrMissingSignature = errors.New("client: SignedServerToAgent missing signature") // ErrMissingPayload is returned when SignedServerToAgent.payload // is empty. The payload carries the inner ServerToAgent; an empty @@ -148,9 +149,9 @@ func (s *attestationState) ProcessEnvelope(ctx context.Context, envelope *protob if chainResp.ErrorMessage != "" { return nil, fmt.Errorf("%w: %s", ErrTrustChainErrorReported, chainResp.ErrorMessage) } - chainDER := make([][]byte, len(chainResp.CertificateChain)) - for i, cert := range chainResp.CertificateChain { - chainDER[i] = cert.GetDerData() + chainDER, err := parsePEMChain(chainResp.CertificateChain) + if err != nil { + return nil, fmt.Errorf("client: parse trust chain PEM: %w", err) } leaf, err := s.verifier.ValidateChain(ctx, chainDER, time.Now()) if err != nil { @@ -158,21 +159,9 @@ func (s *attestationState) ProcessEnvelope(ctx context.Context, envelope *protob } s.leaf = leaf s.firstSeen = true - - // First message MAY be unsigned per the spec (chain validation - // establishes trust at this point). If a signature is present, - // verify it as defence in depth — a server that supplies a - // signature alongside the chain handshake should produce a - // valid one. - if len(envelope.Signature) > 0 { - if err := s.verifier.Verify(ctx, envelope.Payload, envelope.Signature, leaf); err != nil { - return nil, fmt.Errorf("client: verify first message signature: %w", err) - } - } - return envelope.Payload, nil } - // Subsequent messages: signature MUST be present and verifiable. + // Every message — including the first — MUST carry a signature. if len(envelope.Signature) == 0 { return nil, ErrMissingSignature } @@ -182,6 +171,29 @@ func (s *attestationState) ProcessEnvelope(ctx context.Context, envelope *protob return envelope.Payload, nil } +// parsePEMChain decodes a concatenated PEM blob into individual DER byte +// slices ordered intermediates-first, leaf-last — the form expected by +// signing.Verifier.ValidateChain. +func parsePEMChain(pemBytes []byte) ([][]byte, error) { + var chain [][]byte + rest := pemBytes + for len(rest) > 0 { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if block.Type != "CERTIFICATE" { + continue + } + chain = append(chain, block.Bytes) + } + if len(chain) == 0 { + return nil, errors.New("no CERTIFICATE blocks found in PEM") + } + return chain, nil +} + // unwrapServerToAgent is a convenience that combines ProcessEnvelope // with proto.Unmarshal of the resulting payload bytes into msg. If // state is nil, the input bytes are unmarshalled directly as a diff --git a/client/internal/attestation_test.go b/client/internal/attestation_test.go index b551d220..0600d945 100644 --- a/client/internal/attestation_test.go +++ b/client/internal/attestation_test.go @@ -3,6 +3,7 @@ package internal import ( "context" "crypto/x509" + "encoding/pem" "errors" "fmt" "testing" @@ -40,8 +41,10 @@ func newAttestationFixture(t *testing.T) attestationFixture { } // buildFirstEnvelope produces the on-the-wire bytes for the FIRST -// SignedServerToAgent on a connection — carries trust_chain_response -// and an optional signature. +// SignedServerToAgent on a connection — carries trust_chain_response. +// signFirst controls whether a signature is included; the spec requires +// it on every message, so pass true for happy-path tests and false only +// when testing the missing-signature-on-first-message failure path. func (f attestationFixture) buildFirstEnvelope(t *testing.T, inner *protobufs.ServerToAgent, signFirst bool) *protobufs.SignedServerToAgent { t.Helper() payload, err := proto.Marshal(inner) @@ -49,15 +52,15 @@ func (f attestationFixture) buildFirstEnvelope(t *testing.T, inner *protobufs.Se chainDER, err := f.signer.ChainDER(context.Background()) require.NoError(t, err) - chain := make([]*protobufs.TrustChainResponse_Certificate, len(chainDER)) - for i, c := range chainDER { - chain[i] = &protobufs.TrustChainResponse_Certificate{DerData: c} + var pemChain []byte + for _, der := range chainDER { + pemChain = append(pemChain, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})...) } env := &protobufs.SignedServerToAgent{ Payload: payload, TrustChainResponse: &protobufs.TrustChainResponse{ - CertificateChain: chain, + CertificateChain: pemChain, }, } if signFirst { @@ -88,7 +91,7 @@ func TestAttestationState_FirstAndSubsequent(t *testing.T) { ctx := context.Background() first := &protobufs.ServerToAgent{InstanceUid: []byte("first-msg-uid000")} - firstEnv := f.buildFirstEnvelope(t, first, false /* signFirst */) + firstEnv := f.buildFirstEnvelope(t, first, true /* signFirst — mandatory per spec */) payload, err := state.ProcessEnvelope(ctx, firstEnv) require.NoError(t, err) @@ -111,21 +114,34 @@ func TestAttestationState_FirstAndSubsequent(t *testing.T) { require.Equal(t, second.InstanceUid, secondParsed.InstanceUid) } -// TestAttestationState_FirstMessageMayBeSigned confirms that when a -// server chooses to sign the first envelope (defence-in-depth), the -// signature is verified. -func TestAttestationState_FirstMessageMayBeSigned(t *testing.T) { +// TestAttestationState_FirstMessageMustBeSigned confirms that the first +// envelope's signature is verified against the freshly validated leaf. +func TestAttestationState_FirstMessageMustBeSigned(t *testing.T) { f := newAttestationFixture(t) state := newAttestationState(f.verifier) ctx := context.Background() inner := &protobufs.ServerToAgent{InstanceUid: []byte("signed-first-uid")} - env := f.buildFirstEnvelope(t, inner, true /* signFirst */) + env := f.buildFirstEnvelope(t, inner, true) _, err := state.ProcessEnvelope(ctx, env) require.NoError(t, err) } +// TestAttestationState_MissingSignatureOnFirst confirms that the first +// message is rejected when its signature is absent. +func TestAttestationState_MissingSignatureOnFirst(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier) + ctx := context.Background() + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("no-sig-first-uid")} + env := f.buildFirstEnvelope(t, inner, false /* no signature */) + + _, err := state.ProcessEnvelope(ctx, env) + require.ErrorIs(t, err, ErrMissingSignature) +} + // TestAttestationState_FirstMessageSignedButTampered confirms that // when the first message carries a signature but it's invalid, the // state rejects it. @@ -208,7 +224,7 @@ func TestAttestationState_MissingSignatureOnSubsequent(t *testing.T) { ctx := context.Background() first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} - _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, false)) + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, true)) require.NoError(t, err) second := &protobufs.ServerToAgent{InstanceUid: []byte("second0000000000")} @@ -228,7 +244,7 @@ func TestAttestationState_TamperedSignatureOnSubsequent(t *testing.T) { ctx := context.Background() first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} - _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, false)) + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, true)) require.NoError(t, err) second := &protobufs.ServerToAgent{InstanceUid: []byte("second0000000000")} @@ -275,7 +291,7 @@ func TestUnwrapServerToAgent_WithState_HappyPath(t *testing.T) { state := newAttestationState(f.verifier) inner := &protobufs.ServerToAgent{InstanceUid: []byte("envelope-uid0000")} - env := f.buildFirstEnvelope(t, inner, false) + env := f.buildFirstEnvelope(t, inner, true) envBytes, err := proto.Marshal(env) require.NoError(t, err) @@ -310,7 +326,7 @@ func TestAttestationState_Reset(t *testing.T) { // Drive the state through a successful handshake. first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} - _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, false)) + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, true)) require.NoError(t, err) require.True(t, state.firstSeen) require.NotNil(t, state.leaf) @@ -331,7 +347,7 @@ func TestAttestationState_Reset(t *testing.T) { require.ErrorIs(t, err, ErrMissingTrustChain) // A fresh first-message envelope succeeds after Reset. - thirdEnv := f.buildFirstEnvelope(t, second, false) + thirdEnv := f.buildFirstEnvelope(t, second, true) _, err = state.ProcessEnvelope(ctx, thirdEnv) require.NoError(t, err) } diff --git a/protobufs/opamp.pb.go b/protobufs/opamp.pb.go index a202c073..924d1be4 100644 --- a/protobufs/opamp.pb.go +++ b/protobufs/opamp.pb.go @@ -16,19 +16,18 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 -// protoc v7.34.1 +// protoc-gen-go v1.36.9 +// protoc v7.35.0 // source: opamp.proto package protobufs import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" - - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const ( @@ -1389,11 +1388,12 @@ func (x *ServerToAgent) GetCustomMessage() *CustomMessage { // Status: [Development] type TrustChainResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - // The certificate chain, ordered from the first intermediate certificate - // down to the signing leaf certificate. The root certificate is excluded; - // the Agent already possesses the root as its pre-configured payload - // trust anchor. - CertificateChain []*TrustChainResponse_Certificate `protobuf:"bytes,1,rep,name=certificate_chain,json=certificateChain,proto3" json:"certificate_chain,omitempty"` + // PEM-encoded certificate chain, ordered from the first intermediate + // certificate down to the signing leaf certificate. The root certificate + // is excluded; the Agent already possesses the root as its pre-configured + // payload trust anchor. Multiple certificates are concatenated in a single + // PEM blob, consistent with the encoding used by TLSCertificate. + CertificateChain []byte `protobuf:"bytes,1,opt,name=certificate_chain,json=certificateChain,proto3" json:"certificate_chain,omitempty"` // Human-readable error message indicating why the Server could not // satisfy the trust chain request. If error_message is set, the Agent // MUST terminate the connection. @@ -1432,7 +1432,7 @@ func (*TrustChainResponse) Descriptor() ([]byte, []int) { return file_opamp_proto_rawDescGZIP(), []int{8} } -func (x *TrustChainResponse) GetCertificateChain() []*TrustChainResponse_Certificate { +func (x *TrustChainResponse) GetCertificateChain() []byte { if x != nil { return x.CertificateChain } @@ -1465,21 +1465,19 @@ type SignedServerToAgent struct { // Serialised bytes of a ServerToAgent message. The Agent verifies the // detached signature over these exact bytes, without re-marshalling, // and then unmarshals them into a ServerToAgent for normal processing. - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - // Detached signature over the bytes of the payload field. MAY be empty - // on the first SignedServerToAgent of a connection: trust on the first - // message is established by validating the certificate chain carried in - // trust_chain_response against the Agent's pre-configured payload trust - // anchor. MUST be present and verifiable on every subsequent - // SignedServerToAgent. - Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + Payload []byte `protobuf:"bytes,14,opt,name=payload,proto3" json:"payload,omitempty"` + // Detached signature over the bytes of the payload field. MUST be + // present and verifiable on every SignedServerToAgent, including the + // first. There is no exception for the first message. + Signature []byte `protobuf:"bytes,15,opt,name=signature,proto3" json:"signature,omitempty"` // Sent only in the first SignedServerToAgent on a connection. Carries - // the signing certificate chain the Agent will use to verify signatures - // on subsequent messages. If the Agent set + // the signing certificate chain the Agent uses to validate the leaf + // certificate and verify signatures on all messages including this one. + // If the Agent set // RequiresPayloadTrustVerification but the first SignedServerToAgent // does not include a usable trust_chain_response, the Agent MUST // terminate the connection. - TrustChainResponse *TrustChainResponse `protobuf:"bytes,3,opt,name=trust_chain_response,json=trustChainResponse,proto3" json:"trust_chain_response,omitempty"` + TrustChainResponse *TrustChainResponse `protobuf:"bytes,16,opt,name=trust_chain_response,json=trustChainResponse,proto3" json:"trust_chain_response,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1542,7 +1540,7 @@ func (x *SignedServerToAgent) GetTrustChainResponse() *TrustChainResponse { type OpAMPConnectionSettings struct { state protoimpl.MessageState `protogen:"open.v1"` // OpAMP Server URL This MUST be a WebSocket or HTTP URL and MUST be non-empty, for - // example: "wss://example.com:4318/v1/opamp" + // example: "wss://example.com:4320/v1/opamp" DestinationEndpoint string `protobuf:"bytes,1,opt,name=destination_endpoint,json=destinationEndpoint,proto3" json:"destination_endpoint,omitempty"` // Optional headers to use when connecting. Typically used to set access tokens or // other authorization headers. For HTTP-based protocols the Agent should @@ -1885,7 +1883,7 @@ type TLSConnectionSettings struct { InsecureSkipVerify bool `protobuf:"varint,3,opt,name=insecure_skip_verify,json=insecureSkipVerify,proto3" json:"insecure_skip_verify,omitempty"` // Minimum accepted TLS version; default "1.2". MinVersion string `protobuf:"bytes,4,opt,name=min_version,json=minVersion,proto3" json:"min_version,omitempty"` - // Minimum accepted TLS version; default "". + // Maximum accepted TLS version; default "". MaxVersion string `protobuf:"bytes,5,opt,name=max_version,json=maxVersion,proto3" json:"max_version,omitempty"` // Explicit list of cipher suites. CipherSuites []string `protobuf:"bytes,6,rep,name=cipher_suites,json=cipherSuites,proto3" json:"cipher_suites,omitempty"` @@ -2389,7 +2387,7 @@ func (x *PackagesAvailable) GetAllPackagesHash() []byte { // Status: [Beta] type PackageAvailable struct { state protoimpl.MessageState `protogen:"open.v1"` - Type PackageType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.PackageType" json:"type,omitempty"` + Type PackageType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.v1.PackageType" json:"type,omitempty"` // The package version that is available on the Server side. The Agent may for // example use this information to avoid downloading a package that was previously // already downloaded and failed to install. @@ -2551,7 +2549,7 @@ func (x *DownloadableFile) GetHeaders() *Headers { type ServerErrorResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Type ServerErrorResponseType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.ServerErrorResponseType" json:"type,omitempty"` + Type ServerErrorResponseType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.v1.ServerErrorResponseType" json:"type,omitempty"` // Error message in the string form, typically human readable. ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` // Types that are valid to be assigned to Details: @@ -2682,7 +2680,7 @@ func (x *RetryInfo) GetRetryAfterNanoseconds() uint64 { // Status: [Beta] type ServerToAgentCommand struct { state protoimpl.MessageState `protogen:"open.v1"` - Type CommandType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.CommandType" json:"type,omitempty"` + Type CommandType `protobuf:"varint,1,opt,name=type,proto3,enum=opamp.proto.v1.CommandType" json:"type,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2957,7 +2955,7 @@ type RemoteConfigStatus struct { // it has for the Agent and if the hashes are different the Server MUST include // the remote_config field in the response in the ServerToAgent message. LastRemoteConfigHash []byte `protobuf:"bytes,1,opt,name=last_remote_config_hash,json=lastRemoteConfigHash,proto3" json:"last_remote_config_hash,omitempty"` - Status RemoteConfigStatuses `protobuf:"varint,2,opt,name=status,proto3,enum=opamp.proto.RemoteConfigStatuses" json:"status,omitempty"` + Status RemoteConfigStatuses `protobuf:"varint,2,opt,name=status,proto3,enum=opamp.proto.v1.RemoteConfigStatuses" json:"status,omitempty"` // Optional error message if status==FAILED. ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` unknownFields protoimpl.UnknownFields @@ -3018,13 +3016,13 @@ func (x *RemoteConfigStatus) GetErrorMessage() string { // Status: [Development] type ConnectionSettingsStatus struct { state protoimpl.MessageState `protogen:"open.v1"` - // The hash of the connection settings that was last recieved by this Agent + // The hash of the connection settings that was last received by this Agent // in the connection_settings.hash field. The Server SHOULD compare this // hash with the OfferedConnectionSettings hash it has for the Agent and if // the hashes are different the Server MUST include the connection_settings // field in the response in the ServerToAgent message. LastConnectionSettingsHash []byte `protobuf:"bytes,1,opt,name=last_connection_settings_hash,json=lastConnectionSettingsHash,proto3" json:"last_connection_settings_hash,omitempty"` - Status ConnectionSettingsStatuses `protobuf:"varint,2,opt,name=status,proto3,enum=opamp.proto.ConnectionSettingsStatuses" json:"status,omitempty"` + Status ConnectionSettingsStatuses `protobuf:"varint,2,opt,name=status,proto3,enum=opamp.proto.v1.ConnectionSettingsStatuses" json:"status,omitempty"` // Optional error message if status==FAILED. ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` unknownFields protoimpl.UnknownFields @@ -3200,7 +3198,7 @@ type PackageStatus struct { // the Agent already has a version of the package successfully installed, the // Server offers a different version, but the Agent fails to install that version. ServerOfferedHash []byte `protobuf:"bytes,5,opt,name=server_offered_hash,json=serverOfferedHash,proto3" json:"server_offered_hash,omitempty"` - Status PackageStatusEnum `protobuf:"varint,6,opt,name=status,proto3,enum=opamp.proto.PackageStatusEnum" json:"status,omitempty"` + Status PackageStatusEnum `protobuf:"varint,6,opt,name=status,proto3,enum=opamp.proto.v1.PackageStatusEnum" json:"status,omitempty"` // Error message if the status is erroneous. ErrorMessage string `protobuf:"bytes,7,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` // Optional details that may be of interest to a user. @@ -3690,136 +3688,89 @@ func (x *CustomMessage) GetData() []byte { return nil } -type TrustChainResponse_Certificate struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The certificate in DER format. - DerData []byte `protobuf:"bytes,1,opt,name=der_data,json=derData,proto3" json:"der_data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TrustChainResponse_Certificate) Reset() { - *x = TrustChainResponse_Certificate{} - mi := &file_opamp_proto_msgTypes[41] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TrustChainResponse_Certificate) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TrustChainResponse_Certificate) ProtoMessage() {} - -func (x *TrustChainResponse_Certificate) ProtoReflect() protoreflect.Message { - mi := &file_opamp_proto_msgTypes[41] - 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 TrustChainResponse_Certificate.ProtoReflect.Descriptor instead. -func (*TrustChainResponse_Certificate) Descriptor() ([]byte, []int) { - return file_opamp_proto_rawDescGZIP(), []int{8, 0} -} - -func (x *TrustChainResponse_Certificate) GetDerData() []byte { - if x != nil { - return x.DerData - } - return nil -} - var File_opamp_proto protoreflect.FileDescriptor const file_opamp_proto_rawDesc = "" + "\n" + - "\vopamp.proto\x12\vopamp.proto\x1a\x0eanyvalue.proto\"\xf6\a\n" + + "\vopamp.proto\x12\x0eopamp.proto.v1\x1a\x0eanyvalue.proto\"\x97\b\n" + "\rAgentToServer\x12!\n" + "\finstance_uid\x18\x01 \x01(\fR\vinstanceUid\x12!\n" + - "\fsequence_num\x18\x02 \x01(\x04R\vsequenceNum\x12J\n" + - "\x11agent_description\x18\x03 \x01(\v2\x1d.opamp.proto.AgentDescriptionR\x10agentDescription\x12\"\n" + - "\fcapabilities\x18\x04 \x01(\x04R\fcapabilities\x124\n" + - "\x06health\x18\x05 \x01(\v2\x1c.opamp.proto.ComponentHealthR\x06health\x12G\n" + - "\x10effective_config\x18\x06 \x01(\v2\x1c.opamp.proto.EffectiveConfigR\x0feffectiveConfig\x12Q\n" + - "\x14remote_config_status\x18\a \x01(\v2\x1f.opamp.proto.RemoteConfigStatusR\x12remoteConfigStatus\x12G\n" + - "\x10package_statuses\x18\b \x01(\v2\x1c.opamp.proto.PackageStatusesR\x0fpackageStatuses\x12G\n" + - "\x10agent_disconnect\x18\t \x01(\v2\x1c.opamp.proto.AgentDisconnectR\x0fagentDisconnect\x12\x14\n" + + "\fsequence_num\x18\x02 \x01(\x04R\vsequenceNum\x12M\n" + + "\x11agent_description\x18\x03 \x01(\v2 .opamp.proto.v1.AgentDescriptionR\x10agentDescription\x12\"\n" + + "\fcapabilities\x18\x04 \x01(\x04R\fcapabilities\x127\n" + + "\x06health\x18\x05 \x01(\v2\x1f.opamp.proto.v1.ComponentHealthR\x06health\x12J\n" + + "\x10effective_config\x18\x06 \x01(\v2\x1f.opamp.proto.v1.EffectiveConfigR\x0feffectiveConfig\x12T\n" + + "\x14remote_config_status\x18\a \x01(\v2\".opamp.proto.v1.RemoteConfigStatusR\x12remoteConfigStatus\x12J\n" + + "\x10package_statuses\x18\b \x01(\v2\x1f.opamp.proto.v1.PackageStatusesR\x0fpackageStatuses\x12J\n" + + "\x10agent_disconnect\x18\t \x01(\v2\x1f.opamp.proto.v1.AgentDisconnectR\x0fagentDisconnect\x12\x14\n" + "\x05flags\x18\n" + - " \x01(\x04R\x05flags\x12f\n" + - "\x1bconnection_settings_request\x18\v \x01(\v2&.opamp.proto.ConnectionSettingsRequestR\x19connectionSettingsRequest\x12P\n" + - "\x13custom_capabilities\x18\f \x01(\v2\x1f.opamp.proto.CustomCapabilitiesR\x12customCapabilities\x12A\n" + - "\x0ecustom_message\x18\r \x01(\v2\x1a.opamp.proto.CustomMessageR\rcustomMessage\x12S\n" + - "\x14available_components\x18\x0e \x01(\v2 .opamp.proto.AvailableComponentsR\x13availableComponents\x12c\n" + - "\x1aconnection_settings_status\x18\x0f \x01(\v2%.opamp.proto.ConnectionSettingsStatusR\x18connectionSettingsStatus\"\x11\n" + - "\x0fAgentDisconnect\"^\n" + - "\x19ConnectionSettingsRequest\x12A\n" + - "\x05opamp\x18\x01 \x01(\v2+.opamp.proto.OpAMPConnectionSettingsRequestR\x05opamp\"r\n" + - "\x1eOpAMPConnectionSettingsRequest\x12P\n" + - "\x13certificate_request\x18\x01 \x01(\v2\x1f.opamp.proto.CertificateRequestR\x12certificateRequest\"&\n" + + " \x01(\x04R\x05flags\x12i\n" + + "\x1bconnection_settings_request\x18\v \x01(\v2).opamp.proto.v1.ConnectionSettingsRequestR\x19connectionSettingsRequest\x12S\n" + + "\x13custom_capabilities\x18\f \x01(\v2\".opamp.proto.v1.CustomCapabilitiesR\x12customCapabilities\x12D\n" + + "\x0ecustom_message\x18\r \x01(\v2\x1d.opamp.proto.v1.CustomMessageR\rcustomMessage\x12V\n" + + "\x14available_components\x18\x0e \x01(\v2#.opamp.proto.v1.AvailableComponentsR\x13availableComponents\x12f\n" + + "\x1aconnection_settings_status\x18\x0f \x01(\v2(.opamp.proto.v1.ConnectionSettingsStatusR\x18connectionSettingsStatus\"\x11\n" + + "\x0fAgentDisconnect\"a\n" + + "\x19ConnectionSettingsRequest\x12D\n" + + "\x05opamp\x18\x01 \x01(\v2..opamp.proto.v1.OpAMPConnectionSettingsRequestR\x05opamp\"u\n" + + "\x1eOpAMPConnectionSettingsRequest\x12S\n" + + "\x13certificate_request\x18\x01 \x01(\v2\".opamp.proto.v1.CertificateRequestR\x12certificateRequest\"&\n" + "\x12CertificateRequest\x12\x10\n" + - "\x03csr\x18\x01 \x01(\fR\x03csr\"\xd9\x01\n" + - "\x13AvailableComponents\x12P\n" + + "\x03csr\x18\x01 \x01(\fR\x03csr\"\xdf\x01\n" + + "\x13AvailableComponents\x12S\n" + "\n" + - "components\x18\x01 \x03(\v20.opamp.proto.AvailableComponents.ComponentsEntryR\n" + + "components\x18\x01 \x03(\v23.opamp.proto.v1.AvailableComponents.ComponentsEntryR\n" + "components\x12\x12\n" + - "\x04hash\x18\x02 \x01(\fR\x04hash\x1a\\\n" + + "\x04hash\x18\x02 \x01(\fR\x04hash\x1a_\n" + "\x0fComponentsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + - "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.ComponentDetailsR\x05value:\x028\x01\"\x88\x02\n" + - "\x10ComponentDetails\x121\n" + - "\bmetadata\x18\x01 \x03(\v2\x15.opamp.proto.KeyValueR\bmetadata\x12^\n" + - "\x11sub_component_map\x18\x02 \x03(\v22.opamp.proto.ComponentDetails.SubComponentMapEntryR\x0fsubComponentMap\x1aa\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x126\n" + + "\x05value\x18\x02 \x01(\v2 .opamp.proto.v1.ComponentDetailsR\x05value:\x028\x01\"\x91\x02\n" + + "\x10ComponentDetails\x124\n" + + "\bmetadata\x18\x01 \x03(\v2\x18.opamp.proto.v1.KeyValueR\bmetadata\x12a\n" + + "\x11sub_component_map\x18\x02 \x03(\v25.opamp.proto.v1.ComponentDetails.SubComponentMapEntryR\x0fsubComponentMap\x1ad\n" + "\x14SubComponentMapEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + - "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.ComponentDetailsR\x05value:\x028\x01\"\xf5\x05\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x126\n" + + "\x05value\x18\x02 \x01(\v2 .opamp.proto.v1.ComponentDetailsR\x05value:\x028\x01\"\xa8\x06\n" + "\rServerToAgent\x12!\n" + - "\finstance_uid\x18\x01 \x01(\fR\vinstanceUid\x12G\n" + - "\x0eerror_response\x18\x02 \x01(\v2 .opamp.proto.ServerErrorResponseR\rerrorResponse\x12C\n" + - "\rremote_config\x18\x03 \x01(\v2\x1e.opamp.proto.AgentRemoteConfigR\fremoteConfig\x12V\n" + - "\x13connection_settings\x18\x04 \x01(\v2%.opamp.proto.ConnectionSettingsOffersR\x12connectionSettings\x12M\n" + - "\x12packages_available\x18\x05 \x01(\v2\x1e.opamp.proto.PackagesAvailableR\x11packagesAvailable\x12\x14\n" + + "\finstance_uid\x18\x01 \x01(\fR\vinstanceUid\x12J\n" + + "\x0eerror_response\x18\x02 \x01(\v2#.opamp.proto.v1.ServerErrorResponseR\rerrorResponse\x12F\n" + + "\rremote_config\x18\x03 \x01(\v2!.opamp.proto.v1.AgentRemoteConfigR\fremoteConfig\x12Y\n" + + "\x13connection_settings\x18\x04 \x01(\v2(.opamp.proto.v1.ConnectionSettingsOffersR\x12connectionSettings\x12P\n" + + "\x12packages_available\x18\x05 \x01(\v2!.opamp.proto.v1.PackagesAvailableR\x11packagesAvailable\x12\x14\n" + "\x05flags\x18\x06 \x01(\x04R\x05flags\x12\"\n" + - "\fcapabilities\x18\a \x01(\x04R\fcapabilities\x12S\n" + - "\x14agent_identification\x18\b \x01(\v2 .opamp.proto.AgentIdentificationR\x13agentIdentification\x12;\n" + - "\acommand\x18\t \x01(\v2!.opamp.proto.ServerToAgentCommandR\acommand\x12P\n" + + "\fcapabilities\x18\a \x01(\x04R\fcapabilities\x12V\n" + + "\x14agent_identification\x18\b \x01(\v2#.opamp.proto.v1.AgentIdentificationR\x13agentIdentification\x12>\n" + + "\acommand\x18\t \x01(\v2$.opamp.proto.v1.ServerToAgentCommandR\acommand\x12S\n" + "\x13custom_capabilities\x18\n" + - " \x01(\v2\x1f.opamp.proto.CustomCapabilitiesR\x12customCapabilities\x12A\n" + - "\x0ecustom_message\x18\v \x01(\v2\x1a.opamp.proto.CustomMessageR\rcustomMessageJ\x04\b\f\x10\rJ\x04\b\r\x10\x0eR\x14trust_chain_responseR\tsignature\"\xbd\x01\n" + - "\x12TrustChainResponse\x12X\n" + - "\x11certificate_chain\x18\x01 \x03(\v2+.opamp.proto.TrustChainResponse.CertificateR\x10certificateChain\x12#\n" + - "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x1a(\n" + - "\vCertificate\x12\x19\n" + - "\bder_data\x18\x01 \x01(\fR\aderData\"\xa0\x01\n" + + " \x01(\v2\".opamp.proto.v1.CustomCapabilitiesR\x12customCapabilities\x12D\n" + + "\x0ecustom_message\x18\v \x01(\v2\x1d.opamp.proto.v1.CustomMessageR\rcustomMessageJ\x04\b\f\x10\rJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11R\x14trust_chain_responseR\tsignatureR\apayload\"f\n" + + "\x12TrustChainResponse\x12+\n" + + "\x11certificate_chain\x18\x01 \x01(\fR\x10certificateChain\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\"\xa3\x01\n" + "\x13SignedServerToAgent\x12\x18\n" + - "\apayload\x18\x01 \x01(\fR\apayload\x12\x1c\n" + - "\tsignature\x18\x02 \x01(\fR\tsignature\x12Q\n" + - "\x14trust_chain_response\x18\x03 \x01(\v2\x1f.opamp.proto.TrustChainResponseR\x12trustChainResponse\"\xeb\x02\n" + + "\apayload\x18\x0e \x01(\fR\apayload\x12\x1c\n" + + "\tsignature\x18\x0f \x01(\fR\tsignature\x12T\n" + + "\x14trust_chain_response\x18\x10 \x01(\v2\".opamp.proto.v1.TrustChainResponseR\x12trustChainResponse\"\xf7\x02\n" + "\x17OpAMPConnectionSettings\x121\n" + - "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x12.\n" + - "\aheaders\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\x12=\n" + - "\vcertificate\x18\x03 \x01(\v2\x1b.opamp.proto.TLSCertificateR\vcertificate\x12<\n" + - "\x1aheartbeat_interval_seconds\x18\x04 \x01(\x04R\x18heartbeatIntervalSeconds\x124\n" + - "\x03tls\x18\x05 \x01(\v2\".opamp.proto.TLSConnectionSettingsR\x03tls\x12:\n" + - "\x05proxy\x18\x06 \x01(\v2$.opamp.proto.ProxyConnectionSettingsR\x05proxy\"\xb1\x02\n" + + "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x121\n" + + "\aheaders\x18\x02 \x01(\v2\x17.opamp.proto.v1.HeadersR\aheaders\x12@\n" + + "\vcertificate\x18\x03 \x01(\v2\x1e.opamp.proto.v1.TLSCertificateR\vcertificate\x12<\n" + + "\x1aheartbeat_interval_seconds\x18\x04 \x01(\x04R\x18heartbeatIntervalSeconds\x127\n" + + "\x03tls\x18\x05 \x01(\v2%.opamp.proto.v1.TLSConnectionSettingsR\x03tls\x12=\n" + + "\x05proxy\x18\x06 \x01(\v2'.opamp.proto.v1.ProxyConnectionSettingsR\x05proxy\"\xbd\x02\n" + "\x1bTelemetryConnectionSettings\x121\n" + - "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x12.\n" + - "\aheaders\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\x12=\n" + - "\vcertificate\x18\x03 \x01(\v2\x1b.opamp.proto.TLSCertificateR\vcertificate\x124\n" + - "\x03tls\x18\x04 \x01(\v2\".opamp.proto.TLSConnectionSettingsR\x03tls\x12:\n" + - "\x05proxy\x18\x05 \x01(\v2$.opamp.proto.ProxyConnectionSettingsR\x05proxy\"\xcf\x03\n" + + "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x121\n" + + "\aheaders\x18\x02 \x01(\v2\x17.opamp.proto.v1.HeadersR\aheaders\x12@\n" + + "\vcertificate\x18\x03 \x01(\v2\x1e.opamp.proto.v1.TLSCertificateR\vcertificate\x127\n" + + "\x03tls\x18\x04 \x01(\v2%.opamp.proto.v1.TLSConnectionSettingsR\x03tls\x12=\n" + + "\x05proxy\x18\x05 \x01(\v2'.opamp.proto.v1.ProxyConnectionSettingsR\x05proxy\"\xde\x03\n" + "\x17OtherConnectionSettings\x121\n" + - "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x12.\n" + - "\aheaders\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\x12=\n" + - "\vcertificate\x18\x03 \x01(\v2\x1b.opamp.proto.TLSCertificateR\vcertificate\x12^\n" + - "\x0eother_settings\x18\x04 \x03(\v27.opamp.proto.OtherConnectionSettings.OtherSettingsEntryR\rotherSettings\x124\n" + - "\x03tls\x18\x05 \x01(\v2\".opamp.proto.TLSConnectionSettingsR\x03tls\x12:\n" + - "\x05proxy\x18\x06 \x01(\v2$.opamp.proto.ProxyConnectionSettingsR\x05proxy\x1a@\n" + + "\x14destination_endpoint\x18\x01 \x01(\tR\x13destinationEndpoint\x121\n" + + "\aheaders\x18\x02 \x01(\v2\x17.opamp.proto.v1.HeadersR\aheaders\x12@\n" + + "\vcertificate\x18\x03 \x01(\v2\x1e.opamp.proto.v1.TLSCertificateR\vcertificate\x12a\n" + + "\x0eother_settings\x18\x04 \x03(\v2:.opamp.proto.v1.OtherConnectionSettings.OtherSettingsEntryR\rotherSettings\x127\n" + + "\x03tls\x18\x05 \x01(\v2%.opamp.proto.v1.TLSConnectionSettingsR\x03tls\x12=\n" + + "\x05proxy\x18\x06 \x01(\v2'.opamp.proto.v1.ProxyConnectionSettingsR\x05proxy\x1a@\n" + "\x12OtherSettingsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x98\x02\n" + @@ -3831,12 +3782,12 @@ const file_opamp_proto_rawDesc = "" + "minVersion\x12\x1f\n" + "\vmax_version\x18\x05 \x01(\tR\n" + "maxVersion\x12#\n" + - "\rcipher_suites\x18\x06 \x03(\tR\fcipherSuites\"j\n" + + "\rcipher_suites\x18\x06 \x03(\tR\fcipherSuites\"m\n" + "\x17ProxyConnectionSettings\x12\x10\n" + - "\x03url\x18\x01 \x01(\tR\x03url\x12=\n" + - "\x0fconnect_headers\x18\x02 \x01(\v2\x14.opamp.proto.HeadersR\x0econnectHeaders\"8\n" + - "\aHeaders\x12-\n" + - "\aheaders\x18\x01 \x03(\v2\x13.opamp.proto.HeaderR\aheaders\"0\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12@\n" + + "\x0fconnect_headers\x18\x02 \x01(\v2\x17.opamp.proto.v1.HeadersR\x0econnectHeaders\";\n" + + "\aHeaders\x120\n" + + "\aheaders\x18\x01 \x03(\v2\x16.opamp.proto.v1.HeaderR\aheaders\"0\n" + "\x06Header\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value\"^\n" + @@ -3844,101 +3795,101 @@ const file_opamp_proto_rawDesc = "" + "\x04cert\x18\x01 \x01(\fR\x04cert\x12\x1f\n" + "\vprivate_key\x18\x02 \x01(\fR\n" + "privateKey\x12\x17\n" + - "\aca_cert\x18\x03 \x01(\fR\x06caCert\"\x98\x04\n" + + "\aca_cert\x18\x03 \x01(\fR\x06caCert\"\xaa\x04\n" + "\x18ConnectionSettingsOffers\x12\x12\n" + - "\x04hash\x18\x01 \x01(\fR\x04hash\x12:\n" + - "\x05opamp\x18\x02 \x01(\v2$.opamp.proto.OpAMPConnectionSettingsR\x05opamp\x12I\n" + - "\vown_metrics\x18\x03 \x01(\v2(.opamp.proto.TelemetryConnectionSettingsR\n" + - "ownMetrics\x12G\n" + + "\x04hash\x18\x01 \x01(\fR\x04hash\x12=\n" + + "\x05opamp\x18\x02 \x01(\v2'.opamp.proto.v1.OpAMPConnectionSettingsR\x05opamp\x12L\n" + + "\vown_metrics\x18\x03 \x01(\v2+.opamp.proto.v1.TelemetryConnectionSettingsR\n" + + "ownMetrics\x12J\n" + "\n" + - "own_traces\x18\x04 \x01(\v2(.opamp.proto.TelemetryConnectionSettingsR\townTraces\x12C\n" + - "\bown_logs\x18\x05 \x01(\v2(.opamp.proto.TelemetryConnectionSettingsR\aownLogs\x12h\n" + - "\x11other_connections\x18\x06 \x03(\v2;.opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntryR\x10otherConnections\x1ai\n" + + "own_traces\x18\x04 \x01(\v2+.opamp.proto.v1.TelemetryConnectionSettingsR\townTraces\x12F\n" + + "\bown_logs\x18\x05 \x01(\v2+.opamp.proto.v1.TelemetryConnectionSettingsR\aownLogs\x12k\n" + + "\x11other_connections\x18\x06 \x03(\v2>.opamp.proto.v1.ConnectionSettingsOffers.OtherConnectionsEntryR\x10otherConnections\x1al\n" + "\x15OtherConnectionsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x12:\n" + - "\x05value\x18\x02 \x01(\v2$.opamp.proto.OtherConnectionSettingsR\x05value:\x028\x01\"\xe5\x01\n" + - "\x11PackagesAvailable\x12H\n" + - "\bpackages\x18\x01 \x03(\v2,.opamp.proto.PackagesAvailable.PackagesEntryR\bpackages\x12*\n" + - "\x11all_packages_hash\x18\x02 \x01(\fR\x0fallPackagesHash\x1aZ\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12=\n" + + "\x05value\x18\x02 \x01(\v2'.opamp.proto.v1.OtherConnectionSettingsR\x05value:\x028\x01\"\xeb\x01\n" + + "\x11PackagesAvailable\x12K\n" + + "\bpackages\x18\x01 \x03(\v2/.opamp.proto.v1.PackagesAvailable.PackagesEntryR\bpackages\x12*\n" + + "\x11all_packages_hash\x18\x02 \x01(\fR\x0fallPackagesHash\x1a]\n" + "\rPackagesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + - "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.PackageAvailableR\x05value:\x028\x01\"\xa1\x01\n" + - "\x10PackageAvailable\x12,\n" + - "\x04type\x18\x01 \x01(\x0e2\x18.opamp.proto.PackageTypeR\x04type\x12\x18\n" + - "\aversion\x18\x02 \x01(\tR\aversion\x121\n" + - "\x04file\x18\x03 \x01(\v2\x1d.opamp.proto.DownloadableFileR\x04file\x12\x12\n" + - "\x04hash\x18\x04 \x01(\fR\x04hash\"\xa6\x01\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x126\n" + + "\x05value\x18\x02 \x01(\v2 .opamp.proto.v1.PackageAvailableR\x05value:\x028\x01\"\xa7\x01\n" + + "\x10PackageAvailable\x12/\n" + + "\x04type\x18\x01 \x01(\x0e2\x1b.opamp.proto.v1.PackageTypeR\x04type\x12\x18\n" + + "\aversion\x18\x02 \x01(\tR\aversion\x124\n" + + "\x04file\x18\x03 \x01(\v2 .opamp.proto.v1.DownloadableFileR\x04file\x12\x12\n" + + "\x04hash\x18\x04 \x01(\fR\x04hash\"\xa9\x01\n" + "\x10DownloadableFile\x12!\n" + "\fdownload_url\x18\x01 \x01(\tR\vdownloadUrl\x12!\n" + "\fcontent_hash\x18\x02 \x01(\fR\vcontentHash\x12\x1c\n" + - "\tsignature\x18\x03 \x01(\fR\tsignature\x12.\n" + - "\aheaders\x18\x04 \x01(\v2\x14.opamp.proto.HeadersR\aheaders\"\xb8\x01\n" + - "\x13ServerErrorResponse\x128\n" + - "\x04type\x18\x01 \x01(\x0e2$.opamp.proto.ServerErrorResponseTypeR\x04type\x12#\n" + - "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x127\n" + + "\tsignature\x18\x03 \x01(\fR\tsignature\x121\n" + + "\aheaders\x18\x04 \x01(\v2\x17.opamp.proto.v1.HeadersR\aheaders\"\xbe\x01\n" + + "\x13ServerErrorResponse\x12;\n" + + "\x04type\x18\x01 \x01(\x0e2'.opamp.proto.v1.ServerErrorResponseTypeR\x04type\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x12:\n" + "\n" + - "retry_info\x18\x03 \x01(\v2\x16.opamp.proto.RetryInfoH\x00R\tretryInfoB\t\n" + + "retry_info\x18\x03 \x01(\v2\x19.opamp.proto.v1.RetryInfoH\x00R\tretryInfoB\t\n" + "\aDetails\"C\n" + "\tRetryInfo\x126\n" + - "\x17retry_after_nanoseconds\x18\x01 \x01(\x04R\x15retryAfterNanoseconds\"D\n" + - "\x14ServerToAgentCommand\x12,\n" + - "\x04type\x18\x01 \x01(\x0e2\x18.opamp.proto.CommandTypeR\x04type\"\xb5\x01\n" + - "\x10AgentDescription\x12L\n" + - "\x16identifying_attributes\x18\x01 \x03(\v2\x15.opamp.proto.KeyValueR\x15identifyingAttributes\x12S\n" + - "\x1anon_identifying_attributes\x18\x02 \x03(\v2\x15.opamp.proto.KeyValueR\x18nonIdentifyingAttributes\"\x93\x03\n" + + "\x17retry_after_nanoseconds\x18\x01 \x01(\x04R\x15retryAfterNanoseconds\"G\n" + + "\x14ServerToAgentCommand\x12/\n" + + "\x04type\x18\x01 \x01(\x0e2\x1b.opamp.proto.v1.CommandTypeR\x04type\"\xbb\x01\n" + + "\x10AgentDescription\x12O\n" + + "\x16identifying_attributes\x18\x01 \x03(\v2\x18.opamp.proto.v1.KeyValueR\x15identifyingAttributes\x12V\n" + + "\x1anon_identifying_attributes\x18\x02 \x03(\v2\x18.opamp.proto.v1.KeyValueR\x18nonIdentifyingAttributes\"\x99\x03\n" + "\x0fComponentHealth\x12\x18\n" + "\ahealthy\x18\x01 \x01(\bR\ahealthy\x12/\n" + "\x14start_time_unix_nano\x18\x02 \x01(\x06R\x11startTimeUnixNano\x12\x1d\n" + "\n" + "last_error\x18\x03 \x01(\tR\tlastError\x12\x16\n" + "\x06status\x18\x04 \x01(\tR\x06status\x121\n" + - "\x15status_time_unix_nano\x18\x05 \x01(\x06R\x12statusTimeUnixNano\x12f\n" + - "\x14component_health_map\x18\x06 \x03(\v24.opamp.proto.ComponentHealth.ComponentHealthMapEntryR\x12componentHealthMap\x1ac\n" + + "\x15status_time_unix_nano\x18\x05 \x01(\x06R\x12statusTimeUnixNano\x12i\n" + + "\x14component_health_map\x18\x06 \x03(\v27.opamp.proto.v1.ComponentHealth.ComponentHealthMapEntryR\x12componentHealthMap\x1af\n" + "\x17ComponentHealthMapEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + - "\x05value\x18\x02 \x01(\v2\x1c.opamp.proto.ComponentHealthR\x05value:\x028\x01\"M\n" + - "\x0fEffectiveConfig\x12:\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x125\n" + + "\x05value\x18\x02 \x01(\v2\x1f.opamp.proto.v1.ComponentHealthR\x05value:\x028\x01\"P\n" + + "\x0fEffectiveConfig\x12=\n" + "\n" + - "config_map\x18\x01 \x01(\v2\x1b.opamp.proto.AgentConfigMapR\tconfigMap\"\xab\x01\n" + + "config_map\x18\x01 \x01(\v2\x1e.opamp.proto.v1.AgentConfigMapR\tconfigMap\"\xae\x01\n" + "\x12RemoteConfigStatus\x125\n" + - "\x17last_remote_config_hash\x18\x01 \x01(\fR\x14lastRemoteConfigHash\x129\n" + - "\x06status\x18\x02 \x01(\x0e2!.opamp.proto.RemoteConfigStatusesR\x06status\x12#\n" + - "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\"\xc3\x01\n" + + "\x17last_remote_config_hash\x18\x01 \x01(\fR\x14lastRemoteConfigHash\x12<\n" + + "\x06status\x18\x02 \x01(\x0e2$.opamp.proto.v1.RemoteConfigStatusesR\x06status\x12#\n" + + "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\"\xc6\x01\n" + "\x18ConnectionSettingsStatus\x12A\n" + - "\x1dlast_connection_settings_hash\x18\x01 \x01(\fR\x1alastConnectionSettingsHash\x12?\n" + - "\x06status\x18\x02 \x01(\x0e2'.opamp.proto.ConnectionSettingsStatusesR\x06status\x12#\n" + - "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\"\xa1\x02\n" + - "\x0fPackageStatuses\x12F\n" + - "\bpackages\x18\x01 \x03(\v2*.opamp.proto.PackageStatuses.PackagesEntryR\bpackages\x12H\n" + + "\x1dlast_connection_settings_hash\x18\x01 \x01(\fR\x1alastConnectionSettingsHash\x12B\n" + + "\x06status\x18\x02 \x01(\x0e2*.opamp.proto.v1.ConnectionSettingsStatusesR\x06status\x12#\n" + + "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\"\xa7\x02\n" + + "\x0fPackageStatuses\x12I\n" + + "\bpackages\x18\x01 \x03(\v2-.opamp.proto.v1.PackageStatuses.PackagesEntryR\bpackages\x12H\n" + "!server_provided_all_packages_hash\x18\x02 \x01(\fR\x1dserverProvidedAllPackagesHash\x12#\n" + - "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\x1aW\n" + + "\rerror_message\x18\x03 \x01(\tR\ferrorMessage\x1aZ\n" + "\rPackagesEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x120\n" + - "\x05value\x18\x02 \x01(\v2\x1a.opamp.proto.PackageStatusR\x05value:\x028\x01\"\x88\x03\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x123\n" + + "\x05value\x18\x02 \x01(\v2\x1d.opamp.proto.v1.PackageStatusR\x05value:\x028\x01\"\x8e\x03\n" + "\rPackageStatus\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12*\n" + "\x11agent_has_version\x18\x02 \x01(\tR\x0fagentHasVersion\x12$\n" + "\x0eagent_has_hash\x18\x03 \x01(\fR\fagentHasHash\x124\n" + "\x16server_offered_version\x18\x04 \x01(\tR\x14serverOfferedVersion\x12.\n" + - "\x13server_offered_hash\x18\x05 \x01(\fR\x11serverOfferedHash\x126\n" + - "\x06status\x18\x06 \x01(\x0e2\x1e.opamp.proto.PackageStatusEnumR\x06status\x12#\n" + - "\rerror_message\x18\a \x01(\tR\ferrorMessage\x12N\n" + - "\x10download_details\x18\b \x01(\v2#.opamp.proto.PackageDownloadDetailsR\x0fdownloadDetails\"~\n" + + "\x13server_offered_hash\x18\x05 \x01(\fR\x11serverOfferedHash\x129\n" + + "\x06status\x18\x06 \x01(\x0e2!.opamp.proto.v1.PackageStatusEnumR\x06status\x12#\n" + + "\rerror_message\x18\a \x01(\tR\ferrorMessage\x12Q\n" + + "\x10download_details\x18\b \x01(\v2&.opamp.proto.v1.PackageDownloadDetailsR\x0fdownloadDetails\"~\n" + "\x16PackageDownloadDetails\x12)\n" + "\x10download_percent\x18\x01 \x01(\x01R\x0fdownloadPercent\x129\n" + "\x19download_bytes_per_second\x18\x02 \x01(\x01R\x16downloadBytesPerSecond\"?\n" + "\x13AgentIdentification\x12(\n" + - "\x10new_instance_uid\x18\x01 \x01(\fR\x0enewInstanceUid\"i\n" + - "\x11AgentRemoteConfig\x123\n" + - "\x06config\x18\x01 \x01(\v2\x1b.opamp.proto.AgentConfigMapR\x06config\x12\x1f\n" + + "\x10new_instance_uid\x18\x01 \x01(\fR\x0enewInstanceUid\"l\n" + + "\x11AgentRemoteConfig\x126\n" + + "\x06config\x18\x01 \x01(\v2\x1e.opamp.proto.v1.AgentConfigMapR\x06config\x12\x1f\n" + "\vconfig_hash\x18\x02 \x01(\fR\n" + - "configHash\"\xb7\x01\n" + - "\x0eAgentConfigMap\x12I\n" + + "configHash\"\xbd\x01\n" + + "\x0eAgentConfigMap\x12L\n" + "\n" + - "config_map\x18\x01 \x03(\v2*.opamp.proto.AgentConfigMap.ConfigMapEntryR\tconfigMap\x1aZ\n" + + "config_map\x18\x01 \x03(\v2-.opamp.proto.v1.AgentConfigMap.ConfigMapEntryR\tconfigMap\x1a]\n" + "\x0eConfigMapEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + - "\x05value\x18\x02 \x01(\v2\x1c.opamp.proto.AgentConfigFileR\x05value:\x028\x01\"H\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x125\n" + + "\x05value\x18\x02 \x01(\v2\x1f.opamp.proto.v1.AgentConfigFileR\x05value:\x028\x01\"H\n" + "\x0fAgentConfigFile\x12\x12\n" + "\x04body\x18\x01 \x01(\fR\x04body\x12!\n" + "\fcontent_type\x18\x02 \x01(\tR\vcontentType\"8\n" + @@ -4010,7 +3961,7 @@ const file_opamp_proto_rawDesc = "" + " PackageStatusEnum_InstallPending\x10\x01\x12 \n" + "\x1cPackageStatusEnum_Installing\x10\x02\x12#\n" + "\x1fPackageStatusEnum_InstallFailed\x10\x03\x12!\n" + - "\x1dPackageStatusEnum_Downloading\x10\x04B.Z,github.com/open-telemetry/opamp-go/protobufsb\x06proto3" + "\x1dPackageStatusEnum_Downloading\x10\x04B?Z,github.com/open-telemetry/opamp-go/protobufs\xaa\x02\x0eOpAmp.Proto.V1b\x06proto3" var ( file_opamp_proto_rawDescOnce sync.Once @@ -4025,145 +3976,143 @@ func file_opamp_proto_rawDescGZIP() []byte { } var file_opamp_proto_enumTypes = make([]protoimpl.EnumInfo, 10) -var file_opamp_proto_msgTypes = make([]protoimpl.MessageInfo, 48) +var file_opamp_proto_msgTypes = make([]protoimpl.MessageInfo, 47) var file_opamp_proto_goTypes = []any{ - (AgentToServerFlags)(0), // 0: opamp.proto.AgentToServerFlags - (ServerToAgentFlags)(0), // 1: opamp.proto.ServerToAgentFlags - (ServerCapabilities)(0), // 2: opamp.proto.ServerCapabilities - (PackageType)(0), // 3: opamp.proto.PackageType - (ServerErrorResponseType)(0), // 4: opamp.proto.ServerErrorResponseType - (CommandType)(0), // 5: opamp.proto.CommandType - (AgentCapabilities)(0), // 6: opamp.proto.AgentCapabilities - (ConnectionSettingsStatuses)(0), // 7: opamp.proto.ConnectionSettingsStatuses - (RemoteConfigStatuses)(0), // 8: opamp.proto.RemoteConfigStatuses - (PackageStatusEnum)(0), // 9: opamp.proto.PackageStatusEnum - (*AgentToServer)(nil), // 10: opamp.proto.AgentToServer - (*AgentDisconnect)(nil), // 11: opamp.proto.AgentDisconnect - (*ConnectionSettingsRequest)(nil), // 12: opamp.proto.ConnectionSettingsRequest - (*OpAMPConnectionSettingsRequest)(nil), // 13: opamp.proto.OpAMPConnectionSettingsRequest - (*CertificateRequest)(nil), // 14: opamp.proto.CertificateRequest - (*AvailableComponents)(nil), // 15: opamp.proto.AvailableComponents - (*ComponentDetails)(nil), // 16: opamp.proto.ComponentDetails - (*ServerToAgent)(nil), // 17: opamp.proto.ServerToAgent - (*TrustChainResponse)(nil), // 18: opamp.proto.TrustChainResponse - (*SignedServerToAgent)(nil), // 19: opamp.proto.SignedServerToAgent - (*OpAMPConnectionSettings)(nil), // 20: opamp.proto.OpAMPConnectionSettings - (*TelemetryConnectionSettings)(nil), // 21: opamp.proto.TelemetryConnectionSettings - (*OtherConnectionSettings)(nil), // 22: opamp.proto.OtherConnectionSettings - (*TLSConnectionSettings)(nil), // 23: opamp.proto.TLSConnectionSettings - (*ProxyConnectionSettings)(nil), // 24: opamp.proto.ProxyConnectionSettings - (*Headers)(nil), // 25: opamp.proto.Headers - (*Header)(nil), // 26: opamp.proto.Header - (*TLSCertificate)(nil), // 27: opamp.proto.TLSCertificate - (*ConnectionSettingsOffers)(nil), // 28: opamp.proto.ConnectionSettingsOffers - (*PackagesAvailable)(nil), // 29: opamp.proto.PackagesAvailable - (*PackageAvailable)(nil), // 30: opamp.proto.PackageAvailable - (*DownloadableFile)(nil), // 31: opamp.proto.DownloadableFile - (*ServerErrorResponse)(nil), // 32: opamp.proto.ServerErrorResponse - (*RetryInfo)(nil), // 33: opamp.proto.RetryInfo - (*ServerToAgentCommand)(nil), // 34: opamp.proto.ServerToAgentCommand - (*AgentDescription)(nil), // 35: opamp.proto.AgentDescription - (*ComponentHealth)(nil), // 36: opamp.proto.ComponentHealth - (*EffectiveConfig)(nil), // 37: opamp.proto.EffectiveConfig - (*RemoteConfigStatus)(nil), // 38: opamp.proto.RemoteConfigStatus - (*ConnectionSettingsStatus)(nil), // 39: opamp.proto.ConnectionSettingsStatus - (*PackageStatuses)(nil), // 40: opamp.proto.PackageStatuses - (*PackageStatus)(nil), // 41: opamp.proto.PackageStatus - (*PackageDownloadDetails)(nil), // 42: opamp.proto.PackageDownloadDetails - (*AgentIdentification)(nil), // 43: opamp.proto.AgentIdentification - (*AgentRemoteConfig)(nil), // 44: opamp.proto.AgentRemoteConfig - (*AgentConfigMap)(nil), // 45: opamp.proto.AgentConfigMap - (*AgentConfigFile)(nil), // 46: opamp.proto.AgentConfigFile - (*CustomCapabilities)(nil), // 47: opamp.proto.CustomCapabilities - (*CustomMessage)(nil), // 48: opamp.proto.CustomMessage - nil, // 49: opamp.proto.AvailableComponents.ComponentsEntry - nil, // 50: opamp.proto.ComponentDetails.SubComponentMapEntry - (*TrustChainResponse_Certificate)(nil), // 51: opamp.proto.TrustChainResponse.Certificate - nil, // 52: opamp.proto.OtherConnectionSettings.OtherSettingsEntry - nil, // 53: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry - nil, // 54: opamp.proto.PackagesAvailable.PackagesEntry - nil, // 55: opamp.proto.ComponentHealth.ComponentHealthMapEntry - nil, // 56: opamp.proto.PackageStatuses.PackagesEntry - nil, // 57: opamp.proto.AgentConfigMap.ConfigMapEntry - (*KeyValue)(nil), // 58: opamp.proto.KeyValue + (AgentToServerFlags)(0), // 0: opamp.proto.v1.AgentToServerFlags + (ServerToAgentFlags)(0), // 1: opamp.proto.v1.ServerToAgentFlags + (ServerCapabilities)(0), // 2: opamp.proto.v1.ServerCapabilities + (PackageType)(0), // 3: opamp.proto.v1.PackageType + (ServerErrorResponseType)(0), // 4: opamp.proto.v1.ServerErrorResponseType + (CommandType)(0), // 5: opamp.proto.v1.CommandType + (AgentCapabilities)(0), // 6: opamp.proto.v1.AgentCapabilities + (ConnectionSettingsStatuses)(0), // 7: opamp.proto.v1.ConnectionSettingsStatuses + (RemoteConfigStatuses)(0), // 8: opamp.proto.v1.RemoteConfigStatuses + (PackageStatusEnum)(0), // 9: opamp.proto.v1.PackageStatusEnum + (*AgentToServer)(nil), // 10: opamp.proto.v1.AgentToServer + (*AgentDisconnect)(nil), // 11: opamp.proto.v1.AgentDisconnect + (*ConnectionSettingsRequest)(nil), // 12: opamp.proto.v1.ConnectionSettingsRequest + (*OpAMPConnectionSettingsRequest)(nil), // 13: opamp.proto.v1.OpAMPConnectionSettingsRequest + (*CertificateRequest)(nil), // 14: opamp.proto.v1.CertificateRequest + (*AvailableComponents)(nil), // 15: opamp.proto.v1.AvailableComponents + (*ComponentDetails)(nil), // 16: opamp.proto.v1.ComponentDetails + (*ServerToAgent)(nil), // 17: opamp.proto.v1.ServerToAgent + (*TrustChainResponse)(nil), // 18: opamp.proto.v1.TrustChainResponse + (*SignedServerToAgent)(nil), // 19: opamp.proto.v1.SignedServerToAgent + (*OpAMPConnectionSettings)(nil), // 20: opamp.proto.v1.OpAMPConnectionSettings + (*TelemetryConnectionSettings)(nil), // 21: opamp.proto.v1.TelemetryConnectionSettings + (*OtherConnectionSettings)(nil), // 22: opamp.proto.v1.OtherConnectionSettings + (*TLSConnectionSettings)(nil), // 23: opamp.proto.v1.TLSConnectionSettings + (*ProxyConnectionSettings)(nil), // 24: opamp.proto.v1.ProxyConnectionSettings + (*Headers)(nil), // 25: opamp.proto.v1.Headers + (*Header)(nil), // 26: opamp.proto.v1.Header + (*TLSCertificate)(nil), // 27: opamp.proto.v1.TLSCertificate + (*ConnectionSettingsOffers)(nil), // 28: opamp.proto.v1.ConnectionSettingsOffers + (*PackagesAvailable)(nil), // 29: opamp.proto.v1.PackagesAvailable + (*PackageAvailable)(nil), // 30: opamp.proto.v1.PackageAvailable + (*DownloadableFile)(nil), // 31: opamp.proto.v1.DownloadableFile + (*ServerErrorResponse)(nil), // 32: opamp.proto.v1.ServerErrorResponse + (*RetryInfo)(nil), // 33: opamp.proto.v1.RetryInfo + (*ServerToAgentCommand)(nil), // 34: opamp.proto.v1.ServerToAgentCommand + (*AgentDescription)(nil), // 35: opamp.proto.v1.AgentDescription + (*ComponentHealth)(nil), // 36: opamp.proto.v1.ComponentHealth + (*EffectiveConfig)(nil), // 37: opamp.proto.v1.EffectiveConfig + (*RemoteConfigStatus)(nil), // 38: opamp.proto.v1.RemoteConfigStatus + (*ConnectionSettingsStatus)(nil), // 39: opamp.proto.v1.ConnectionSettingsStatus + (*PackageStatuses)(nil), // 40: opamp.proto.v1.PackageStatuses + (*PackageStatus)(nil), // 41: opamp.proto.v1.PackageStatus + (*PackageDownloadDetails)(nil), // 42: opamp.proto.v1.PackageDownloadDetails + (*AgentIdentification)(nil), // 43: opamp.proto.v1.AgentIdentification + (*AgentRemoteConfig)(nil), // 44: opamp.proto.v1.AgentRemoteConfig + (*AgentConfigMap)(nil), // 45: opamp.proto.v1.AgentConfigMap + (*AgentConfigFile)(nil), // 46: opamp.proto.v1.AgentConfigFile + (*CustomCapabilities)(nil), // 47: opamp.proto.v1.CustomCapabilities + (*CustomMessage)(nil), // 48: opamp.proto.v1.CustomMessage + nil, // 49: opamp.proto.v1.AvailableComponents.ComponentsEntry + nil, // 50: opamp.proto.v1.ComponentDetails.SubComponentMapEntry + nil, // 51: opamp.proto.v1.OtherConnectionSettings.OtherSettingsEntry + nil, // 52: opamp.proto.v1.ConnectionSettingsOffers.OtherConnectionsEntry + nil, // 53: opamp.proto.v1.PackagesAvailable.PackagesEntry + nil, // 54: opamp.proto.v1.ComponentHealth.ComponentHealthMapEntry + nil, // 55: opamp.proto.v1.PackageStatuses.PackagesEntry + nil, // 56: opamp.proto.v1.AgentConfigMap.ConfigMapEntry + (*KeyValue)(nil), // 57: opamp.proto.v1.KeyValue } var file_opamp_proto_depIdxs = []int32{ - 35, // 0: opamp.proto.AgentToServer.agent_description:type_name -> opamp.proto.AgentDescription - 36, // 1: opamp.proto.AgentToServer.health:type_name -> opamp.proto.ComponentHealth - 37, // 2: opamp.proto.AgentToServer.effective_config:type_name -> opamp.proto.EffectiveConfig - 38, // 3: opamp.proto.AgentToServer.remote_config_status:type_name -> opamp.proto.RemoteConfigStatus - 40, // 4: opamp.proto.AgentToServer.package_statuses:type_name -> opamp.proto.PackageStatuses - 11, // 5: opamp.proto.AgentToServer.agent_disconnect:type_name -> opamp.proto.AgentDisconnect - 12, // 6: opamp.proto.AgentToServer.connection_settings_request:type_name -> opamp.proto.ConnectionSettingsRequest - 47, // 7: opamp.proto.AgentToServer.custom_capabilities:type_name -> opamp.proto.CustomCapabilities - 48, // 8: opamp.proto.AgentToServer.custom_message:type_name -> opamp.proto.CustomMessage - 15, // 9: opamp.proto.AgentToServer.available_components:type_name -> opamp.proto.AvailableComponents - 39, // 10: opamp.proto.AgentToServer.connection_settings_status:type_name -> opamp.proto.ConnectionSettingsStatus - 13, // 11: opamp.proto.ConnectionSettingsRequest.opamp:type_name -> opamp.proto.OpAMPConnectionSettingsRequest - 14, // 12: opamp.proto.OpAMPConnectionSettingsRequest.certificate_request:type_name -> opamp.proto.CertificateRequest - 49, // 13: opamp.proto.AvailableComponents.components:type_name -> opamp.proto.AvailableComponents.ComponentsEntry - 58, // 14: opamp.proto.ComponentDetails.metadata:type_name -> opamp.proto.KeyValue - 50, // 15: opamp.proto.ComponentDetails.sub_component_map:type_name -> opamp.proto.ComponentDetails.SubComponentMapEntry - 32, // 16: opamp.proto.ServerToAgent.error_response:type_name -> opamp.proto.ServerErrorResponse - 44, // 17: opamp.proto.ServerToAgent.remote_config:type_name -> opamp.proto.AgentRemoteConfig - 28, // 18: opamp.proto.ServerToAgent.connection_settings:type_name -> opamp.proto.ConnectionSettingsOffers - 29, // 19: opamp.proto.ServerToAgent.packages_available:type_name -> opamp.proto.PackagesAvailable - 43, // 20: opamp.proto.ServerToAgent.agent_identification:type_name -> opamp.proto.AgentIdentification - 34, // 21: opamp.proto.ServerToAgent.command:type_name -> opamp.proto.ServerToAgentCommand - 47, // 22: opamp.proto.ServerToAgent.custom_capabilities:type_name -> opamp.proto.CustomCapabilities - 48, // 23: opamp.proto.ServerToAgent.custom_message:type_name -> opamp.proto.CustomMessage - 51, // 24: opamp.proto.TrustChainResponse.certificate_chain:type_name -> opamp.proto.TrustChainResponse.Certificate - 18, // 25: opamp.proto.SignedServerToAgent.trust_chain_response:type_name -> opamp.proto.TrustChainResponse - 25, // 26: opamp.proto.OpAMPConnectionSettings.headers:type_name -> opamp.proto.Headers - 27, // 27: opamp.proto.OpAMPConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 23, // 28: opamp.proto.OpAMPConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 24, // 29: opamp.proto.OpAMPConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 25, // 30: opamp.proto.TelemetryConnectionSettings.headers:type_name -> opamp.proto.Headers - 27, // 31: opamp.proto.TelemetryConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 23, // 32: opamp.proto.TelemetryConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 24, // 33: opamp.proto.TelemetryConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 25, // 34: opamp.proto.OtherConnectionSettings.headers:type_name -> opamp.proto.Headers - 27, // 35: opamp.proto.OtherConnectionSettings.certificate:type_name -> opamp.proto.TLSCertificate - 52, // 36: opamp.proto.OtherConnectionSettings.other_settings:type_name -> opamp.proto.OtherConnectionSettings.OtherSettingsEntry - 23, // 37: opamp.proto.OtherConnectionSettings.tls:type_name -> opamp.proto.TLSConnectionSettings - 24, // 38: opamp.proto.OtherConnectionSettings.proxy:type_name -> opamp.proto.ProxyConnectionSettings - 25, // 39: opamp.proto.ProxyConnectionSettings.connect_headers:type_name -> opamp.proto.Headers - 26, // 40: opamp.proto.Headers.headers:type_name -> opamp.proto.Header - 20, // 41: opamp.proto.ConnectionSettingsOffers.opamp:type_name -> opamp.proto.OpAMPConnectionSettings - 21, // 42: opamp.proto.ConnectionSettingsOffers.own_metrics:type_name -> opamp.proto.TelemetryConnectionSettings - 21, // 43: opamp.proto.ConnectionSettingsOffers.own_traces:type_name -> opamp.proto.TelemetryConnectionSettings - 21, // 44: opamp.proto.ConnectionSettingsOffers.own_logs:type_name -> opamp.proto.TelemetryConnectionSettings - 53, // 45: opamp.proto.ConnectionSettingsOffers.other_connections:type_name -> opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry - 54, // 46: opamp.proto.PackagesAvailable.packages:type_name -> opamp.proto.PackagesAvailable.PackagesEntry - 3, // 47: opamp.proto.PackageAvailable.type:type_name -> opamp.proto.PackageType - 31, // 48: opamp.proto.PackageAvailable.file:type_name -> opamp.proto.DownloadableFile - 25, // 49: opamp.proto.DownloadableFile.headers:type_name -> opamp.proto.Headers - 4, // 50: opamp.proto.ServerErrorResponse.type:type_name -> opamp.proto.ServerErrorResponseType - 33, // 51: opamp.proto.ServerErrorResponse.retry_info:type_name -> opamp.proto.RetryInfo - 5, // 52: opamp.proto.ServerToAgentCommand.type:type_name -> opamp.proto.CommandType - 58, // 53: opamp.proto.AgentDescription.identifying_attributes:type_name -> opamp.proto.KeyValue - 58, // 54: opamp.proto.AgentDescription.non_identifying_attributes:type_name -> opamp.proto.KeyValue - 55, // 55: opamp.proto.ComponentHealth.component_health_map:type_name -> opamp.proto.ComponentHealth.ComponentHealthMapEntry - 45, // 56: opamp.proto.EffectiveConfig.config_map:type_name -> opamp.proto.AgentConfigMap - 8, // 57: opamp.proto.RemoteConfigStatus.status:type_name -> opamp.proto.RemoteConfigStatuses - 7, // 58: opamp.proto.ConnectionSettingsStatus.status:type_name -> opamp.proto.ConnectionSettingsStatuses - 56, // 59: opamp.proto.PackageStatuses.packages:type_name -> opamp.proto.PackageStatuses.PackagesEntry - 9, // 60: opamp.proto.PackageStatus.status:type_name -> opamp.proto.PackageStatusEnum - 42, // 61: opamp.proto.PackageStatus.download_details:type_name -> opamp.proto.PackageDownloadDetails - 45, // 62: opamp.proto.AgentRemoteConfig.config:type_name -> opamp.proto.AgentConfigMap - 57, // 63: opamp.proto.AgentConfigMap.config_map:type_name -> opamp.proto.AgentConfigMap.ConfigMapEntry - 16, // 64: opamp.proto.AvailableComponents.ComponentsEntry.value:type_name -> opamp.proto.ComponentDetails - 16, // 65: opamp.proto.ComponentDetails.SubComponentMapEntry.value:type_name -> opamp.proto.ComponentDetails - 22, // 66: opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntry.value:type_name -> opamp.proto.OtherConnectionSettings - 30, // 67: opamp.proto.PackagesAvailable.PackagesEntry.value:type_name -> opamp.proto.PackageAvailable - 36, // 68: opamp.proto.ComponentHealth.ComponentHealthMapEntry.value:type_name -> opamp.proto.ComponentHealth - 41, // 69: opamp.proto.PackageStatuses.PackagesEntry.value:type_name -> opamp.proto.PackageStatus - 46, // 70: opamp.proto.AgentConfigMap.ConfigMapEntry.value:type_name -> opamp.proto.AgentConfigFile - 71, // [71:71] is the sub-list for method output_type - 71, // [71:71] is the sub-list for method input_type - 71, // [71:71] is the sub-list for extension type_name - 71, // [71:71] is the sub-list for extension extendee - 0, // [0:71] is the sub-list for field type_name + 35, // 0: opamp.proto.v1.AgentToServer.agent_description:type_name -> opamp.proto.v1.AgentDescription + 36, // 1: opamp.proto.v1.AgentToServer.health:type_name -> opamp.proto.v1.ComponentHealth + 37, // 2: opamp.proto.v1.AgentToServer.effective_config:type_name -> opamp.proto.v1.EffectiveConfig + 38, // 3: opamp.proto.v1.AgentToServer.remote_config_status:type_name -> opamp.proto.v1.RemoteConfigStatus + 40, // 4: opamp.proto.v1.AgentToServer.package_statuses:type_name -> opamp.proto.v1.PackageStatuses + 11, // 5: opamp.proto.v1.AgentToServer.agent_disconnect:type_name -> opamp.proto.v1.AgentDisconnect + 12, // 6: opamp.proto.v1.AgentToServer.connection_settings_request:type_name -> opamp.proto.v1.ConnectionSettingsRequest + 47, // 7: opamp.proto.v1.AgentToServer.custom_capabilities:type_name -> opamp.proto.v1.CustomCapabilities + 48, // 8: opamp.proto.v1.AgentToServer.custom_message:type_name -> opamp.proto.v1.CustomMessage + 15, // 9: opamp.proto.v1.AgentToServer.available_components:type_name -> opamp.proto.v1.AvailableComponents + 39, // 10: opamp.proto.v1.AgentToServer.connection_settings_status:type_name -> opamp.proto.v1.ConnectionSettingsStatus + 13, // 11: opamp.proto.v1.ConnectionSettingsRequest.opamp:type_name -> opamp.proto.v1.OpAMPConnectionSettingsRequest + 14, // 12: opamp.proto.v1.OpAMPConnectionSettingsRequest.certificate_request:type_name -> opamp.proto.v1.CertificateRequest + 49, // 13: opamp.proto.v1.AvailableComponents.components:type_name -> opamp.proto.v1.AvailableComponents.ComponentsEntry + 57, // 14: opamp.proto.v1.ComponentDetails.metadata:type_name -> opamp.proto.v1.KeyValue + 50, // 15: opamp.proto.v1.ComponentDetails.sub_component_map:type_name -> opamp.proto.v1.ComponentDetails.SubComponentMapEntry + 32, // 16: opamp.proto.v1.ServerToAgent.error_response:type_name -> opamp.proto.v1.ServerErrorResponse + 44, // 17: opamp.proto.v1.ServerToAgent.remote_config:type_name -> opamp.proto.v1.AgentRemoteConfig + 28, // 18: opamp.proto.v1.ServerToAgent.connection_settings:type_name -> opamp.proto.v1.ConnectionSettingsOffers + 29, // 19: opamp.proto.v1.ServerToAgent.packages_available:type_name -> opamp.proto.v1.PackagesAvailable + 43, // 20: opamp.proto.v1.ServerToAgent.agent_identification:type_name -> opamp.proto.v1.AgentIdentification + 34, // 21: opamp.proto.v1.ServerToAgent.command:type_name -> opamp.proto.v1.ServerToAgentCommand + 47, // 22: opamp.proto.v1.ServerToAgent.custom_capabilities:type_name -> opamp.proto.v1.CustomCapabilities + 48, // 23: opamp.proto.v1.ServerToAgent.custom_message:type_name -> opamp.proto.v1.CustomMessage + 18, // 24: opamp.proto.v1.SignedServerToAgent.trust_chain_response:type_name -> opamp.proto.v1.TrustChainResponse + 25, // 25: opamp.proto.v1.OpAMPConnectionSettings.headers:type_name -> opamp.proto.v1.Headers + 27, // 26: opamp.proto.v1.OpAMPConnectionSettings.certificate:type_name -> opamp.proto.v1.TLSCertificate + 23, // 27: opamp.proto.v1.OpAMPConnectionSettings.tls:type_name -> opamp.proto.v1.TLSConnectionSettings + 24, // 28: opamp.proto.v1.OpAMPConnectionSettings.proxy:type_name -> opamp.proto.v1.ProxyConnectionSettings + 25, // 29: opamp.proto.v1.TelemetryConnectionSettings.headers:type_name -> opamp.proto.v1.Headers + 27, // 30: opamp.proto.v1.TelemetryConnectionSettings.certificate:type_name -> opamp.proto.v1.TLSCertificate + 23, // 31: opamp.proto.v1.TelemetryConnectionSettings.tls:type_name -> opamp.proto.v1.TLSConnectionSettings + 24, // 32: opamp.proto.v1.TelemetryConnectionSettings.proxy:type_name -> opamp.proto.v1.ProxyConnectionSettings + 25, // 33: opamp.proto.v1.OtherConnectionSettings.headers:type_name -> opamp.proto.v1.Headers + 27, // 34: opamp.proto.v1.OtherConnectionSettings.certificate:type_name -> opamp.proto.v1.TLSCertificate + 51, // 35: opamp.proto.v1.OtherConnectionSettings.other_settings:type_name -> opamp.proto.v1.OtherConnectionSettings.OtherSettingsEntry + 23, // 36: opamp.proto.v1.OtherConnectionSettings.tls:type_name -> opamp.proto.v1.TLSConnectionSettings + 24, // 37: opamp.proto.v1.OtherConnectionSettings.proxy:type_name -> opamp.proto.v1.ProxyConnectionSettings + 25, // 38: opamp.proto.v1.ProxyConnectionSettings.connect_headers:type_name -> opamp.proto.v1.Headers + 26, // 39: opamp.proto.v1.Headers.headers:type_name -> opamp.proto.v1.Header + 20, // 40: opamp.proto.v1.ConnectionSettingsOffers.opamp:type_name -> opamp.proto.v1.OpAMPConnectionSettings + 21, // 41: opamp.proto.v1.ConnectionSettingsOffers.own_metrics:type_name -> opamp.proto.v1.TelemetryConnectionSettings + 21, // 42: opamp.proto.v1.ConnectionSettingsOffers.own_traces:type_name -> opamp.proto.v1.TelemetryConnectionSettings + 21, // 43: opamp.proto.v1.ConnectionSettingsOffers.own_logs:type_name -> opamp.proto.v1.TelemetryConnectionSettings + 52, // 44: opamp.proto.v1.ConnectionSettingsOffers.other_connections:type_name -> opamp.proto.v1.ConnectionSettingsOffers.OtherConnectionsEntry + 53, // 45: opamp.proto.v1.PackagesAvailable.packages:type_name -> opamp.proto.v1.PackagesAvailable.PackagesEntry + 3, // 46: opamp.proto.v1.PackageAvailable.type:type_name -> opamp.proto.v1.PackageType + 31, // 47: opamp.proto.v1.PackageAvailable.file:type_name -> opamp.proto.v1.DownloadableFile + 25, // 48: opamp.proto.v1.DownloadableFile.headers:type_name -> opamp.proto.v1.Headers + 4, // 49: opamp.proto.v1.ServerErrorResponse.type:type_name -> opamp.proto.v1.ServerErrorResponseType + 33, // 50: opamp.proto.v1.ServerErrorResponse.retry_info:type_name -> opamp.proto.v1.RetryInfo + 5, // 51: opamp.proto.v1.ServerToAgentCommand.type:type_name -> opamp.proto.v1.CommandType + 57, // 52: opamp.proto.v1.AgentDescription.identifying_attributes:type_name -> opamp.proto.v1.KeyValue + 57, // 53: opamp.proto.v1.AgentDescription.non_identifying_attributes:type_name -> opamp.proto.v1.KeyValue + 54, // 54: opamp.proto.v1.ComponentHealth.component_health_map:type_name -> opamp.proto.v1.ComponentHealth.ComponentHealthMapEntry + 45, // 55: opamp.proto.v1.EffectiveConfig.config_map:type_name -> opamp.proto.v1.AgentConfigMap + 8, // 56: opamp.proto.v1.RemoteConfigStatus.status:type_name -> opamp.proto.v1.RemoteConfigStatuses + 7, // 57: opamp.proto.v1.ConnectionSettingsStatus.status:type_name -> opamp.proto.v1.ConnectionSettingsStatuses + 55, // 58: opamp.proto.v1.PackageStatuses.packages:type_name -> opamp.proto.v1.PackageStatuses.PackagesEntry + 9, // 59: opamp.proto.v1.PackageStatus.status:type_name -> opamp.proto.v1.PackageStatusEnum + 42, // 60: opamp.proto.v1.PackageStatus.download_details:type_name -> opamp.proto.v1.PackageDownloadDetails + 45, // 61: opamp.proto.v1.AgentRemoteConfig.config:type_name -> opamp.proto.v1.AgentConfigMap + 56, // 62: opamp.proto.v1.AgentConfigMap.config_map:type_name -> opamp.proto.v1.AgentConfigMap.ConfigMapEntry + 16, // 63: opamp.proto.v1.AvailableComponents.ComponentsEntry.value:type_name -> opamp.proto.v1.ComponentDetails + 16, // 64: opamp.proto.v1.ComponentDetails.SubComponentMapEntry.value:type_name -> opamp.proto.v1.ComponentDetails + 22, // 65: opamp.proto.v1.ConnectionSettingsOffers.OtherConnectionsEntry.value:type_name -> opamp.proto.v1.OtherConnectionSettings + 30, // 66: opamp.proto.v1.PackagesAvailable.PackagesEntry.value:type_name -> opamp.proto.v1.PackageAvailable + 36, // 67: opamp.proto.v1.ComponentHealth.ComponentHealthMapEntry.value:type_name -> opamp.proto.v1.ComponentHealth + 41, // 68: opamp.proto.v1.PackageStatuses.PackagesEntry.value:type_name -> opamp.proto.v1.PackageStatus + 46, // 69: opamp.proto.v1.AgentConfigMap.ConfigMapEntry.value:type_name -> opamp.proto.v1.AgentConfigFile + 70, // [70:70] is the sub-list for method output_type + 70, // [70:70] is the sub-list for method input_type + 70, // [70:70] is the sub-list for extension type_name + 70, // [70:70] is the sub-list for extension extendee + 0, // [0:70] is the sub-list for field type_name } func init() { file_opamp_proto_init() } @@ -4181,7 +4130,7 @@ func file_opamp_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_opamp_proto_rawDesc), len(file_opamp_proto_rawDesc)), NumEnums: 10, - NumMessages: 48, + NumMessages: 47, NumExtensions: 0, NumServices: 0, }, diff --git a/server/attestation.go b/server/attestation.go index a9830d9f..63d58a5b 100644 --- a/server/attestation.go +++ b/server/attestation.go @@ -2,6 +2,7 @@ package server import ( "context" + "encoding/pem" "fmt" "sync/atomic" @@ -67,11 +68,11 @@ func (s *connectionSigningState) signOutgoing(ctx context.Context, msg *protobuf // transitioned firstSent from false to true — guaranteeing exactly // one envelope carries the trust chain across concurrent callers. if s.firstSent.CompareAndSwap(false, true) { - chain := make([]*protobufs.TrustChainResponse_Certificate, len(s.chainDER)) - for i, der := range s.chainDER { - chain[i] = &protobufs.TrustChainResponse_Certificate{DerData: der} + var pemChain []byte + for _, der := range s.chainDER { + pemChain = append(pemChain, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})...) } - env.TrustChainResponse = &protobufs.TrustChainResponse{CertificateChain: chain} + env.TrustChainResponse = &protobufs.TrustChainResponse{CertificateChain: pemChain} } return env, nil } diff --git a/server/attestation_test.go b/server/attestation_test.go index 8372b744..44ec04e2 100644 --- a/server/attestation_test.go +++ b/server/attestation_test.go @@ -3,6 +3,7 @@ package server import ( "context" "crypto/x509" + "encoding/pem" "errors" "net/http" "sync/atomic" @@ -154,12 +155,20 @@ func TestAddOffersAttestationBit_Idempotent(t *testing.T) { require.Equal(t, offersBit, addOffersAttestationBit(offersBit), "no-op when already set") } -// derChainFromResponse extracts the DER bytes from a -// TrustChainResponse for the paired verifier's ValidateChain call. +// derChainFromResponse decodes the PEM blob in TrustChainResponse into +// DER byte slices for the paired verifier's ValidateChain call. func derChainFromResponse(resp *protobufs.TrustChainResponse) [][]byte { - out := make([][]byte, len(resp.CertificateChain)) - for i, c := range resp.CertificateChain { - out[i] = c.GetDerData() + var out [][]byte + rest := resp.CertificateChain + for len(rest) > 0 { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if block.Type == "CERTIFICATE" { + out = append(out, block.Bytes) + } } return out } From d007bbb45ed77206039d43a9ef71ed6b6513cd1a Mon Sep 17 00:00:00 2001 From: Stanley Liu Date: Mon, 15 Jun 2026 16:21:14 -0400 Subject: [PATCH 18/22] Add Message Attestation example with out-of-process policy server Introduces a self-contained three-component demo of the attestation architecture described in supplementary-guidelines.md. policysrv (new): a minimal out-of-process policy/signing server that generates an ephemeral ECDSA P-256 CA and leaf on startup, writes the CA to /tmp/opamp-policy-ca.pem for agents to use as their trust anchor, and exposes POST /v1/sign, GET /v1/chain, and GET /v1/ca. The distribution server delegates all signing here and never holds the private key. signing.RemoteSigner (new): implements signing.Signer by posting payloads to /v1/sign and fetching the certificate chain from /v1/chain, keeping the key isolation boundary at the HTTP layer. server example: gains --policy-server to construct a RemoteSigner and pass it as PayloadSigner into server.Settings, enabling signed ServerToAgent delivery without the distribution server touching key material. agent example: gains --attestation-ca (env: AGENT_ATTESTATION_CA) to load a trust anchor via signing.VerifierFromFile, store it with WithPayloadVerifier, advertise RequiresPayloadTrustVerification, and forward PayloadVerifier into types.StartSettings so every inbound message is verified by the client layer. --- internal/examples/agent/agent/agent.go | 19 +++ internal/examples/agent/main.go | 31 ++++ internal/examples/policysrv/main.go | 157 ++++++++++++++++++ internal/examples/server/main.go | 18 +- internal/examples/server/opampsrv/opampsrv.go | 25 ++- signing/remote_signer.go | 114 +++++++++++++ signing/remote_signer_test.go | 103 ++++++++++++ 7 files changed, 458 insertions(+), 9 deletions(-) create mode 100644 internal/examples/policysrv/main.go create mode 100644 signing/remote_signer.go create mode 100644 signing/remote_signer_test.go diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go index 96fa5efd..06f548fe 100644 --- a/internal/examples/agent/agent/agent.go +++ b/internal/examples/agent/agent/agent.go @@ -27,6 +27,7 @@ import ( "github.com/open-telemetry/opamp-go/client/types" "github.com/open-telemetry/opamp-go/internal/examples/config" "github.com/open-telemetry/opamp-go/protobufs" + "github.com/open-telemetry/opamp-go/signing" ) var localConfig = []byte(` @@ -87,6 +88,11 @@ type Agent struct { // lastConnectionSettingsHash stores the hash of the most recently received // ConnectionSettingsOffers, used when reporting connection settings status. lastConnectionSettingsHash []byte + + // payloadVerifier, when non-nil, enables Message Attestation: every + // inbound ServerToAgent message must arrive in a SignedServerToAgent + // envelope whose signature chains to this verifier's trust anchor. + payloadVerifier signing.Verifier } type proxySettings struct { @@ -138,6 +144,15 @@ func WithNoClientCertRequest() Option { } } +// WithPayloadVerifier enables Message Attestation. Every inbound ServerToAgent +// message must arrive in a SignedServerToAgent envelope whose signature chains +// to the trust anchor embedded in v. +func WithPayloadVerifier(v signing.Verifier) Option { + return func(agent *Agent) { + agent.payloadVerifier = v + } +} + func NewAgent(agentConfig *config.AgentConfig, options ...Option) *Agent { agent := &Agent{ logger: &Logger{Logger: log.Default()}, @@ -190,6 +205,7 @@ func (agent *Agent) connect(ops ...settingsOp) error { OpAMPServerURL: agent.agentConfig.Endpoint, HeartbeatInterval: agent.agentConfig.HeartbeatInterval, InstanceUid: types.InstanceUid(agent.instanceId), + PayloadVerifier: agent.payloadVerifier, Callbacks: types.Callbacks{ OnConnect: func(ctx context.Context) { agent.logger.Debugf(ctx, "Connected to the server.") @@ -232,6 +248,9 @@ func (agent *Agent) connect(ops ...settingsOp) error { protobufs.AgentCapabilities_AgentCapabilities_ReportsOwnMetrics | protobufs.AgentCapabilities_AgentCapabilities_AcceptsOpAMPConnectionSettings | protobufs.AgentCapabilities_AgentCapabilities_ReportsConnectionSettingsStatus + if agent.payloadVerifier != nil { + supportedCapabilities |= protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification + } err = agent.client.SetCapabilities(&supportedCapabilities) if err != nil { return err diff --git a/internal/examples/agent/main.go b/internal/examples/agent/main.go index 9a221a05..0cdd03cf 100644 --- a/internal/examples/agent/main.go +++ b/internal/examples/agent/main.go @@ -15,6 +15,7 @@ import ( opampinternal "github.com/open-telemetry/opamp-go/internal" "github.com/open-telemetry/opamp-go/internal/examples/agent/agent" "github.com/open-telemetry/opamp-go/internal/examples/config" + "github.com/open-telemetry/opamp-go/signing" "github.com/google/uuid" "go.opentelemetry.io/collector/config/configtls" @@ -42,6 +43,11 @@ type flagConfig struct { // scaleCount = 1 runs a normal agent // scaleCount > 1 runs scale test agents (pre-assigned IDs, no initial cert request) scaleCount uint64 + // attestationCAFile, when non-empty, enables Message Attestation: every + // inbound ServerToAgent must carry a valid signature chaining to the CA + // certificate stored at this path. Run internal/examples/policysrv first + // to generate the CA and start the signing server. + attestationCAFile string } func (cfg flagConfig) verifyArgs() error { @@ -157,6 +163,10 @@ func loadEnv(cfg *flagConfig) { cfg.scaleCount = count } } + + if s, ok := os.LookupEnv("AGENT_ATTESTATION_CA"); ok { + cfg.attestationCAFile = s + } } func main() { @@ -172,6 +182,13 @@ func main() { flag.DurationVar(&cfg.heartbeat, "heartbeat", time.Second*30, "Heartbeat duration (env var: AGENT_HEARTBEAT).") flag.BoolVar(&cfg.quietAgent, "quite-agent", false, "Disable agent logger (env var: AGENT_QUIET).") flag.Uint64Var(&cfg.scaleCount, "scale-count", 1, "The number of agents to start in scale mode (env var: AGENT_SCALE_COUNT).") + flag.StringVar(&cfg.attestationCAFile, "attestation-ca", "", + "Path to a PEM-encoded CA certificate used to verify signed ServerToAgent messages\n"+ + "(Message Attestation). When set, the agent declares the\n"+ + "RequiresPayloadTrustVerification capability and rejects any message whose\n"+ + "signature does not chain to this CA. Obtain the CA from the policy server's\n"+ + "/v1/ca endpoint or from /tmp/opamp-policy-ca.pem after running policysrv\n"+ + "(env var: AGENT_ATTESTATION_CA).") flag.Parse() loadEnv(&cfg) @@ -211,6 +228,17 @@ func main() { // If an error is encountered when starting an agent, it is return along with all started agents. func runScale(ctx context.Context, cfg flagConfig) ([]*agent.Agent, error) { nopLogger := &opampinternal.NopLogger{} + + var verifier signing.Verifier + if cfg.attestationCAFile != "" { + v, err := signing.VerifierFromFile(cfg.attestationCAFile) + if err != nil { + return nil, fmt.Errorf("load attestation CA: %w", err) + } + verifier = v + log.Printf("Message Attestation enabled — trust anchor: %s", cfg.attestationCAFile) + } + agentConfig := &config.AgentConfig{ Endpoint: cfg.endpoint, HeartbeatInterval: &cfg.heartbeat, @@ -239,6 +267,9 @@ func runScale(ctx context.Context, cfg flagConfig) ([]*agent.Agent, error) { agent.WithAgentType(cfg.agentType), agent.WithAgentVersion(cfg.agentVersion), } + if verifier != nil { + opts = append(opts, agent.WithPayloadVerifier(verifier)) + } if cfg.quietAgent { opts = append(opts, agent.WithLogger(nopLogger)) } diff --git a/internal/examples/policysrv/main.go b/internal/examples/policysrv/main.go new file mode 100644 index 00000000..7b67826f --- /dev/null +++ b/internal/examples/policysrv/main.go @@ -0,0 +1,157 @@ +// policysrv is a minimal example of an out-of-process OpAMP policy/signing +// server, as described in supplementary-guidelines.md. +// +// # Architecture +// +// The OpAMP distribution server (internal/examples/server) holds no private +// key material. Before delivering each ServerToAgent message it calls this +// server's /v1/sign endpoint to obtain a signature over the payload bytes. +// The structural isolation is the key security property: an attacker who +// compromises the distribution server gains the ability to send messages, +// but cannot produce valid signatures without also compromising this server. +// +// Agent ──AgentToServer──► OpAMP Server ──sign request──► Policy Server +// ◄──signature────────────────── +// ◄──SignedServerToAgent─────────── +// +// # Policy enforcement +// +// In this example the server signs every request unconditionally. A +// production policy server would decode the ServerToAgent payload before +// signing and reject messages that violate organizational constraints, for +// example: +// - Deny ServerToAgentCommand messages to immutable agents. +// - Enforce per-team RemoteConfig ownership. +// - Require that only the latest approved component version may be installed. +// +// # Usage (three separate terminals, all run from internal/examples/) +// +// # Terminal 1 – policy/signing server +// go run ./policysrv +// +// # Terminal 2 – OpAMP distribution server (points at policy server) +// go run ./server --policy-server http://localhost:4322 +// +// # Terminal 3 – agent (pre-provisioned with the CA cert written above) +// go run ./agent --attestation-ca /tmp/opamp-policy-ca.pem +// +// In production the CA certificate would be distributed out-of-band via +// configuration management tooling (Ansible, Chef, Puppet, or a secrets +// manager), or compiled into the agent binary. The /v1/ca endpoint is +// provided for demo convenience only and is not part of the OpAMP protocol. +package main + +import ( + "context" + "crypto/x509" + "encoding/pem" + "io" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/open-telemetry/opamp-go/signing" +) + +const ( + listenAddr = ":4322" + caOutPath = "/tmp/opamp-policy-ca.pem" +) + +func main() { + // Generate an ephemeral CA and signing leaf. + // In production: load the CA and leaf private key from an HSM or + // secrets manager; never write the private key to disk. + ca, caKey, err := signing.GenerateCA(signing.AlgorithmECDSAP256SHA256, signing.CertOptions{}) + if err != nil { + log.Fatalf("generate CA: %v", err) + } + leaf, leafKey, err := signing.GenerateLeaf(signing.AlgorithmECDSAP256SHA256, ca, caKey, signing.CertOptions{}) + if err != nil { + log.Fatalf("generate leaf: %v", err) + } + signer, err := signing.NewLocalSigner(leafKey, []*x509.Certificate{leaf}) + if err != nil { + log.Fatalf("new signer: %v", err) + } + + caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Raw}) + leafPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leaf.Raw}) + + // Write the CA cert for agents to load as their payload trust anchor. + // In production this step is replaced by your configuration-management + // pipeline; do not derive trust anchors from the network at runtime. + if err := os.WriteFile(caOutPath, caPEM, 0o644); err != nil { + log.Fatalf("write CA cert: %v", err) + } + log.Printf("CA certificate → %s", caOutPath) + + mux := http.NewServeMux() + + // POST /v1/sign + // The OpAMP server sends the serialised ServerToAgent payload here. + // This handler signs it and returns the raw signature bytes. + // + // Production note: decode the payload (proto.Unmarshal into + // protobufs.ServerToAgent) here to apply policy before signing. + mux.HandleFunc("POST /v1/sign", func(w http.ResponseWriter, r *http.Request) { + payload, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "read body: "+err.Error(), http.StatusBadRequest) + return + } + sig, err := signer.Sign(r.Context(), payload) + if err != nil { + http.Error(w, "sign: "+err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/octet-stream") + _, _ = w.Write(sig) + log.Printf("[policy] signed %d-byte payload → %d-byte signature", len(payload), len(sig)) + }) + + // GET /v1/chain + // Returns the PEM-encoded signing certificate chain (leaf only here; + // include any intermediates between the leaf and the root CA). + // The root CA is excluded — agents already possess it as their trust anchor. + mux.HandleFunc("GET /v1/chain", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/x-pem-file") + _, _ = w.Write(leafPEM) + }) + + // GET /v1/ca + // Returns the CA certificate in PEM. + // Demo convenience only — not part of the OpAMP protocol. In + // production, provision the CA cert out-of-band. + mux.HandleFunc("GET /v1/ca", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/x-pem-file") + _, _ = w.Write(caPEM) + }) + + ln, err := net.Listen("tcp", listenAddr) + if err != nil { + log.Fatalf("listen on %s: %v", listenAddr, err) + } + srv := &http.Server{Handler: mux} + + log.Printf("Policy server listening on %s", listenAddr) + log.Println("Next steps (run from internal/examples/):") + log.Printf(" OpAMP server: go run ./server --policy-server http://localhost%s", listenAddr) + log.Printf(" Agent: go run ./agent --attestation-ca %s", caOutPath) + + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + + go func() { + if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed { + log.Fatalf("serve: %v", err) + } + }() + + <-stop + log.Println("Shutting down…") + _ = srv.Shutdown(context.Background()) +} diff --git a/internal/examples/server/main.go b/internal/examples/server/main.go index 349190c1..b82a867a 100644 --- a/internal/examples/server/main.go +++ b/internal/examples/server/main.go @@ -9,6 +9,7 @@ import ( "github.com/open-telemetry/opamp-go/internal/examples/server/data" "github.com/open-telemetry/opamp-go/internal/examples/server/opampsrv" "github.com/open-telemetry/opamp-go/internal/examples/server/uisrv" + "github.com/open-telemetry/opamp-go/signing" ) var logger = log.New(log.Default().Writer(), "[MAIN] ", log.Default().Flags()|log.Lmsgprefix|log.Lmicroseconds) @@ -17,6 +18,13 @@ func main() { var emitMetrics bool flag.BoolVar(&emitMetrics, "emit-metrics", false, "Emit metrics to stdout.") + var policyServerURL string + flag.StringVar(&policyServerURL, "policy-server", "", + "Base URL of the out-of-process policy/signing server (e.g. http://localhost:4322).\n"+ + "When set, every outbound ServerToAgent message is signed via that server,\n"+ + "demonstrating the isolated signing architecture from supplementary-guidelines.md.\n"+ + "Run internal/examples/policysrv first to start a local policy server.") + flag.Parse() curDir, err := os.Getwd() @@ -24,10 +32,18 @@ func main() { panic(err) } + // If a policy server URL is provided, create a RemoteSigner that delegates + // all signing to it. The OpAMP server itself never touches the private key. + var payloadSigner signing.Signer + if policyServerURL != "" { + payloadSigner = signing.NewRemoteSigner(policyServerURL) + logger.Printf("Message Attestation enabled — signing via policy server at %s", policyServerURL) + } + logger.Println("OpAMP Server starting...") uisrv.Start(curDir) - opampSrv := opampsrv.NewServer(&data.AllAgents, emitMetrics) + opampSrv := opampsrv.NewServer(&data.AllAgents, emitMetrics, payloadSigner) opampSrv.Start() logger.Println("OpAMP Server running...") diff --git a/internal/examples/server/opampsrv/opampsrv.go b/internal/examples/server/opampsrv/opampsrv.go index 80fe0907..d83d3479 100644 --- a/internal/examples/server/opampsrv/opampsrv.go +++ b/internal/examples/server/opampsrv/opampsrv.go @@ -14,16 +14,23 @@ import ( "github.com/open-telemetry/opamp-go/protobufs" "github.com/open-telemetry/opamp-go/server" "github.com/open-telemetry/opamp-go/server/types" + "github.com/open-telemetry/opamp-go/signing" ) type Server struct { - opampSrv server.OpAMPServer - agents *data.Agents - logger *Logger - metrics *metricsTracker + opampSrv server.OpAMPServer + agents *data.Agents + logger *Logger + metrics *metricsTracker + payloadSigner signing.Signer } -func NewServer(agents *data.Agents, emitMetrics bool) *Server { +// NewServer creates a new OpAMP server. payloadSigner, when non-nil, enables +// Message Attestation: every outbound ServerToAgent message is wrapped in a +// SignedServerToAgent envelope signed by the given signer. Use +// signing.NewRemoteSigner to delegate signing to an out-of-process policy +// server as recommended in supplementary-guidelines.md. +func NewServer(agents *data.Agents, emitMetrics bool, payloadSigner signing.Signer) *Server { logger := &Logger{ log.New( log.Default().Writer(), @@ -38,9 +45,10 @@ func NewServer(agents *data.Agents, emitMetrics bool) *Server { } srv := &Server{ - agents: agents, - logger: logger, - metrics: metrics, + agents: agents, + logger: logger, + metrics: metrics, + payloadSigner: payloadSigner, } srv.opampSrv = server.New(logger) @@ -51,6 +59,7 @@ func NewServer(agents *data.Agents, emitMetrics bool) *Server { func (srv *Server) Start() { settings := server.StartSettings{ Settings: server.Settings{ + PayloadSigner: srv.payloadSigner, Callbacks: types.Callbacks{ OnConnecting: func(request *http.Request) types.ConnectionResponse { return types.ConnectionResponse{ diff --git a/signing/remote_signer.go b/signing/remote_signer.go new file mode 100644 index 00000000..5c060f85 --- /dev/null +++ b/signing/remote_signer.go @@ -0,0 +1,114 @@ +package signing + +import ( + "bytes" + "context" + "encoding/pem" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +// RemoteSigner implements [Signer] by delegating to an out-of-process HTTP +// signing service. This is the recommended production deployment pattern +// described in supplementary-guidelines.md: the OpAMP distribution server +// holds no private key material, and signing is performed by a separate +// policy server that is not reachable from the network edge. +// +// The signing service must expose two endpoints: +// +// POST /v1/sign — request body: raw payload bytes to sign +// — response body: raw signature bytes +// GET /v1/chain — response body: PEM-encoded certificate chain +// — (intermediates first, signing leaf last, root excluded) +// +// In production the policy server may additionally enforce organizational +// policy — inspecting the decoded payload to deny message types, enforce +// per-team permissions, or apply fleet-wide invariants — before delegating +// to an HSM or secrets manager for the actual signature. +type RemoteSigner struct { + baseURL string + client *http.Client +} + +var _ Signer = (*RemoteSigner)(nil) + +// NewRemoteSigner returns a RemoteSigner that calls the signing service at +// baseURL (e.g. "http://policy-server:4322"). A 10-second per-request +// timeout is applied. +func NewRemoteSigner(baseURL string) *RemoteSigner { + return &RemoteSigner{ + baseURL: strings.TrimRight(baseURL, "/"), + client: &http.Client{Timeout: 10 * time.Second}, + } +} + +// Sign implements [Signer] by POST-ing payload to /v1/sign and returning +// the response body as the detached signature. +func (s *RemoteSigner) Sign(ctx context.Context, payload []byte) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, + s.baseURL+"/v1/sign", bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("remote signer: build sign request: %w", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + resp, err := s.client.Do(req) + if err != nil { + return nil, fmt.Errorf("remote signer: sign request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("remote signer: read sign response: %w", err) + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("remote signer: sign returned HTTP %d: %s", resp.StatusCode, body) + } + return body, nil +} + +// ChainDER implements [Signer] by GET-ing /v1/chain and decoding the +// returned PEM blob into DER byte slices ordered intermediates-first, +// leaf-last. +func (s *RemoteSigner) ChainDER(ctx context.Context) ([][]byte, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, + s.baseURL+"/v1/chain", nil) + if err != nil { + return nil, fmt.Errorf("remote signer: build chain request: %w", err) + } + + resp, err := s.client.Do(req) + if err != nil { + return nil, fmt.Errorf("remote signer: chain request: %w", err) + } + defer resp.Body.Close() + + pemBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("remote signer: read chain response: %w", err) + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("remote signer: chain returned HTTP %d: %s", resp.StatusCode, pemBytes) + } + + var chain [][]byte + rest := pemBytes + for len(rest) > 0 { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if block.Type == "CERTIFICATE" { + chain = append(chain, block.Bytes) + } + } + if len(chain) == 0 { + return nil, fmt.Errorf("remote signer: chain response contained no CERTIFICATE PEM blocks") + } + return chain, nil +} diff --git a/signing/remote_signer_test.go b/signing/remote_signer_test.go new file mode 100644 index 00000000..1574b1c2 --- /dev/null +++ b/signing/remote_signer_test.go @@ -0,0 +1,103 @@ +package signing_test + +import ( + "context" + "encoding/pem" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opamp-go/signing" +) + +func TestRemoteSigner_Sign_HappyPath(t *testing.T) { + const wantSig = "fake-signature-bytes" + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/v1/sign", r.URL.Path) + _, _ = w.Write([]byte(wantSig)) + })) + defer srv.Close() + + s := signing.NewRemoteSigner(srv.URL) + sig, err := s.Sign(context.Background(), []byte("test-payload")) + require.NoError(t, err) + require.Equal(t, []byte(wantSig), sig) +} + +func TestRemoteSigner_Sign_HTTPError(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "policy denied", http.StatusForbidden) + })) + defer srv.Close() + + s := signing.NewRemoteSigner(srv.URL) + _, err := s.Sign(context.Background(), []byte("test-payload")) + require.Error(t, err) + require.Contains(t, err.Error(), "403") +} + +func TestRemoteSigner_ChainDER_HappyPath(t *testing.T) { + ca, caKey, err := signing.GenerateCA(signing.AlgorithmECDSAP256SHA256, signing.CertOptions{}) + require.NoError(t, err) + leaf, _, err := signing.GenerateLeaf(signing.AlgorithmECDSAP256SHA256, ca, caKey, signing.CertOptions{}) + require.NoError(t, err) + + // Server returns PEM chain (leaf only in this test). + pemChain := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leaf.Raw}) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + require.Equal(t, "/v1/chain", r.URL.Path) + _, _ = w.Write(pemChain) + })) + defer srv.Close() + + s := signing.NewRemoteSigner(srv.URL) + chain, err := s.ChainDER(context.Background()) + require.NoError(t, err) + require.Len(t, chain, 1) + require.Equal(t, leaf.Raw, chain[0]) +} + +func TestRemoteSigner_ChainDER_EmptyBody(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) // 200 but no PEM blocks + })) + defer srv.Close() + + s := signing.NewRemoteSigner(srv.URL) + _, err := s.ChainDER(context.Background()) + require.Error(t, err) + require.Contains(t, err.Error(), "no CERTIFICATE PEM blocks") +} + +func TestRemoteSigner_ChainDER_HTTPError(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "unavailable", http.StatusServiceUnavailable) + })) + defer srv.Close() + + s := signing.NewRemoteSigner(srv.URL) + _, err := s.ChainDER(context.Background()) + require.Error(t, err) + require.Contains(t, err.Error(), "503") +} + +func TestRemoteSigner_TrailingSlashStripped(t *testing.T) { + called := false + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + require.Equal(t, "/v1/sign", r.URL.Path) + _, _ = w.Write([]byte("sig")) + })) + defer srv.Close() + + // Pass URL with trailing slash — NewRemoteSigner must strip it. + s := signing.NewRemoteSigner(srv.URL + "/") + _, err := s.Sign(context.Background(), []byte("p")) + require.NoError(t, err) + require.True(t, called) +} From 6afac2a7fb12ba3268b72f62dd7ab6add63c6d4b Mon Sep 17 00:00:00 2001 From: Stanley Liu Date: Wed, 1 Jul 2026 16:33:57 -0400 Subject: [PATCH 19/22] Add opt-in TOFU flow, SAN verification, update tests --- client/httpclient.go | 1 + client/internal/attestation.go | 49 +++++++++-- client/internal/attestation_test.go | 26 +++--- client/internal/clientcommon.go | 30 ++++++- client/internal/httpsender.go | 13 ++- client/internal/wsreceiver.go | 11 ++- client/internal/wsreceiver_test.go | 10 +-- client/types/startsettings.go | 23 +++++ client/wsclient.go | 2 + internal/examples/agent/agent/agent.go | 18 ++++ internal/examples/agent/main.go | 30 +++++++ internal/examples/policysrv/main.go | 11 ++- .../integrationtest/attestation_e2e_test.go | 5 ++ protobufs/opamp.pb.go | 83 +++++++++++++------ server/attestation.go | 60 +++++++++++--- server/attestation_test.go | 10 +-- server/serverimpl.go | 6 +- signing/certs.go | 13 +++ signing/loader.go | 14 ++++ signing/local_signer.go | 29 ++++++- signing/remote_signer.go | 24 ++++++ signing/tofu.go | 76 +++++++++++++++++ signing/types.go | 13 +++ 23 files changed, 474 insertions(+), 83 deletions(-) create mode 100644 signing/tofu.go diff --git a/client/httpclient.go b/client/httpclient.go index a7157cbe..9b301a95 100644 --- a/client/httpclient.go +++ b/client/httpclient.go @@ -156,6 +156,7 @@ func (c *httpClient) runUntilStopped(ctx context.Context) { &c.common.PackageSyncMutex, c.common.DownloadReporterInterval, c.common.PayloadVerifier, + c.common.PayloadTOFUStore, ) } diff --git a/client/internal/attestation.go b/client/internal/attestation.go index 6e1fe732..52986998 100644 --- a/client/internal/attestation.go +++ b/client/internal/attestation.go @@ -30,6 +30,17 @@ var ( // satisfy the handshake. ErrTrustChainErrorReported = errors.New("client: server reported trust chain error") + // ErrSANMismatch is returned when the leaf certificate's Subject + // Alternative Name entries do not contain a dNSName or iPAddress + // that matches the OpAMP server the Agent is connected to. Per the + // spec this is a fatal handshake error. + ErrSANMismatch = errors.New("client: leaf certificate SAN does not match server hostname") + + // ErrTOFUAnchorMissing is returned during TOFU enrollment when the + // Server's TrustChainResponse does not include the expected + // tofu_trust_anchor field. + ErrTOFUAnchorMissing = errors.New("client: TOFU enrollment requested but TrustChainResponse.tofu_trust_anchor is absent") + // ErrMissingSignature is returned when a SignedServerToAgent is // missing its signature field. Every message MUST be signed, // including the first. @@ -63,7 +74,9 @@ var ( // format is byte-identical to upstream and no attestationState is // created at all; payload trust is simply not negotiated. type attestationState struct { - verifier signing.Verifier + verifier signing.Verifier + serverName string // hostname for SAN verification + tofuStore signing.TOFUStore // non-nil when in TOFU enrollment mode mu sync.Mutex firstSeen bool @@ -71,10 +84,11 @@ type attestationState struct { } // newAttestationState constructs a per-connection attestation state. -// verifier MUST be non-nil; callers without a configured verifier -// should not construct an attestationState at all. -func newAttestationState(verifier signing.Verifier) *attestationState { - return &attestationState{verifier: verifier} +// verifier is nil in TOFU enrollment mode (tofuStore non-nil); in that case +// the verifier is bootstrapped from the first TrustChainResponse. +// serverName is the hostname (without port) of the OpAMP server. +func newAttestationState(verifier signing.Verifier, serverName string, tofuStore signing.TOFUStore) *attestationState { + return &attestationState{verifier: verifier, serverName: serverName, tofuStore: tofuStore} } // Reset clears the per-connection handshake state. After Reset, the @@ -106,6 +120,8 @@ func isAttestationFailure(err error) bool { } return errors.Is(err, ErrMissingTrustChain) || errors.Is(err, ErrTrustChainErrorReported) || + errors.Is(err, ErrSANMismatch) || + errors.Is(err, ErrTOFUAnchorMissing) || errors.Is(err, ErrMissingSignature) || errors.Is(err, ErrMissingPayload) || errors.Is(err, ErrEmptyInnerServerToAgent) || @@ -153,10 +169,33 @@ func (s *attestationState) ProcessEnvelope(ctx context.Context, envelope *protob if err != nil { return nil, fmt.Errorf("client: parse trust chain PEM: %w", err) } + + // TOFU enrollment: bootstrap the verifier from the root CA the + // Server included in tofu_trust_anchor, then persist it. + if s.tofuStore != nil { + if len(chainResp.TofuTrustAnchor) == 0 { + return nil, ErrTOFUAnchorMissing + } + v, err := signing.VerifierFromPEM(chainResp.TofuTrustAnchor) + if err != nil { + return nil, fmt.Errorf("client: TOFU: parse trust anchor: %w", err) + } + if err := s.tofuStore.Save(chainResp.TofuTrustAnchor); err != nil { + return nil, fmt.Errorf("client: TOFU: persist trust anchor: %w", err) + } + s.verifier = v + s.tofuStore = nil // enrolled; store no longer needed this session + } + leaf, err := s.verifier.ValidateChain(ctx, chainDER, time.Now()) if err != nil { return nil, fmt.Errorf("client: validate trust chain: %w", err) } + if s.serverName != "" { + if err := leaf.VerifyHostname(s.serverName); err != nil { + return nil, fmt.Errorf("%w: %v", ErrSANMismatch, err) + } + } s.leaf = leaf s.firstSeen = true } diff --git a/client/internal/attestation_test.go b/client/internal/attestation_test.go index 0600d945..96af0c69 100644 --- a/client/internal/attestation_test.go +++ b/client/internal/attestation_test.go @@ -87,7 +87,7 @@ func (f attestationFixture) buildSignedEnvelope(t *testing.T, inner *protobufs.S // signature verification (subsequent envelopes). func TestAttestationState_FirstAndSubsequent(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() first := &protobufs.ServerToAgent{InstanceUid: []byte("first-msg-uid000")} @@ -118,7 +118,7 @@ func TestAttestationState_FirstAndSubsequent(t *testing.T) { // envelope's signature is verified against the freshly validated leaf. func TestAttestationState_FirstMessageMustBeSigned(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() inner := &protobufs.ServerToAgent{InstanceUid: []byte("signed-first-uid")} @@ -132,7 +132,7 @@ func TestAttestationState_FirstMessageMustBeSigned(t *testing.T) { // message is rejected when its signature is absent. func TestAttestationState_MissingSignatureOnFirst(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() inner := &protobufs.ServerToAgent{InstanceUid: []byte("no-sig-first-uid")} @@ -147,7 +147,7 @@ func TestAttestationState_MissingSignatureOnFirst(t *testing.T) { // state rejects it. func TestAttestationState_FirstMessageSignedButTampered(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() inner := &protobufs.ServerToAgent{InstanceUid: []byte("tampered-first0")} @@ -163,7 +163,7 @@ func TestAttestationState_FirstMessageSignedButTampered(t *testing.T) { // when the first envelope lacks trust_chain_response. func TestAttestationState_MissingTrustChain(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() inner := &protobufs.ServerToAgent{InstanceUid: []byte("no-chain-uid000")} @@ -180,7 +180,7 @@ func TestAttestationState_MissingTrustChain(t *testing.T) { // ErrTrustChainErrorReported. func TestAttestationState_TrustChainErrorReported(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() inner := &protobufs.ServerToAgent{InstanceUid: []byte("err-msg-uid00000")} @@ -204,7 +204,7 @@ func TestAttestationState_UnknownCA(t *testing.T) { // Build a SECOND fixture with a different CA — its envelope won't // validate against f.verifier. other := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() inner := &protobufs.ServerToAgent{InstanceUid: []byte("unknown-ca-uid00")} @@ -220,7 +220,7 @@ func TestAttestationState_UnknownCA(t *testing.T) { // rejected. func TestAttestationState_MissingSignatureOnSubsequent(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} @@ -240,7 +240,7 @@ func TestAttestationState_MissingSignatureOnSubsequent(t *testing.T) { // flipped byte in the signature is rejected. func TestAttestationState_TamperedSignatureOnSubsequent(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} @@ -261,7 +261,7 @@ func TestAttestationState_TamperedSignatureOnSubsequent(t *testing.T) { // hide signaling issues. func TestAttestationState_EmptyPayload(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() env := &protobufs.SignedServerToAgent{} @@ -288,7 +288,7 @@ func TestUnwrapServerToAgent_NilState_PassThrough(t *testing.T) { // returned. func TestUnwrapServerToAgent_WithState_HappyPath(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) inner := &protobufs.ServerToAgent{InstanceUid: []byte("envelope-uid0000")} env := f.buildFirstEnvelope(t, inner, true) @@ -305,7 +305,7 @@ func TestUnwrapServerToAgent_WithState_HappyPath(t *testing.T) { // is negotiated are rejected. func TestUnwrapServerToAgent_WithState_GarbageBytes(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) var msg protobufs.ServerToAgent // Bytes that don't decode as SignedServerToAgent — proto3 is @@ -321,7 +321,7 @@ func TestUnwrapServerToAgent_WithState_GarbageBytes(t *testing.T) { // poll. func TestAttestationState_Reset(t *testing.T) { f := newAttestationFixture(t) - state := newAttestationState(f.verifier) + state := newAttestationState(f.verifier, "", nil) ctx := context.Background() // Drive the state through a successful handshake. diff --git a/client/internal/clientcommon.go b/client/internal/clientcommon.go index dcf0ba27..7105e15c 100644 --- a/client/internal/clientcommon.go +++ b/client/internal/clientcommon.go @@ -27,6 +27,7 @@ var ( ErrReportsConnectionSettingsStatusNotSet = errors.New("ReportsConnectionSettingsStatus capability is not set") ErrPayloadVerifierMissing = errors.New("PayloadVerifier must be set when RequiresPayloadTrustVerification capability is enabled") ErrPayloadVerifierWithoutCapability = errors.New("PayloadVerifier set but RequiresPayloadTrustVerification capability is not enabled") + ErrTOFULoadFailed = errors.New("PayloadTOFUStore.Load failed at startup") errAlreadyStarted = errors.New("already started") errCannotStopNotStarted = errors.New("cannot stop because not started") @@ -59,6 +60,11 @@ type ClientCommon struct { // declared capability set. PayloadVerifier signing.Verifier + // PayloadTOFUStore, when non-nil, backs TOFU enrollment. Set from + // StartSettings.PayloadTOFUStore. Nil after the trust anchor has been + // loaded from the store and promoted to PayloadVerifier. + PayloadTOFUStore signing.TOFUStore + // The transport-specific sender. sender Sender @@ -109,7 +115,7 @@ func (c *ClientCommon) validateCapabilities(capabilities protobufs.AgentCapabili } requiresAttestation := capabilities&protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification != 0 switch { - case requiresAttestation && c.PayloadVerifier == nil: + case requiresAttestation && c.PayloadVerifier == nil && c.PayloadTOFUStore == nil: return ErrPayloadVerifierMissing case !requiresAttestation && c.PayloadVerifier != nil: return ErrPayloadVerifierWithoutCapability @@ -152,9 +158,27 @@ func (c *ClientCommon) PrepareStart( // Prepare package statuses. c.PackagesStateProvider = settings.PackagesStateProvider - // Store the payload trust verifier before capability validation so - // the capability ↔ verifier consistency check has both values. + // Wire up payload trust verification. PayloadVerifier takes precedence; + // if only PayloadTOFUStore is set, try to load a previously-persisted + // anchor. If found, promote it to PayloadVerifier (normal path). If + // not found, keep PayloadTOFUStore set so the transport-level code + // enters TOFU enrollment mode on connection. c.PayloadVerifier = settings.PayloadVerifier + if c.PayloadVerifier == nil && settings.PayloadTOFUStore != nil { + anchorPEM, err := settings.PayloadTOFUStore.Load() + if err != nil { + return fmt.Errorf("%w: %v", ErrTOFULoadFailed, err) + } + if len(anchorPEM) > 0 { + v, err := signing.VerifierFromPEM(anchorPEM) + if err != nil { + return fmt.Errorf("%w: persisted anchor is invalid: %v", ErrTOFULoadFailed, err) + } + c.PayloadVerifier = v + } else { + c.PayloadTOFUStore = settings.PayloadTOFUStore + } + } if err := c.validateCapabilities(c.ClientSyncedState.Capabilities()); err != nil { return err } diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go index 5507fda3..cb1b6fa1 100644 --- a/client/internal/httpsender.go +++ b/client/internal/httpsender.go @@ -130,19 +130,24 @@ func (h *HTTPSender) SetProxy(proxy string, headers http.Header) error { // Run continues until ctx is cancelled. func (h *HTTPSender) Run( ctx context.Context, - url string, + serverURL string, callbacks types.Callbacks, clientSyncedState *ClientSyncedState, packagesStateProvider types.PackagesStateProvider, packageSyncMutex *sync.Mutex, reporterInterval time.Duration, payloadVerifier signing.Verifier, + tofuStore signing.TOFUStore, ) { - h.url = url + h.url = serverURL h.callbacks = callbacks h.receiveProcessor = newReceivedProcessor(h.logger, callbacks, h, clientSyncedState, packagesStateProvider, packageSyncMutex, reporterInterval) - if payloadVerifier != nil { - h.attestation = newAttestationState(payloadVerifier) + if payloadVerifier != nil || tofuStore != nil { + var serverName string + if parsed, err := url.Parse(h.url); err == nil { + serverName = parsed.Hostname() + } + h.attestation = newAttestationState(payloadVerifier, serverName, tofuStore) } // we need to detect if the redirect was ever set, if not, we want default behaviour diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go index 0c9d5df3..37b1bf69 100644 --- a/client/internal/wsreceiver.go +++ b/client/internal/wsreceiver.go @@ -3,6 +3,7 @@ package internal import ( "context" "fmt" + "net/url" "sync" "time" @@ -54,6 +55,8 @@ func NewWSReceiver( packageSyncMutex *sync.Mutex, reporterInterval time.Duration, payloadVerifier signing.Verifier, + serverURL string, + tofuStore signing.TOFUStore, ) *wsReceiver { w := &wsReceiver{ conn: conn, @@ -63,8 +66,12 @@ func NewWSReceiver( processor: newReceivedProcessor(logger, callbacks, sender, clientSyncedState, packagesStateProvider, packageSyncMutex, reporterInterval), stopped: make(chan struct{}), } - if payloadVerifier != nil { - w.attestation = newAttestationState(payloadVerifier) + if payloadVerifier != nil || tofuStore != nil { + var serverName string + if parsed, err := url.Parse(serverURL); err == nil { + serverName = parsed.Hostname() + } + w.attestation = newAttestationState(payloadVerifier, serverName, tofuStore) } return w diff --git a/client/internal/wsreceiver_test.go b/client/internal/wsreceiver_test.go index 6315416e..d015f1ce 100644 --- a/client/internal/wsreceiver_test.go +++ b/client/internal/wsreceiver_test.go @@ -93,7 +93,7 @@ func TestServerToAgentCommand(t *testing.T) { sender := WSSender{} capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsRestartCommand clientSyncedState.SetCapabilities(&capabilities) - receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil) + receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil, "", nil) receiver.processor.ProcessReceivedMessage(context.Background(), &protobufs.ServerToAgent{ Command: test.command, }) @@ -148,7 +148,7 @@ func TestServerToAgentCommandExclusive(t *testing.T) { } clientSyncedState := ClientSyncedState{} clientSyncedState.SetCapabilities(&test.capabilities) - receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, nil, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil) + receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, nil, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil, "", nil) receiver.processor.ProcessReceivedMessage(context.Background(), &protobufs.ServerToAgent{ Command: &protobufs.ServerToAgentCommand{ Type: protobufs.CommandType_CommandType_Restart, @@ -211,7 +211,7 @@ func TestReceiverLoopStop(t *testing.T) { sender := WSSender{} capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsRestartCommand clientSyncedState.SetCapabilities(&capabilities) - receiver := NewWSReceiver(TestLogger{t}, callbacks, conn, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil) + receiver := NewWSReceiver(TestLogger{t}, callbacks, conn, &sender, &clientSyncedState, nil, new(sync.Mutex), time.Second, nil, "", nil) ctx, cancel := context.WithCancel(context.Background()) go func() { @@ -254,7 +254,7 @@ func TestWSPackageUpdatesInParallel(t *testing.T) { capabilities := protobufs.AgentCapabilities_AgentCapabilities_AcceptsPackages sender := NewSender(&internal.NopLogger{}) clientSyncedState.SetCapabilities(&capabilities) - receiver := NewWSReceiver(&internal.NopLogger{}, callbacks, nil, sender, clientSyncedState, localPackageState, &mux, time.Second, nil) + receiver := NewWSReceiver(&internal.NopLogger{}, callbacks, nil, sender, clientSyncedState, localPackageState, &mux, time.Second, nil, "", nil) receiver.processor.ProcessReceivedMessage(ctx, &protobufs.ServerToAgent{ @@ -369,7 +369,7 @@ func TestRecieveMessage(t *testing.T) { state := &ClientSyncedState{} capabilities := protobufs.AgentCapabilities_AgentCapabilities_ReportsStatus state.SetCapabilities(&capabilities) - rec := NewWSReceiver(&internal.NopLogger{}, callbacks, conn, NewSender(&internal.NopLogger{}), state, nil, new(sync.Mutex), time.Second, nil) + rec := NewWSReceiver(&internal.NopLogger{}, callbacks, conn, NewSender(&internal.NopLogger{}), state, nil, new(sync.Mutex), time.Second, nil, "", nil) err = rec.receiveMessage(context.Background(), &protobufs.ServerToAgent{}) if tc.hasError { diff --git a/client/types/startsettings.go b/client/types/startsettings.go index 97297a7e..0ba3226c 100644 --- a/client/types/startsettings.go +++ b/client/types/startsettings.go @@ -69,6 +69,29 @@ type StartSettings struct { // one from a PEM-encoded CA bundle. PayloadVerifier signing.Verifier + // PayloadTOFUStore enables Trust On First Use (TOFU) enrollment for the + // payload trust anchor. Mutually exclusive with PayloadVerifier: if + // PayloadVerifier is also set it takes precedence and PayloadTOFUStore + // is ignored. + // + // On startup the client calls PayloadTOFUStore.Load(): + // - If a trust anchor is returned, it is used as PayloadVerifier for + // this session (normal attestation path). + // - If no anchor is stored yet, the client advertises + // AgentCapabilities_AcceptsPayloadTrustAnchorTOFU alongside + // AgentCapabilities_RequiresPayloadTrustVerification, accepts the + // root CA from the first TrustChainResponse.tofu_trust_anchor, and + // persists it via PayloadTOFUStore.Save(). + // + // WARNING: TOFU provides no security on the first connection; a + // compromised distribution server can install an attacker-controlled + // trust anchor. Disable by default and enable only for environments + // where the first connection is considered sufficiently trusted. + // Requires persistent storage across restarts — agents running in + // stateless container environments without a persistent volume will + // repeat TOFU enrollment on every restart. + PayloadTOFUStore signing.TOFUStore + // Defines the capabilities of the Agent. AgentCapabilities_ReportsStatus bit does not need to // be set in this field, it will be set automatically since it is required by OpAMP protocol. // Deprecated: Use client.SetCapabilities() instead. diff --git a/client/wsclient.go b/client/wsclient.go index feb44f2e..f95af337 100644 --- a/client/wsclient.go +++ b/client/wsclient.go @@ -406,6 +406,8 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) (atte &c.common.PackageSyncMutex, c.common.DownloadReporterInterval, c.common.PayloadVerifier, + c.url.String(), + c.common.PayloadTOFUStore, ) // When the wsclient is closed, the context passed to runOneCycle will be canceled. diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go index 06f548fe..5eac11b1 100644 --- a/internal/examples/agent/agent/agent.go +++ b/internal/examples/agent/agent/agent.go @@ -93,6 +93,10 @@ type Agent struct { // inbound ServerToAgent message must arrive in a SignedServerToAgent // envelope whose signature chains to this verifier's trust anchor. payloadVerifier signing.Verifier + + // payloadTOFUStore, when non-nil, enables TOFU enrollment for the + // payload trust anchor. Mutually exclusive with payloadVerifier. + payloadTOFUStore signing.TOFUStore } type proxySettings struct { @@ -153,6 +157,16 @@ func WithPayloadVerifier(v signing.Verifier) Option { } } +// WithPayloadTOFUStore enables TOFU enrollment for the payload trust anchor. +// On first connection the agent accepts and persists the root CA delivered by +// the server; on subsequent connections the persisted anchor is used directly. +// Mutually exclusive with WithPayloadVerifier. +func WithPayloadTOFUStore(s signing.TOFUStore) Option { + return func(agent *Agent) { + agent.payloadTOFUStore = s + } +} + func NewAgent(agentConfig *config.AgentConfig, options ...Option) *Agent { agent := &Agent{ logger: &Logger{Logger: log.Default()}, @@ -206,6 +220,7 @@ func (agent *Agent) connect(ops ...settingsOp) error { HeartbeatInterval: agent.agentConfig.HeartbeatInterval, InstanceUid: types.InstanceUid(agent.instanceId), PayloadVerifier: agent.payloadVerifier, + PayloadTOFUStore: agent.payloadTOFUStore, Callbacks: types.Callbacks{ OnConnect: func(ctx context.Context) { agent.logger.Debugf(ctx, "Connected to the server.") @@ -250,6 +265,9 @@ func (agent *Agent) connect(ops ...settingsOp) error { protobufs.AgentCapabilities_AgentCapabilities_ReportsConnectionSettingsStatus if agent.payloadVerifier != nil { supportedCapabilities |= protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification + } else if agent.payloadTOFUStore != nil { + supportedCapabilities |= protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification | + protobufs.AgentCapabilities_AgentCapabilities_AcceptsPayloadTrustAnchorTOFU } err = agent.client.SetCapabilities(&supportedCapabilities) if err != nil { diff --git a/internal/examples/agent/main.go b/internal/examples/agent/main.go index 0cdd03cf..3b9088f8 100644 --- a/internal/examples/agent/main.go +++ b/internal/examples/agent/main.go @@ -48,6 +48,13 @@ type flagConfig struct { // certificate stored at this path. Run internal/examples/policysrv first // to generate the CA and start the signing server. attestationCAFile string + + // attestationTOFUStoreFile, when non-empty, enables TOFU enrollment for + // Message Attestation. On the first connection the agent accepts and + // persists the root CA delivered by the server; on subsequent connections + // the persisted anchor is loaded from this file. Mutually exclusive with + // --attestation-ca. + attestationTOFUStoreFile string } func (cfg flagConfig) verifyArgs() error { @@ -167,6 +174,10 @@ func loadEnv(cfg *flagConfig) { if s, ok := os.LookupEnv("AGENT_ATTESTATION_CA"); ok { cfg.attestationCAFile = s } + + if s, ok := os.LookupEnv("AGENT_ATTESTATION_TOFU_STORE"); ok { + cfg.attestationTOFUStoreFile = s + } } func main() { @@ -189,6 +200,13 @@ func main() { "signature does not chain to this CA. Obtain the CA from the policy server's\n"+ "/v1/ca endpoint or from /tmp/opamp-policy-ca.pem after running policysrv\n"+ "(env var: AGENT_ATTESTATION_CA).") + flag.StringVar(&cfg.attestationTOFUStoreFile, "attestation-tofu-store", "", + "Path to a file used as the TOFU trust anchor store for Message Attestation.\n"+ + "On the first connection the agent accepts and persists the root CA delivered\n"+ + "by the server; on subsequent connections the persisted anchor is loaded from\n"+ + "this file. Disabled by default. Mutually exclusive with --attestation-ca.\n"+ + "WARNING: TOFU provides no security on the first connection.\n"+ + "(env var: AGENT_ATTESTATION_TOFU_STORE).") flag.Parse() loadEnv(&cfg) @@ -239,6 +257,15 @@ func runScale(ctx context.Context, cfg flagConfig) ([]*agent.Agent, error) { log.Printf("Message Attestation enabled — trust anchor: %s", cfg.attestationCAFile) } + var tofuStore signing.TOFUStore + if cfg.attestationTOFUStoreFile != "" { + if cfg.attestationCAFile != "" { + return nil, fmt.Errorf("--attestation-ca and --attestation-tofu-store are mutually exclusive") + } + tofuStore = signing.NewFileTOFUStore(cfg.attestationTOFUStoreFile) + log.Printf("Message Attestation TOFU enrollment enabled — store: %s", cfg.attestationTOFUStoreFile) + } + agentConfig := &config.AgentConfig{ Endpoint: cfg.endpoint, HeartbeatInterval: &cfg.heartbeat, @@ -270,6 +297,9 @@ func runScale(ctx context.Context, cfg flagConfig) ([]*agent.Agent, error) { if verifier != nil { opts = append(opts, agent.WithPayloadVerifier(verifier)) } + if tofuStore != nil { + opts = append(opts, agent.WithPayloadTOFUStore(tofuStore)) + } if cfg.quietAgent { opts = append(opts, agent.WithLogger(nopLogger)) } diff --git a/internal/examples/policysrv/main.go b/internal/examples/policysrv/main.go index 7b67826f..9da5d941 100644 --- a/internal/examples/policysrv/main.go +++ b/internal/examples/policysrv/main.go @@ -69,14 +69,21 @@ func main() { if err != nil { log.Fatalf("generate CA: %v", err) } - leaf, leafKey, err := signing.GenerateLeaf(signing.AlgorithmECDSAP256SHA256, ca, caKey, signing.CertOptions{}) + leaf, leafKey, err := signing.GenerateLeaf(signing.AlgorithmECDSAP256SHA256, ca, caKey, signing.CertOptions{ + // SAN required by the spec: the leaf must match the OpAMP distribution server's hostname. + // The example server binds to 0.0.0.0:4320; agents may connect by hostname or IP, so + // include both. Production deployments set these to the actual hostname(s) or IP(s). + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + }) if err != nil { log.Fatalf("generate leaf: %v", err) } - signer, err := signing.NewLocalSigner(leafKey, []*x509.Certificate{leaf}) + localSigner, err := signing.NewLocalSigner(leafKey, []*x509.Certificate{leaf}) if err != nil { log.Fatalf("new signer: %v", err) } + signer := localSigner.WithRootCA(ca) caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Raw}) leafPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leaf.Raw}) diff --git a/internal/integrationtest/attestation_e2e_test.go b/internal/integrationtest/attestation_e2e_test.go index 7e00c3b5..e4574f4c 100644 --- a/internal/integrationtest/attestation_e2e_test.go +++ b/internal/integrationtest/attestation_e2e_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/x509" "fmt" + "net" "net/http" "strings" "sync" @@ -67,6 +68,10 @@ func newFixture(t *testing.T, alg signing.Algorithm) e2eFixture { // need an expired leaf, a custom CN, etc. use this directly. func newFixtureWithLeafOpts(t *testing.T, alg signing.Algorithm, leafOpts signing.CertOptions) e2eFixture { t.Helper() + // All e2e tests connect to 127.0.0.1; include it as an IP SAN so the + // client-side SAN check passes. Callers that need to test SAN-mismatch + // rejection should construct the signing state directly instead. + leafOpts.IPAddresses = append(leafOpts.IPAddresses, net.ParseIP("127.0.0.1")) ca, caKey, err := signing.GenerateCA(alg, signing.CertOptions{}) require.NoError(t, err) leaf, leafKey, err := signing.GenerateLeaf(alg, ca, caKey, leafOpts) diff --git a/protobufs/opamp.pb.go b/protobufs/opamp.pb.go index 924d1be4..133a84de 100644 --- a/protobufs/opamp.pb.go +++ b/protobufs/opamp.pb.go @@ -443,30 +443,40 @@ const ( // If the Server does not offer this capability, the Agent MUST terminate // the connection. See the Message Attestation section of the specification. // Status: [Development] - AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification AgentCapabilities = 65536 // Add new capabilities here, continuing with the least significant unused bit. + AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification AgentCapabilities = 65536 + // The Agent supports Trust On First Use (TOFU) enrollment for the payload + // trust anchor. When set alongside + // AgentCapabilities_RequiresPayloadTrustVerification it signals that the + // Agent has no pre-configured trust anchor and asks the Server to include + // the root CA in trust_chain_response.tofu_trust_anchor so the Agent can + // bootstrap and persist it. MUST NOT be set if the Agent already has a + // persisted or operator-configured trust anchor. + // Status: [Development] + AgentCapabilities_AgentCapabilities_AcceptsPayloadTrustAnchorTOFU AgentCapabilities = 131072 // Add new capabilities here, continuing with the least significant unused bit. ) // Enum value maps for AgentCapabilities. var ( AgentCapabilities_name = map[int32]string{ - 0: "AgentCapabilities_Unspecified", - 1: "AgentCapabilities_ReportsStatus", - 2: "AgentCapabilities_AcceptsRemoteConfig", - 4: "AgentCapabilities_ReportsEffectiveConfig", - 8: "AgentCapabilities_AcceptsPackages", - 16: "AgentCapabilities_ReportsPackageStatuses", - 32: "AgentCapabilities_ReportsOwnTraces", - 64: "AgentCapabilities_ReportsOwnMetrics", - 128: "AgentCapabilities_ReportsOwnLogs", - 256: "AgentCapabilities_AcceptsOpAMPConnectionSettings", - 512: "AgentCapabilities_AcceptsOtherConnectionSettings", - 1024: "AgentCapabilities_AcceptsRestartCommand", - 2048: "AgentCapabilities_ReportsHealth", - 4096: "AgentCapabilities_ReportsRemoteConfig", - 8192: "AgentCapabilities_ReportsHeartbeat", - 16384: "AgentCapabilities_ReportsAvailableComponents", - 32768: "AgentCapabilities_ReportsConnectionSettingsStatus", - 65536: "AgentCapabilities_RequiresPayloadTrustVerification", + 0: "AgentCapabilities_Unspecified", + 1: "AgentCapabilities_ReportsStatus", + 2: "AgentCapabilities_AcceptsRemoteConfig", + 4: "AgentCapabilities_ReportsEffectiveConfig", + 8: "AgentCapabilities_AcceptsPackages", + 16: "AgentCapabilities_ReportsPackageStatuses", + 32: "AgentCapabilities_ReportsOwnTraces", + 64: "AgentCapabilities_ReportsOwnMetrics", + 128: "AgentCapabilities_ReportsOwnLogs", + 256: "AgentCapabilities_AcceptsOpAMPConnectionSettings", + 512: "AgentCapabilities_AcceptsOtherConnectionSettings", + 1024: "AgentCapabilities_AcceptsRestartCommand", + 2048: "AgentCapabilities_ReportsHealth", + 4096: "AgentCapabilities_ReportsRemoteConfig", + 8192: "AgentCapabilities_ReportsHeartbeat", + 16384: "AgentCapabilities_ReportsAvailableComponents", + 32768: "AgentCapabilities_ReportsConnectionSettingsStatus", + 65536: "AgentCapabilities_RequiresPayloadTrustVerification", + 131072: "AgentCapabilities_AcceptsPayloadTrustAnchorTOFU", } AgentCapabilities_value = map[string]int32{ "AgentCapabilities_Unspecified": 0, @@ -487,6 +497,7 @@ var ( "AgentCapabilities_ReportsAvailableComponents": 16384, "AgentCapabilities_ReportsConnectionSettingsStatus": 32768, "AgentCapabilities_RequiresPayloadTrustVerification": 65536, + "AgentCapabilities_AcceptsPayloadTrustAnchorTOFU": 131072, } ) @@ -1397,9 +1408,20 @@ type TrustChainResponse struct { // Human-readable error message indicating why the Server could not // satisfy the trust chain request. If error_message is set, the Agent // MUST terminate the connection. - ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + // PEM-encoded root CA certificate used as the payload trust anchor. + // Set only during Trust On First Use (TOFU) enrollment: the Server + // includes this when the Agent has advertised + // AgentCapabilities_AcceptsPayloadTrustAnchorTOFU and has no + // pre-configured trust anchor. The Agent MUST persist this certificate + // and use it as the payload trust anchor for all subsequent connections. + // The Agent MUST NOT update a previously persisted or operator-configured + // trust anchor. If the Agent has a pre-configured trust anchor this + // field MUST be ignored. + // Status: [Development] + TofuTrustAnchor []byte `protobuf:"bytes,3,opt,name=tofu_trust_anchor,json=tofuTrustAnchor,proto3" json:"tofu_trust_anchor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *TrustChainResponse) Reset() { @@ -1446,6 +1468,13 @@ func (x *TrustChainResponse) GetErrorMessage() string { return "" } +func (x *TrustChainResponse) GetTofuTrustAnchor() []byte { + if x != nil { + return x.TofuTrustAnchor + } + return nil +} + // SignedServerToAgent wraps a ServerToAgent message when the payload trust // verification handshake has been negotiated between Server and Agent. When // both AgentCapabilities_RequiresPayloadTrustVerification (set by the Agent) @@ -3743,10 +3772,11 @@ const file_opamp_proto_rawDesc = "" + "\acommand\x18\t \x01(\v2$.opamp.proto.v1.ServerToAgentCommandR\acommand\x12S\n" + "\x13custom_capabilities\x18\n" + " \x01(\v2\".opamp.proto.v1.CustomCapabilitiesR\x12customCapabilities\x12D\n" + - "\x0ecustom_message\x18\v \x01(\v2\x1d.opamp.proto.v1.CustomMessageR\rcustomMessageJ\x04\b\f\x10\rJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11R\x14trust_chain_responseR\tsignatureR\apayload\"f\n" + + "\x0ecustom_message\x18\v \x01(\v2\x1d.opamp.proto.v1.CustomMessageR\rcustomMessageJ\x04\b\f\x10\rJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11R\x14trust_chain_responseR\tsignatureR\apayload\"\x92\x01\n" + "\x12TrustChainResponse\x12+\n" + "\x11certificate_chain\x18\x01 \x01(\fR\x10certificateChain\x12#\n" + - "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\"\xa3\x01\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x12*\n" + + "\x11tofu_trust_anchor\x18\x03 \x01(\fR\x0ftofuTrustAnchor\"\xa3\x01\n" + "\x13SignedServerToAgent\x12\x18\n" + "\apayload\x18\x0e \x01(\fR\apayload\x12\x1c\n" + "\tsignature\x18\x0f \x01(\fR\tsignature\x12T\n" + @@ -3926,7 +3956,7 @@ const file_opamp_proto_rawDesc = "" + "\"ServerErrorResponseType_BadRequest\x10\x01\x12'\n" + "#ServerErrorResponseType_Unavailable\x10\x02*&\n" + "\vCommandType\x12\x17\n" + - "\x13CommandType_Restart\x10\x00*\xbf\x06\n" + + "\x13CommandType_Restart\x10\x00*\xf6\x06\n" + "\x11AgentCapabilities\x12!\n" + "\x1dAgentCapabilities_Unspecified\x10\x00\x12#\n" + "\x1fAgentCapabilities_ReportsStatus\x10\x01\x12)\n" + @@ -3945,7 +3975,8 @@ const file_opamp_proto_rawDesc = "" + "\"AgentCapabilities_ReportsHeartbeat\x10\x80@\x122\n" + ",AgentCapabilities_ReportsAvailableComponents\x10\x80\x80\x01\x127\n" + "1AgentCapabilities_ReportsConnectionSettingsStatus\x10\x80\x80\x02\x128\n" + - "2AgentCapabilities_RequiresPayloadTrustVerification\x10\x80\x80\x04*\xba\x01\n" + + "2AgentCapabilities_RequiresPayloadTrustVerification\x10\x80\x80\x04\x125\n" + + "/AgentCapabilities_AcceptsPayloadTrustAnchorTOFU\x10\x80\x80\b*\xba\x01\n" + "\x1aConnectionSettingsStatuses\x12$\n" + " ConnectionSettingsStatuses_UNSET\x10\x00\x12&\n" + "\"ConnectionSettingsStatuses_APPLIED\x10\x01\x12'\n" + diff --git a/server/attestation.go b/server/attestation.go index 63d58a5b..ed27ae56 100644 --- a/server/attestation.go +++ b/server/attestation.go @@ -23,16 +23,18 @@ import ( // whether the chain has already been delivered on this connection so // that exactly one outbound envelope carries it. type connectionSigningState struct { - signer signing.Signer - chainDER [][]byte // snapshot - firstSent atomic.Bool + signer signing.Signer + chainDER [][]byte // snapshot + tofuAnchorPEM []byte // non-empty iff this connection is a TOFU enrollment + tofuError string // non-empty when TOFU requested but anchor unavailable + firstSent atomic.Bool } // newConnectionSigningState constructs the per-connection state by -// asking the signer for its current chain. Errors here propagate to -// the server and prevent the connection from being established with -// signing enabled. -func newConnectionSigningState(ctx context.Context, signer signing.Signer) (*connectionSigningState, error) { +// asking the signer for its current chain. When tofu is true the signer +// must also implement TrustAnchorProvider; the root CA is fetched and +// stored to be included in the first outbound TrustChainResponse. +func newConnectionSigningState(ctx context.Context, signer signing.Signer, tofu bool) (*connectionSigningState, error) { if signer == nil { return nil, fmt.Errorf("server: nil signer") } @@ -40,10 +42,24 @@ func newConnectionSigningState(ctx context.Context, signer signing.Signer) (*con if err != nil { return nil, fmt.Errorf("server: fetch signing chain: %w", err) } - return &connectionSigningState{ + state := &connectionSigningState{ signer: signer, chainDER: chain, - }, nil + } + if tofu { + tap, ok := signer.(signing.TrustAnchorProvider) + if !ok { + state.tofuError = "server cannot provide TOFU trust anchor: signer does not implement TrustAnchorProvider" + } else { + anchorPEM, err := tap.TrustAnchorPEM(ctx) + if err != nil { + state.tofuError = fmt.Sprintf("server cannot provide TOFU trust anchor: %v", err) + } else { + state.tofuAnchorPEM = anchorPEM + } + } + } + return state, nil } // signOutgoing produces a SignedServerToAgent envelope wrapping msg. @@ -68,11 +84,23 @@ func (s *connectionSigningState) signOutgoing(ctx context.Context, msg *protobuf // transitioned firstSent from false to true — guaranteeing exactly // one envelope carries the trust chain across concurrent callers. if s.firstSent.CompareAndSwap(false, true) { - var pemChain []byte - for _, der := range s.chainDER { - pemChain = append(pemChain, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})...) + if s.tofuError != "" { + // TOFU was requested but the server cannot fulfil it. Per the + // spec the server MUST set error_message; the agent will + // terminate the connection on receipt. + env.TrustChainResponse = &protobufs.TrustChainResponse{ + ErrorMessage: s.tofuError, + } + } else { + var pemChain []byte + for _, der := range s.chainDER { + pemChain = append(pemChain, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})...) + } + env.TrustChainResponse = &protobufs.TrustChainResponse{ + CertificateChain: pemChain, + TofuTrustAnchor: s.tofuAnchorPEM, // nil unless TOFU enrollment + } } - env.TrustChainResponse = &protobufs.TrustChainResponse{CertificateChain: pemChain} } return env, nil } @@ -84,6 +112,12 @@ func agentRequiresAttestation(capabilities uint64) bool { return capabilities&uint64(protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification) != 0 } +// agentRequestsTOFU reports whether the agent is requesting TOFU +// enrollment (no pre-configured trust anchor; needs the root CA). +func agentRequestsTOFU(capabilities uint64) bool { + return capabilities&uint64(protobufs.AgentCapabilities_AgentCapabilities_AcceptsPayloadTrustAnchorTOFU) != 0 +} + // addOffersAttestationBit returns capabilities with the // ServerCapabilities_OffersPayloadTrustVerification bit set. It is a // no-op if the bit is already set. diff --git a/server/attestation_test.go b/server/attestation_test.go index 44ec04e2..0bcc802d 100644 --- a/server/attestation_test.go +++ b/server/attestation_test.go @@ -53,7 +53,7 @@ func TestConnectionSigningState_FirstAndSubsequent(t *testing.T) { ctx := context.Background() f := newServerSigningFixture(t) - state, err := newConnectionSigningState(ctx, f.signer) + state, err := newConnectionSigningState(ctx, f.signer, false) require.NoError(t, err) first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} @@ -86,7 +86,7 @@ func TestConnectionSigningState_FirstAndSubsequent(t *testing.T) { func TestConnectionSigningState_ConcurrentFirstSend(t *testing.T) { ctx := context.Background() f := newServerSigningFixture(t) - state, err := newConnectionSigningState(ctx, f.signer) + state, err := newConnectionSigningState(ctx, f.signer, false) require.NoError(t, err) // Drive two concurrent calls; exactly one should carry the chain. @@ -119,7 +119,7 @@ func TestConnectionSigningState_ConcurrentFirstSend(t *testing.T) { // TestConnectionSigningState_NilSigner rejects a nil signer at // construction. func TestConnectionSigningState_NilSigner(t *testing.T) { - _, err := newConnectionSigningState(context.Background(), nil) + _, err := newConnectionSigningState(context.Background(), nil, false) require.Error(t, err) } @@ -127,7 +127,7 @@ func TestConnectionSigningState_NilSigner(t *testing.T) { // signer's ChainDER call. func TestConnectionSigningState_SignerError(t *testing.T) { bad := &failingSigner{chainErr: errors.New("chain unavailable")} - _, err := newConnectionSigningState(context.Background(), bad) + _, err := newConnectionSigningState(context.Background(), bad, false) require.Error(t, err) } @@ -350,7 +350,7 @@ func TestSignOutgoing_MidStreamSignFailure_PropagatesError(t *testing.T) { signErr := errors.New("e2e test: synthetic signer failure") bad := &failingSigner{signErr: signErr} - state, err := newConnectionSigningState(ctx, bad) + state, err := newConnectionSigningState(ctx, bad, false) require.NoError(t, err, "ChainDER should succeed; the failure is on Sign") _, err = state.signOutgoing(ctx, &protobufs.ServerToAgent{InstanceUid: testInstanceUid}) diff --git a/server/serverimpl.go b/server/serverimpl.go index da5c96b1..1496df79 100644 --- a/server/serverimpl.go +++ b/server/serverimpl.go @@ -311,7 +311,8 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co // were rejected pre-negotiation (see ErrSendBeforeNegotiated). if !agentConn.isNegotiated() { if s.settings.PayloadSigner != nil && agentRequiresAttestation(request.Capabilities) { - state, err := newConnectionSigningState(msgContext, s.settings.PayloadSigner) + tofu := agentRequestsTOFU(request.Capabilities) + state, err := newConnectionSigningState(msgContext, s.settings.PayloadSigner, tofu) if err != nil { s.logger.Errorf(msgContext, "Cannot fetch signing certificate chain: %v", err) break @@ -464,7 +465,8 @@ func (s *server) handlePlainHTTPRequest(req *http.Request, w http.ResponseWriter response.Capabilities = addOffersAttestationBit(response.Capabilities) if agentRequiresAttestation(request.Capabilities) { - state, sigErr := newConnectionSigningState(req.Context(), s.settings.PayloadSigner) + tofu := agentRequestsTOFU(request.Capabilities) + state, sigErr := newConnectionSigningState(req.Context(), s.settings.PayloadSigner, tofu) if sigErr != nil { s.logger.Errorf(req.Context(), "Cannot fetch signing certificate chain: %v", sigErr) w.WriteHeader(http.StatusInternalServerError) diff --git a/signing/certs.go b/signing/certs.go index abd2359d..557a79ed 100644 --- a/signing/certs.go +++ b/signing/certs.go @@ -11,6 +11,7 @@ import ( "crypto/x509/pkix" "fmt" "math/big" + "net" "time" ) @@ -26,6 +27,16 @@ type CertOptions struct { NotAfter time.Time // CommonName overrides the certificate's Subject CommonName. CommonName string + // DNSNames sets the dNSName Subject Alternative Name entries on the + // leaf certificate. Per the OpAMP Message Attestation spec the leaf + // MUST include a SAN that matches the OpAMP distribution server's + // hostname so the Agent can bind the signing certificate to a + // specific server during the connection-time handshake. + DNSNames []string + // IPAddresses sets the iPAddress Subject Alternative Name entries + // on the leaf certificate. Use when the Agent connects to the + // OpAMP server by IP address rather than hostname. + IPAddresses []net.IP } func (o CertOptions) notBefore() time.Time { @@ -117,6 +128,8 @@ func GenerateLeaf(alg Algorithm, ca *x509.Certificate, caKey crypto.Signer, opts KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, SignatureAlgorithm: sigAlg, + DNSNames: opts.DNSNames, + IPAddresses: opts.IPAddresses, } der, err := x509.CreateCertificate(rand.Reader, tmpl, ca, pub, caKey) diff --git a/signing/loader.go b/signing/loader.go index 08123d7f..53898984 100644 --- a/signing/loader.go +++ b/signing/loader.go @@ -40,6 +40,20 @@ func VerifierFromFile(caPath string) (*LocalVerifier, error) { return NewLocalVerifier(pool) } +// VerifierFromPEM constructs a LocalVerifier whose trust anchor pool is +// populated from pemBytes. Useful when the CA certificate bytes are already +// in memory (for example, after a TOFU enrollment). +func VerifierFromPEM(pemBytes []byte) (*LocalVerifier, error) { + if len(pemBytes) == 0 { + return nil, fmt.Errorf("%w: empty PEM bytes", ErrLoadCAFile) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(pemBytes) { + return nil, fmt.Errorf("%w: no valid PEM certificates in supplied bytes", ErrLoadCAFile) + } + return NewLocalVerifier(pool) +} + // LocalSignerFromFiles constructs a LocalSigner from PEM-encoded files: // // - keyPath: path to a PEM file containing the leaf signing private diff --git a/signing/local_signer.go b/signing/local_signer.go index 4e08fcbc..550a0891 100644 --- a/signing/local_signer.go +++ b/signing/local_signer.go @@ -4,6 +4,7 @@ import ( "context" "crypto" "crypto/x509" + "encoding/pem" "errors" ) @@ -25,9 +26,10 @@ var ErrNilKey = errors.New("signing: nil private key") // implementations in the Go standard library are themselves // concurrency-safe. type LocalSigner struct { - key crypto.Signer - alg Algorithm - chainDER [][]byte + key crypto.Signer + alg Algorithm + chainDER [][]byte + rootCADER []byte // set via WithRootCA; nil unless TOFU is supported } // NewLocalSigner constructs a LocalSigner from the supplied private @@ -105,3 +107,24 @@ func (s *LocalSigner) ChainDER(ctx context.Context) ([][]byte, error) { func (s *LocalSigner) Algorithm() Algorithm { return s.alg } + +// WithRootCA attaches the root CA certificate to this signer, enabling +// [TrustAnchorProvider] support. The root CA is included in +// trust_chain_response.tofu_trust_anchor during TOFU enrollment so that +// Agents with no pre-configured trust anchor can bootstrap and persist it. +// Returns the receiver for chaining. +func (s *LocalSigner) WithRootCA(ca *x509.Certificate) *LocalSigner { + der := make([]byte, len(ca.Raw)) + copy(der, ca.Raw) + s.rootCADER = der + return s +} + +// TrustAnchorPEM implements [TrustAnchorProvider]. Returns the PEM-encoded +// root CA set by [WithRootCA]. Returns an error if WithRootCA was not called. +func (s *LocalSigner) TrustAnchorPEM(_ context.Context) ([]byte, error) { + if len(s.rootCADER) == 0 { + return nil, errors.New("signing: no root CA configured on LocalSigner (call WithRootCA first)") + } + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.rootCADER}), nil +} diff --git a/signing/remote_signer.go b/signing/remote_signer.go index 5c060f85..47ddaab4 100644 --- a/signing/remote_signer.go +++ b/signing/remote_signer.go @@ -34,6 +34,7 @@ type RemoteSigner struct { } var _ Signer = (*RemoteSigner)(nil) +var _ TrustAnchorProvider = (*RemoteSigner)(nil) // NewRemoteSigner returns a RemoteSigner that calls the signing service at // baseURL (e.g. "http://policy-server:4322"). A 10-second per-request @@ -71,6 +72,29 @@ func (s *RemoteSigner) Sign(ctx context.Context, payload []byte) ([]byte, error) return body, nil } +// TrustAnchorPEM implements [TrustAnchorProvider] by GET-ing /v1/ca on the +// remote policy server. The response MUST be a PEM-encoded CA certificate. +func (s *RemoteSigner) TrustAnchorPEM(ctx context.Context) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, + s.baseURL+"/v1/ca", nil) + if err != nil { + return nil, fmt.Errorf("remote signer: build CA request: %w", err) + } + resp, err := s.client.Do(req) + if err != nil { + return nil, fmt.Errorf("remote signer: CA request: %w", err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("remote signer: read CA response: %w", err) + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("remote signer: CA returned HTTP %d: %s", resp.StatusCode, body) + } + return body, nil +} + // ChainDER implements [Signer] by GET-ing /v1/chain and decoding the // returned PEM blob into DER byte slices ordered intermediates-first, // leaf-last. diff --git a/signing/tofu.go b/signing/tofu.go new file mode 100644 index 00000000..39686173 --- /dev/null +++ b/signing/tofu.go @@ -0,0 +1,76 @@ +package signing + +import ( + "errors" + "fmt" + "os" +) + +// TOFUStore persists the payload trust anchor acquired during a Trust On First +// Use (TOFU) enrollment. The Agent calls Save once — on first connection — and +// Load on every subsequent startup. +// +// Implementations MUST be idempotent on Save: if a trust anchor is already +// stored, a second Save call MUST be a no-op. This prevents a reconnecting +// agent from overwriting a valid anchor with a potentially attacker-supplied +// one. +type TOFUStore interface { + // Load returns the PEM-encoded trust anchor bytes saved by a previous + // Save call, or nil if no anchor has been stored yet. + Load() ([]byte, error) + + // Save persists pemBytes as the trust anchor. Called at most once per + // store lifetime; subsequent calls MUST be ignored if an anchor is + // already present. + Save(pemBytes []byte) error +} + +// ErrTOFUStoreSave wraps failures to persist the TOFU trust anchor. +var ErrTOFUStoreSave = errors.New("signing: save TOFU trust anchor") + +// FileTOFUStore implements [TOFUStore] by reading and writing a single PEM +// file. The file is created on first Save with 0o600 permissions. If the +// file already exists when Save is called, Save is a no-op (idempotent as +// required by the interface contract). +// +// The store is safe for concurrent use within one process, but does not use +// file locking; two processes writing to the same path concurrently may +// corrupt the file. +type FileTOFUStore struct { + path string +} + +// NewFileTOFUStore returns a FileTOFUStore that persists the trust anchor at +// path. The path does not need to exist yet; it is created on first Save. +func NewFileTOFUStore(path string) *FileTOFUStore { + return &FileTOFUStore{path: path} +} + +// Load reads the trust anchor from the file. Returns nil, nil if the file +// does not exist. +func (s *FileTOFUStore) Load() ([]byte, error) { + data, err := os.ReadFile(s.path) + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("signing: load TOFU trust anchor: %w", err) + } + return data, nil +} + +// Save writes pemBytes to the file only if the file does not already exist. +func (s *FileTOFUStore) Save(pemBytes []byte) error { + f, err := os.OpenFile(s.path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) + if errors.Is(err, os.ErrExist) { + return nil // idempotent: already stored + } + if err != nil { + return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) + } + defer f.Close() + if _, err := f.Write(pemBytes); err != nil { + return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) + } + return nil +} diff --git a/signing/types.go b/signing/types.go index 03287855..b8b34be8 100644 --- a/signing/types.go +++ b/signing/types.go @@ -74,6 +74,19 @@ type Signer interface { ChainDER(ctx context.Context) ([][]byte, error) } +// TrustAnchorProvider is an optional interface that [Signer] implementations +// may satisfy when they also hold the root CA certificate. The OpAMP server +// checks for this interface (via type assertion) to populate +// trust_chain_response.tofu_trust_anchor during TOFU enrollment. +// Signers that do not hold the root CA (for example, a remote HSM-backed +// signer that only exposes the leaf chain) need not implement this interface; +// TOFU enrollment will simply not be available for those deployments. +type TrustAnchorProvider interface { + // TrustAnchorPEM returns the PEM-encoded root CA certificate that Agents + // should use as their payload trust anchor. + TrustAnchorPEM(ctx context.Context) ([]byte, error) +} + // Verifier validates a delivered trust chain and verifies detached // signatures against the resulting leaf certificate. // From 8f41ed17383d88659cf22bcccc3fa26c55196690 Mon Sep 17 00:00:00 2001 From: Stanley Liu Date: Thu, 2 Jul 2026 16:31:45 -0400 Subject: [PATCH 20/22] Apply attestation-failure backoff in HTTP polling loop --- client/internal/httpsender.go | 50 +++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go index cb1b6fa1..e488d54b 100644 --- a/client/internal/httpsender.go +++ b/client/internal/httpsender.go @@ -158,13 +158,36 @@ func (h *HTTPSender) Run( } } + // attestBackoff mirrors the pattern used by the WebSocket client's + // runUntilStopped: attestation failures at the application level + // are distinct from transport errors (the TCP connection is fine, + // the server just failed verification). Without a separate backoff + // the agent would retry at the full polling rate — up to 1 req/s + // for aggressive heartbeat intervals — against a potentially + // compromised server. Exponential backoff with no max elapsed time + // matches the WS client's behaviour. + attestBackoff := backoff.NewExponentialBackOff() + attestBackoff.MaxElapsedTime = 0 + for { pollingTimer := time.NewTimer(time.Millisecond * time.Duration(h.pollingIntervalMs.Load())) select { case <-h.hasPendingMessage: // Have something to send. Stop the polling timer and send what we have. pollingTimer.Stop() - h.makeOneRequestRoundtrip(ctx) + if attestationFailed := h.makeOneRequestRoundtrip(ctx); attestationFailed { + interval := attestBackoff.NextBackOff() + h.logger.Errorf(ctx, "Payload trust verification failed, will retry in %v.", interval) + timer := time.NewTimer(interval) + select { + case <-timer.C: + case <-ctx.Done(): + timer.Stop() + return + } + } else { + attestBackoff.Reset() + } case <-pollingTimer.C: // Polling interval has passed. Force a status update. @@ -205,18 +228,19 @@ func (h *HTTPSender) SetRequestHeader(baseHeaders http.Header, headerFunc func(h // makeOneRequestRoundtrip sends a request and receives a response. // It will retry the request if the server responds with too many -// requests or unavailable status. -func (h *HTTPSender) makeOneRequestRoundtrip(ctx context.Context) { +// requests or unavailable status. It returns true if the response +// failed attestation verification so the caller can apply backoff. +func (h *HTTPSender) makeOneRequestRoundtrip(ctx context.Context) bool { resp, err := h.sendRequestWithRetries(ctx) if err != nil { h.logger.Errorf(ctx, "%v", err) - return + return false } if resp == nil { // No request was sent and nothing to receive. - return + return false } - h.receiveResponse(ctx, resp) + return h.receiveResponse(ctx, resp) } // requestResult represents the outcome of a single HTTP request attempt. @@ -388,12 +412,15 @@ func (h *HTTPSender) prepareRequest(ctx context.Context) (*requestWrapper, error return &req, nil } -func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) { +// receiveResponse decodes and processes a server response. It returns +// true when the response failed payload trust verification so the +// caller can apply attestation-specific backoff before retrying. +func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) bool { msgBytes, err := io.ReadAll(resp.Body) if err != nil { _ = resp.Body.Close() h.logger.Errorf(ctx, "cannot read response body: %v", err) - return + return false } _ = resp.Body.Close() @@ -417,13 +444,14 @@ func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) { if h.attestation != nil && isAttestationFailure(err) { h.logger.Errorf(ctx, "Payload trust verification failed; resetting attestation state: %v", err) h.attestation.Reset() - } else { - h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) + return true } - return + h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) + return false } h.receiveProcessor.ProcessReceivedMessage(ctx, &response) + return false } func (h *HTTPSender) SetHeartbeatInterval(duration time.Duration) error { From d6feacb75020e14681b3f1cc8a6fe5ebc171bff2 Mon Sep 17 00:00:00 2001 From: Stanley Liu Date: Thu, 2 Jul 2026 19:26:17 -0400 Subject: [PATCH 21/22] Fix bugs with TOFU atomic write, leaf-key algorithm, and SAN check for empty string hostname --- client/internal/attestation.go | 21 ++++++++++++--- client/internal/wsreceiver.go | 7 ++++- signing/algorithm.go | 48 +++++++++++----------------------- signing/tofu.go | 46 +++++++++++++++++++++++++++++--- 4 files changed, 80 insertions(+), 42 deletions(-) diff --git a/client/internal/attestation.go b/client/internal/attestation.go index 52986998..d0a4321c 100644 --- a/client/internal/attestation.go +++ b/client/internal/attestation.go @@ -36,6 +36,14 @@ var ( // spec this is a fatal handshake error. ErrSANMismatch = errors.New("client: leaf certificate SAN does not match server hostname") + // ErrServerNameUnavailable is returned when payload trust + // verification is enabled but the Agent could not determine the + // server hostname to check the leaf certificate's SAN against (for + // example, the server URL was empty or unparseable). SAN + // verification is mandatory when attestation is on, so rather than + // silently skip it the handshake fails closed. + ErrServerNameUnavailable = errors.New("client: cannot verify leaf certificate SAN: server hostname unavailable") + // ErrTOFUAnchorMissing is returned during TOFU enrollment when the // Server's TrustChainResponse does not include the expected // tofu_trust_anchor field. @@ -191,10 +199,15 @@ func (s *attestationState) ProcessEnvelope(ctx context.Context, envelope *protob if err != nil { return nil, fmt.Errorf("client: validate trust chain: %w", err) } - if s.serverName != "" { - if err := leaf.VerifyHostname(s.serverName); err != nil { - return nil, fmt.Errorf("%w: %v", ErrSANMismatch, err) - } + // SAN verification is mandatory when attestation is enabled. An + // empty serverName means the server hostname could not be + // resolved from the connection URL; fail closed rather than + // silently accept any certificate. + if s.serverName == "" { + return nil, ErrServerNameUnavailable + } + if err := leaf.VerifyHostname(s.serverName); err != nil { + return nil, fmt.Errorf("%w: %v", ErrSANMismatch, err) } s.leaf = leaf s.firstSeen = true diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go index 37b1bf69..55eaef4a 100644 --- a/client/internal/wsreceiver.go +++ b/client/internal/wsreceiver.go @@ -68,7 +68,12 @@ func NewWSReceiver( } if payloadVerifier != nil || tofuStore != nil { var serverName string - if parsed, err := url.Parse(serverURL); err == nil { + if parsed, err := url.Parse(serverURL); err != nil { + // Fail closed downstream: an empty serverName makes + // ProcessEnvelope reject the handshake with + // ErrServerNameUnavailable rather than skip SAN verification. + logger.Errorf(context.Background(), "Cannot parse server URL %q for SAN verification: %v", serverURL, err) + } else { serverName = parsed.Hostname() } w.attestation = newAttestationState(payloadVerifier, serverName, tofuStore) diff --git a/signing/algorithm.go b/signing/algorithm.go index 4b2b166a..da102485 100644 --- a/signing/algorithm.go +++ b/signing/algorithm.go @@ -18,27 +18,25 @@ import ( // below this size are rejected even if the rest of the chain validates. const rsaMinModulusBits = 2048 -// ErrUnsupportedAlgorithm indicates that a certificate's public key -// (or the algorithm declared by the issuer's signature on the cert) -// is not in the supported set: it is the wrong key type, an -// unsupported ECDSA curve, an RSA key below rsaMinModulusBits, or the -// declared SignatureAlgorithm does not match the leaf's actual key -// type/curve. +// ErrUnsupportedAlgorithm indicates that a certificate's public key is +// not in the supported set: it is the wrong key type, an unsupported +// ECDSA curve, or an RSA key below rsaMinModulusBits. var ErrUnsupportedAlgorithm = errors.New("signing: unsupported signature algorithm") // algorithmFromCert derives the Algorithm to use for signature -// operations involving cert. Dispatching on the leaf's own public key -// type (rather than on cert.SignatureAlgorithm, which describes the -// issuer's signing of the cert itself) is the correct authority: the -// Algorithm controls how a payload is signed/verified, and that has to -// match the leaf key's algorithm and curve, not the issuer's. +// operations involving cert, dispatching on the leaf's own public key +// type and (for ECDSA) curve. This is the correct authority: the +// Algorithm controls how a payload is signed/verified, so it must match +// the leaf key's type and curve. // -// The function additionally cross-checks cert.SignatureAlgorithm -// against the leaf key so that a certificate whose declared algorithm -// is inconsistent with its pubkey is rejected up front. This prevents -// a within-family mismatch (e.g., a P-384 CA issuing a P-256 leaf with -// SignatureAlgorithm=ECDSAWithSHA384) from silently accepting the -// wrong hash size at sign/verify time. +// cert.SignatureAlgorithm is deliberately NOT consulted. That field +// describes the algorithm the issuer used to sign this certificate, +// which is independent of the leaf key: a P-384 CA may legitimately +// issue a P-256 leaf, in which case cert.SignatureAlgorithm is +// ECDSAWithSHA384 even though the leaf signs payloads with P-256/SHA-256. +// The payload algorithm is fully determined by the leaf key returned +// here, so the issuer's signing algorithm is irrelevant and checking it +// would only reject valid cross-algorithm PKI hierarchies. // // Minimum RSA modulus is rsaMinModulusBits. func algorithmFromCert(cert *x509.Certificate) (Algorithm, error) { @@ -46,16 +44,8 @@ func algorithmFromCert(cert *x509.Certificate) (Algorithm, error) { case *ecdsa.PublicKey: switch pub.Curve { case elliptic.P256(): - if cert.SignatureAlgorithm != x509.ECDSAWithSHA256 { - return AlgorithmUnspecified, fmt.Errorf("%w: P-256 leaf with mismatched declared algorithm %s", - ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) - } return AlgorithmECDSAP256SHA256, nil case elliptic.P384(): - if cert.SignatureAlgorithm != x509.ECDSAWithSHA384 { - return AlgorithmUnspecified, fmt.Errorf("%w: P-384 leaf with mismatched declared algorithm %s", - ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) - } return AlgorithmECDSAP384SHA384, nil default: curveName := "unknown" @@ -74,16 +64,8 @@ func algorithmFromCert(cert *x509.Certificate) (Algorithm, error) { return AlgorithmUnspecified, fmt.Errorf("%w: RSA key %d bits < %d", ErrUnsupportedAlgorithm, bits, rsaMinModulusBits) } - if cert.SignatureAlgorithm != x509.SHA256WithRSA { - return AlgorithmUnspecified, fmt.Errorf("%w: RSA leaf with mismatched declared algorithm %s", - ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) - } return AlgorithmRSAPKCS1v15SHA256, nil case ed25519.PublicKey: - if cert.SignatureAlgorithm != x509.PureEd25519 { - return AlgorithmUnspecified, fmt.Errorf("%w: Ed25519 leaf with mismatched declared algorithm %s", - ErrUnsupportedAlgorithm, cert.SignatureAlgorithm) - } return AlgorithmEd25519, nil default: return AlgorithmUnspecified, fmt.Errorf("%w: unsupported public key type %T", diff --git a/signing/tofu.go b/signing/tofu.go index 39686173..77a866f9 100644 --- a/signing/tofu.go +++ b/signing/tofu.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" ) // TOFUStore persists the payload trust anchor acquired during a Trust On First @@ -60,16 +61,53 @@ func (s *FileTOFUStore) Load() ([]byte, error) { } // Save writes pemBytes to the file only if the file does not already exist. +// +// The write is atomic: pemBytes is first written in full to a temporary +// file in the same directory, then hard-linked into place with os.Link, +// which fails if the target already exists. This preserves the write-once +// (idempotent) contract while guaranteeing the anchor file is never left +// in a partially-written state — a crash, disk-full, or short write during +// the temp write leaves only the temp file (which is removed), never a +// truncated or empty anchor that would permanently shadow future Saves. func (s *FileTOFUStore) Save(pemBytes []byte) error { - f, err := os.OpenFile(s.path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) - if errors.Is(err, os.ErrExist) { + // Fast path: anchor already present, nothing to do. + if _, err := os.Stat(s.path); err == nil { return nil // idempotent: already stored + } else if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) } + + tmp, err := os.CreateTemp(filepath.Dir(s.path), ".tofu-*.tmp") if err != nil { return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) } - defer f.Close() - if _, err := f.Write(pemBytes); err != nil { + tmpName := tmp.Name() + // Remove the temp file on every path: on error, and after a successful + // link (the linked target keeps the content; the temp name is redundant). + defer os.Remove(tmpName) + + if err := tmp.Chmod(0o600); err != nil { + tmp.Close() + return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) + } + if _, err := tmp.Write(pemBytes); err != nil { + tmp.Close() + return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) + } + if err := tmp.Sync(); err != nil { + tmp.Close() + return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) + } + if err := tmp.Close(); err != nil { + return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) + } + + // os.Link fails with ErrExist if the anchor was created concurrently + // (or between the Stat above and here), preserving write-once semantics. + if err := os.Link(tmpName, s.path); err != nil { + if errors.Is(err, os.ErrExist) { + return nil // idempotent: another writer won the race + } return fmt.Errorf("%w: %v", ErrTOFUStoreSave, err) } return nil From 7a4d404e2926e6fbe08ce151c50264caec5d56c1 Mon Sep 17 00:00:00 2001 From: Stanley Liu Date: Thu, 2 Jul 2026 22:56:44 -0400 Subject: [PATCH 22/22] Fix WS client tight reconnect loop: back off on abnormal close when attestation enabled --- client/internal/wsreceiver.go | 24 +++++++++++ client/wsclient.go | 75 ++++++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go index 55eaef4a..f661b6b1 100644 --- a/client/internal/wsreceiver.go +++ b/client/internal/wsreceiver.go @@ -35,6 +35,15 @@ type wsReceiver struct { // of a payload trust verification failure. Safe to read only after // <-IsStopped() returns. attestationFailure bool + + // Set to true (before stopped is closed) when the loop exits because + // of an abnormal connection close while attestation is enabled. A + // server that accepts the connection and then drops it without a + // normal-closure handshake is, in an attestation deployment, almost + // always failing to sign (e.g. its signing/policy backend is down); + // the caller uses this to back off instead of reconnecting in a tight + // loop. Safe to read only after <-IsStopped() returns. + connectionError bool } // NewWSReceiver creates a new Receiver that uses WebSocket to receive @@ -98,6 +107,13 @@ func (r *wsReceiver) WasAttestationFailure() bool { return r.attestationFailure } +// WasConnectionError reports whether the receiver stopped because of an +// abnormal connection close while attestation is enabled. Only valid after +// <-IsStopped() returns. +func (r *wsReceiver) WasConnectionError() bool { + return r.connectionError +} + // ReceiverLoop runs the receiver loop. // To stop the receiver cancel the context and close the websocket connection func (r *wsReceiver) ReceiverLoop(ctx context.Context) { @@ -150,6 +166,14 @@ func (r *wsReceiver) ReceiverLoop(ctx context.Context) { } if !websocket.IsCloseError(res.err, websocket.CloseNormalClosure) { r.logger.Errorf(ctx, "Unexpected error while receiving: %v", res.err) + // When attestation is enabled, an abnormal close + // usually means the server terminated the connection + // because it could not attest (e.g. its signing/policy + // backend is unavailable). Signal the caller so it + // applies backoff instead of a tight reconnect loop. + if r.attestation != nil { + r.connectionError = true + } } return } diff --git a/client/wsclient.go b/client/wsclient.go index 3c61c52d..94c15bb1 100644 --- a/client/wsclient.go +++ b/client/wsclient.go @@ -366,7 +366,7 @@ func (c *wsClient) ensureConnected(ctx context.Context) error { // Returns true if the cycle ended because of a payload trust verification // failure (wrong CA, bad signature, etc.). The caller should apply exponential // backoff before retrying in that case. -func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) (attestationFailed bool) { +func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) (attestationFailed bool, connectionFailed bool) { if err := c.ensureConnected(ctx); err != nil { // Can't connect, so can't move forward. This currently happens when we // are being stopped. @@ -431,6 +431,13 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) (atte if err := c.sender.StoppingErr(); err != nil { c.common.Logger.Debugf(ctx, "Error stopping the sender: %v", err) + // If the sender noticed the broken connection first (before the + // receiver), still treat it as an abnormal connection failure + // when attestation is enabled so the caller backs off. + if c.common.PayloadVerifier != nil || c.common.PayloadTOFUStore != nil { + connectionFailed = true + } + stopReceiver() <-r.IsStopped() break @@ -452,6 +459,7 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) (atte stopSender() <-c.sender.IsStopped() attestationFailed = r.WasAttestationFailure() + connectionFailed = r.WasConnectionError() } return } @@ -460,43 +468,64 @@ func (c *wsClient) runUntilStopped(ctx context.Context) { // Iterates until we detect that the client is stopping. sendFirstMessage := true - // Separate backoff for attestation failures. ensureConnected already - // backs off TCP-level failures within a single runOneCycle call, but - // when the transport connects and only the application-level - // attestation check fails the receiver stops, runOneCycle returns, - // and ensureConnected would immediately succeed again on the next - // call (TCP is fine). Without this outer backoff the client would - // spin in a tight reject-reconnect loop as fast as the network - // allows, which is contrary to the spec's SHOULD-exponential-backoff - // requirement for attestation failures. - attestBackoff := backoff.NewExponentialBackOff() - attestBackoff.MaxElapsedTime = 0 // retry forever + // Separate backoff for application-level reconnects. ensureConnected + // already backs off TCP-level dial failures within a single + // runOneCycle call, but two failure modes connect successfully at the + // transport layer and only fail afterwards: + // 1. the client's own attestation check rejects a message, or + // 2. the server accepts the connection and then drops it abnormally + // (with attestation on, this almost always means the server + // cannot sign — e.g. its signing/policy backend is down). + // In both cases ensureConnected would immediately succeed again on the + // next call (TCP is fine). Without this outer backoff the client would + // spin in a tight reconnect loop as fast as the network allows, which + // is contrary to the spec's SHOULD-exponential-backoff requirement. + reconnectBackoff := backoff.NewExponentialBackOff() + reconnectBackoff.MaxElapsedTime = 0 // retry forever for { if c.common.IsStopping() { return } - if attestationFailed := c.runOneCycle(ctx, sendFirstMessage); attestationFailed { - interval := attestBackoff.NextBackOff() + attestationFailed, connectionFailed := c.runOneCycle(ctx, sendFirstMessage) + switch { + case attestationFailed: + interval := reconnectBackoff.NextBackOff() c.common.Logger.Errorf(ctx, "Payload trust verification failed, will retry in %v.", interval) - timer := time.NewTimer(interval) - select { - case <-timer.C: - case <-ctx.Done(): - timer.Stop() + if !c.sleepWithBackoff(ctx, interval) { + return + } + case connectionFailed: + interval := reconnectBackoff.NextBackOff() + c.common.Logger.Errorf(ctx, "Connection closed abnormally, will retry in %v.", interval) + if !c.sleepWithBackoff(ctx, interval) { return } - } else { - // Non-attestation cycle: reset so the next attestation - // failure starts backoff from the initial interval again. - attestBackoff.Reset() + default: + // Productive cycle: reset so the next failure starts backoff + // from the initial interval again. + reconnectBackoff.Reset() } sendFirstMessage = false } } +// sleepWithBackoff waits for the given interval or until the context is +// cancelled. It returns true if the interval elapsed, or false if the +// context was cancelled (the caller should stop). +func (c *wsClient) sleepWithBackoff(ctx context.Context, interval time.Duration) bool { + timer := time.NewTimer(interval) + defer timer.Stop() + select { + case <-timer.C: + return true + case <-ctx.Done(): + return false + } +} + // useProxy sets the websocket dialer to use the passed proxy URL. // If the proxy has no schema http is used. // This method is not thread safe and must be called before c.dialer is used.