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/client/httpclient.go b/client/httpclient.go index aff87319..62dffc6f 100644 --- a/client/httpclient.go +++ b/client/httpclient.go @@ -163,6 +163,8 @@ func (c *httpClient) runUntilStopped(ctx context.Context) { c.common.PackagesStateProvider, &c.common.PackageSyncMutex, c.common.DownloadReporterInterval, + c.common.PayloadVerifier, + c.common.PayloadTOFUStore, ) } diff --git a/client/httpclient_test.go b/client/httpclient_test.go index efa6b8aa..94897021 100644 --- a/client/httpclient_test.go +++ b/client/httpclient_test.go @@ -294,6 +294,12 @@ 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, http.StatusTemporaryRedirect), }, diff --git a/client/internal/attestation.go b/client/internal/attestation.go new file mode 100644 index 00000000..d0a4321c --- /dev/null +++ b/client/internal/attestation.go @@ -0,0 +1,281 @@ +package internal + +import ( + "context" + "crypto/x509" + "encoding/pem" + "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") + + // 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") + + // 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. + 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. + ErrMissingSignature = errors.New("client: SignedServerToAgent missing signature") + + // 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") + + // 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 +// 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 + serverName string // hostname for SAN verification + tofuStore signing.TOFUStore // non-nil when in TOFU enrollment mode + + mu sync.Mutex + firstSeen bool + leaf *x509.Certificate +} + +// newAttestationState constructs a per-connection attestation state. +// 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 +// 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, ErrSANMismatch) || + errors.Is(err, ErrTOFUAnchorMissing) || + 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) || + 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 +// 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, err := parsePEMChain(chainResp.CertificateChain) + 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) + } + // 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 + } + + // Every message — including the first — MUST carry a signature. + 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 +} + +// 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 +// 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) + } + // 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/client/internal/attestation_test.go b/client/internal/attestation_test.go new file mode 100644 index 00000000..96af0c69 --- /dev/null +++ b/client/internal/attestation_test.go @@ -0,0 +1,380 @@ +package internal + +import ( + "context" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "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. +// 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) + require.NoError(t, err) + + chainDER, err := f.signer.ChainDER(context.Background()) + require.NoError(t, err) + 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: pemChain, + }, + } + 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, "", nil) + ctx := context.Background() + + first := &protobufs.ServerToAgent{InstanceUid: []byte("first-msg-uid000")} + firstEnv := f.buildFirstEnvelope(t, first, true /* signFirst — mandatory per spec */) + + 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_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, "", nil) + ctx := context.Background() + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("signed-first-uid")} + 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, "", nil) + 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. +func TestAttestationState_FirstMessageSignedButTampered(t *testing.T) { + f := newAttestationFixture(t) + state := newAttestationState(f.verifier, "", nil) + 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, "", nil) + 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, "", nil) + 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, "", nil) + 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, "", nil) + ctx := context.Background() + + first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, true)) + 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, "", nil) + ctx := context.Background() + + first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, true)) + 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, "", nil) + 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, "", nil) + + inner := &protobufs.ServerToAgent{InstanceUid: []byte("envelope-uid0000")} + env := f.buildFirstEnvelope(t, inner, true) + 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, "", nil) + + 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) +} + +// 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, "", nil) + ctx := context.Background() + + // Drive the state through a successful handshake. + first := &protobufs.ServerToAgent{InstanceUid: []byte("first00000000000")} + _, err := state.ProcessEnvelope(ctx, f.buildFirstEnvelope(t, first, true)) + 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, true) + _, 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/clientcommon.go b/client/internal/clientcommon.go index 50afa587..7105e15c 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,9 @@ 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") + ErrTOFULoadFailed = errors.New("PayloadTOFUStore.Load failed at startup") errAlreadyStarted = errors.New("already started") errCannotStopNotStarted = errors.New("cannot stop because not started") @@ -46,6 +50,21 @@ 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 + + // 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 @@ -94,6 +113,13 @@ func (c *ClientCommon) validateCapabilities(capabilities protobufs.AgentCapabili return ErrPackagesStateProviderNotSet } } + requiresAttestation := capabilities&protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification != 0 + switch { + case requiresAttestation && c.PayloadVerifier == nil && c.PayloadTOFUStore == nil: + return ErrPayloadVerifierMissing + case !requiresAttestation && c.PayloadVerifier != nil: + return ErrPayloadVerifierWithoutCapability + } return nil } @@ -132,6 +158,27 @@ func (c *ClientCommon) PrepareStart( // Prepare package statuses. c.PackagesStateProvider = settings.PackagesStateProvider + // 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/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 715d1fbc..cc25a388 100644 --- a/client/internal/httpsender.go +++ b/client/internal/httpsender.go @@ -21,6 +21,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 @@ -92,6 +99,12 @@ func (h *HTTPSender) SetHTTPClient(client *http.Client) { h.client = client } +// SetMaxMessageSize sets the maximum message size in bytes. Messages +// larger than this limit are rejected before sending. +func (h *HTTPSender) SetMaxMessageSize(maxMessageSize int64) { + h.maxMessageSize = internal.ResolveMaxMessageSize(maxMessageSize) +} + // SetProxy will force each request to use passed proxy and use the passed headers when making a CONNECT request to the proxy. // If the proxy has no schema http is used. // This method is not thread safe and must be called before h.client is used. @@ -129,16 +142,25 @@ 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 || 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 if callbacks.CheckRedirect != nil { @@ -148,13 +170,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. @@ -195,18 +240,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. @@ -378,62 +424,75 @@ func (h *HTTPSender) prepareRequest(ctx context.Context) (*requestWrapper, error return &req, nil } -func (h *HTTPSender) responseBodyReader(resp *http.Response) (io.Reader, func(), error) { - closeBody := func() { - _ = resp.Body.Close() - } - if resp.Header.Get(headerContentEncoding) != encodingTypeGZip { - return resp.Body, closeBody, nil - } - - gzipReader, err := gzip.NewReader(resp.Body) +// 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 := h.readResponseBody(resp) if err != nil { - closeBody() - return nil, func() {}, err + h.logger.Errorf(ctx, "cannot read response body: %v", err) + return false } - return gzipReader, func() { - _ = gzipReader.Close() - _ = resp.Body.Close() - }, nil -} -func (h *HTTPSender) readResponseBody(resp *http.Response) ([]byte, error) { - body, closeBody, err := h.responseBodyReader(resp) - if err != nil { - return nil, err + var response protobufs.ServerToAgent + 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, 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. + // + // 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() + return true + } + h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) + return false } - defer closeBody() - // Do not drain oversized responses after the limit is hit. Reading to EOF - // would preserve HTTP/1 keep-alive, but would also let a peer force - // unbounded network and decompression work after MaxMessageSize is exceeded. - return internal.ReadAllLimited(body, h.maxMessageSize, "response body") + h.receiveProcessor.ProcessReceivedMessage(ctx, &response) + return false } -func (h *HTTPSender) discardResponseBody(resp *http.Response) error { - body, closeBody, err := h.responseBodyReader(resp) - if err != nil { - return err +// readResponseBody reads the response body, decompressing gzip if indicated +// by Content-Encoding, and enforces maxMessageSize. +func (h *HTTPSender) readResponseBody(resp *http.Response) ([]byte, error) { + defer resp.Body.Close() + if resp.Header.Get(headerContentEncoding) == encodingTypeGZip { + gr, err := gzip.NewReader(resp.Body) + if err != nil { + return nil, err + } + defer gr.Close() + return internal.ReadAllLimited(gr, h.maxMessageSize, "response body") } - defer closeBody() - - return internal.CopyDiscardLimited(body, h.maxMessageSize, "response body") + return internal.ReadAllLimited(resp.Body, h.maxMessageSize, "response body") } -func (h *HTTPSender) receiveResponse(ctx context.Context, resp *http.Response) { - msgBytes, err := h.readResponseBody(resp) - if err != nil { - h.logger.Errorf(ctx, "cannot read response body: %v", err) - return - } - - var response protobufs.ServerToAgent - if err := proto.Unmarshal(msgBytes, &response); err != nil { - h.logger.Errorf(ctx, "cannot unmarshal response: %v", err) - return +// discardResponseBody drains and closes the response body, decompressing +// gzip if indicated by Content-Encoding and enforcing maxMessageSize. This +// allows the underlying TCP connection to be reused for subsequent requests. +func (h *HTTPSender) discardResponseBody(resp *http.Response) error { + defer resp.Body.Close() + if resp.Header.Get(headerContentEncoding) == encodingTypeGZip { + gr, err := gzip.NewReader(resp.Body) + if err != nil { + return err + } + defer gr.Close() + return internal.CopyDiscardLimited(gr, h.maxMessageSize, "response body") } - - h.receiveProcessor.ProcessReceivedMessage(ctx, &response) + return internal.CopyDiscardLimited(resp.Body, h.maxMessageSize, "response body") } func (h *HTTPSender) SetHeartbeatInterval(duration time.Duration) error { @@ -460,10 +519,6 @@ func (h *HTTPSender) EnableCompression() { h.compressionEnabled = true } -func (h *HTTPSender) SetMaxMessageSize(maxMessageSize int64) { - h.maxMessageSize = internal.ResolveMaxMessageSize(maxMessageSize) -} - func (h *HTTPSender) AddTLSConfig(config *tls.Config) { if config != nil { tlsTransport := &http.Transport{} diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go index 834036ee..f661b6b1 100644 --- a/client/internal/wsreceiver.go +++ b/client/internal/wsreceiver.go @@ -3,6 +3,7 @@ package internal import ( "context" "fmt" + "net/url" "sync" "time" @@ -10,6 +11,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 +22,38 @@ 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{} + + // 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 + + // 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 -// 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 +63,9 @@ func NewWSReceiver( packagesStateProvider types.PackagesStateProvider, packageSyncMutex *sync.Mutex, reporterInterval time.Duration, + payloadVerifier signing.Verifier, + serverURL string, + tofuStore signing.TOFUStore, ) *wsReceiver { w := &wsReceiver{ conn: conn, @@ -44,6 +75,18 @@ func NewWSReceiver( processor: newReceivedProcessor(logger, callbacks, sender, clientSyncedState, packagesStateProvider, packageSyncMutex, reporterInterval), stopped: make(chan struct{}), } + if payloadVerifier != nil || tofuStore != nil { + var serverName string + 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) + } return w } @@ -58,6 +101,19 @@ 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 +} + +// 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) { @@ -78,7 +134,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} }() @@ -87,8 +143,37 @@ 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() + } + // Mark before returning so the caller can read + // WasAttestationFailure() after <-IsStopped(). + r.attestationFailure = true + return + } 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 } @@ -98,7 +183,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 +191,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..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) + 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) + 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) + 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) + receiver := NewWSReceiver(&internal.NopLogger{}, callbacks, nil, sender, clientSyncedState, localPackageState, &mux, time.Second, nil, "", 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, "", 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 934335c1..66ff21c6 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. @@ -57,6 +58,44 @@ 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 + + // 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 1f412269..94c15bb1 100644 --- a/client/wsclient.go +++ b/client/wsclient.go @@ -362,7 +362,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, 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. @@ -409,6 +413,9 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) { c.common.PackagesStateProvider, &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. @@ -424,6 +431,13 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) { 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 @@ -444,22 +458,74 @@ func (c *wsClient) runOneCycle(ctx context.Context, sendFirstMessage bool) { stopSender() <-c.sender.IsStopped() + attestationFailed = r.WasAttestationFailure() + connectionFailed = r.WasConnectionError() } + return } func (c *wsClient) runUntilStopped(ctx context.Context) { // Iterates until we detect that the client is stopping. sendFirstMessage := true + + // 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 } - c.runOneCycle(ctx, sendFirstMessage) + 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) + 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 + } + 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. diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go index 96fa5efd..5eac11b1 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,15 @@ 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 + + // payloadTOFUStore, when non-nil, enables TOFU enrollment for the + // payload trust anchor. Mutually exclusive with payloadVerifier. + payloadTOFUStore signing.TOFUStore } type proxySettings struct { @@ -138,6 +148,25 @@ 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 + } +} + +// 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()}, @@ -190,6 +219,8 @@ func (agent *Agent) connect(ops ...settingsOp) error { OpAMPServerURL: agent.agentConfig.Endpoint, 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.") @@ -232,6 +263,12 @@ 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 + } else if agent.payloadTOFUStore != nil { + supportedCapabilities |= protobufs.AgentCapabilities_AgentCapabilities_RequiresPayloadTrustVerification | + protobufs.AgentCapabilities_AgentCapabilities_AcceptsPayloadTrustAnchorTOFU + } 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..3b9088f8 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,18 @@ 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 + + // 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 { @@ -157,6 +170,14 @@ func loadEnv(cfg *flagConfig) { cfg.scaleCount = count } } + + 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() { @@ -172,6 +193,20 @@ 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.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) @@ -211,6 +246,26 @@ 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) + } + + 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, @@ -239,6 +294,12 @@ 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 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 new file mode 100644 index 00000000..9da5d941 --- /dev/null +++ b/internal/examples/policysrv/main.go @@ -0,0 +1,164 @@ +// 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{ + // 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) + } + 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}) + + // 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 8b7f702b..7f2de6e2 100644 --- a/internal/examples/server/opampsrv/opampsrv.go +++ b/internal/examples/server/opampsrv/opampsrv.go @@ -16,16 +16,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(), @@ -40,9 +47,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) @@ -53,6 +61,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/internal/integrationtest/attestation_e2e_test.go b/internal/integrationtest/attestation_e2e_test.go new file mode 100644 index 00000000..e4574f4c --- /dev/null +++ b/internal/integrationtest/attestation_e2e_test.go @@ -0,0 +1,854 @@ +package integrationtest + +import ( + "context" + "crypto/x509" + "fmt" + "net" + "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. + // 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). 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 +// 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() + // 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) + 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 +} + +// 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 (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 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 (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 +// (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 +} + +// 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 +// 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()) + + 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 + // 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. 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. 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"), + })) + + 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") + 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()) + + require.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()) + + assertRejected(t, logger, &msgN) +} + +// 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()) + + assertRejected(t, logger, &msgN) + 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()) + + assertRejected(t, logger, &msgN) + 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 := &controlledSigner{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. 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 + // controlledSigner wrapper. + conn := *savedConn.Load() + require.NoError(t, conn.Send(context.Background(), &protobufs.ServerToAgent{ + InstanceUid: []byte("tampered-uid0000"), + })) + + 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. + 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()) + + 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. 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 + + 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()) + + 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(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") +} + +// 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") +} 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 diff --git a/internal/opamp-spec b/internal/opamp-spec index 49860fd6..1f75ca1f 160000 --- a/internal/opamp-spec +++ b/internal/opamp-spec @@ -1 +1 @@ -Subproject commit 49860fd67177e4f80d9778831472ab106e4ae951 +Subproject commit 1f75ca1f63a0006e627bc8f23c13a9279a1232ac diff --git a/internal/wsmessage.go b/internal/wsmessage.go index cfaaaa66..0118caca 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, maxMessageSize int64) error { 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..133a84de 100644 --- a/protobufs/opamp.pb.go +++ b/protobufs/opamp.pb.go @@ -16,18 +16,18 @@ // 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.9 +// protoc v7.35.0 // source: opamp.proto package protobufs import ( - reflect "reflect" - sync "sync" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" ) const ( @@ -169,19 +169,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 +198,7 @@ var ( "ServerCapabilities_AcceptsPackagesStatus": 16, "ServerCapabilities_OffersConnectionSettings": 32, "ServerCapabilities_AcceptsConnectionSettingsRequest": 64, + "ServerCapabilities_OffersPayloadTrustVerification": 128, } ) @@ -430,48 +437,67 @@ 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 + // 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", + 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, - "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, + "AgentCapabilities_AcceptsPayloadTrustAnchorTOFU": 131072, } ) @@ -689,10 +715,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 +780,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 +799,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 +927,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 +947,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 +966,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 +990,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 +1016,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 +1040,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 +1064,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 +1089,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 +1115,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 +1143,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 +1173,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 +1188,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 +1208,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 +1238,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 +1281,15 @@ 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"` + 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 +1300,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,17 +1392,184 @@ func (x *ServerToAgent) GetCustomMessage() *CustomMessage { 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"` + // 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. + 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() { + *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() []byte { + if x != nil { + return x.CertificateChain + } + return nil +} + +func (x *TrustChainResponse) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + 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) +// 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,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 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,16,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. // 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" + // 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 @@ -1431,16 +1601,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[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OpAMPConnectionSettings) String() string { @@ -1450,8 +1620,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[10] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1463,7 +1633,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{10} } func (x *OpAMPConnectionSettings) GetDestinationEndpoint() string { @@ -1513,10 +1683,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 +1706,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[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TelemetryConnectionSettings) String() string { @@ -1558,8 +1725,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[11] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1571,7 +1738,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{11} } func (x *TelemetryConnectionSettings) GetDestinationEndpoint() string { @@ -1631,10 +1798,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 +1816,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[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *OtherConnectionSettings) String() string { @@ -1677,8 +1841,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[12] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1690,7 +1854,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{12} } func (x *OtherConnectionSettings) GetDestinationEndpoint() string { @@ -1739,31 +1903,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 "". + // 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"` + 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[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TLSConnectionSettings) String() string { @@ -1773,8 +1934,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[13] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1786,7 +1947,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{13} } func (x *TLSConnectionSettings) GetCaPemContents() string { @@ -1833,10 +1994,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 +2002,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[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ProxyConnectionSettings) String() string { @@ -1862,8 +2020,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[14] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1875,7 +2033,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{14} } func (x *ProxyConnectionSettings) GetUrl() string { @@ -1894,20 +2052,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[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Headers) String() string { @@ -1917,8 +2072,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[15] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1930,7 +2085,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{15} } func (x *Headers) GetHeaders() []*Header { @@ -1942,21 +2097,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[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Header) String() string { @@ -1966,8 +2118,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[16] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1979,7 +2131,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{16} } func (x *Header) GetKey() string { @@ -1998,10 +2150,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 +2161,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[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TLSCertificate) String() string { @@ -2031,8 +2180,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[17] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2044,7 +2193,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{17} } func (x *TLSCertificate) GetCert() []byte { @@ -2070,10 +2219,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 +2257,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[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ConnectionSettingsOffers) String() string { @@ -2130,8 +2276,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[18] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2143,7 +2289,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{18} } func (x *ConnectionSettingsOffers) GetHash() []byte { @@ -2191,12 +2337,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 +2350,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[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackagesAvailable) String() string { @@ -2225,8 +2368,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[19] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2238,7 +2381,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{19} } func (x *PackagesAvailable) GetPackages() map[string]*PackageAvailable { @@ -2272,11 +2415,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.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. @@ -2287,16 +2427,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[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageAvailable) String() string { @@ -2306,8 +2446,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[20] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2319,7 +2459,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{20} } func (x *PackageAvailable) GetType() PackageType { @@ -2352,10 +2492,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 +2513,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[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DownloadableFile) String() string { @@ -2395,8 +2532,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[21] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2408,7 +2545,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{21} } func (x *DownloadableFile) GetDownloadUrl() string { @@ -2440,25 +2577,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.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 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[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerErrorResponse) String() string { @@ -2468,8 +2603,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[22] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2481,7 +2616,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{22} } func (x *ServerErrorResponse) GetType() ServerErrorResponseType { @@ -2498,16 +2633,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 +2661,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[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RetryInfo) String() string { @@ -2547,8 +2681,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[23] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2560,7 +2694,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{23} } func (x *RetryInfo) GetRetryAfterNanoseconds() uint64 { @@ -2574,20 +2708,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.v1.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[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ServerToAgentCommand) String() string { @@ -2597,8 +2728,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[24] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2610,7 +2741,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{24} } func (x *ServerToAgentCommand) GetType() CommandType { @@ -2621,25 +2752,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 +2777,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[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentDescription) String() string { @@ -2675,8 +2803,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[25] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2688,7 +2816,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{25} } func (x *AgentDescription) GetIdentifyingAttributes() []*KeyValue { @@ -2708,10 +2836,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 +2854,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[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ComponentHealth) String() string { @@ -2748,8 +2873,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[26] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2761,7 +2886,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{26} } func (x *ComponentHealth) GetHealthy() bool { @@ -2807,21 +2932,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[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EffectiveConfig) String() string { @@ -2831,8 +2953,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[27] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2844,7 +2966,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{27} } func (x *EffectiveConfig) GetConfigMap() *AgentConfigMap { @@ -2855,28 +2977,25 @@ 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 // 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"` + 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[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RemoteConfigStatus) String() string { @@ -2886,8 +3005,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[28] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2899,7 +3018,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{28} } func (x *RemoteConfigStatus) GetLastRemoteConfigHash() []byte { @@ -2925,28 +3044,25 @@ func (x *RemoteConfigStatus) GetErrorMessage() string { // Status: [Development] type ConnectionSettingsStatus struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The hash of the connection settings that was last recieved by this Agent + state protoimpl.MessageState `protogen:"open.v1"` + // 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"` + 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[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ConnectionSettingsStatus) String() string { @@ -2956,8 +3072,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[29] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2969,7 +3085,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{29} } func (x *ConnectionSettingsStatus) GetLastConnectionSettingsHash() []byte { @@ -2997,13 +3113,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 +3128,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[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageStatuses) String() string { @@ -3034,8 +3147,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[30] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3047,7 +3160,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{30} } func (x *PackageStatuses) GetPackages() map[string]*PackageStatus { @@ -3074,10 +3187,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"` @@ -3117,22 +3227,22 @@ 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. // 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[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageStatus) String() string { @@ -3142,8 +3252,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[31] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3155,7 +3265,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{31} } func (x *PackageStatus) GetName() string { @@ -3217,23 +3327,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[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PackageDownloadDetails) String() string { @@ -3243,8 +3350,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[32] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3256,7 +3363,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{32} } func (x *PackageDownloadDetails) GetDownloadPercent() float64 { @@ -3276,23 +3383,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[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentIdentification) String() string { @@ -3302,8 +3406,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[33] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3315,7 +3419,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{33} } func (x *AgentIdentification) GetNewInstanceUid() []byte { @@ -3326,10 +3430,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 +3445,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[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentRemoteConfig) String() string { @@ -3363,8 +3464,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[34] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3376,7 +3477,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{34} } func (x *AgentRemoteConfig) GetConfig() *AgentConfigMap { @@ -3394,25 +3495,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[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentConfigMap) String() string { @@ -3422,8 +3520,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[35] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3435,7 +3533,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{35} } func (x *AgentConfigMap) GetConfigMap() map[string]*AgentConfigFile { @@ -3446,25 +3544,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[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentConfigFile) String() string { @@ -3474,8 +3569,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[36] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3487,7 +3582,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{36} } func (x *AgentConfigFile) GetBody() []byte { @@ -3505,24 +3600,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[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CustomCapabilities) String() string { @@ -3532,8 +3624,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[37] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3545,7 +3637,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{37} } func (x *CustomCapabilities) GetCapabilities() []string { @@ -3556,10 +3648,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 +3661,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[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CustomMessage) String() string { @@ -3591,8 +3680,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[38] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3604,7 +3693,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{38} } func (x *CustomMessage) GetCapability() string { @@ -3630,806 +3719,431 @@ func (x *CustomMessage) GetData() []byte { var File_opamp_proto protoreflect.FileDescriptor -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, -} +const file_opamp_proto_rawDesc = "" + + "\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\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\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\"\xdf\x01\n" + + "\x13AvailableComponents\x12S\n" + + "\n" + + "components\x18\x01 \x03(\v23.opamp.proto.v1.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\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\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\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\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\".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\"\x92\x01\n" + + "\x12TrustChainResponse\x12+\n" + + "\x11certificate_chain\x18\x01 \x01(\fR\x10certificateChain\x12#\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" + + "\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\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\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\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" + + "\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\"m\n" + + "\x17ProxyConnectionSettings\x12\x10\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" + + "\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\"\xaa\x04\n" + + "\x18ConnectionSettingsOffers\x12\x12\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.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.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\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\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\x19.opamp.proto.v1.RetryInfoH\x00R\tretryInfoB\t\n" + + "\aDetails\"C\n" + + "\tRetryInfo\x126\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\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\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\x1e.opamp.proto.v1.AgentConfigMapR\tconfigMap\"\xae\x01\n" + + "\x12RemoteConfigStatus\x125\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\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\x1aZ\n" + + "\rPackagesEntry\x12\x10\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\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\"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\"\xbd\x01\n" + + "\x0eAgentConfigMap\x12L\n" + + "\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\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" + + "\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*\xf6\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\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" + + "#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/protobufs\xaa\x02\x0eOpAmp.Proto.V1b\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{}{ - (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 - (*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 +var file_opamp_proto_msgTypes = make([]protoimpl.MessageInfo, 47) +var file_opamp_proto_goTypes = []any{ + (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{ - 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 - 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 - 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 - 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 + 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() } @@ -4438,462 +4152,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[22].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 +4171,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 } diff --git a/server/attestation.go b/server/attestation.go new file mode 100644 index 00000000..ed27ae56 --- /dev/null +++ b/server/attestation.go @@ -0,0 +1,126 @@ +package server + +import ( + "context" + "encoding/pem" + "fmt" + "sync/atomic" + + "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). 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 + 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. 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") + } + chain, err := signer.ChainDER(ctx) + if err != nil { + return nil, fmt.Errorf("server: fetch signing chain: %w", err) + } + state := &connectionSigningState{ + signer: signer, + chainDER: chain, + } + 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. +// 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, + } + + // 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) { + 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 + } + } + } + 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 +} + +// 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. +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..0bcc802d --- /dev/null +++ b/server/attestation_test.go @@ -0,0 +1,359 @@ +package server + +import ( + "context" + "crypto/x509" + "encoding/pem" + "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, false) + 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, false) + 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, false) + 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, false) + 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 decodes the PEM blob in TrustChainResponse into +// DER byte slices for the paired verifier's ValidateChain call. +func derChainFromResponse(resp *protobufs.TrustChainResponse) [][]byte { + 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 +} + +// 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 — 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) + + 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 + // 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, false) + 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") +} diff --git a/server/server.go b/server/server.go index 6570d2fe..8f4839f8 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. @@ -35,6 +36,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 ae65b270..e2232ad6 100644 --- a/server/serverimpl.go +++ b/server/serverimpl.go @@ -242,7 +242,7 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co if s.settings.MaxMessageSize >= 0 { wsConn.SetReadLimit(s.settings.MaxMessageSize) } - agentConn := newWSConnection(wsConn, s.settings.MaxMessageSize) + agentConn := newWSConnection(wsConn, s.settings.MaxMessageSize, s.settings.PayloadSigner != nil) defer func() { // Close the connection when all is done. @@ -305,6 +305,27 @@ 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. markNegotiated also unblocks Send for callers that + // were rejected pre-negotiation (see ErrSendBeforeNegotiated). + if !agentConn.isNegotiated() { + if s.settings.PayloadSigner != nil && agentRequiresAttestation(request.Capabilities) { + 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 + } + agentConn.enableSigning(state) + } + agentConn.markNegotiated() + } + response := connectionCallbacks.OnMessage(msgContext, agentConn, &request) if response == nil { // No send message when 'response' is empty continue @@ -319,6 +340,15 @@ func (s *server) handleWSConnection(reqCtx context.Context, wsConn *websocket.Co } sentCustomCapabilities = true } + // 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) + } err = agentConn.Send(msgContext, response) if err != nil { @@ -434,8 +464,45 @@ 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. + // + // 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 { + // 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) + + if agentRequiresAttestation(request.Capabilities) { + 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) + 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/serverimpl_test.go b/server/serverimpl_test.go index f587f061..65dddba9 100644 --- a/server/serverimpl_test.go +++ b/server/serverimpl_test.go @@ -283,7 +283,7 @@ func TestDisconnectClientWSConnection(t *testing.T) { assert.True(t, atomic.LoadInt32(&connectionCloseCalled) == 0) // Close connection from client side - clientConn := newWSConnection(conn, sharedinternal.DefaultMaxMessageSize) + clientConn := newWSConnection(conn, sharedinternal.DefaultMaxMessageSize, false) err = clientConn.Disconnect() assert.NoError(t, err) diff --git a/server/wsconnection.go b/server/wsconnection.go index 9f59aad7..416a2e32 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, @@ -23,22 +36,87 @@ type wsConnection struct { closed atomic.Bool maxMessageSize int64 + + // 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, maxMessageSize int64) types.Connection { - return &wsConnection{wsConn: wsConn, maxMessageSize: maxMessageSize} +func newWSConnection(wsConn *websocket.Conn, maxMessageSize int64, requiresNegotiation bool) *wsConnection { + return &wsConnection{ + wsConn: wsConn, + maxMessageSize: maxMessageSize, + 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. +func (c *wsConnection) enableSigning(state *connectionSigningState) { + 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 { 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 { + if c.requiresNegotiation && !c.negotiated.Load() { + return ErrSendBeforeNegotiated + } + c.connMutex.Lock() defer c.connMutex.Unlock() + if state := c.signing.Load(); state != nil { + env, err := state.signOutgoing(ctx, message) + if err != nil { + return err + } + return internal.WriteWSMessage(c.wsConn, env, c.maxMessageSize) + } + return internal.WriteWSMessage(c.wsConn, message, c.maxMessageSize) } diff --git a/signing/adversarial_test.go b/signing/adversarial_test.go new file mode 100644 index 00000000..376f2382 --- /dev/null +++ b/signing/adversarial_test.go @@ -0,0 +1,309 @@ +package signing + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "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, which + // always returns ErrSignatureMismatch. + err = verifier.Verify(ctx, payload, ecSig, leafRsa) + require.ErrorIs(t, err, ErrSignatureMismatch) +} + +// 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 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{}) + require.NoError(t, err) + leaf, _, err := GenerateLeaf(AlgorithmECDSAP256SHA256, ca, caKey, CertOptions{}) + require.NoError(t, err) + + // 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}) + keyDER, err := x509.MarshalPKCS8PrivateKey(caKey) + require.NoError(t, err) + junkPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + combined := append(append([]byte{}, leafPEM...), junkPEM...) + + 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 new file mode 100644 index 00000000..da102485 --- /dev/null +++ b/signing/algorithm.go @@ -0,0 +1,169 @@ +package signing + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "errors" + "fmt" +) + +// 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 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 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. +// +// 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) { + switch pub := cert.PublicKey.(type) { + case *ecdsa.PublicKey: + switch pub.Curve { + case elliptic.P256(): + return AlgorithmECDSAP256SHA256, nil + case elliptic.P384(): + 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) + } + return AlgorithmRSAPKCS1v15SHA256, nil + case ed25519.PublicKey: + return AlgorithmEd25519, nil + default: + return AlgorithmUnspecified, fmt.Errorf("%w: unsupported public key type %T", + ErrUnsupportedAlgorithm, pub) + } +} + +// 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/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 +} diff --git a/signing/certs.go b/signing/certs.go new file mode 100644 index 00000000..557a79ed --- /dev/null +++ b/signing/certs.go @@ -0,0 +1,187 @@ +package signing + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "net" + "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 + // 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 { + 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, + DNSNames: opts.DNSNames, + IPAddresses: opts.IPAddresses, + } + + 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/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/loader.go b/signing/loader.go new file mode 100644 index 00000000..53898984 --- /dev/null +++ b/signing/loader.go @@ -0,0 +1,147 @@ +package signing + +import ( + "crypto" + "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) +} + +// 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 +// 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) + } + // 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 { + 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 + } + // PKCS#1 for legacy RSA private keys. + if k, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { + return k, 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, PKCS#1, EC — 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..550a0891 --- /dev/null +++ b/signing/local_signer.go @@ -0,0 +1,130 @@ +package signing + +import ( + "context" + "crypto" + "crypto/x509" + "encoding/pem" + "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 + rootCADER []byte // set via WithRootCA; nil unless TOFU is supported +} + +// 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 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 + } + 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 +} + +// 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/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..92468c52 --- /dev/null +++ b/signing/local_verifier.go @@ -0,0 +1,67 @@ +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 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 + } + 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/remote_signer.go b/signing/remote_signer.go new file mode 100644 index 00000000..47ddaab4 --- /dev/null +++ b/signing/remote_signer.go @@ -0,0 +1,138 @@ +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) +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 +// 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 +} + +// 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. +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) +} diff --git a/signing/tofu.go b/signing/tofu.go new file mode 100644 index 00000000..77a866f9 --- /dev/null +++ b/signing/tofu.go @@ -0,0 +1,114 @@ +package signing + +import ( + "errors" + "fmt" + "os" + "path/filepath" +) + +// 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. +// +// 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 { + // 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) + } + 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 +} diff --git a/signing/types.go b/signing/types.go new file mode 100644 index 00000000..b8b34be8 --- /dev/null +++ b/signing/types.go @@ -0,0 +1,116 @@ +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) +} + +// 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. +// +// 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. 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 +}