mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-20 20:39:08 +02:00
Add per-connection user-approval prompts for VNC
This commit is contained in:
@@ -364,6 +364,9 @@ func setupSetConfigReq(customDNSAddressConverted []byte, cmd *cobra.Command, pro
|
|||||||
if cmd.Flag(serverVNCAllowedFlag).Changed {
|
if cmd.Flag(serverVNCAllowedFlag).Changed {
|
||||||
req.ServerVNCAllowed = &serverVNCAllowed
|
req.ServerVNCAllowed = &serverVNCAllowed
|
||||||
}
|
}
|
||||||
|
if cmd.Flag(disableVNCApprovalFlag).Changed {
|
||||||
|
req.DisableVNCApproval = &disableVNCApproval
|
||||||
|
}
|
||||||
if cmd.Flag(enableSSHRootFlag).Changed {
|
if cmd.Flag(enableSSHRootFlag).Changed {
|
||||||
req.EnableSSHRoot = &enableSSHRoot
|
req.EnableSSHRoot = &enableSSHRoot
|
||||||
}
|
}
|
||||||
@@ -473,6 +476,9 @@ func setupConfig(customDNSAddressConverted []byte, cmd *cobra.Command, configFil
|
|||||||
if cmd.Flag(serverVNCAllowedFlag).Changed {
|
if cmd.Flag(serverVNCAllowedFlag).Changed {
|
||||||
ic.ServerVNCAllowed = &serverVNCAllowed
|
ic.ServerVNCAllowed = &serverVNCAllowed
|
||||||
}
|
}
|
||||||
|
if cmd.Flag(disableVNCApprovalFlag).Changed {
|
||||||
|
ic.DisableVNCApproval = &disableVNCApproval
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.Flag(enableSSHRootFlag).Changed {
|
if cmd.Flag(enableSSHRootFlag).Changed {
|
||||||
ic.EnableSSHRoot = &enableSSHRoot
|
ic.EnableSSHRoot = &enableSSHRoot
|
||||||
@@ -604,6 +610,9 @@ func setupLoginRequest(providedSetupKey string, customDNSAddressConverted []byte
|
|||||||
if cmd.Flag(serverVNCAllowedFlag).Changed {
|
if cmd.Flag(serverVNCAllowedFlag).Changed {
|
||||||
loginRequest.ServerVNCAllowed = &serverVNCAllowed
|
loginRequest.ServerVNCAllowed = &serverVNCAllowed
|
||||||
}
|
}
|
||||||
|
if cmd.Flag(disableVNCApprovalFlag).Changed {
|
||||||
|
loginRequest.DisableVNCApproval = &disableVNCApproval
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.Flag(enableSSHRootFlag).Changed {
|
if cmd.Flag(enableSSHRootFlag).Changed {
|
||||||
loginRequest.EnableSSHRoot = &enableSSHRoot
|
loginRequest.EnableSSHRoot = &enableSSHRoot
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
const serverVNCAllowedFlag = "allow-server-vnc"
|
const (
|
||||||
|
serverVNCAllowedFlag = "allow-server-vnc"
|
||||||
|
disableVNCApprovalFlag = "disable-vnc-approval"
|
||||||
|
)
|
||||||
|
|
||||||
var serverVNCAllowed bool
|
var (
|
||||||
|
serverVNCAllowed bool
|
||||||
|
disableVNCApproval bool
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
upCmd.PersistentFlags().BoolVar(&serverVNCAllowed, serverVNCAllowedFlag, false, "Allow embedded VNC server on peer")
|
upCmd.PersistentFlags().BoolVar(&serverVNCAllowed, serverVNCAllowedFlag, false, "Allow embedded VNC server on peer")
|
||||||
|
upCmd.PersistentFlags().BoolVar(&disableVNCApproval, disableVNCApprovalFlag, false, "Disable per-connection user approval prompts for the embedded VNC server")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
// Package approval brokers per-attempt user-accept prompts for inbound
|
||||||
|
// remote access (VNC today, SSH and others in the future). A caller pushes
|
||||||
|
// a Prompt; the broker emits a SystemEvent on the daemon→UI stream and
|
||||||
|
// blocks until the UI calls the daemon's RespondApproval RPC, the per-
|
||||||
|
// request timeout fires, or no subscriber is connected. The latter case
|
||||||
|
// fails closed so a backgrounded UI cannot silently bypass the gate.
|
||||||
|
package approval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metadata keys the broker reserves on the emitted SystemEvent. Callers
|
||||||
|
// should not set these themselves; values in Prompt.Metadata that collide
|
||||||
|
// are overwritten by the broker.
|
||||||
|
const (
|
||||||
|
MetaRequestID = "request_id"
|
||||||
|
MetaKind = "kind"
|
||||||
|
MetaExpiresAt = "expires_at"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Kind values for the well-known prompt subjects. New subsystems should
|
||||||
|
// add a constant here so the UI can dispatch on a known string.
|
||||||
|
const (
|
||||||
|
KindVNC = "vnc"
|
||||||
|
KindSSH = "ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultTimeout is the wall-clock window the user has to accept or deny a
|
||||||
|
// pending approval before the broker fails closed and returns ErrTimeout.
|
||||||
|
// Kept well under typical VNC client and dashboard connection timeouts so
|
||||||
|
// the RFB rejection actually reaches the browser instead of racing the
|
||||||
|
// browser's own "connection timed out" message.
|
||||||
|
const DefaultTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
// timeoutValue returns the active timeout. It's a var so tests in this
|
||||||
|
// package can shorten the wait without exposing a setter on the public
|
||||||
|
// API. Production code always sees DefaultTimeout.
|
||||||
|
var timeoutValue = func() time.Duration { return DefaultTimeout }
|
||||||
|
|
||||||
|
// ErrNoSubscriber indicates no UI is connected to consume the prompt.
|
||||||
|
// The caller must reject the underlying connection (fail-closed).
|
||||||
|
var ErrNoSubscriber = errors.New("no UI subscriber connected for approval")
|
||||||
|
|
||||||
|
// ErrTimeout indicates the user did not respond within DefaultTimeout.
|
||||||
|
var ErrTimeout = errors.New("approval timed out")
|
||||||
|
|
||||||
|
// ErrDenied indicates the user explicitly denied the connection.
|
||||||
|
var ErrDenied = errors.New("approval denied")
|
||||||
|
|
||||||
|
// EventPublisher is the subset of peer.Status used to emit prompts.
|
||||||
|
type EventPublisher interface {
|
||||||
|
PublishEvent(
|
||||||
|
severity proto.SystemEvent_Severity,
|
||||||
|
category proto.SystemEvent_Category,
|
||||||
|
msg string,
|
||||||
|
userMsg string,
|
||||||
|
metadata map[string]string,
|
||||||
|
)
|
||||||
|
HasEventSubscribers() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt describes the pending request shown to the user. Kind selects
|
||||||
|
// the UI dispatch path (e.g. "vnc", "ssh"). Subject is the human-readable
|
||||||
|
// one-liner the UI may show as a title or notification body. Metadata is
|
||||||
|
// passed through verbatim and is the subsystem-specific payload (peer
|
||||||
|
// name, source IP, mode, etc.).
|
||||||
|
type Prompt struct {
|
||||||
|
Kind string
|
||||||
|
Subject string
|
||||||
|
Metadata map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decision carries the user's response to an approval prompt. ViewOnly is
|
||||||
|
// only meaningful when Accept is true; it lets the host grant the
|
||||||
|
// connection but signal the requester that input control is withheld.
|
||||||
|
type Decision struct {
|
||||||
|
Accept bool
|
||||||
|
ViewOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broker holds in-flight approval requests keyed by request ID.
|
||||||
|
type Broker struct {
|
||||||
|
pub EventPublisher
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
pending map[string]chan Decision
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a broker that publishes prompts via pub.
|
||||||
|
func New(pub EventPublisher) *Broker {
|
||||||
|
return &Broker{
|
||||||
|
pub: pub,
|
||||||
|
pending: make(map[string]chan Decision),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request emits a SystemEvent for p and blocks until the UI calls Respond,
|
||||||
|
// ctx is cancelled, or DefaultTimeout elapses. Returns a Decision when
|
||||||
|
// the user replied; ErrDenied / ErrTimeout / ErrNoSubscriber / ctx.Err
|
||||||
|
// otherwise. Callers must treat any non-nil error as a deny.
|
||||||
|
func (b *Broker) Request(ctx context.Context, p Prompt) (Decision, error) {
|
||||||
|
var zero Decision
|
||||||
|
if b == nil || b.pub == nil {
|
||||||
|
return zero, fmt.Errorf("approval broker not configured")
|
||||||
|
}
|
||||||
|
if !b.pub.HasEventSubscribers() {
|
||||||
|
return zero, ErrNoSubscriber
|
||||||
|
}
|
||||||
|
|
||||||
|
id := uuid.NewString()
|
||||||
|
resp := make(chan Decision, 1)
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
b.pending[id] = resp
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
defer b.dropPending(id)
|
||||||
|
|
||||||
|
timeout := timeoutValue()
|
||||||
|
expiresAt := time.Now().Add(timeout)
|
||||||
|
meta := make(map[string]string, len(p.Metadata)+3)
|
||||||
|
for k, v := range p.Metadata {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
meta[MetaRequestID] = id
|
||||||
|
meta[MetaKind] = p.Kind
|
||||||
|
meta[MetaExpiresAt] = expiresAt.UTC().Format(time.RFC3339)
|
||||||
|
|
||||||
|
subject := p.Subject
|
||||||
|
if subject == "" {
|
||||||
|
subject = fmt.Sprintf("%s connection requires approval", p.Kind)
|
||||||
|
}
|
||||||
|
b.pub.PublishEvent(proto.SystemEvent_INFO, proto.SystemEvent_APPROVAL, subject, subject, meta)
|
||||||
|
log.Debugf("approval request %s (%s) emitted: %s", id, p.Kind, subject)
|
||||||
|
|
||||||
|
timer := time.NewTimer(timeout)
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case d := <-resp:
|
||||||
|
if !d.Accept {
|
||||||
|
return zero, ErrDenied
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
case <-timer.C:
|
||||||
|
return zero, ErrTimeout
|
||||||
|
case <-ctx.Done():
|
||||||
|
return zero, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond delivers the user's decision for id. Returns true when a pending
|
||||||
|
// request matched and was woken, false when id was unknown or already done.
|
||||||
|
func (b *Broker) Respond(id string, d Decision) bool {
|
||||||
|
if b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b.mu.Lock()
|
||||||
|
ch, ok := b.pending[id]
|
||||||
|
if ok {
|
||||||
|
delete(b.pending, id)
|
||||||
|
}
|
||||||
|
b.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case ch <- d:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Broker) dropPending(id string) {
|
||||||
|
b.mu.Lock()
|
||||||
|
delete(b.pending, id)
|
||||||
|
b.mu.Unlock()
|
||||||
|
}
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
package approval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fakePublisher records published events and reports whether subscribers
|
||||||
|
// are connected. The subscribers flag is the security-critical signal:
|
||||||
|
// when false the broker must refuse to emit and the gate must fail closed.
|
||||||
|
type fakePublisher struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
subscribers bool
|
||||||
|
events []*proto.SystemEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePublisher) PublishEvent(
|
||||||
|
severity proto.SystemEvent_Severity,
|
||||||
|
category proto.SystemEvent_Category,
|
||||||
|
msg string,
|
||||||
|
userMsg string,
|
||||||
|
metadata map[string]string,
|
||||||
|
) {
|
||||||
|
p.mu.Lock()
|
||||||
|
p.events = append(p.events, &proto.SystemEvent{
|
||||||
|
Severity: severity,
|
||||||
|
Category: category,
|
||||||
|
Message: msg,
|
||||||
|
UserMessage: userMsg,
|
||||||
|
Metadata: metadata,
|
||||||
|
})
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePublisher) HasEventSubscribers() bool {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
return p.subscribers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePublisher) lastEvent(t *testing.T) *proto.SystemEvent {
|
||||||
|
t.Helper()
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
require.NotEmpty(t, p.events, "publisher saw no events")
|
||||||
|
return p.events[len(p.events)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePublisher) eventCount() int {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
return len(p.events)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRequestNoSubscriberFailsClosed is the core fail-closed invariant:
|
||||||
|
// when the UI is not subscribed, the broker must refuse without emitting
|
||||||
|
// an event or arming a waiter. A regression here is a silent bypass.
|
||||||
|
func TestRequestNoSubscriberFailsClosed(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: false}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
_, err := b.Request(context.Background(), Prompt{Kind: KindVNC, Subject: "test"})
|
||||||
|
assert.ErrorIs(t, err, ErrNoSubscriber)
|
||||||
|
assert.Equal(t, 0, pub.eventCount(), "no event must be emitted when fail-closed")
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
pending := len(b.pending)
|
||||||
|
b.mu.Unlock()
|
||||||
|
assert.Equal(t, 0, pending, "no waiter must be registered on fail-closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRequestTimeoutDenies verifies that a request without a UI response
|
||||||
|
// returns ErrTimeout (deny) rather than nil (silent accept). Uses a short
|
||||||
|
// per-test broker timeout via Respond after the fact to keep the test fast.
|
||||||
|
func TestRequestTimeoutDenies(t *testing.T) {
|
||||||
|
// Replace DefaultTimeout for the lifetime of this test.
|
||||||
|
orig := DefaultTimeout
|
||||||
|
defaultTimeout(t, 60*time.Millisecond)
|
||||||
|
defer defaultTimeout(t, orig)
|
||||||
|
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
_, err := b.Request(context.Background(), Prompt{Kind: KindVNC, Subject: "test"})
|
||||||
|
assert.ErrorIs(t, err, ErrTimeout, "missing user response must yield ErrTimeout, not nil")
|
||||||
|
assert.GreaterOrEqual(t, time.Since(start), 50*time.Millisecond, "timeout fired prematurely")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRequestDenied returns ErrDenied when the UI responds with false.
|
||||||
|
func TestRequestDenied(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
var requestID string
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- requestErr(b, context.Background(), Prompt{Kind: KindVNC, Subject: "test"})
|
||||||
|
}()
|
||||||
|
|
||||||
|
requestID = waitForRequestID(t, pub)
|
||||||
|
require.True(t, b.Respond(requestID, Decision{Accept: false}))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
assert.ErrorIs(t, err, ErrDenied)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("Request did not return after Respond(false)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRequestAccepted is the happy path. Failure here doesn't bypass the
|
||||||
|
// gate but breaks the feature.
|
||||||
|
func TestRequestAccepted(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- requestErr(b, context.Background(), Prompt{Kind: KindVNC, Subject: "test"})
|
||||||
|
}()
|
||||||
|
|
||||||
|
id := waitForRequestID(t, pub)
|
||||||
|
require.True(t, b.Respond(id, Decision{Accept: true}))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
assert.NoError(t, err)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("Request did not return after Respond(true)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRequestCtxCancelDenies verifies that an upstream cancel (e.g. the
|
||||||
|
// engine shutting down mid-prompt) returns the cancel error rather than
|
||||||
|
// nil. A nil here would be a silent bypass on shutdown races.
|
||||||
|
func TestRequestCtxCancelDenies(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- requestErr(b, ctx, Prompt{Kind: KindVNC, Subject: "test"})
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait until the prompt is in flight so cancel races a live waiter.
|
||||||
|
_ = waitForRequestID(t, pub)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
assert.ErrorIs(t, err, context.Canceled)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("Request did not return after ctx cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRespondUnknownIsNoop ensures a stray RespondApproval RPC cannot
|
||||||
|
// affect or accidentally accept any in-flight request whose id it doesn't
|
||||||
|
// match. Also confirms it doesn't panic.
|
||||||
|
func TestRespondUnknownIsNoop(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
// No in-flight prompts: Respond returns false.
|
||||||
|
assert.False(t, b.Respond("does-not-exist", Decision{Accept: true}))
|
||||||
|
|
||||||
|
// With an in-flight prompt, a wrong id still returns false and the
|
||||||
|
// prompt remains armed (eventually timing out as a deny).
|
||||||
|
defaultTimeout(t, 60*time.Millisecond)
|
||||||
|
defer defaultTimeout(t, DefaultTimeout)
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- requestErr(b, context.Background(), Prompt{Kind: KindVNC})
|
||||||
|
}()
|
||||||
|
realID := waitForRequestID(t, pub)
|
||||||
|
assert.False(t, b.Respond("totally-bogus", Decision{Accept: true}), "unknown id must not match")
|
||||||
|
assert.NotEqual(t, "totally-bogus", realID)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
assert.ErrorIs(t, err, ErrTimeout, "armed prompt must still time out, not accept")
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("prompt did not resolve")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRespondAfterTimeoutNoop confirms a late accept response can't
|
||||||
|
// retroactively flip a denied (timed-out) request. The dropPending defer
|
||||||
|
// in Request must have removed the entry by the time Respond races in.
|
||||||
|
func TestRespondAfterTimeoutNoop(t *testing.T) {
|
||||||
|
defaultTimeout(t, 30*time.Millisecond)
|
||||||
|
defer defaultTimeout(t, DefaultTimeout)
|
||||||
|
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- requestErr(b, context.Background(), Prompt{Kind: KindVNC})
|
||||||
|
}()
|
||||||
|
id := waitForRequestID(t, pub)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
require.ErrorIs(t, err, ErrTimeout)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("prompt did not time out")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, b.Respond(id, Decision{Accept: true}), "late respond must be no-op")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRespondDoubleNoop ensures a duplicate ack from the UI doesn't leak
|
||||||
|
// past the matched waiter or panic on a closed/full channel.
|
||||||
|
func TestRespondDoubleNoop(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- requestErr(b, context.Background(), Prompt{Kind: KindVNC})
|
||||||
|
}()
|
||||||
|
id := waitForRequestID(t, pub)
|
||||||
|
require.True(t, b.Respond(id, Decision{Accept: true}))
|
||||||
|
assert.False(t, b.Respond(id, Decision{Accept: false}), "second response must be no-op")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
assert.NoError(t, err)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("prompt did not resolve")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNilBrokerRequestErrors guards the engine pre-init path where the
|
||||||
|
// broker may not yet exist (or its publisher is nil): Request must
|
||||||
|
// error, never silently accept.
|
||||||
|
func TestNilBrokerRequestErrors(t *testing.T) {
|
||||||
|
var b *Broker
|
||||||
|
_, err := b.Request(context.Background(), Prompt{Kind: KindVNC})
|
||||||
|
assert.Error(t, err, "nil broker must error, never silently accept")
|
||||||
|
|
||||||
|
b2 := New(nil)
|
||||||
|
_, err = b2.Request(context.Background(), Prompt{Kind: KindVNC})
|
||||||
|
assert.Error(t, err, "broker with nil publisher must error, never silently accept")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPromptMetadataInjected confirms the broker stamps request_id, kind,
|
||||||
|
// and expires_at on the emitted event. The UI relies on these keys; if
|
||||||
|
// they are dropped, the user cannot route the prompt and the response
|
||||||
|
// path breaks (which fails closed via timeout).
|
||||||
|
func TestPromptMetadataInjected(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
done <- requestErr(b, context.Background(), Prompt{
|
||||||
|
Kind: KindVNC,
|
||||||
|
Subject: "VNC connection from peerA",
|
||||||
|
Metadata: map[string]string{"peer_name": "peerA"},
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
id := waitForRequestID(t, pub)
|
||||||
|
ev := pub.lastEvent(t)
|
||||||
|
|
||||||
|
assert.Equal(t, proto.SystemEvent_APPROVAL, ev.Category)
|
||||||
|
assert.Equal(t, KindVNC, ev.Metadata[MetaKind])
|
||||||
|
assert.Equal(t, id, ev.Metadata[MetaRequestID])
|
||||||
|
assert.NotEmpty(t, ev.Metadata[MetaExpiresAt])
|
||||||
|
assert.Equal(t, "peerA", ev.Metadata["peer_name"], "caller metadata must pass through")
|
||||||
|
|
||||||
|
require.True(t, b.Respond(id, Decision{Accept: true}))
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentRequests verifies that two concurrent prompts are tracked
|
||||||
|
// independently. A bug that aliases ids would let one Respond unblock
|
||||||
|
// the wrong waiter (a silent accept across prompts).
|
||||||
|
func TestConcurrentRequests(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
const n = 20
|
||||||
|
results := make(chan error, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
go func() {
|
||||||
|
results <- requestErr(b, context.Background(), Prompt{Kind: KindVNC})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := waitForNRequestIDs(t, pub, n)
|
||||||
|
require.Len(t, ids, n)
|
||||||
|
|
||||||
|
// Deny exactly half, accept the rest. Track outcome per id so we can
|
||||||
|
// match each Request's return value against the response we sent.
|
||||||
|
denySet := make(map[string]bool, n)
|
||||||
|
for i, id := range ids {
|
||||||
|
deny := i%2 == 0
|
||||||
|
denySet[id] = deny
|
||||||
|
require.True(t, b.Respond(id, Decision{Accept: !deny}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all returns and check no nil errors slipped past a deny.
|
||||||
|
var accepted, denied atomic.Int32
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
select {
|
||||||
|
case err := <-results:
|
||||||
|
if err == nil {
|
||||||
|
accepted.Add(1)
|
||||||
|
} else {
|
||||||
|
assert.ErrorIs(t, err, ErrDenied)
|
||||||
|
denied.Add(1)
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatalf("only got %d/%d responses", i, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Equal(t, int32(n/2), denied.Load())
|
||||||
|
assert.Equal(t, int32(n/2), accepted.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForRequestID blocks until the publisher sees its next event and
|
||||||
|
// returns the request_id stamped on it.
|
||||||
|
func waitForRequestID(t *testing.T, pub *fakePublisher) string {
|
||||||
|
t.Helper()
|
||||||
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
pub.mu.Lock()
|
||||||
|
count := len(pub.events)
|
||||||
|
var id string
|
||||||
|
if count > 0 {
|
||||||
|
id = pub.events[count-1].Metadata[MetaRequestID]
|
||||||
|
}
|
||||||
|
pub.mu.Unlock()
|
||||||
|
if id != "" {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
}
|
||||||
|
t.Fatal("timeout waiting for emitted event")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForNRequestIDs(t *testing.T, pub *fakePublisher, n int) []string {
|
||||||
|
t.Helper()
|
||||||
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
pub.mu.Lock()
|
||||||
|
count := len(pub.events)
|
||||||
|
pub.mu.Unlock()
|
||||||
|
if count >= n {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
}
|
||||||
|
pub.mu.Lock()
|
||||||
|
defer pub.mu.Unlock()
|
||||||
|
out := make([]string, 0, len(pub.events))
|
||||||
|
seen := make(map[string]struct{}, len(pub.events))
|
||||||
|
for _, ev := range pub.events {
|
||||||
|
id := ev.Metadata[MetaRequestID]
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, dup := seen[id]; dup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
out = append(out, id)
|
||||||
|
}
|
||||||
|
if len(out) < n {
|
||||||
|
t.Fatalf("only got %d/%d request ids", len(out), n)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultTimeout swaps the broker's per-request wall-clock window so the
|
||||||
|
// timeout tests run quickly. Restores the prior value on the next call.
|
||||||
|
func defaultTimeout(t *testing.T, d time.Duration) {
|
||||||
|
t.Helper()
|
||||||
|
if d <= 0 {
|
||||||
|
t.Fatal("defaultTimeout must be > 0")
|
||||||
|
}
|
||||||
|
timeoutValue = func() time.Duration { return d }
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestErr wraps Broker.Request to drop the Decision when tests only
|
||||||
|
// care about the error path. Keeps the goroutine bodies tight.
|
||||||
|
func requestErr(b *Broker, ctx context.Context, p Prompt) error {
|
||||||
|
_, err := b.Request(ctx, p)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRequestViewOnly checks the view-only outcome flows through Request's
|
||||||
|
// Decision return without being silently swallowed.
|
||||||
|
func TestRequestViewOnly(t *testing.T) {
|
||||||
|
pub := &fakePublisher{subscribers: true}
|
||||||
|
b := New(pub)
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
d Decision
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
done := make(chan result, 1)
|
||||||
|
go func() {
|
||||||
|
d, err := b.Request(context.Background(), Prompt{Kind: KindVNC})
|
||||||
|
done <- result{d, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
id := waitForRequestID(t, pub)
|
||||||
|
require.True(t, b.Respond(id, Decision{Accept: true, ViewOnly: true}))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case r := <-done:
|
||||||
|
assert.NoError(t, r.err)
|
||||||
|
assert.True(t, r.d.Accept)
|
||||||
|
assert.True(t, r.d.ViewOnly, "ViewOnly must survive the round-trip")
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("view-only request did not resolve")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -563,6 +563,7 @@ func createEngineConfig(key wgtypes.Key, config *profilemanager.Config, peerConf
|
|||||||
RosenpassPermissive: config.RosenpassPermissive,
|
RosenpassPermissive: config.RosenpassPermissive,
|
||||||
ServerSSHAllowed: util.ReturnBoolWithDefaultTrue(config.ServerSSHAllowed),
|
ServerSSHAllowed: util.ReturnBoolWithDefaultTrue(config.ServerSSHAllowed),
|
||||||
ServerVNCAllowed: config.ServerVNCAllowed != nil && *config.ServerVNCAllowed,
|
ServerVNCAllowed: config.ServerVNCAllowed != nil && *config.ServerVNCAllowed,
|
||||||
|
DisableVNCApproval: config.DisableVNCApproval,
|
||||||
EnableSSHRoot: config.EnableSSHRoot,
|
EnableSSHRoot: config.EnableSSHRoot,
|
||||||
EnableSSHSFTP: config.EnableSSHSFTP,
|
EnableSSHSFTP: config.EnableSSHSFTP,
|
||||||
EnableSSHLocalPortForwarding: config.EnableSSHLocalPortForwarding,
|
EnableSSHLocalPortForwarding: config.EnableSSHLocalPortForwarding,
|
||||||
|
|||||||
@@ -639,6 +639,9 @@ func (g *BundleGenerator) addCommonConfigFields(configContent *strings.Builder)
|
|||||||
if g.internalConfig.ServerVNCAllowed != nil {
|
if g.internalConfig.ServerVNCAllowed != nil {
|
||||||
configContent.WriteString(fmt.Sprintf("ServerVNCAllowed: %v\n", *g.internalConfig.ServerVNCAllowed))
|
configContent.WriteString(fmt.Sprintf("ServerVNCAllowed: %v\n", *g.internalConfig.ServerVNCAllowed))
|
||||||
}
|
}
|
||||||
|
if g.internalConfig.DisableVNCApproval != nil {
|
||||||
|
configContent.WriteString(fmt.Sprintf("DisableVNCApproval: %v\n", *g.internalConfig.DisableVNCApproval))
|
||||||
|
}
|
||||||
|
|
||||||
configContent.WriteString(fmt.Sprintf("DisableClientRoutes: %v\n", g.internalConfig.DisableClientRoutes))
|
configContent.WriteString(fmt.Sprintf("DisableClientRoutes: %v\n", g.internalConfig.DisableClientRoutes))
|
||||||
configContent.WriteString(fmt.Sprintf("DisableServerRoutes: %v\n", g.internalConfig.DisableServerRoutes))
|
configContent.WriteString(fmt.Sprintf("DisableServerRoutes: %v\n", g.internalConfig.DisableServerRoutes))
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface/udpmux"
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
"github.com/netbirdio/netbird/client/internal/acl"
|
"github.com/netbirdio/netbird/client/internal/acl"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/approval"
|
||||||
"github.com/netbirdio/netbird/client/internal/debug"
|
"github.com/netbirdio/netbird/client/internal/debug"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
||||||
@@ -124,6 +125,7 @@ type EngineConfig struct {
|
|||||||
|
|
||||||
ServerSSHAllowed bool
|
ServerSSHAllowed bool
|
||||||
ServerVNCAllowed bool
|
ServerVNCAllowed bool
|
||||||
|
DisableVNCApproval *bool
|
||||||
EnableSSHRoot *bool
|
EnableSSHRoot *bool
|
||||||
EnableSSHSFTP *bool
|
EnableSSHSFTP *bool
|
||||||
EnableSSHLocalPortForwarding *bool
|
EnableSSHLocalPortForwarding *bool
|
||||||
@@ -205,8 +207,9 @@ type Engine struct {
|
|||||||
|
|
||||||
networkMonitor *networkmonitor.NetworkMonitor
|
networkMonitor *networkmonitor.NetworkMonitor
|
||||||
|
|
||||||
sshServer sshServer
|
sshServer sshServer
|
||||||
vncSrv vncServer
|
vncSrv vncServer
|
||||||
|
approvalBroker *approval.Broker
|
||||||
|
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
|
|
||||||
@@ -287,6 +290,7 @@ func NewEngine(
|
|||||||
TURNs: []*stun.URI{},
|
TURNs: []*stun.URI{},
|
||||||
networkSerial: 0,
|
networkSerial: 0,
|
||||||
statusRecorder: services.StatusRecorder,
|
statusRecorder: services.StatusRecorder,
|
||||||
|
approvalBroker: approval.New(services.StatusRecorder),
|
||||||
stateManager: services.StateManager,
|
stateManager: services.StateManager,
|
||||||
portForwardManager: portforward.NewManager(),
|
portForwardManager: portforward.NewManager(),
|
||||||
checks: services.Checks,
|
checks: services.Checks,
|
||||||
@@ -2608,3 +2612,16 @@ func decodeRelayIP(b []byte) netip.Addr {
|
|||||||
}
|
}
|
||||||
return ip.Unmap()
|
return ip.Unmap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RespondApproval relays the user's decision for a pending approval to
|
||||||
|
// the broker. viewOnly is honoured only when accept is true. Returns
|
||||||
|
// true when the request_id matched a live prompt.
|
||||||
|
func (e *Engine) RespondApproval(requestID string, accept, viewOnly bool) bool {
|
||||||
|
if e == nil || e.approvalBroker == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return e.approvalBroker.Respond(requestID, approval.Decision{
|
||||||
|
Accept: accept,
|
||||||
|
ViewOnly: accept && viewOnly,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
|
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/iface/netstack"
|
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
||||||
sshauth "github.com/netbirdio/netbird/client/ssh/auth"
|
sshauth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
sshconfig "github.com/netbirdio/netbird/client/ssh/config"
|
sshconfig "github.com/netbirdio/netbird/client/ssh/config"
|
||||||
sshserver "github.com/netbirdio/netbird/client/ssh/server"
|
sshserver "github.com/netbirdio/netbird/client/ssh/server"
|
||||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
|
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/approval"
|
||||||
"github.com/netbirdio/netbird/client/internal/metrics"
|
"github.com/netbirdio/netbird/client/internal/metrics"
|
||||||
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
|
||||||
sshauth "github.com/netbirdio/netbird/client/ssh/auth"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
sshauth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
vncserver "github.com/netbirdio/netbird/client/vnc/server"
|
vncserver "github.com/netbirdio/netbird/client/vnc/server"
|
||||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
sshuserhash "github.com/netbirdio/netbird/shared/sshauth"
|
sshuserhash "github.com/netbirdio/netbird/shared/sshauth"
|
||||||
@@ -118,6 +120,7 @@ func (e *Engine) startVNCServer() error {
|
|||||||
if serviceMode {
|
if serviceMode {
|
||||||
log.Info("VNC: running as system service, enabling service mode (per-session agent proxy)")
|
log.Info("VNC: running as system service, enabling service mode (per-session agent proxy)")
|
||||||
}
|
}
|
||||||
|
requireApproval := e.config.DisableVNCApproval == nil || !*e.config.DisableVNCApproval
|
||||||
srv := vncserver.New(vncserver.Config{
|
srv := vncserver.New(vncserver.Config{
|
||||||
Capturer: capturer,
|
Capturer: capturer,
|
||||||
Injector: injector,
|
Injector: injector,
|
||||||
@@ -125,6 +128,8 @@ func (e *Engine) startVNCServer() error {
|
|||||||
ServiceMode: serviceMode,
|
ServiceMode: serviceMode,
|
||||||
SessionRecorder: sessionRecorder,
|
SessionRecorder: sessionRecorder,
|
||||||
NetstackNet: e.wgInterface.GetNet(),
|
NetstackNet: e.wgInterface.GetNet(),
|
||||||
|
RequireApproval: requireApproval,
|
||||||
|
Approver: &vncApprover{broker: e.approvalBroker, statusRecorder: e.statusRecorder},
|
||||||
})
|
})
|
||||||
|
|
||||||
listenAddr := netip.AddrPortFrom(netbirdIP, vncInternalPort)
|
listenAddr := netip.AddrPortFrom(netbirdIP, vncInternalPort)
|
||||||
@@ -152,7 +157,6 @@ func (e *Engine) startVNCServer() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// updateVNCServerAuth updates VNC fine-grained access control from management.
|
// updateVNCServerAuth updates VNC fine-grained access control from management.
|
||||||
func (e *Engine) updateVNCServerAuth(vncAuth *mgmProto.VNCAuth) {
|
func (e *Engine) updateVNCServerAuth(vncAuth *mgmProto.VNCAuth) {
|
||||||
if vncAuth == nil || e.vncSrv == nil {
|
if vncAuth == nil || e.vncSrv == nil {
|
||||||
@@ -192,8 +196,9 @@ func (e *Engine) updateVNCServerAuth(vncAuth *mgmProto.VNCAuth) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sessionPubKeys = append(sessionPubKeys, sshauth.SessionPubKey{
|
sessionPubKeys = append(sessionPubKeys, sshauth.SessionPubKey{
|
||||||
PubKey: pub,
|
PubKey: pub,
|
||||||
UserIDHash: sshuserhash.UserIDHash(hash),
|
UserIDHash: sshuserhash.UserIDHash(hash),
|
||||||
|
DisplayName: e.GetDisplayName(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,3 +243,52 @@ func (e *Engine) stopVNCServer() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vncApprover adapts the generic approval.Broker for the VNC server.
|
||||||
|
type vncApprover struct {
|
||||||
|
broker *approval.Broker
|
||||||
|
statusRecorder *peer.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *vncApprover) Request(ctx context.Context, info vncserver.ApprovalInfo) (vncserver.ApprovalDecision, error) {
|
||||||
|
// Resolve the source overlay IP to a peer FQDN for the prompt label.
|
||||||
|
if info.PeerName == "" && info.SourceIP != "" && a.statusRecorder != nil {
|
||||||
|
if fqdn, ok := a.statusRecorder.PeerByIP(info.SourceIP); ok {
|
||||||
|
info.PeerName = fqdn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subject := fmt.Sprintf("VNC connection from %s", displayPeer(info))
|
||||||
|
meta := map[string]string{
|
||||||
|
"peer_name": info.PeerName,
|
||||||
|
"peer_pubkey": info.PeerPubKey,
|
||||||
|
"source_ip": info.SourceIP,
|
||||||
|
"mode": info.Mode,
|
||||||
|
"username": info.Username,
|
||||||
|
"initiator": info.Initiator,
|
||||||
|
}
|
||||||
|
d, err := a.broker.Request(ctx, approval.Prompt{
|
||||||
|
Kind: approval.KindVNC,
|
||||||
|
Subject: subject,
|
||||||
|
Metadata: meta,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return vncserver.ApprovalDecision{}, err
|
||||||
|
}
|
||||||
|
return vncserver.ApprovalDecision{ViewOnly: d.ViewOnly}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayPeer(info vncserver.ApprovalInfo) string {
|
||||||
|
if info.Initiator != "" {
|
||||||
|
return info.Initiator
|
||||||
|
}
|
||||||
|
if info.PeerName != "" {
|
||||||
|
return info.PeerName
|
||||||
|
}
|
||||||
|
if info.SourceIP != "" {
|
||||||
|
return info.SourceIP
|
||||||
|
}
|
||||||
|
if info.PeerPubKey != "" {
|
||||||
|
return info.PeerPubKey
|
||||||
|
}
|
||||||
|
return "unknown peer"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1191,6 +1191,15 @@ func (d *Status) SubscribeToEvents() *EventSubscription {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasEventSubscribers reports whether any client is currently subscribed
|
||||||
|
// to the daemon's SystemEvent stream. Used by the VNC approval broker to
|
||||||
|
// fail closed when no UI is connected to prompt the user.
|
||||||
|
func (d *Status) HasEventSubscribers() bool {
|
||||||
|
d.eventMux.Lock()
|
||||||
|
defer d.eventMux.Unlock()
|
||||||
|
return len(d.eventStreams) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// UnsubscribeFromEvents removes an event subscription
|
// UnsubscribeFromEvents removes an event subscription
|
||||||
func (d *Status) UnsubscribeFromEvents(sub *EventSubscription) {
|
func (d *Status) UnsubscribeFromEvents(sub *EventSubscription) {
|
||||||
if sub == nil {
|
if sub == nil {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ type ConfigInput struct {
|
|||||||
PreSharedKey *string
|
PreSharedKey *string
|
||||||
ServerSSHAllowed *bool
|
ServerSSHAllowed *bool
|
||||||
ServerVNCAllowed *bool
|
ServerVNCAllowed *bool
|
||||||
|
DisableVNCApproval *bool
|
||||||
EnableSSHRoot *bool
|
EnableSSHRoot *bool
|
||||||
EnableSSHSFTP *bool
|
EnableSSHSFTP *bool
|
||||||
EnableSSHLocalPortForwarding *bool
|
EnableSSHLocalPortForwarding *bool
|
||||||
@@ -118,6 +119,7 @@ type Config struct {
|
|||||||
RosenpassPermissive bool
|
RosenpassPermissive bool
|
||||||
ServerSSHAllowed *bool
|
ServerSSHAllowed *bool
|
||||||
ServerVNCAllowed *bool
|
ServerVNCAllowed *bool
|
||||||
|
DisableVNCApproval *bool
|
||||||
EnableSSHRoot *bool
|
EnableSSHRoot *bool
|
||||||
EnableSSHSFTP *bool
|
EnableSSHSFTP *bool
|
||||||
EnableSSHLocalPortForwarding *bool
|
EnableSSHLocalPortForwarding *bool
|
||||||
@@ -435,6 +437,18 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
|
|||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.DisableVNCApproval != nil {
|
||||||
|
if config.DisableVNCApproval == nil || *input.DisableVNCApproval != *config.DisableVNCApproval {
|
||||||
|
if *input.DisableVNCApproval {
|
||||||
|
log.Infof("disabling VNC connection approval prompt")
|
||||||
|
} else {
|
||||||
|
log.Infof("enabling VNC connection approval prompt")
|
||||||
|
}
|
||||||
|
config.DisableVNCApproval = input.DisableVNCApproval
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if input.EnableSSHRoot != nil && input.EnableSSHRoot != config.EnableSSHRoot {
|
if input.EnableSSHRoot != nil && input.EnableSSHRoot != config.EnableSSHRoot {
|
||||||
if *input.EnableSSHRoot {
|
if *input.EnableSSHRoot {
|
||||||
log.Infof("enabling SSH root login")
|
log.Infof("enabling SSH root login")
|
||||||
|
|||||||
+222
-75
@@ -203,6 +203,7 @@ const (
|
|||||||
SystemEvent_AUTHENTICATION SystemEvent_Category = 2
|
SystemEvent_AUTHENTICATION SystemEvent_Category = 2
|
||||||
SystemEvent_CONNECTIVITY SystemEvent_Category = 3
|
SystemEvent_CONNECTIVITY SystemEvent_Category = 3
|
||||||
SystemEvent_SYSTEM SystemEvent_Category = 4
|
SystemEvent_SYSTEM SystemEvent_Category = 4
|
||||||
|
SystemEvent_APPROVAL SystemEvent_Category = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for SystemEvent_Category.
|
// Enum value maps for SystemEvent_Category.
|
||||||
@@ -213,6 +214,7 @@ var (
|
|||||||
2: "AUTHENTICATION",
|
2: "AUTHENTICATION",
|
||||||
3: "CONNECTIVITY",
|
3: "CONNECTIVITY",
|
||||||
4: "SYSTEM",
|
4: "SYSTEM",
|
||||||
|
5: "APPROVAL",
|
||||||
}
|
}
|
||||||
SystemEvent_Category_value = map[string]int32{
|
SystemEvent_Category_value = map[string]int32{
|
||||||
"NETWORK": 0,
|
"NETWORK": 0,
|
||||||
@@ -220,6 +222,7 @@ var (
|
|||||||
"AUTHENTICATION": 2,
|
"AUTHENTICATION": 2,
|
||||||
"CONNECTIVITY": 3,
|
"CONNECTIVITY": 3,
|
||||||
"SYSTEM": 4,
|
"SYSTEM": 4,
|
||||||
|
"APPROVAL": 5,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -344,6 +347,7 @@ type LoginRequest struct {
|
|||||||
SshJWTCacheTTL *int32 `protobuf:"varint,39,opt,name=sshJWTCacheTTL,proto3,oneof" json:"sshJWTCacheTTL,omitempty"`
|
SshJWTCacheTTL *int32 `protobuf:"varint,39,opt,name=sshJWTCacheTTL,proto3,oneof" json:"sshJWTCacheTTL,omitempty"`
|
||||||
DisableIpv6 *bool `protobuf:"varint,40,opt,name=disable_ipv6,json=disableIpv6,proto3,oneof" json:"disable_ipv6,omitempty"`
|
DisableIpv6 *bool `protobuf:"varint,40,opt,name=disable_ipv6,json=disableIpv6,proto3,oneof" json:"disable_ipv6,omitempty"`
|
||||||
ServerVNCAllowed *bool `protobuf:"varint,41,opt,name=serverVNCAllowed,proto3,oneof" json:"serverVNCAllowed,omitempty"`
|
ServerVNCAllowed *bool `protobuf:"varint,41,opt,name=serverVNCAllowed,proto3,oneof" json:"serverVNCAllowed,omitempty"`
|
||||||
|
DisableVNCApproval *bool `protobuf:"varint,42,opt,name=disableVNCApproval,proto3,oneof" json:"disableVNCApproval,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -666,6 +670,13 @@ func (x *LoginRequest) GetServerVNCAllowed() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) GetDisableVNCApproval() bool {
|
||||||
|
if x != nil && x.DisableVNCApproval != nil {
|
||||||
|
return *x.DisableVNCApproval
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
NeedsSSOLogin bool `protobuf:"varint,1,opt,name=needsSSOLogin,proto3" json:"needsSSOLogin,omitempty"`
|
NeedsSSOLogin bool `protobuf:"varint,1,opt,name=needsSSOLogin,proto3" json:"needsSSOLogin,omitempty"`
|
||||||
@@ -1200,6 +1211,7 @@ type GetConfigResponse struct {
|
|||||||
SshJWTCacheTTL int32 `protobuf:"varint,26,opt,name=sshJWTCacheTTL,proto3" json:"sshJWTCacheTTL,omitempty"`
|
SshJWTCacheTTL int32 `protobuf:"varint,26,opt,name=sshJWTCacheTTL,proto3" json:"sshJWTCacheTTL,omitempty"`
|
||||||
DisableIpv6 bool `protobuf:"varint,27,opt,name=disable_ipv6,json=disableIpv6,proto3" json:"disable_ipv6,omitempty"`
|
DisableIpv6 bool `protobuf:"varint,27,opt,name=disable_ipv6,json=disableIpv6,proto3" json:"disable_ipv6,omitempty"`
|
||||||
ServerVNCAllowed bool `protobuf:"varint,28,opt,name=serverVNCAllowed,proto3" json:"serverVNCAllowed,omitempty"`
|
ServerVNCAllowed bool `protobuf:"varint,28,opt,name=serverVNCAllowed,proto3" json:"serverVNCAllowed,omitempty"`
|
||||||
|
DisableVNCApproval bool `protobuf:"varint,29,opt,name=disableVNCApproval,proto3" json:"disableVNCApproval,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -1430,6 +1442,13 @@ func (x *GetConfigResponse) GetServerVNCAllowed() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *GetConfigResponse) GetDisableVNCApproval() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisableVNCApproval
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// PeerState contains the latest state of a peer
|
// PeerState contains the latest state of a peer
|
||||||
type PeerState struct {
|
type PeerState struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
@@ -4191,6 +4210,7 @@ type SetConfigRequest struct {
|
|||||||
SshJWTCacheTTL *int32 `protobuf:"varint,34,opt,name=sshJWTCacheTTL,proto3,oneof" json:"sshJWTCacheTTL,omitempty"`
|
SshJWTCacheTTL *int32 `protobuf:"varint,34,opt,name=sshJWTCacheTTL,proto3,oneof" json:"sshJWTCacheTTL,omitempty"`
|
||||||
DisableIpv6 *bool `protobuf:"varint,35,opt,name=disable_ipv6,json=disableIpv6,proto3,oneof" json:"disable_ipv6,omitempty"`
|
DisableIpv6 *bool `protobuf:"varint,35,opt,name=disable_ipv6,json=disableIpv6,proto3,oneof" json:"disable_ipv6,omitempty"`
|
||||||
ServerVNCAllowed *bool `protobuf:"varint,36,opt,name=serverVNCAllowed,proto3,oneof" json:"serverVNCAllowed,omitempty"`
|
ServerVNCAllowed *bool `protobuf:"varint,36,opt,name=serverVNCAllowed,proto3,oneof" json:"serverVNCAllowed,omitempty"`
|
||||||
|
DisableVNCApproval *bool `protobuf:"varint,37,opt,name=disableVNCApproval,proto3,oneof" json:"disableVNCApproval,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -4477,6 +4497,13 @@ func (x *SetConfigRequest) GetServerVNCAllowed() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SetConfigRequest) GetDisableVNCApproval() bool {
|
||||||
|
if x != nil && x.DisableVNCApproval != nil {
|
||||||
|
return *x.DisableVNCApproval
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type SetConfigResponse struct {
|
type SetConfigResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
@@ -6325,6 +6352,109 @@ func (*StopBundleCaptureResponse) Descriptor() ([]byte, []int) {
|
|||||||
return file_daemon_proto_rawDescGZIP(), []int{95}
|
return file_daemon_proto_rawDescGZIP(), []int{95}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RespondApprovalRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
// request_id matches the SystemEvent metadata key emitted by the daemon
|
||||||
|
// when a subsystem awaits user approval for an inbound connection.
|
||||||
|
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
|
||||||
|
// accept is true if the user approved the request, false if they
|
||||||
|
// denied it. A missing or unknown request_id is treated as a no-op.
|
||||||
|
Accept bool `protobuf:"varint,2,opt,name=accept,proto3" json:"accept,omitempty"`
|
||||||
|
// view_only signals that the user granted the connection but withheld
|
||||||
|
// input control. Only meaningful when accept is true; ignored when
|
||||||
|
// accept is false.
|
||||||
|
ViewOnly bool `protobuf:"varint,3,opt,name=view_only,json=viewOnly,proto3" json:"view_only,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RespondApprovalRequest) Reset() {
|
||||||
|
*x = RespondApprovalRequest{}
|
||||||
|
mi := &file_daemon_proto_msgTypes[96]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RespondApprovalRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RespondApprovalRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RespondApprovalRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[96]
|
||||||
|
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 RespondApprovalRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RespondApprovalRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{96}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RespondApprovalRequest) GetRequestId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RequestId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RespondApprovalRequest) GetAccept() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Accept
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RespondApprovalRequest) GetViewOnly() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.ViewOnly
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type RespondApprovalResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RespondApprovalResponse) Reset() {
|
||||||
|
*x = RespondApprovalResponse{}
|
||||||
|
mi := &file_daemon_proto_msgTypes[97]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RespondApprovalResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RespondApprovalResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RespondApprovalResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[97]
|
||||||
|
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 RespondApprovalResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RespondApprovalResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{97}
|
||||||
|
}
|
||||||
|
|
||||||
type PortInfo_Range struct {
|
type PortInfo_Range struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"`
|
Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"`
|
||||||
@@ -6335,7 +6465,7 @@ type PortInfo_Range struct {
|
|||||||
|
|
||||||
func (x *PortInfo_Range) Reset() {
|
func (x *PortInfo_Range) Reset() {
|
||||||
*x = PortInfo_Range{}
|
*x = PortInfo_Range{}
|
||||||
mi := &file_daemon_proto_msgTypes[97]
|
mi := &file_daemon_proto_msgTypes[99]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -6347,7 +6477,7 @@ func (x *PortInfo_Range) String() string {
|
|||||||
func (*PortInfo_Range) ProtoMessage() {}
|
func (*PortInfo_Range) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
|
func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_daemon_proto_msgTypes[97]
|
mi := &file_daemon_proto_msgTypes[99]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -6382,7 +6512,7 @@ var File_daemon_proto protoreflect.FileDescriptor
|
|||||||
const file_daemon_proto_rawDesc = "" +
|
const file_daemon_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\fdaemon.proto\x12\x06daemon\x1a google/protobuf/descriptor.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\"\x0e\n" +
|
"\fdaemon.proto\x12\x06daemon\x1a google/protobuf/descriptor.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\"\x0e\n" +
|
||||||
"\fEmptyRequest\"\xb5\x13\n" +
|
"\fEmptyRequest\"\x81\x14\n" +
|
||||||
"\fLoginRequest\x12\x1a\n" +
|
"\fLoginRequest\x12\x1a\n" +
|
||||||
"\bsetupKey\x18\x01 \x01(\tR\bsetupKey\x12&\n" +
|
"\bsetupKey\x18\x01 \x01(\tR\bsetupKey\x12&\n" +
|
||||||
"\fpreSharedKey\x18\x02 \x01(\tB\x02\x18\x01R\fpreSharedKey\x12$\n" +
|
"\fpreSharedKey\x18\x02 \x01(\tB\x02\x18\x01R\fpreSharedKey\x12$\n" +
|
||||||
@@ -6428,7 +6558,8 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x0edisableSSHAuth\x18& \x01(\bH\x19R\x0edisableSSHAuth\x88\x01\x01\x12+\n" +
|
"\x0edisableSSHAuth\x18& \x01(\bH\x19R\x0edisableSSHAuth\x88\x01\x01\x12+\n" +
|
||||||
"\x0esshJWTCacheTTL\x18' \x01(\x05H\x1aR\x0esshJWTCacheTTL\x88\x01\x01\x12&\n" +
|
"\x0esshJWTCacheTTL\x18' \x01(\x05H\x1aR\x0esshJWTCacheTTL\x88\x01\x01\x12&\n" +
|
||||||
"\fdisable_ipv6\x18( \x01(\bH\x1bR\vdisableIpv6\x88\x01\x01\x12/\n" +
|
"\fdisable_ipv6\x18( \x01(\bH\x1bR\vdisableIpv6\x88\x01\x01\x12/\n" +
|
||||||
"\x10serverVNCAllowed\x18) \x01(\bH\x1cR\x10serverVNCAllowed\x88\x01\x01B\x13\n" +
|
"\x10serverVNCAllowed\x18) \x01(\bH\x1cR\x10serverVNCAllowed\x88\x01\x01\x123\n" +
|
||||||
|
"\x12disableVNCApproval\x18* \x01(\bH\x1dR\x12disableVNCApproval\x88\x01\x01B\x13\n" +
|
||||||
"\x11_rosenpassEnabledB\x10\n" +
|
"\x11_rosenpassEnabledB\x10\n" +
|
||||||
"\x0e_interfaceNameB\x10\n" +
|
"\x0e_interfaceNameB\x10\n" +
|
||||||
"\x0e_wireguardPortB\x17\n" +
|
"\x0e_wireguardPortB\x17\n" +
|
||||||
@@ -6457,7 +6588,8 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x0f_disableSSHAuthB\x11\n" +
|
"\x0f_disableSSHAuthB\x11\n" +
|
||||||
"\x0f_sshJWTCacheTTLB\x0f\n" +
|
"\x0f_sshJWTCacheTTLB\x0f\n" +
|
||||||
"\r_disable_ipv6B\x13\n" +
|
"\r_disable_ipv6B\x13\n" +
|
||||||
"\x11_serverVNCAllowed\"\xb5\x01\n" +
|
"\x11_serverVNCAllowedB\x15\n" +
|
||||||
|
"\x13_disableVNCApproval\"\xb5\x01\n" +
|
||||||
"\rLoginResponse\x12$\n" +
|
"\rLoginResponse\x12$\n" +
|
||||||
"\rneedsSSOLogin\x18\x01 \x01(\bR\rneedsSSOLogin\x12\x1a\n" +
|
"\rneedsSSOLogin\x18\x01 \x01(\bR\rneedsSSOLogin\x12\x1a\n" +
|
||||||
"\buserCode\x18\x02 \x01(\tR\buserCode\x12(\n" +
|
"\buserCode\x18\x02 \x01(\tR\buserCode\x12(\n" +
|
||||||
@@ -6490,7 +6622,7 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\fDownResponse\"P\n" +
|
"\fDownResponse\"P\n" +
|
||||||
"\x10GetConfigRequest\x12 \n" +
|
"\x10GetConfigRequest\x12 \n" +
|
||||||
"\vprofileName\x18\x01 \x01(\tR\vprofileName\x12\x1a\n" +
|
"\vprofileName\x18\x01 \x01(\tR\vprofileName\x12\x1a\n" +
|
||||||
"\busername\x18\x02 \x01(\tR\busername\"\xaa\t\n" +
|
"\busername\x18\x02 \x01(\tR\busername\"\xda\t\n" +
|
||||||
"\x11GetConfigResponse\x12$\n" +
|
"\x11GetConfigResponse\x12$\n" +
|
||||||
"\rmanagementUrl\x18\x01 \x01(\tR\rmanagementUrl\x12\x1e\n" +
|
"\rmanagementUrl\x18\x01 \x01(\tR\rmanagementUrl\x12\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
@@ -6523,7 +6655,8 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x0edisableSSHAuth\x18\x19 \x01(\bR\x0edisableSSHAuth\x12&\n" +
|
"\x0edisableSSHAuth\x18\x19 \x01(\bR\x0edisableSSHAuth\x12&\n" +
|
||||||
"\x0esshJWTCacheTTL\x18\x1a \x01(\x05R\x0esshJWTCacheTTL\x12!\n" +
|
"\x0esshJWTCacheTTL\x18\x1a \x01(\x05R\x0esshJWTCacheTTL\x12!\n" +
|
||||||
"\fdisable_ipv6\x18\x1b \x01(\bR\vdisableIpv6\x12*\n" +
|
"\fdisable_ipv6\x18\x1b \x01(\bR\vdisableIpv6\x12*\n" +
|
||||||
"\x10serverVNCAllowed\x18\x1c \x01(\bR\x10serverVNCAllowed\"\x92\x06\n" +
|
"\x10serverVNCAllowed\x18\x1c \x01(\bR\x10serverVNCAllowed\x12.\n" +
|
||||||
|
"\x12disableVNCApproval\x18\x1d \x01(\bR\x12disableVNCApproval\"\x92\x06\n" +
|
||||||
"\tPeerState\x12\x0e\n" +
|
"\tPeerState\x12\x0e\n" +
|
||||||
"\x02IP\x18\x01 \x01(\tR\x02IP\x12\x16\n" +
|
"\x02IP\x18\x01 \x01(\tR\x02IP\x12\x16\n" +
|
||||||
"\x06pubKey\x18\x02 \x01(\tR\x06pubKey\x12\x1e\n" +
|
"\x06pubKey\x18\x02 \x01(\tR\x06pubKey\x12\x1e\n" +
|
||||||
@@ -6715,7 +6848,7 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x13TracePacketResponse\x12*\n" +
|
"\x13TracePacketResponse\x12*\n" +
|
||||||
"\x06stages\x18\x01 \x03(\v2\x12.daemon.TraceStageR\x06stages\x12+\n" +
|
"\x06stages\x18\x01 \x03(\v2\x12.daemon.TraceStageR\x06stages\x12+\n" +
|
||||||
"\x11final_disposition\x18\x02 \x01(\bR\x10finalDisposition\"\x12\n" +
|
"\x11final_disposition\x18\x02 \x01(\bR\x10finalDisposition\"\x12\n" +
|
||||||
"\x10SubscribeRequest\"\x93\x04\n" +
|
"\x10SubscribeRequest\"\xa1\x04\n" +
|
||||||
"\vSystemEvent\x12\x0e\n" +
|
"\vSystemEvent\x12\x0e\n" +
|
||||||
"\x02id\x18\x01 \x01(\tR\x02id\x128\n" +
|
"\x02id\x18\x01 \x01(\tR\x02id\x128\n" +
|
||||||
"\bseverity\x18\x02 \x01(\x0e2\x1c.daemon.SystemEvent.SeverityR\bseverity\x128\n" +
|
"\bseverity\x18\x02 \x01(\x0e2\x1c.daemon.SystemEvent.SeverityR\bseverity\x128\n" +
|
||||||
@@ -6731,14 +6864,15 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x04INFO\x10\x00\x12\v\n" +
|
"\x04INFO\x10\x00\x12\v\n" +
|
||||||
"\aWARNING\x10\x01\x12\t\n" +
|
"\aWARNING\x10\x01\x12\t\n" +
|
||||||
"\x05ERROR\x10\x02\x12\f\n" +
|
"\x05ERROR\x10\x02\x12\f\n" +
|
||||||
"\bCRITICAL\x10\x03\"R\n" +
|
"\bCRITICAL\x10\x03\"`\n" +
|
||||||
"\bCategory\x12\v\n" +
|
"\bCategory\x12\v\n" +
|
||||||
"\aNETWORK\x10\x00\x12\a\n" +
|
"\aNETWORK\x10\x00\x12\a\n" +
|
||||||
"\x03DNS\x10\x01\x12\x12\n" +
|
"\x03DNS\x10\x01\x12\x12\n" +
|
||||||
"\x0eAUTHENTICATION\x10\x02\x12\x10\n" +
|
"\x0eAUTHENTICATION\x10\x02\x12\x10\n" +
|
||||||
"\fCONNECTIVITY\x10\x03\x12\n" +
|
"\fCONNECTIVITY\x10\x03\x12\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x06SYSTEM\x10\x04\"\x12\n" +
|
"\x06SYSTEM\x10\x04\x12\f\n" +
|
||||||
|
"\bAPPROVAL\x10\x05\"\x12\n" +
|
||||||
"\x10GetEventsRequest\"@\n" +
|
"\x10GetEventsRequest\"@\n" +
|
||||||
"\x11GetEventsResponse\x12+\n" +
|
"\x11GetEventsResponse\x12+\n" +
|
||||||
"\x06events\x18\x01 \x03(\v2\x13.daemon.SystemEventR\x06events\"{\n" +
|
"\x06events\x18\x01 \x03(\v2\x13.daemon.SystemEventR\x06events\"{\n" +
|
||||||
@@ -6747,7 +6881,7 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\busername\x18\x02 \x01(\tH\x01R\busername\x88\x01\x01B\x0e\n" +
|
"\busername\x18\x02 \x01(\tH\x01R\busername\x88\x01\x01B\x0e\n" +
|
||||||
"\f_profileNameB\v\n" +
|
"\f_profileNameB\v\n" +
|
||||||
"\t_username\"\x17\n" +
|
"\t_username\"\x17\n" +
|
||||||
"\x15SwitchProfileResponse\"\xde\x11\n" +
|
"\x15SwitchProfileResponse\"\xaa\x12\n" +
|
||||||
"\x10SetConfigRequest\x12\x1a\n" +
|
"\x10SetConfigRequest\x12\x1a\n" +
|
||||||
"\busername\x18\x01 \x01(\tR\busername\x12 \n" +
|
"\busername\x18\x01 \x01(\tR\busername\x12 \n" +
|
||||||
"\vprofileName\x18\x02 \x01(\tR\vprofileName\x12$\n" +
|
"\vprofileName\x18\x02 \x01(\tR\vprofileName\x12$\n" +
|
||||||
@@ -6788,7 +6922,8 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x0edisableSSHAuth\x18! \x01(\bH\x16R\x0edisableSSHAuth\x88\x01\x01\x12+\n" +
|
"\x0edisableSSHAuth\x18! \x01(\bH\x16R\x0edisableSSHAuth\x88\x01\x01\x12+\n" +
|
||||||
"\x0esshJWTCacheTTL\x18\" \x01(\x05H\x17R\x0esshJWTCacheTTL\x88\x01\x01\x12&\n" +
|
"\x0esshJWTCacheTTL\x18\" \x01(\x05H\x17R\x0esshJWTCacheTTL\x88\x01\x01\x12&\n" +
|
||||||
"\fdisable_ipv6\x18# \x01(\bH\x18R\vdisableIpv6\x88\x01\x01\x12/\n" +
|
"\fdisable_ipv6\x18# \x01(\bH\x18R\vdisableIpv6\x88\x01\x01\x12/\n" +
|
||||||
"\x10serverVNCAllowed\x18$ \x01(\bH\x19R\x10serverVNCAllowed\x88\x01\x01B\x13\n" +
|
"\x10serverVNCAllowed\x18$ \x01(\bH\x19R\x10serverVNCAllowed\x88\x01\x01\x123\n" +
|
||||||
|
"\x12disableVNCApproval\x18% \x01(\bH\x1aR\x12disableVNCApproval\x88\x01\x01B\x13\n" +
|
||||||
"\x11_rosenpassEnabledB\x10\n" +
|
"\x11_rosenpassEnabledB\x10\n" +
|
||||||
"\x0e_interfaceNameB\x10\n" +
|
"\x0e_interfaceNameB\x10\n" +
|
||||||
"\x0e_wireguardPortB\x17\n" +
|
"\x0e_wireguardPortB\x17\n" +
|
||||||
@@ -6814,7 +6949,8 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x0f_disableSSHAuthB\x11\n" +
|
"\x0f_disableSSHAuthB\x11\n" +
|
||||||
"\x0f_sshJWTCacheTTLB\x0f\n" +
|
"\x0f_sshJWTCacheTTLB\x0f\n" +
|
||||||
"\r_disable_ipv6B\x13\n" +
|
"\r_disable_ipv6B\x13\n" +
|
||||||
"\x11_serverVNCAllowed\"\x13\n" +
|
"\x11_serverVNCAllowedB\x15\n" +
|
||||||
|
"\x13_disableVNCApproval\"\x13\n" +
|
||||||
"\x11SetConfigResponse\"Q\n" +
|
"\x11SetConfigResponse\"Q\n" +
|
||||||
"\x11AddProfileRequest\x12\x1a\n" +
|
"\x11AddProfileRequest\x12\x1a\n" +
|
||||||
"\busername\x18\x01 \x01(\tR\busername\x12 \n" +
|
"\busername\x18\x01 \x01(\tR\busername\x12 \n" +
|
||||||
@@ -6925,7 +7061,13 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\atimeout\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\atimeout\"\x1c\n" +
|
"\atimeout\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\atimeout\"\x1c\n" +
|
||||||
"\x1aStartBundleCaptureResponse\"\x1a\n" +
|
"\x1aStartBundleCaptureResponse\"\x1a\n" +
|
||||||
"\x18StopBundleCaptureRequest\"\x1b\n" +
|
"\x18StopBundleCaptureRequest\"\x1b\n" +
|
||||||
"\x19StopBundleCaptureResponse*b\n" +
|
"\x19StopBundleCaptureResponse\"l\n" +
|
||||||
|
"\x16RespondApprovalRequest\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"request_id\x18\x01 \x01(\tR\trequestId\x12\x16\n" +
|
||||||
|
"\x06accept\x18\x02 \x01(\bR\x06accept\x12\x1b\n" +
|
||||||
|
"\tview_only\x18\x03 \x01(\bR\bviewOnly\"\x19\n" +
|
||||||
|
"\x17RespondApprovalResponse*b\n" +
|
||||||
"\bLogLevel\x12\v\n" +
|
"\bLogLevel\x12\v\n" +
|
||||||
"\aUNKNOWN\x10\x00\x12\t\n" +
|
"\aUNKNOWN\x10\x00\x12\t\n" +
|
||||||
"\x05PANIC\x10\x01\x12\t\n" +
|
"\x05PANIC\x10\x01\x12\t\n" +
|
||||||
@@ -6943,7 +7085,7 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"EXPOSE_UDP\x10\x03\x12\x0e\n" +
|
"EXPOSE_UDP\x10\x03\x12\x0e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"EXPOSE_TLS\x10\x042\xaf\x17\n" +
|
"EXPOSE_TLS\x10\x042\x85\x18\n" +
|
||||||
"\rDaemonService\x126\n" +
|
"\rDaemonService\x126\n" +
|
||||||
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
|
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
|
||||||
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
|
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
|
||||||
@@ -6986,7 +7128,8 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\x0fStartCPUProfile\x12\x1e.daemon.StartCPUProfileRequest\x1a\x1f.daemon.StartCPUProfileResponse\"\x00\x12Q\n" +
|
"\x0fStartCPUProfile\x12\x1e.daemon.StartCPUProfileRequest\x1a\x1f.daemon.StartCPUProfileResponse\"\x00\x12Q\n" +
|
||||||
"\x0eStopCPUProfile\x12\x1d.daemon.StopCPUProfileRequest\x1a\x1e.daemon.StopCPUProfileResponse\"\x00\x12W\n" +
|
"\x0eStopCPUProfile\x12\x1d.daemon.StopCPUProfileRequest\x1a\x1e.daemon.StopCPUProfileResponse\"\x00\x12W\n" +
|
||||||
"\x12GetInstallerResult\x12\x1e.daemon.InstallerResultRequest\x1a\x1f.daemon.InstallerResultResponse\"\x00\x12M\n" +
|
"\x12GetInstallerResult\x12\x1e.daemon.InstallerResultRequest\x1a\x1f.daemon.InstallerResultResponse\"\x00\x12M\n" +
|
||||||
"\rExposeService\x12\x1c.daemon.ExposeServiceRequest\x1a\x1a.daemon.ExposeServiceEvent\"\x000\x01B\bZ\x06/protob\x06proto3"
|
"\rExposeService\x12\x1c.daemon.ExposeServiceRequest\x1a\x1a.daemon.ExposeServiceEvent\"\x000\x01\x12T\n" +
|
||||||
|
"\x0fRespondApproval\x12\x1e.daemon.RespondApprovalRequest\x1a\x1f.daemon.RespondApprovalResponse\"\x00B\bZ\x06/protob\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_daemon_proto_rawDescOnce sync.Once
|
file_daemon_proto_rawDescOnce sync.Once
|
||||||
@@ -7001,7 +7144,7 @@ func file_daemon_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 99)
|
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 101)
|
||||||
var file_daemon_proto_goTypes = []any{
|
var file_daemon_proto_goTypes = []any{
|
||||||
(LogLevel)(0), // 0: daemon.LogLevel
|
(LogLevel)(0), // 0: daemon.LogLevel
|
||||||
(ExposeProtocol)(0), // 1: daemon.ExposeProtocol
|
(ExposeProtocol)(0), // 1: daemon.ExposeProtocol
|
||||||
@@ -7103,18 +7246,20 @@ var file_daemon_proto_goTypes = []any{
|
|||||||
(*StartBundleCaptureResponse)(nil), // 97: daemon.StartBundleCaptureResponse
|
(*StartBundleCaptureResponse)(nil), // 97: daemon.StartBundleCaptureResponse
|
||||||
(*StopBundleCaptureRequest)(nil), // 98: daemon.StopBundleCaptureRequest
|
(*StopBundleCaptureRequest)(nil), // 98: daemon.StopBundleCaptureRequest
|
||||||
(*StopBundleCaptureResponse)(nil), // 99: daemon.StopBundleCaptureResponse
|
(*StopBundleCaptureResponse)(nil), // 99: daemon.StopBundleCaptureResponse
|
||||||
nil, // 100: daemon.Network.ResolvedIPsEntry
|
(*RespondApprovalRequest)(nil), // 100: daemon.RespondApprovalRequest
|
||||||
(*PortInfo_Range)(nil), // 101: daemon.PortInfo.Range
|
(*RespondApprovalResponse)(nil), // 101: daemon.RespondApprovalResponse
|
||||||
nil, // 102: daemon.SystemEvent.MetadataEntry
|
nil, // 102: daemon.Network.ResolvedIPsEntry
|
||||||
(*durationpb.Duration)(nil), // 103: google.protobuf.Duration
|
(*PortInfo_Range)(nil), // 103: daemon.PortInfo.Range
|
||||||
(*timestamppb.Timestamp)(nil), // 104: google.protobuf.Timestamp
|
nil, // 104: daemon.SystemEvent.MetadataEntry
|
||||||
|
(*durationpb.Duration)(nil), // 105: google.protobuf.Duration
|
||||||
|
(*timestamppb.Timestamp)(nil), // 106: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_daemon_proto_depIdxs = []int32{
|
var file_daemon_proto_depIdxs = []int32{
|
||||||
103, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
105, // 0: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||||
27, // 1: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
27, // 1: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
||||||
104, // 2: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
106, // 2: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
||||||
104, // 3: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
106, // 3: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
||||||
103, // 4: daemon.PeerState.latency:type_name -> google.protobuf.Duration
|
105, // 4: daemon.PeerState.latency:type_name -> google.protobuf.Duration
|
||||||
23, // 5: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo
|
23, // 5: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo
|
||||||
25, // 6: daemon.VNCServerState.sessions:type_name -> daemon.VNCSessionInfo
|
25, // 6: daemon.VNCServerState.sessions:type_name -> daemon.VNCSessionInfo
|
||||||
20, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
20, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
||||||
@@ -7127,8 +7272,8 @@ var file_daemon_proto_depIdxs = []int32{
|
|||||||
24, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState
|
24, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState
|
||||||
26, // 15: daemon.FullStatus.vncServerState:type_name -> daemon.VNCServerState
|
26, // 15: daemon.FullStatus.vncServerState:type_name -> daemon.VNCServerState
|
||||||
33, // 16: daemon.ListNetworksResponse.routes:type_name -> daemon.Network
|
33, // 16: daemon.ListNetworksResponse.routes:type_name -> daemon.Network
|
||||||
100, // 17: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry
|
102, // 17: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry
|
||||||
101, // 18: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range
|
103, // 18: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range
|
||||||
34, // 19: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo
|
34, // 19: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo
|
||||||
34, // 20: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo
|
34, // 20: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo
|
||||||
35, // 21: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule
|
35, // 21: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule
|
||||||
@@ -7139,15 +7284,15 @@ var file_daemon_proto_depIdxs = []int32{
|
|||||||
54, // 26: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage
|
54, // 26: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage
|
||||||
2, // 27: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity
|
2, // 27: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity
|
||||||
3, // 28: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category
|
3, // 28: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category
|
||||||
104, // 29: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp
|
106, // 29: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||||
102, // 30: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry
|
104, // 30: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry
|
||||||
57, // 31: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent
|
57, // 31: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent
|
||||||
103, // 32: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
105, // 32: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||||
70, // 33: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile
|
70, // 33: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile
|
||||||
1, // 34: daemon.ExposeServiceRequest.protocol:type_name -> daemon.ExposeProtocol
|
1, // 34: daemon.ExposeServiceRequest.protocol:type_name -> daemon.ExposeProtocol
|
||||||
93, // 35: daemon.ExposeServiceEvent.ready:type_name -> daemon.ExposeServiceReady
|
93, // 35: daemon.ExposeServiceEvent.ready:type_name -> daemon.ExposeServiceReady
|
||||||
103, // 36: daemon.StartCaptureRequest.duration:type_name -> google.protobuf.Duration
|
105, // 36: daemon.StartCaptureRequest.duration:type_name -> google.protobuf.Duration
|
||||||
103, // 37: daemon.StartBundleCaptureRequest.timeout:type_name -> google.protobuf.Duration
|
105, // 37: daemon.StartBundleCaptureRequest.timeout:type_name -> google.protobuf.Duration
|
||||||
32, // 38: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList
|
32, // 38: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList
|
||||||
5, // 39: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
5, // 39: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
||||||
7, // 40: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
7, // 40: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
||||||
@@ -7188,47 +7333,49 @@ var file_daemon_proto_depIdxs = []int32{
|
|||||||
87, // 75: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
87, // 75: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
||||||
89, // 76: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
89, // 76: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
||||||
91, // 77: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
|
91, // 77: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
|
||||||
6, // 78: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
100, // 78: daemon.DaemonService.RespondApproval:input_type -> daemon.RespondApprovalRequest
|
||||||
8, // 79: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
6, // 79: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||||
10, // 80: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
8, // 80: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
||||||
12, // 81: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
10, // 81: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||||
14, // 82: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
12, // 82: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||||
16, // 83: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
14, // 83: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||||
29, // 84: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
16, // 84: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
||||||
31, // 85: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
29, // 85: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
||||||
31, // 86: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
31, // 86: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||||
36, // 87: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
31, // 87: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||||
38, // 88: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
36, // 88: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
||||||
40, // 89: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
38, // 89: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
||||||
42, // 90: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
40, // 90: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
||||||
45, // 91: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
42, // 91: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
||||||
47, // 92: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
45, // 92: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
||||||
49, // 93: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
47, // 93: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
||||||
51, // 94: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
49, // 94: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
||||||
55, // 95: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
51, // 95: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
||||||
95, // 96: daemon.DaemonService.StartCapture:output_type -> daemon.CapturePacket
|
55, // 96: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
||||||
97, // 97: daemon.DaemonService.StartBundleCapture:output_type -> daemon.StartBundleCaptureResponse
|
95, // 97: daemon.DaemonService.StartCapture:output_type -> daemon.CapturePacket
|
||||||
99, // 98: daemon.DaemonService.StopBundleCapture:output_type -> daemon.StopBundleCaptureResponse
|
97, // 98: daemon.DaemonService.StartBundleCapture:output_type -> daemon.StartBundleCaptureResponse
|
||||||
57, // 99: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
99, // 99: daemon.DaemonService.StopBundleCapture:output_type -> daemon.StopBundleCaptureResponse
|
||||||
59, // 100: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
57, // 100: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
||||||
61, // 101: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
59, // 101: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
||||||
63, // 102: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
61, // 102: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
||||||
65, // 103: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
63, // 103: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
||||||
67, // 104: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
65, // 104: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
||||||
69, // 105: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
67, // 105: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
||||||
72, // 106: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
69, // 106: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
||||||
74, // 107: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
72, // 107: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
||||||
76, // 108: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
74, // 108: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
||||||
78, // 109: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse
|
76, // 109: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
||||||
80, // 110: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
78, // 110: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse
|
||||||
82, // 111: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
80, // 111: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
||||||
84, // 112: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
82, // 112: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
||||||
86, // 113: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
84, // 113: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
||||||
88, // 114: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
86, // 114: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
||||||
90, // 115: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
88, // 115: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
||||||
92, // 116: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
|
90, // 116: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
||||||
78, // [78:117] is the sub-list for method output_type
|
92, // 117: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
|
||||||
39, // [39:78] is the sub-list for method input_type
|
101, // 118: daemon.DaemonService.RespondApproval:output_type -> daemon.RespondApprovalResponse
|
||||||
|
79, // [79:119] is the sub-list for method output_type
|
||||||
|
39, // [39:79] is the sub-list for method input_type
|
||||||
39, // [39:39] is the sub-list for extension type_name
|
39, // [39:39] is the sub-list for extension type_name
|
||||||
39, // [39:39] is the sub-list for extension extendee
|
39, // [39:39] is the sub-list for extension extendee
|
||||||
0, // [0:39] is the sub-list for field type_name
|
0, // [0:39] is the sub-list for field type_name
|
||||||
@@ -7261,7 +7408,7 @@ func file_daemon_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)),
|
||||||
NumEnums: 4,
|
NumEnums: 4,
|
||||||
NumMessages: 99,
|
NumMessages: 101,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -119,6 +119,14 @@ service DaemonService {
|
|||||||
|
|
||||||
// ExposeService exposes a local port via the NetBird reverse proxy
|
// ExposeService exposes a local port via the NetBird reverse proxy
|
||||||
rpc ExposeService(ExposeServiceRequest) returns (stream ExposeServiceEvent) {}
|
rpc ExposeService(ExposeServiceRequest) returns (stream ExposeServiceEvent) {}
|
||||||
|
|
||||||
|
// RespondApproval delivers the user's accept/deny decision for a
|
||||||
|
// pending user-approval prompt. The daemon pushes the prompt as a
|
||||||
|
// SystemEvent with category APPROVAL and metadata key "request_id";
|
||||||
|
// the UI calls this RPC with the same request_id to unblock whichever
|
||||||
|
// subsystem (VNC, SSH, ...) is waiting. The "kind" metadata key tells
|
||||||
|
// the UI which subsystem the prompt belongs to.
|
||||||
|
rpc RespondApproval(RespondApprovalRequest) returns (RespondApprovalResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -207,6 +215,8 @@ message LoginRequest {
|
|||||||
optional bool disable_ipv6 = 40;
|
optional bool disable_ipv6 = 40;
|
||||||
|
|
||||||
optional bool serverVNCAllowed = 41;
|
optional bool serverVNCAllowed = 41;
|
||||||
|
|
||||||
|
optional bool disableVNCApproval = 42;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
@@ -318,6 +328,8 @@ message GetConfigResponse {
|
|||||||
bool disable_ipv6 = 27;
|
bool disable_ipv6 = 27;
|
||||||
|
|
||||||
bool serverVNCAllowed = 28;
|
bool serverVNCAllowed = 28;
|
||||||
|
|
||||||
|
bool disableVNCApproval = 29;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerState contains the latest state of a peer
|
// PeerState contains the latest state of a peer
|
||||||
@@ -616,6 +628,7 @@ message SystemEvent {
|
|||||||
AUTHENTICATION = 2;
|
AUTHENTICATION = 2;
|
||||||
CONNECTIVITY = 3;
|
CONNECTIVITY = 3;
|
||||||
SYSTEM = 4;
|
SYSTEM = 4;
|
||||||
|
APPROVAL = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
string id = 1;
|
string id = 1;
|
||||||
@@ -701,6 +714,8 @@ message SetConfigRequest {
|
|||||||
optional bool disable_ipv6 = 35;
|
optional bool disable_ipv6 = 35;
|
||||||
|
|
||||||
optional bool serverVNCAllowed = 36;
|
optional bool serverVNCAllowed = 36;
|
||||||
|
|
||||||
|
optional bool disableVNCApproval = 37;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetConfigResponse{}
|
message SetConfigResponse{}
|
||||||
@@ -895,3 +910,18 @@ message StartBundleCaptureRequest {
|
|||||||
message StartBundleCaptureResponse {}
|
message StartBundleCaptureResponse {}
|
||||||
message StopBundleCaptureRequest {}
|
message StopBundleCaptureRequest {}
|
||||||
message StopBundleCaptureResponse {}
|
message StopBundleCaptureResponse {}
|
||||||
|
|
||||||
|
message RespondApprovalRequest {
|
||||||
|
// request_id matches the SystemEvent metadata key emitted by the daemon
|
||||||
|
// when a subsystem awaits user approval for an inbound connection.
|
||||||
|
string request_id = 1;
|
||||||
|
// accept is true if the user approved the request, false if they
|
||||||
|
// denied it. A missing or unknown request_id is treated as a no-op.
|
||||||
|
bool accept = 2;
|
||||||
|
// view_only signals that the user granted the connection but withheld
|
||||||
|
// input control. Only meaningful when accept is true; ignored when
|
||||||
|
// accept is false.
|
||||||
|
bool view_only = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RespondApprovalResponse {}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const (
|
|||||||
DaemonService_StopCPUProfile_FullMethodName = "/daemon.DaemonService/StopCPUProfile"
|
DaemonService_StopCPUProfile_FullMethodName = "/daemon.DaemonService/StopCPUProfile"
|
||||||
DaemonService_GetInstallerResult_FullMethodName = "/daemon.DaemonService/GetInstallerResult"
|
DaemonService_GetInstallerResult_FullMethodName = "/daemon.DaemonService/GetInstallerResult"
|
||||||
DaemonService_ExposeService_FullMethodName = "/daemon.DaemonService/ExposeService"
|
DaemonService_ExposeService_FullMethodName = "/daemon.DaemonService/ExposeService"
|
||||||
|
DaemonService_RespondApproval_FullMethodName = "/daemon.DaemonService/RespondApproval"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DaemonServiceClient is the client API for DaemonService service.
|
// DaemonServiceClient is the client API for DaemonService service.
|
||||||
@@ -134,6 +135,13 @@ type DaemonServiceClient interface {
|
|||||||
GetInstallerResult(ctx context.Context, in *InstallerResultRequest, opts ...grpc.CallOption) (*InstallerResultResponse, error)
|
GetInstallerResult(ctx context.Context, in *InstallerResultRequest, opts ...grpc.CallOption) (*InstallerResultResponse, error)
|
||||||
// ExposeService exposes a local port via the NetBird reverse proxy
|
// ExposeService exposes a local port via the NetBird reverse proxy
|
||||||
ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ExposeServiceEvent], error)
|
ExposeService(ctx context.Context, in *ExposeServiceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ExposeServiceEvent], error)
|
||||||
|
// RespondApproval delivers the user's accept/deny decision for a
|
||||||
|
// pending user-approval prompt. The daemon pushes the prompt as a
|
||||||
|
// SystemEvent with category APPROVAL and metadata key "request_id";
|
||||||
|
// the UI calls this RPC with the same request_id to unblock whichever
|
||||||
|
// subsystem (VNC, SSH, ...) is waiting. The "kind" metadata key tells
|
||||||
|
// the UI which subsystem the prompt belongs to.
|
||||||
|
RespondApproval(ctx context.Context, in *RespondApprovalRequest, opts ...grpc.CallOption) (*RespondApprovalResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type daemonServiceClient struct {
|
type daemonServiceClient struct {
|
||||||
@@ -561,6 +569,16 @@ func (c *daemonServiceClient) ExposeService(ctx context.Context, in *ExposeServi
|
|||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type DaemonService_ExposeServiceClient = grpc.ServerStreamingClient[ExposeServiceEvent]
|
type DaemonService_ExposeServiceClient = grpc.ServerStreamingClient[ExposeServiceEvent]
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) RespondApproval(ctx context.Context, in *RespondApprovalRequest, opts ...grpc.CallOption) (*RespondApprovalResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(RespondApprovalResponse)
|
||||||
|
err := c.cc.Invoke(ctx, DaemonService_RespondApproval_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonServiceServer is the server API for DaemonService service.
|
// DaemonServiceServer is the server API for DaemonService service.
|
||||||
// All implementations must embed UnimplementedDaemonServiceServer
|
// All implementations must embed UnimplementedDaemonServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -635,6 +653,13 @@ type DaemonServiceServer interface {
|
|||||||
GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error)
|
GetInstallerResult(context.Context, *InstallerResultRequest) (*InstallerResultResponse, error)
|
||||||
// ExposeService exposes a local port via the NetBird reverse proxy
|
// ExposeService exposes a local port via the NetBird reverse proxy
|
||||||
ExposeService(*ExposeServiceRequest, grpc.ServerStreamingServer[ExposeServiceEvent]) error
|
ExposeService(*ExposeServiceRequest, grpc.ServerStreamingServer[ExposeServiceEvent]) error
|
||||||
|
// RespondApproval delivers the user's accept/deny decision for a
|
||||||
|
// pending user-approval prompt. The daemon pushes the prompt as a
|
||||||
|
// SystemEvent with category APPROVAL and metadata key "request_id";
|
||||||
|
// the UI calls this RPC with the same request_id to unblock whichever
|
||||||
|
// subsystem (VNC, SSH, ...) is waiting. The "kind" metadata key tells
|
||||||
|
// the UI which subsystem the prompt belongs to.
|
||||||
|
RespondApproval(context.Context, *RespondApprovalRequest) (*RespondApprovalResponse, error)
|
||||||
mustEmbedUnimplementedDaemonServiceServer()
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,6 +787,9 @@ func (UnimplementedDaemonServiceServer) GetInstallerResult(context.Context, *Ins
|
|||||||
func (UnimplementedDaemonServiceServer) ExposeService(*ExposeServiceRequest, grpc.ServerStreamingServer[ExposeServiceEvent]) error {
|
func (UnimplementedDaemonServiceServer) ExposeService(*ExposeServiceRequest, grpc.ServerStreamingServer[ExposeServiceEvent]) error {
|
||||||
return status.Error(codes.Unimplemented, "method ExposeService not implemented")
|
return status.Error(codes.Unimplemented, "method ExposeService not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) RespondApproval(context.Context, *RespondApprovalRequest) (*RespondApprovalResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method RespondApproval not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||||
func (UnimplementedDaemonServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedDaemonServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@@ -1464,6 +1492,24 @@ func _DaemonService_ExposeService_Handler(srv interface{}, stream grpc.ServerStr
|
|||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type DaemonService_ExposeServiceServer = grpc.ServerStreamingServer[ExposeServiceEvent]
|
type DaemonService_ExposeServiceServer = grpc.ServerStreamingServer[ExposeServiceEvent]
|
||||||
|
|
||||||
|
func _DaemonService_RespondApproval_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RespondApprovalRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).RespondApproval(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: DaemonService_RespondApproval_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).RespondApproval(ctx, req.(*RespondApprovalRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -1615,6 +1661,10 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetInstallerResult",
|
MethodName: "GetInstallerResult",
|
||||||
Handler: _DaemonService_GetInstallerResult_Handler,
|
Handler: _DaemonService_GetInstallerResult_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RespondApproval",
|
||||||
|
Handler: _DaemonService_RespondApproval_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigReques
|
|||||||
config.DisableAutoConnect = msg.DisableAutoConnect
|
config.DisableAutoConnect = msg.DisableAutoConnect
|
||||||
config.ServerSSHAllowed = msg.ServerSSHAllowed
|
config.ServerSSHAllowed = msg.ServerSSHAllowed
|
||||||
config.ServerVNCAllowed = msg.ServerVNCAllowed
|
config.ServerVNCAllowed = msg.ServerVNCAllowed
|
||||||
|
config.DisableVNCApproval = msg.DisableVNCApproval
|
||||||
config.NetworkMonitor = msg.NetworkMonitor
|
config.NetworkMonitor = msg.NetworkMonitor
|
||||||
config.DisableClientRoutes = msg.DisableClientRoutes
|
config.DisableClientRoutes = msg.DisableClientRoutes
|
||||||
config.DisableServerRoutes = msg.DisableServerRoutes
|
config.DisableServerRoutes = msg.DisableServerRoutes
|
||||||
@@ -1448,6 +1449,27 @@ func (s *Server) ExposeService(req *proto.ExposeServiceRequest, srv proto.Daemon
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RespondApproval relays the user's accept/deny decision for a pending
|
||||||
|
// approval prompt to the engine's broker. Unknown or already-resolved
|
||||||
|
// request_ids are silently no-op'd so a slow UI cannot deny a prompt the
|
||||||
|
// user already handled (or that already timed out).
|
||||||
|
func (s *Server) RespondApproval(_ context.Context, msg *proto.RespondApprovalRequest) (*proto.RespondApprovalResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
connectClient := s.connectClient
|
||||||
|
s.mutex.Unlock()
|
||||||
|
if connectClient == nil {
|
||||||
|
return nil, gstatus.Errorf(codes.FailedPrecondition, "client not initialized")
|
||||||
|
}
|
||||||
|
engine := connectClient.Engine()
|
||||||
|
if engine == nil {
|
||||||
|
return nil, gstatus.Errorf(codes.FailedPrecondition, "engine not running")
|
||||||
|
}
|
||||||
|
if !engine.RespondApproval(msg.GetRequestId(), msg.GetAccept(), msg.GetViewOnly()) {
|
||||||
|
log.Debugf("approval response for unknown request_id %s", msg.GetRequestId())
|
||||||
|
}
|
||||||
|
return &proto.RespondApprovalResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isUnixRunningDesktop() bool {
|
func isUnixRunningDesktop() bool {
|
||||||
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
|
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
|
||||||
return false
|
return false
|
||||||
@@ -1565,6 +1587,7 @@ func (s *Server) GetConfig(ctx context.Context, req *proto.GetConfigRequest) (*p
|
|||||||
DisableAutoConnect: cfg.DisableAutoConnect,
|
DisableAutoConnect: cfg.DisableAutoConnect,
|
||||||
ServerSSHAllowed: *cfg.ServerSSHAllowed,
|
ServerSSHAllowed: *cfg.ServerSSHAllowed,
|
||||||
ServerVNCAllowed: cfg.ServerVNCAllowed != nil && *cfg.ServerVNCAllowed,
|
ServerVNCAllowed: cfg.ServerVNCAllowed != nil && *cfg.ServerVNCAllowed,
|
||||||
|
DisableVNCApproval: cfg.DisableVNCApproval != nil && *cfg.DisableVNCApproval,
|
||||||
RosenpassEnabled: cfg.RosenpassEnabled,
|
RosenpassEnabled: cfg.RosenpassEnabled,
|
||||||
RosenpassPermissive: cfg.RosenpassPermissive,
|
RosenpassPermissive: cfg.RosenpassPermissive,
|
||||||
LazyConnectionEnabled: cfg.LazyConnectionEnabled,
|
LazyConnectionEnabled: cfg.LazyConnectionEnabled,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ func TestSetConfig_AllFieldsSaved(t *testing.T) {
|
|||||||
rosenpassPermissive := true
|
rosenpassPermissive := true
|
||||||
serverSSHAllowed := true
|
serverSSHAllowed := true
|
||||||
serverVNCAllowed := true
|
serverVNCAllowed := true
|
||||||
|
disableVNCApproval := true
|
||||||
interfaceName := "utun100"
|
interfaceName := "utun100"
|
||||||
wireguardPort := int64(51820)
|
wireguardPort := int64(51820)
|
||||||
preSharedKey := "test-psk"
|
preSharedKey := "test-psk"
|
||||||
@@ -85,6 +86,7 @@ func TestSetConfig_AllFieldsSaved(t *testing.T) {
|
|||||||
RosenpassPermissive: &rosenpassPermissive,
|
RosenpassPermissive: &rosenpassPermissive,
|
||||||
ServerSSHAllowed: &serverSSHAllowed,
|
ServerSSHAllowed: &serverSSHAllowed,
|
||||||
ServerVNCAllowed: &serverVNCAllowed,
|
ServerVNCAllowed: &serverVNCAllowed,
|
||||||
|
DisableVNCApproval: &disableVNCApproval,
|
||||||
InterfaceName: &interfaceName,
|
InterfaceName: &interfaceName,
|
||||||
WireguardPort: &wireguardPort,
|
WireguardPort: &wireguardPort,
|
||||||
OptionalPreSharedKey: &preSharedKey,
|
OptionalPreSharedKey: &preSharedKey,
|
||||||
@@ -131,6 +133,8 @@ func TestSetConfig_AllFieldsSaved(t *testing.T) {
|
|||||||
require.Equal(t, serverSSHAllowed, *cfg.ServerSSHAllowed)
|
require.Equal(t, serverSSHAllowed, *cfg.ServerSSHAllowed)
|
||||||
require.NotNil(t, cfg.ServerVNCAllowed)
|
require.NotNil(t, cfg.ServerVNCAllowed)
|
||||||
require.Equal(t, serverVNCAllowed, *cfg.ServerVNCAllowed)
|
require.Equal(t, serverVNCAllowed, *cfg.ServerVNCAllowed)
|
||||||
|
require.NotNil(t, cfg.DisableVNCApproval)
|
||||||
|
require.Equal(t, disableVNCApproval, *cfg.DisableVNCApproval)
|
||||||
require.Equal(t, interfaceName, cfg.WgIface)
|
require.Equal(t, interfaceName, cfg.WgIface)
|
||||||
require.Equal(t, int(wireguardPort), cfg.WgPort)
|
require.Equal(t, int(wireguardPort), cfg.WgPort)
|
||||||
require.Equal(t, preSharedKey, cfg.PreSharedKey)
|
require.Equal(t, preSharedKey, cfg.PreSharedKey)
|
||||||
@@ -184,6 +188,7 @@ func verifyAllFieldsCovered(t *testing.T, req *proto.SetConfigRequest) {
|
|||||||
"RosenpassPermissive": true,
|
"RosenpassPermissive": true,
|
||||||
"ServerSSHAllowed": true,
|
"ServerSSHAllowed": true,
|
||||||
"ServerVNCAllowed": true,
|
"ServerVNCAllowed": true,
|
||||||
|
"DisableVNCApproval": true,
|
||||||
"InterfaceName": true,
|
"InterfaceName": true,
|
||||||
"WireguardPort": true,
|
"WireguardPort": true,
|
||||||
"OptionalPreSharedKey": true,
|
"OptionalPreSharedKey": true,
|
||||||
@@ -246,6 +251,7 @@ func TestCLIFlags_MappedToSetConfig(t *testing.T) {
|
|||||||
"rosenpass-permissive": "RosenpassPermissive",
|
"rosenpass-permissive": "RosenpassPermissive",
|
||||||
"allow-server-ssh": "ServerSSHAllowed",
|
"allow-server-ssh": "ServerSSHAllowed",
|
||||||
"allow-server-vnc": "ServerVNCAllowed",
|
"allow-server-vnc": "ServerVNCAllowed",
|
||||||
|
"disable-vnc-approval": "DisableVNCApproval",
|
||||||
"interface-name": "InterfaceName",
|
"interface-name": "InterfaceName",
|
||||||
"wireguard-port": "WireguardPort",
|
"wireguard-port": "WireguardPort",
|
||||||
"preshared-key": "OptionalPreSharedKey",
|
"preshared-key": "OptionalPreSharedKey",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
sshauth "github.com/netbirdio/netbird/client/ssh/auth"
|
sshauth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
"github.com/netbirdio/netbird/client/ssh/server"
|
"github.com/netbirdio/netbird/client/ssh/server"
|
||||||
"github.com/netbirdio/netbird/client/ssh/testutil"
|
"github.com/netbirdio/netbird/client/ssh/testutil"
|
||||||
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
|
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||||
sshauth "github.com/netbirdio/netbird/client/ssh/auth"
|
sshauth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
"github.com/netbirdio/netbird/client/ssh/client"
|
"github.com/netbirdio/netbird/client/ssh/client"
|
||||||
"github.com/netbirdio/netbird/client/ssh/detection"
|
"github.com/netbirdio/netbird/client/ssh/detection"
|
||||||
"github.com/netbirdio/netbird/client/ssh/testutil"
|
"github.com/netbirdio/netbird/client/ssh/testutil"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
sshauth "github.com/netbirdio/netbird/client/ssh/auth"
|
sshauth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
"github.com/netbirdio/netbird/client/ssh/detection"
|
"github.com/netbirdio/netbird/client/ssh/detection"
|
||||||
"github.com/netbirdio/netbird/shared/auth"
|
"github.com/netbirdio/netbird/shared/auth"
|
||||||
"github.com/netbirdio/netbird/shared/auth/jwt"
|
"github.com/netbirdio/netbird/shared/auth/jwt"
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
//go:build !(linux && 386)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handleApprovalEvent forks a netbird-ui child process to render the
|
||||||
|
// dialog on its own fyne main loop. Top-level windows opened from a
|
||||||
|
// background goroutine of the tray process don't render reliably on
|
||||||
|
// Linux/GTK, so the rest of the UI (settings, login URL, update) uses
|
||||||
|
// the same fork pattern.
|
||||||
|
func (s *serviceClient) handleApprovalEvent(ev *proto.SystemEvent) {
|
||||||
|
if ev == nil || ev.Category != proto.SystemEvent_APPROVAL {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requestID := ev.Metadata["request_id"]
|
||||||
|
if requestID == "" {
|
||||||
|
log.Warnf("approval event missing request_id: %v", ev.Metadata)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args := []string{
|
||||||
|
"--approval-request-id=" + requestID,
|
||||||
|
"--approval-kind=" + ev.Metadata["kind"],
|
||||||
|
"--approval-initiator=" + ev.Metadata["initiator"],
|
||||||
|
"--approval-peer-name=" + ev.Metadata["peer_name"],
|
||||||
|
"--approval-source-ip=" + ev.Metadata["source_ip"],
|
||||||
|
"--approval-username=" + ev.Metadata["username"],
|
||||||
|
"--approval-expires-at=" + ev.Metadata["expires_at"],
|
||||||
|
"--approval-subject=" + ev.UserMessage,
|
||||||
|
}
|
||||||
|
go s.eventHandler.runSelfCommand(s.ctx, "approval", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// showApprovalUI runs the dialog on the forked process's fyne main loop
|
||||||
|
// and forwards the user's decision to the daemon via RespondApproval.
|
||||||
|
func (s *serviceClient) showApprovalUI(req approvalRequest) {
|
||||||
|
w := s.app.NewWindow(approvalTitle(req.kind))
|
||||||
|
w.Resize(fyne.NewSize(480, 260))
|
||||||
|
w.CenterOnScreen()
|
||||||
|
w.RequestFocus()
|
||||||
|
|
||||||
|
var rows []string
|
||||||
|
if req.initiator != "" {
|
||||||
|
rows = append(rows, "From user: "+req.initiator)
|
||||||
|
}
|
||||||
|
if req.peerName != "" {
|
||||||
|
rows = append(rows, "Via peer: "+req.peerName)
|
||||||
|
}
|
||||||
|
if req.sourceIP != "" && req.sourceIP != req.peerName {
|
||||||
|
rows = append(rows, "Source IP: "+req.sourceIP)
|
||||||
|
}
|
||||||
|
if req.username != "" {
|
||||||
|
rows = append(rows, "OS user: "+req.username)
|
||||||
|
}
|
||||||
|
if len(rows) == 0 {
|
||||||
|
rows = []string{"Remote: " + req.displayPeer()}
|
||||||
|
}
|
||||||
|
body := strings.Join(rows, "\n")
|
||||||
|
bodyLabel := widget.NewLabel(body)
|
||||||
|
bodyLabel.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
|
countdown := widget.NewLabel("")
|
||||||
|
deadline := req.deadline()
|
||||||
|
updateCountdown := func() {
|
||||||
|
remaining := time.Until(deadline).Round(time.Second)
|
||||||
|
if remaining < 0 {
|
||||||
|
remaining = 0
|
||||||
|
}
|
||||||
|
countdown.SetText(fmt.Sprintf("Auto-deny in %s", remaining))
|
||||||
|
}
|
||||||
|
updateCountdown()
|
||||||
|
|
||||||
|
type outcome struct {
|
||||||
|
accept bool
|
||||||
|
viewOnly bool
|
||||||
|
}
|
||||||
|
decided := make(chan outcome, 1)
|
||||||
|
decide := func(o outcome) {
|
||||||
|
select {
|
||||||
|
case decided <- o:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allow := widget.NewButton("Allow", func() { decide(outcome{accept: true}) })
|
||||||
|
allow.Importance = widget.HighImportance
|
||||||
|
allowView := widget.NewButton("Allow (view only)", func() { decide(outcome{accept: true, viewOnly: true}) })
|
||||||
|
deny := widget.NewButton("Deny", func() { decide(outcome{accept: false}) })
|
||||||
|
|
||||||
|
header := widget.NewLabelWithStyle(req.subject, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||||
|
buttonRow := container.NewGridWithColumns(3, allow, allowView, deny)
|
||||||
|
info := container.NewVBox(header, widget.NewSeparator(), bodyLabel, widget.NewSeparator(), countdown)
|
||||||
|
w.SetContent(container.NewPadded(container.NewBorder(nil, buttonRow, nil, nil, info)))
|
||||||
|
w.SetCloseIntercept(func() { decide(outcome{}) })
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
if time.Until(deadline) <= 0 {
|
||||||
|
decide(outcome{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateCountdown()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
o := <-decided
|
||||||
|
s.sendApprovalResponse(req.requestID, o.accept, o.viewOnly)
|
||||||
|
w.Close()
|
||||||
|
s.app.Quit()
|
||||||
|
}()
|
||||||
|
|
||||||
|
w.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) sendApprovalResponse(requestID string, accept, viewOnly bool) {
|
||||||
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("approval response: get daemon client: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(s.ctx, defaultFailTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if _, err := conn.RespondApproval(ctx, &proto.RespondApprovalRequest{
|
||||||
|
RequestId: requestID,
|
||||||
|
Accept: accept,
|
||||||
|
ViewOnly: viewOnly,
|
||||||
|
}); err != nil {
|
||||||
|
log.Warnf("approval response: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// approvalRequest is the parsed --approval-* CLI args that the forked
|
||||||
|
// dialog process consumes.
|
||||||
|
type approvalRequest struct {
|
||||||
|
requestID string
|
||||||
|
kind string
|
||||||
|
initiator string
|
||||||
|
peerName string
|
||||||
|
sourceIP string
|
||||||
|
username string
|
||||||
|
subject string
|
||||||
|
expiresAt string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r approvalRequest) displayPeer() string {
|
||||||
|
switch {
|
||||||
|
case r.initiator != "":
|
||||||
|
return r.initiator
|
||||||
|
case r.peerName != "":
|
||||||
|
return r.peerName
|
||||||
|
case r.sourceIP != "":
|
||||||
|
return r.sourceIP
|
||||||
|
default:
|
||||||
|
return "unknown peer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deadline returns the wall-clock auto-deny moment. Falls back to a short
|
||||||
|
// local window when the daemon's expires_at is missing/unparseable, so a
|
||||||
|
// stale value never leaves the dialog open indefinitely.
|
||||||
|
func (r approvalRequest) deadline() time.Time {
|
||||||
|
if t, err := time.Parse(time.RFC3339, r.expiresAt); err == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return time.Now().Add(13 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func approvalTitle(kind string) string {
|
||||||
|
switch kind {
|
||||||
|
case "vnc":
|
||||||
|
return "Allow VNC Connection?"
|
||||||
|
case "ssh":
|
||||||
|
return "Allow SSH Connection?"
|
||||||
|
default:
|
||||||
|
return "Allow Incoming Connection?"
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
-2
@@ -97,13 +97,24 @@ func main() {
|
|||||||
showQuickActions: flags.showQuickActions,
|
showQuickActions: flags.showQuickActions,
|
||||||
showUpdate: flags.showUpdate,
|
showUpdate: flags.showUpdate,
|
||||||
showUpdateVersion: flags.showUpdateVersion,
|
showUpdateVersion: flags.showUpdateVersion,
|
||||||
|
showApproval: flags.showApproval,
|
||||||
|
approvalRequest: approvalRequest{
|
||||||
|
requestID: flags.approvalRequestID,
|
||||||
|
kind: flags.approvalKind,
|
||||||
|
initiator: flags.approvalInitiator,
|
||||||
|
peerName: flags.approvalPeerName,
|
||||||
|
sourceIP: flags.approvalSourceIP,
|
||||||
|
username: flags.approvalUsername,
|
||||||
|
subject: flags.approvalSubject,
|
||||||
|
expiresAt: flags.approvalExpiresAt,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watch for theme/settings changes to update the icon.
|
// Watch for theme/settings changes to update the icon.
|
||||||
go watchSettingsChanges(a, client)
|
go watchSettingsChanges(a, client)
|
||||||
|
|
||||||
// Run in window mode if any UI flag was set.
|
// Run in window mode if any UI flag was set.
|
||||||
if flags.showSettings || flags.showNetworks || flags.showDebug || flags.showLoginURL || flags.showProfiles || flags.showQuickActions || flags.showUpdate {
|
if flags.showSettings || flags.showNetworks || flags.showDebug || flags.showLoginURL || flags.showProfiles || flags.showQuickActions || flags.showUpdate || flags.showApproval {
|
||||||
a.Run()
|
a.Run()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -140,6 +151,16 @@ type cliFlags struct {
|
|||||||
saveLogsInFile bool
|
saveLogsInFile bool
|
||||||
showUpdate bool
|
showUpdate bool
|
||||||
showUpdateVersion string
|
showUpdateVersion string
|
||||||
|
showApproval bool
|
||||||
|
|
||||||
|
approvalRequestID string
|
||||||
|
approvalKind string
|
||||||
|
approvalInitiator string
|
||||||
|
approvalPeerName string
|
||||||
|
approvalSourceIP string
|
||||||
|
approvalUsername string
|
||||||
|
approvalSubject string
|
||||||
|
approvalExpiresAt string
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseFlags reads and returns all needed command-line flags.
|
// parseFlags reads and returns all needed command-line flags.
|
||||||
@@ -161,6 +182,15 @@ func parseFlags() *cliFlags {
|
|||||||
flag.BoolVar(&flags.showLoginURL, "login-url", false, "show login URL in a popup window")
|
flag.BoolVar(&flags.showLoginURL, "login-url", false, "show login URL in a popup window")
|
||||||
flag.BoolVar(&flags.showUpdate, "update", false, "show update progress window")
|
flag.BoolVar(&flags.showUpdate, "update", false, "show update progress window")
|
||||||
flag.StringVar(&flags.showUpdateVersion, "update-version", "", "version to update to")
|
flag.StringVar(&flags.showUpdateVersion, "update-version", "", "version to update to")
|
||||||
|
flag.BoolVar(&flags.showApproval, "approval", false, "show inbound-connection approval prompt window")
|
||||||
|
flag.StringVar(&flags.approvalRequestID, "approval-request-id", "", "approval prompt: daemon-issued request id")
|
||||||
|
flag.StringVar(&flags.approvalKind, "approval-kind", "", "approval prompt: subsystem kind (vnc, ssh, ...)")
|
||||||
|
flag.StringVar(&flags.approvalInitiator, "approval-initiator", "", "approval prompt: display name of the user who initiated the connection")
|
||||||
|
flag.StringVar(&flags.approvalPeerName, "approval-peer-name", "", "approval prompt: remote peer FQDN")
|
||||||
|
flag.StringVar(&flags.approvalSourceIP, "approval-source-ip", "", "approval prompt: remote source IP")
|
||||||
|
flag.StringVar(&flags.approvalUsername, "approval-username", "", "approval prompt: requested OS username")
|
||||||
|
flag.StringVar(&flags.approvalSubject, "approval-subject", "", "approval prompt: human-readable subject line")
|
||||||
|
flag.StringVar(&flags.approvalExpiresAt, "approval-expires-at", "", "approval prompt: RFC3339 deadline at which the daemon auto-denies")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
return &flags
|
return &flags
|
||||||
}
|
}
|
||||||
@@ -288,6 +318,8 @@ type serviceClient struct {
|
|||||||
sEnableSSHRemotePortForward *widget.Check
|
sEnableSSHRemotePortForward *widget.Check
|
||||||
sDisableSSHAuth *widget.Check
|
sDisableSSHAuth *widget.Check
|
||||||
iSSHJWTCacheTTL *widget.Entry
|
iSSHJWTCacheTTL *widget.Entry
|
||||||
|
sServerVNCAllowed *widget.Check
|
||||||
|
sDisableVNCApproval *widget.Check
|
||||||
|
|
||||||
// observable settings over corresponding iMngURL and iPreSharedKey values.
|
// observable settings over corresponding iMngURL and iPreSharedKey values.
|
||||||
managementURL string
|
managementURL string
|
||||||
@@ -309,6 +341,8 @@ type serviceClient struct {
|
|||||||
enableSSHRemotePortForward bool
|
enableSSHRemotePortForward bool
|
||||||
disableSSHAuth bool
|
disableSSHAuth bool
|
||||||
sshJWTCacheTTL int
|
sshJWTCacheTTL int
|
||||||
|
serverVNCAllowed bool
|
||||||
|
disableVNCApproval bool
|
||||||
|
|
||||||
connected bool
|
connected bool
|
||||||
daemonVersion string
|
daemonVersion string
|
||||||
@@ -356,6 +390,8 @@ type newServiceClientArgs struct {
|
|||||||
showQuickActions bool
|
showQuickActions bool
|
||||||
showUpdate bool
|
showUpdate bool
|
||||||
showUpdateVersion string
|
showUpdateVersion string
|
||||||
|
showApproval bool
|
||||||
|
approvalRequest approvalRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// newServiceClient instance constructor
|
// newServiceClient instance constructor
|
||||||
@@ -396,6 +432,8 @@ func newServiceClient(args *newServiceClientArgs) *serviceClient {
|
|||||||
s.showQuickActionsUI()
|
s.showQuickActionsUI()
|
||||||
case args.showUpdate:
|
case args.showUpdate:
|
||||||
s.showUpdateProgress(ctx, args.showUpdateVersion)
|
s.showUpdateProgress(ctx, args.showUpdateVersion)
|
||||||
|
case args.showApproval:
|
||||||
|
s.showApprovalUI(args.approvalRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@@ -479,6 +517,8 @@ func (s *serviceClient) showSettingsUI() {
|
|||||||
s.sEnableSSHRemotePortForward = widget.NewCheck("Enable SSH Remote Port Forwarding", nil)
|
s.sEnableSSHRemotePortForward = widget.NewCheck("Enable SSH Remote Port Forwarding", nil)
|
||||||
s.sDisableSSHAuth = widget.NewCheck("Disable SSH Authentication", nil)
|
s.sDisableSSHAuth = widget.NewCheck("Disable SSH Authentication", nil)
|
||||||
s.iSSHJWTCacheTTL = widget.NewEntry()
|
s.iSSHJWTCacheTTL = widget.NewEntry()
|
||||||
|
s.sServerVNCAllowed = widget.NewCheck("Allow embedded VNC server on this peer", nil)
|
||||||
|
s.sDisableVNCApproval = widget.NewCheck("Skip per-connection approval prompt for VNC", nil)
|
||||||
|
|
||||||
s.wSettings.SetContent(s.getSettingsForm())
|
s.wSettings.SetContent(s.getSettingsForm())
|
||||||
s.wSettings.Resize(fyne.NewSize(600, 400))
|
s.wSettings.Resize(fyne.NewSize(600, 400))
|
||||||
@@ -591,7 +631,8 @@ func (s *serviceClient) hasSettingsChanged(iMngURL string, port, mtu int64) bool
|
|||||||
s.disableServerRoutes != s.sDisableServerRoutes.Checked ||
|
s.disableServerRoutes != s.sDisableServerRoutes.Checked ||
|
||||||
s.disableIPv6 != s.sDisableIPv6.Checked ||
|
s.disableIPv6 != s.sDisableIPv6.Checked ||
|
||||||
s.blockLANAccess != s.sBlockLANAccess.Checked ||
|
s.blockLANAccess != s.sBlockLANAccess.Checked ||
|
||||||
s.hasSSHChanges()
|
s.hasSSHChanges() ||
|
||||||
|
s.hasVNCChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) applySettingsChanges(iMngURL string, port, mtu int64) error {
|
func (s *serviceClient) applySettingsChanges(iMngURL string, port, mtu int64) error {
|
||||||
@@ -650,6 +691,8 @@ func (s *serviceClient) buildSetConfigRequest(iMngURL string, port, mtu int64) (
|
|||||||
req.EnableSSHLocalPortForwarding = &s.sEnableSSHLocalPortForward.Checked
|
req.EnableSSHLocalPortForwarding = &s.sEnableSSHLocalPortForward.Checked
|
||||||
req.EnableSSHRemotePortForwarding = &s.sEnableSSHRemotePortForward.Checked
|
req.EnableSSHRemotePortForwarding = &s.sEnableSSHRemotePortForward.Checked
|
||||||
req.DisableSSHAuth = &s.sDisableSSHAuth.Checked
|
req.DisableSSHAuth = &s.sDisableSSHAuth.Checked
|
||||||
|
req.ServerVNCAllowed = &s.sServerVNCAllowed.Checked
|
||||||
|
req.DisableVNCApproval = &s.sDisableVNCApproval.Checked
|
||||||
|
|
||||||
sshJWTCacheTTLText := strings.TrimSpace(s.iSSHJWTCacheTTL.Text)
|
sshJWTCacheTTLText := strings.TrimSpace(s.iSSHJWTCacheTTL.Text)
|
||||||
if sshJWTCacheTTLText != "" {
|
if sshJWTCacheTTLText != "" {
|
||||||
@@ -710,10 +753,12 @@ func (s *serviceClient) getSettingsForm() fyne.CanvasObject {
|
|||||||
connectionForm := s.getConnectionForm()
|
connectionForm := s.getConnectionForm()
|
||||||
networkForm := s.getNetworkForm()
|
networkForm := s.getNetworkForm()
|
||||||
sshForm := s.getSSHForm()
|
sshForm := s.getSSHForm()
|
||||||
|
vncForm := s.getVNCForm()
|
||||||
tabs := container.NewAppTabs(
|
tabs := container.NewAppTabs(
|
||||||
container.NewTabItem("Connection", connectionForm),
|
container.NewTabItem("Connection", connectionForm),
|
||||||
container.NewTabItem("Network", networkForm),
|
container.NewTabItem("Network", networkForm),
|
||||||
container.NewTabItem("SSH", sshForm),
|
container.NewTabItem("SSH", sshForm),
|
||||||
|
container.NewTabItem("VNC", vncForm),
|
||||||
)
|
)
|
||||||
saveButton := widget.NewButtonWithIcon("Save", theme.ConfirmIcon(), s.saveSettings)
|
saveButton := widget.NewButtonWithIcon("Save", theme.ConfirmIcon(), s.saveSettings)
|
||||||
saveButton.Importance = widget.HighImportance
|
saveButton.Importance = widget.HighImportance
|
||||||
@@ -754,6 +799,15 @@ func (s *serviceClient) getSSHForm() *widget.Form {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) getVNCForm() *widget.Form {
|
||||||
|
return &widget.Form{
|
||||||
|
Items: []*widget.FormItem{
|
||||||
|
{Text: "Allow VNC Server", Widget: s.sServerVNCAllowed},
|
||||||
|
{Text: "Disable Connection Approval Prompt", Widget: s.sDisableVNCApproval},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serviceClient) hasSSHChanges() bool {
|
func (s *serviceClient) hasSSHChanges() bool {
|
||||||
currentSSHJWTCacheTTL := s.sshJWTCacheTTL
|
currentSSHJWTCacheTTL := s.sshJWTCacheTTL
|
||||||
if text := strings.TrimSpace(s.iSSHJWTCacheTTL.Text); text != "" {
|
if text := strings.TrimSpace(s.iSSHJWTCacheTTL.Text); text != "" {
|
||||||
@@ -772,6 +826,11 @@ func (s *serviceClient) hasSSHChanges() bool {
|
|||||||
s.sshJWTCacheTTL != currentSSHJWTCacheTTL
|
s.sshJWTCacheTTL != currentSSHJWTCacheTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) hasVNCChanges() bool {
|
||||||
|
return s.serverVNCAllowed != s.sServerVNCAllowed.Checked ||
|
||||||
|
s.disableVNCApproval != s.sDisableVNCApproval.Checked
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serviceClient) login(ctx context.Context, openURL bool) (*proto.LoginResponse, error) {
|
func (s *serviceClient) login(ctx context.Context, openURL bool) (*proto.LoginResponse, error) {
|
||||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1120,6 +1179,7 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
|
|
||||||
s.eventManager = event.NewManager(s.notifier, s.addr)
|
s.eventManager = event.NewManager(s.notifier, s.addr)
|
||||||
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
s.eventManager.SetNotificationsEnabled(s.mNotifications.Checked())
|
||||||
|
s.eventManager.AddHandler(s.handleApprovalEvent)
|
||||||
s.eventManager.AddHandler(func(event *proto.SystemEvent) {
|
s.eventManager.AddHandler(func(event *proto.SystemEvent) {
|
||||||
if event.Category == proto.SystemEvent_SYSTEM {
|
if event.Category == proto.SystemEvent_SYSTEM {
|
||||||
s.updateExitNodes()
|
s.updateExitNodes()
|
||||||
@@ -1355,6 +1415,12 @@ func (s *serviceClient) getSrvConfig() {
|
|||||||
if cfg.SSHJWTCacheTTL != nil {
|
if cfg.SSHJWTCacheTTL != nil {
|
||||||
s.sshJWTCacheTTL = *cfg.SSHJWTCacheTTL
|
s.sshJWTCacheTTL = *cfg.SSHJWTCacheTTL
|
||||||
}
|
}
|
||||||
|
if cfg.ServerVNCAllowed != nil {
|
||||||
|
s.serverVNCAllowed = *cfg.ServerVNCAllowed
|
||||||
|
}
|
||||||
|
if cfg.DisableVNCApproval != nil {
|
||||||
|
s.disableVNCApproval = *cfg.DisableVNCApproval
|
||||||
|
}
|
||||||
|
|
||||||
if s.showAdvancedSettings {
|
if s.showAdvancedSettings {
|
||||||
s.iMngURL.SetText(s.managementURL)
|
s.iMngURL.SetText(s.managementURL)
|
||||||
@@ -1395,6 +1461,12 @@ func (s *serviceClient) getSrvConfig() {
|
|||||||
if cfg.SSHJWTCacheTTL != nil {
|
if cfg.SSHJWTCacheTTL != nil {
|
||||||
s.iSSHJWTCacheTTL.SetText(strconv.Itoa(*cfg.SSHJWTCacheTTL))
|
s.iSSHJWTCacheTTL.SetText(strconv.Itoa(*cfg.SSHJWTCacheTTL))
|
||||||
}
|
}
|
||||||
|
if cfg.ServerVNCAllowed != nil {
|
||||||
|
s.sServerVNCAllowed.SetChecked(*cfg.ServerVNCAllowed)
|
||||||
|
}
|
||||||
|
if cfg.DisableVNCApproval != nil {
|
||||||
|
s.sDisableVNCApproval.SetChecked(*cfg.DisableVNCApproval)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.mNotifications == nil {
|
if s.mNotifications == nil {
|
||||||
@@ -1455,6 +1527,7 @@ func protoConfigToConfig(cfg *proto.GetConfigResponse) *profilemanager.Config {
|
|||||||
config.DisableAutoConnect = cfg.DisableAutoConnect
|
config.DisableAutoConnect = cfg.DisableAutoConnect
|
||||||
config.ServerSSHAllowed = &cfg.ServerSSHAllowed
|
config.ServerSSHAllowed = &cfg.ServerSSHAllowed
|
||||||
config.ServerVNCAllowed = &cfg.ServerVNCAllowed
|
config.ServerVNCAllowed = &cfg.ServerVNCAllowed
|
||||||
|
config.DisableVNCApproval = &cfg.DisableVNCApproval
|
||||||
config.RosenpassEnabled = cfg.RosenpassEnabled
|
config.RosenpassEnabled = cfg.RosenpassEnabled
|
||||||
config.RosenpassPermissive = cfg.RosenpassPermissive
|
config.RosenpassPermissive = cfg.RosenpassPermissive
|
||||||
config.DisableNotifications = &cfg.DisableNotifications
|
config.DisableNotifications = &cfg.DisableNotifications
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func (e *Manager) handleEvent(event *proto.SystemEvent) {
|
|||||||
handlers := slices.Clone(e.handlers)
|
handlers := slices.Clone(e.handlers)
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
|
|
||||||
if event.UserMessage != "" && (enabled || event.Severity == proto.SystemEvent_CRITICAL) && !isV6DefaultRoutePartner(event) {
|
if event.UserMessage != "" && (enabled || event.Severity == proto.SystemEvent_CRITICAL) && !isV6DefaultRoutePartner(event) && event.Category != proto.SystemEvent_APPROVAL {
|
||||||
title := e.getEventTitle(event)
|
title := e.getEventTitle(event)
|
||||||
body := event.UserMessage
|
body := event.UserMessage
|
||||||
id := event.Metadata["id"]
|
id := event.Metadata["id"]
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ func (s *Server) handleServiceConnection(conn net.Conn, sa sessionAgent) {
|
|||||||
}
|
}
|
||||||
s.registerConnAuth(conn, header)
|
s.registerConnAuth(conn, header)
|
||||||
|
|
||||||
|
allow, decision := s.gateApproval(conn, header, authedLog)
|
||||||
|
if !allow {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
socketPath, token, err := sa.Resolve(s.ctx)
|
socketPath, token, err := sa.Resolve(s.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
code := RejectCodeCapturerError
|
code := RejectCodeCapturerError
|
||||||
@@ -87,7 +92,7 @@ func (s *Server) handleServiceConnection(conn net.Conn, sa sessionAgent) {
|
|||||||
Reader: io.MultiReader(&headerBuf, conn),
|
Reader: io.MultiReader(&headerBuf, conn),
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
}
|
}
|
||||||
if err := proxyToAgent(s.ctx, replayConn, socketPath, token); err != nil {
|
if err := proxyToAgent(s.ctx, replayConn, socketPath, token, decision.ViewOnly); err != nil {
|
||||||
rejectConnection(conn, codeMessage(RejectCodeCapturerError, err.Error()))
|
rejectConnection(conn, codeMessage(RejectCodeCapturerError, err.Error()))
|
||||||
authedLog.Warnf("VNC connection rejected: agent unreachable: %v", err)
|
authedLog.Warnf("VNC connection rejected: agent unreachable: %v", err)
|
||||||
return
|
return
|
||||||
@@ -124,12 +129,13 @@ func generateAuthToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// proxyToAgent dials the per-session agent's Unix socket, writes the
|
// proxyToAgent dials the per-session agent's Unix socket, writes the
|
||||||
// raw token bytes, then copies bytes both ways until either side closes.
|
// raw token bytes plus a single view-only flag byte, then copies bytes
|
||||||
// The token must precede any RFB byte so the agent's verifyAgentToken
|
// both ways until either side closes. The token + flag prefix must
|
||||||
// can run first. Returns nil once a stream is established; the caller is
|
// precede any RFB byte so the agent's verifyAgentToken can run first.
|
||||||
// responsible for sending an RFB-level rejection on error so the client
|
// Returns nil once a stream is established; the caller is responsible
|
||||||
// sees a reason instead of a bare timeout.
|
// for sending an RFB-level rejection on error so the client sees a
|
||||||
func proxyToAgent(ctx context.Context, client net.Conn, socketPath, authToken string) error {
|
// reason instead of a bare timeout.
|
||||||
|
func proxyToAgent(ctx context.Context, client net.Conn, socketPath, authToken string, viewOnly bool) error {
|
||||||
tokenBytes, err := hex.DecodeString(authToken)
|
tokenBytes, err := hex.DecodeString(authToken)
|
||||||
if err != nil || len(tokenBytes) != agentTokenLen {
|
if err != nil || len(tokenBytes) != agentTokenLen {
|
||||||
return fmt.Errorf("invalid auth token (len=%d): %w", len(tokenBytes), err)
|
return fmt.Errorf("invalid auth token (len=%d): %w", len(tokenBytes), err)
|
||||||
@@ -140,9 +146,14 @@ func proxyToAgent(ctx context.Context, client net.Conn, socketPath, authToken st
|
|||||||
return fmt.Errorf("dial agent at %s: %w", socketPath, err)
|
return fmt.Errorf("dial agent at %s: %w", socketPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := agentConn.Write(tokenBytes); err != nil {
|
preamble := make([]byte, len(tokenBytes)+1)
|
||||||
|
copy(preamble, tokenBytes)
|
||||||
|
if viewOnly {
|
||||||
|
preamble[len(tokenBytes)] = 1
|
||||||
|
}
|
||||||
|
if _, err := agentConn.Write(preamble); err != nil {
|
||||||
_ = agentConn.Close()
|
_ = agentConn.Close()
|
||||||
return fmt.Errorf("send auth token to agent: %w", err)
|
return fmt.Errorf("send auth preamble to agent: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
|
|
||||||
sshauth "github.com/netbirdio/netbird/client/ssh/auth"
|
sshauth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
sshuserhash "github.com/netbirdio/netbird/shared/sshauth"
|
sshuserhash "github.com/netbirdio/netbird/shared/sshauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+137
-28
@@ -23,7 +23,7 @@ import (
|
|||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
sshauth "github.com/netbirdio/netbird/client/ssh/auth"
|
sshauth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connection modes sent by the client in the session header.
|
// Connection modes sent by the client in the session header.
|
||||||
@@ -36,12 +36,14 @@ const (
|
|||||||
// stable so clients can branch on them without parsing free text.
|
// stable so clients can branch on them without parsing free text.
|
||||||
// Format: "CODE: human message".
|
// Format: "CODE: human message".
|
||||||
const (
|
const (
|
||||||
RejectCodeAuthForbidden = "AUTH_FORBIDDEN"
|
RejectCodeAuthForbidden = "AUTH_FORBIDDEN"
|
||||||
RejectCodeSessionError = "SESSION_ERROR"
|
RejectCodeSessionError = "SESSION_ERROR"
|
||||||
RejectCodeCapturerError = "CAPTURER_ERROR"
|
RejectCodeCapturerError = "CAPTURER_ERROR"
|
||||||
RejectCodeUnsupportedOS = "UNSUPPORTED"
|
RejectCodeUnsupportedOS = "UNSUPPORTED"
|
||||||
RejectCodeBadRequest = "BAD_REQUEST"
|
RejectCodeBadRequest = "BAD_REQUEST"
|
||||||
RejectCodeNoConsoleUser = "NO_CONSOLE_USER"
|
RejectCodeNoConsoleUser = "NO_CONSOLE_USER"
|
||||||
|
RejectCodeApprovalDenied = "APPROVAL_DENIED"
|
||||||
|
RejectCodeNoApprover = "NO_APPROVER"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnvVNCDisableDownscale disables any platform-specific framebuffer
|
// EnvVNCDisableDownscale disables any platform-specific framebuffer
|
||||||
@@ -173,11 +175,11 @@ type Server struct {
|
|||||||
network netip.Prefix
|
network netip.Prefix
|
||||||
log *log.Entry
|
log *log.Entry
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
vmgr virtualSessionManager
|
vmgr virtualSessionManager
|
||||||
authorizer *sshauth.Authorizer
|
authorizer *sshauth.Authorizer
|
||||||
netstackNet *netstack.Net
|
netstackNet *netstack.Net
|
||||||
// agentToken holds the raw token bytes for agent-mode auth.
|
// agentToken holds the raw token bytes for agent-mode auth.
|
||||||
@@ -216,6 +218,14 @@ type Server struct {
|
|||||||
// this to its metrics framework.
|
// this to its metrics framework.
|
||||||
sessionRecorder func(SessionTick)
|
sessionRecorder func(SessionTick)
|
||||||
|
|
||||||
|
// requireApproval enables the per-connection user-accept gate. When
|
||||||
|
// true and approver is nil (or returns an error), the connection is
|
||||||
|
// rejected before any agent or session work.
|
||||||
|
requireApproval bool
|
||||||
|
// approver prompts the local user (via the daemon→UI event channel)
|
||||||
|
// to accept or deny each incoming connection.
|
||||||
|
approver Approver
|
||||||
|
|
||||||
// preListener, when non-nil, replaces the TCP listener Start would
|
// preListener, when non-nil, replaces the TCP listener Start would
|
||||||
// open; addr/network args to Start are ignored. Used by the agent's
|
// open; addr/network args to Start are ignored. Used by the agent's
|
||||||
// Unix-socket path.
|
// Unix-socket path.
|
||||||
@@ -275,6 +285,40 @@ type Config struct {
|
|||||||
// addr/network args to Start are then ignored. The agent uses this to
|
// addr/network args to Start are then ignored. The agent uses this to
|
||||||
// listen on a Unix socket.
|
// listen on a Unix socket.
|
||||||
Listener net.Listener
|
Listener net.Listener
|
||||||
|
// RequireApproval gates each accepted connection on a user-side accept
|
||||||
|
// prompt before the proxy/session starts. Requires Approver to be set;
|
||||||
|
// otherwise the gate fails closed.
|
||||||
|
RequireApproval bool
|
||||||
|
// Approver brokers the per-connection prompt to the local user via the
|
||||||
|
// daemon→UI event channel. Nil disables the gate.
|
||||||
|
Approver Approver
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approver decouples the VNC server from the approval broker. A non-nil
|
||||||
|
// error means "do not proceed".
|
||||||
|
type Approver interface {
|
||||||
|
Request(ctx context.Context, info ApprovalInfo) (ApprovalDecision, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApprovalDecision carries the parts of the user's response the VNC
|
||||||
|
// server acts on. Accept is implicit (errors signal deny). ViewOnly puts
|
||||||
|
// the session into read-only mode: the server drops input events.
|
||||||
|
type ApprovalDecision struct {
|
||||||
|
ViewOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApprovalInfo describes the pending connection passed to the approver.
|
||||||
|
// Fields are best-effort; any may be empty.
|
||||||
|
type ApprovalInfo struct {
|
||||||
|
PeerName string
|
||||||
|
PeerPubKey string
|
||||||
|
SourceIP string
|
||||||
|
Mode string
|
||||||
|
Username string
|
||||||
|
// Initiator is the display name of the user who initiated the
|
||||||
|
// connection (typically the dashboard user). Resolved from the
|
||||||
|
// Noise-verified client static pubkey.
|
||||||
|
Initiator string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a VNC server from the provided Config. IdentityKey is the
|
// New creates a VNC server from the provided Config. IdentityKey is the
|
||||||
@@ -287,6 +331,8 @@ func New(cfg Config) *Server {
|
|||||||
identityKey: cfg.IdentityKey,
|
identityKey: cfg.IdentityKey,
|
||||||
serviceMode: cfg.ServiceMode,
|
serviceMode: cfg.ServiceMode,
|
||||||
sessionRecorder: cfg.SessionRecorder,
|
sessionRecorder: cfg.SessionRecorder,
|
||||||
|
requireApproval: cfg.RequireApproval,
|
||||||
|
approver: cfg.Approver,
|
||||||
disableAuth: cfg.DisableAuth,
|
disableAuth: cfg.DisableAuth,
|
||||||
netstackNet: cfg.NetstackNet,
|
netstackNet: cfg.NetstackNet,
|
||||||
preListener: cfg.Listener,
|
preListener: cfg.Listener,
|
||||||
@@ -377,6 +423,59 @@ func (s *Server) untrackConn(c net.Conn) {
|
|||||||
s.sessionsMu.Unlock()
|
s.sessionsMu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gateApproval prompts the local user to accept or deny conn before any
|
||||||
|
// session resources are allocated. On rejection the conn already received
|
||||||
|
// an RFB reject reason; the gate does not close it.
|
||||||
|
func (s *Server) gateApproval(conn net.Conn, header *connectionHeader, connLog *log.Entry) (bool, ApprovalDecision) {
|
||||||
|
if !s.requireApproval {
|
||||||
|
return true, ApprovalDecision{}
|
||||||
|
}
|
||||||
|
if s.approver == nil {
|
||||||
|
rejectConnection(conn, codeMessage(RejectCodeNoApprover, "approval required but no approver configured"))
|
||||||
|
connLog.Warn("VNC connection rejected: approval required but no approver")
|
||||||
|
return false, ApprovalDecision{}
|
||||||
|
}
|
||||||
|
info := ApprovalInfo{
|
||||||
|
SourceIP: sourceIPString(conn.RemoteAddr()),
|
||||||
|
Mode: modeString(header.mode),
|
||||||
|
Username: header.username,
|
||||||
|
}
|
||||||
|
if len(header.clientStatic) == 32 {
|
||||||
|
info.PeerPubKey = hex.EncodeToString(header.clientStatic)
|
||||||
|
if s.authorizer != nil {
|
||||||
|
info.Initiator = s.authorizer.LookupSessionDisplayName(header.clientStatic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decision, err := s.approver.Request(s.ctx, info)
|
||||||
|
if err != nil {
|
||||||
|
rejectConnection(conn, codeMessage(RejectCodeApprovalDenied, err.Error()))
|
||||||
|
connLog.Infof("VNC connection rejected: approval %v", err)
|
||||||
|
return false, ApprovalDecision{}
|
||||||
|
}
|
||||||
|
if decision.ViewOnly {
|
||||||
|
connLog.Info("VNC connection approved by user (view-only)")
|
||||||
|
} else {
|
||||||
|
connLog.Info("VNC connection approved by user")
|
||||||
|
}
|
||||||
|
return true, decision
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceIPString returns the IP portion of a remote address, or the full
|
||||||
|
// string when no port is present (e.g. unix sockets).
|
||||||
|
func sourceIPString(addr net.Addr) string {
|
||||||
|
if addr == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if ta, ok := addr.(*net.TCPAddr); ok && ta != nil {
|
||||||
|
return ta.IP.String()
|
||||||
|
}
|
||||||
|
host, _, err := net.SplitHostPort(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return addr.String()
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
// registerConnAuth records the verified Noise_IK identity for a live
|
// registerConnAuth records the verified Noise_IK identity for a live
|
||||||
// connection so UpdateVNCAuth can later revoke it if policy changes.
|
// connection so UpdateVNCAuth can later revoke it if policy changes.
|
||||||
// No-op when auth is disabled (e.g. agent-mode loopback connections).
|
// No-op when auth is disabled (e.g. agent-mode loopback connections).
|
||||||
@@ -673,7 +772,8 @@ func (s *Server) handleConnection(conn net.Conn) {
|
|||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !s.verifyAgentToken(conn, connLog) {
|
ok, agentViewOnly := s.verifyAgentToken(conn, connLog)
|
||||||
|
if !ok {
|
||||||
connLog.Info("VNC connection rejected: agent token check failed")
|
connLog.Info("VNC connection rejected: agent token check failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -683,13 +783,19 @@ func (s *Server) handleConnection(conn net.Conn) {
|
|||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connLog, sessionUserID, ok := s.authorizeSession(conn, header, connLog)
|
var sessionUserID string
|
||||||
|
connLog, sessionUserID, ok = s.authorizeSession(conn, header, connLog)
|
||||||
if !ok {
|
if !ok {
|
||||||
connLog.Info("VNC connection rejected: auth failed")
|
connLog.Info("VNC connection rejected: auth failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.registerConnAuth(conn, header)
|
s.registerConnAuth(conn, header)
|
||||||
|
|
||||||
|
allow, decision := s.gateApproval(conn, header, connLog)
|
||||||
|
if !allow {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
capturer, injector, sessionCleanup, ok := s.acquireSessionResources(conn, header, &connLog)
|
capturer, injector, sessionCleanup, ok := s.acquireSessionResources(conn, header, &connLog)
|
||||||
if !ok {
|
if !ok {
|
||||||
connLog.Warn("VNC connection rejected: capturer/injector unavailable")
|
connLog.Warn("VNC connection rejected: capturer/injector unavailable")
|
||||||
@@ -726,6 +832,7 @@ func (s *Server) handleConnection(conn net.Conn) {
|
|||||||
serverW: w,
|
serverW: w,
|
||||||
serverH: h,
|
serverH: h,
|
||||||
log: connLog,
|
log: connLog,
|
||||||
|
viewOnly: decision.ViewOnly || agentViewOnly,
|
||||||
}
|
}
|
||||||
sess.serve()
|
sess.serve()
|
||||||
connLog.Infof("VNC connection closed (%dms)", time.Since(start).Milliseconds())
|
connLog.Infof("VNC connection closed (%dms)", time.Since(start).Milliseconds())
|
||||||
@@ -791,8 +898,9 @@ func (s *Server) authenticateSession(header *connectionHeader) (string, error) {
|
|||||||
var vncIdentityMagic = []byte("NBV3")
|
var vncIdentityMagic = []byte("NBV3")
|
||||||
|
|
||||||
// Noise_IK_25519_ChaChaPoly_SHA256 message sizes (with empty payloads).
|
// Noise_IK_25519_ChaChaPoly_SHA256 message sizes (with empty payloads).
|
||||||
// msg1 = e(32) + s_AEAD(32+16) + payload_AEAD(0+16) = 96 bytes
|
//
|
||||||
// msg2 = e(32) + payload_AEAD(0+16) = 48 bytes
|
// msg1 = e(32) + s_AEAD(32+16) + payload_AEAD(0+16) = 96 bytes
|
||||||
|
// msg2 = e(32) + payload_AEAD(0+16) = 48 bytes
|
||||||
const (
|
const (
|
||||||
noiseInitiatorMsgLen = 96
|
noiseInitiatorMsgLen = 96
|
||||||
noiseResponderMsgLen = 48
|
noiseResponderMsgLen = 48
|
||||||
@@ -929,39 +1037,40 @@ func (s *Server) maybeRunNoiseHandshake(conn net.Conn, br *bufio.Reader) ([]byte
|
|||||||
return clientStatic, true, nil
|
return clientStatic, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyAgentToken validates the agent token prefix when configured. Returns
|
// verifyAgentToken validates the agent token prefix when configured and
|
||||||
// false when the token is invalid or unreadable; the connection is closed.
|
// reads the trailing view-only flag byte the daemon writes alongside it.
|
||||||
func (s *Server) verifyAgentToken(conn net.Conn, connLog *log.Entry) bool {
|
// Returns (ok, viewOnly). ok=false closes the connection.
|
||||||
|
func (s *Server) verifyAgentToken(conn net.Conn, connLog *log.Entry) (bool, bool) {
|
||||||
if len(s.agentToken) == 0 {
|
if len(s.agentToken) == 0 {
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
buf := make([]byte, len(s.agentToken))
|
buf := make([]byte, len(s.agentToken)+1)
|
||||||
if err := conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
if err := conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
||||||
connLog.Debugf("set agent token deadline: %v", err)
|
connLog.Debugf("set agent token deadline: %v", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||||
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
// Connect-then-close probes (port liveness checks) hit this
|
// Connect-then-close probes (port liveness checks) hit this
|
||||||
// path on every dial; logging them would just flood the
|
// path on every dial; logging them would just flood the
|
||||||
// daemon log without surfacing a real failure.
|
// daemon log without surfacing a real failure.
|
||||||
connLog.Tracef("agent auth: read token: %v", err)
|
connLog.Tracef("agent auth: read preamble: %v", err)
|
||||||
} else {
|
} else {
|
||||||
connLog.Warnf("agent auth: read token: %v", err)
|
connLog.Warnf("agent auth: read preamble: %v", err)
|
||||||
}
|
}
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
if err := conn.SetReadDeadline(time.Time{}); err != nil {
|
if err := conn.SetReadDeadline(time.Time{}); err != nil {
|
||||||
connLog.Debugf("clear agent token deadline: %v", err)
|
connLog.Debugf("clear agent token deadline: %v", err)
|
||||||
}
|
}
|
||||||
if subtle.ConstantTimeCompare(buf, s.agentToken) != 1 {
|
if subtle.ConstantTimeCompare(buf[:len(s.agentToken)], s.agentToken) != 1 {
|
||||||
connLog.Warn("agent auth: invalid token, rejecting")
|
connLog.Warn("agent auth: invalid token, rejecting")
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return false
|
return false, false
|
||||||
}
|
}
|
||||||
return true
|
return true, buf[len(s.agentToken)] != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// authorizeSession runs the Noise_IK handshake when auth is enabled.
|
// authorizeSession runs the Noise_IK handshake when auth is enabled.
|
||||||
|
|||||||
@@ -3,15 +3,19 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -333,3 +337,180 @@ func TestSessionMode_RejectedWhenNoVMGR(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Contains(t, string(reason), RejectCodeUnsupportedOS)
|
assert.Contains(t, string(reason), RejectCodeUnsupportedOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recordingApprover lets gate tests choose the outcome of the approval
|
||||||
|
// prompt and verify how often (and with what info) the gate calls it.
|
||||||
|
type recordingApprover struct {
|
||||||
|
calls atomic.Int32
|
||||||
|
lastIn ApprovalInfo
|
||||||
|
decision ApprovalDecision
|
||||||
|
respond error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *recordingApprover) Request(_ context.Context, info ApprovalInfo) (ApprovalDecision, error) {
|
||||||
|
r.calls.Add(1)
|
||||||
|
r.lastIn = info
|
||||||
|
if r.respond != nil {
|
||||||
|
return ApprovalDecision{}, r.respond
|
||||||
|
}
|
||||||
|
return r.decision, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// drainRejectClient simulates a remote VNC client just enough that
|
||||||
|
// rejectConnection's handshake-half completes promptly: it reads the
|
||||||
|
// server's "RFB 003.008\n", writes back a placeholder client version, and
|
||||||
|
// drains until EOF. Without this the rejectConnection path would block
|
||||||
|
// for up to two seconds on its SetReadDeadline.
|
||||||
|
func drainRejectClient(t *testing.T, c net.Conn) {
|
||||||
|
t.Helper()
|
||||||
|
go func() {
|
||||||
|
defer c.Close()
|
||||||
|
var srvVer [12]byte
|
||||||
|
if _, err := io.ReadFull(c, srvVer[:]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = c.Write([]byte("RFB 003.008\n"))
|
||||||
|
_, _ = io.Copy(io.Discard, c)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newGateConn returns a server-side conn and a client-side conn linked by
|
||||||
|
// net.Pipe, with the client-side already draining so gateApproval's
|
||||||
|
// rejectConnection path completes without blocking the test.
|
||||||
|
func newGateConn(t *testing.T) net.Conn {
|
||||||
|
t.Helper()
|
||||||
|
srv, cli := net.Pipe()
|
||||||
|
drainRejectClient(t, cli)
|
||||||
|
t.Cleanup(func() { _ = srv.Close() })
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func gateTestServer(requireApproval bool, approver Approver) *Server {
|
||||||
|
return &Server{
|
||||||
|
log: log.WithField("test", "gate"),
|
||||||
|
requireApproval: requireApproval,
|
||||||
|
approver: approver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGateApproval_Disabled_NoApproverCall: when the feature is off the
|
||||||
|
// gate must short-circuit before consulting any approver. A nil approver
|
||||||
|
// must NOT mean "deny" here — that would break upgrades for peers that
|
||||||
|
// haven't opted in yet.
|
||||||
|
func TestGateApproval_Disabled_NoApproverCall(t *testing.T) {
|
||||||
|
app := &recordingApprover{}
|
||||||
|
srv := gateTestServer(false, app)
|
||||||
|
|
||||||
|
conn := newGateConn(t)
|
||||||
|
defer conn.Close()
|
||||||
|
header := &connectionHeader{mode: ModeAttach}
|
||||||
|
|
||||||
|
allowed, _ := srv.gateApproval(conn, header, srv.log)
|
||||||
|
assert.True(t, allowed, "gate must pass through when requireApproval is false")
|
||||||
|
assert.Equal(t, int32(0), app.calls.Load(), "approver must not be called when disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGateApproval_Enabled_NilApproverDenies is the most important
|
||||||
|
// regression test for "no silent bypass": if the feature is enabled but
|
||||||
|
// the broker wasn't wired (a misconfiguration), the gate must REJECT,
|
||||||
|
// not pass through. The reject code must be the dedicated NO_APPROVER so
|
||||||
|
// the failure is unambiguous in logs and on the client side.
|
||||||
|
func TestGateApproval_Enabled_NilApproverDenies(t *testing.T) {
|
||||||
|
srv := gateTestServer(true, nil)
|
||||||
|
|
||||||
|
srvConn, cliConn := net.Pipe()
|
||||||
|
defer srvConn.Close()
|
||||||
|
defer cliConn.Close()
|
||||||
|
|
||||||
|
// Capture the reject reason the gate sends.
|
||||||
|
rejectReason := make(chan string, 1)
|
||||||
|
go func() {
|
||||||
|
var srvVer [12]byte
|
||||||
|
_, _ = io.ReadFull(cliConn, srvVer[:])
|
||||||
|
_, _ = cliConn.Write([]byte("RFB 003.008\n"))
|
||||||
|
// Server sends: 1 byte (numTypes=0), 4 bytes (reason len), reason.
|
||||||
|
var numTypes [1]byte
|
||||||
|
_, _ = io.ReadFull(cliConn, numTypes[:])
|
||||||
|
var lenBuf [4]byte
|
||||||
|
_, _ = io.ReadFull(cliConn, lenBuf[:])
|
||||||
|
reason := make([]byte, binary.BigEndian.Uint32(lenBuf[:]))
|
||||||
|
_, _ = io.ReadFull(cliConn, reason)
|
||||||
|
rejectReason <- string(reason)
|
||||||
|
}()
|
||||||
|
|
||||||
|
header := &connectionHeader{mode: ModeAttach}
|
||||||
|
allowed, _ := srv.gateApproval(srvConn, header, srv.log)
|
||||||
|
assert.False(t, allowed, "missing approver MUST deny; never silently pass")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case reason := <-rejectReason:
|
||||||
|
assert.Contains(t, reason, RejectCodeNoApprover, "reject code must surface the misconfiguration cause")
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("did not observe rejection reason")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGateApproval_ApproverDenies maps every approver error to a deny.
|
||||||
|
// We assert against every Err* the broker can produce so a future caller
|
||||||
|
// adding a new error doesn't accidentally fall into a default-allow.
|
||||||
|
func TestGateApproval_ApproverDenies(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"denied", errors.New("user denied")},
|
||||||
|
{"timeout", errors.New("approval timed out")},
|
||||||
|
{"no_subscriber", errors.New("no UI subscriber connected for approval")},
|
||||||
|
{"ctx_canceled", context.Canceled},
|
||||||
|
{"misc", errors.New("anything else")},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
app := &recordingApprover{respond: tc.err}
|
||||||
|
srv := gateTestServer(true, app)
|
||||||
|
conn := newGateConn(t)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
header := &connectionHeader{mode: ModeAttach}
|
||||||
|
allowed, _ := srv.gateApproval(conn, header, srv.log)
|
||||||
|
assert.False(t, allowed, "approver error %v must deny", tc.err)
|
||||||
|
assert.Equal(t, int32(1), app.calls.Load())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGateApproval_ApproverAccepts confirms the happy path actually
|
||||||
|
// returns true so we know the deny path is not the only outcome the
|
||||||
|
// gate can produce.
|
||||||
|
func TestGateApproval_ApproverAccepts(t *testing.T) {
|
||||||
|
app := &recordingApprover{respond: nil}
|
||||||
|
srv := gateTestServer(true, app)
|
||||||
|
conn := newGateConn(t)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
header := &connectionHeader{mode: ModeAttach, username: "alice"}
|
||||||
|
allowed, _ := srv.gateApproval(conn, header, srv.log)
|
||||||
|
assert.True(t, allowed, "approver returning nil must let the gate pass")
|
||||||
|
assert.Equal(t, int32(1), app.calls.Load())
|
||||||
|
assert.Equal(t, "alice", app.lastIn.Username, "header username must reach the approver")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGateApproval_PassesPubKeyHex confirms the gate hex-encodes the
|
||||||
|
// 32-byte client static key into ApprovalInfo.PeerPubKey so the prompt's
|
||||||
|
// metadata identifies which peer is connecting. A wrong-length key must
|
||||||
|
// NOT bypass the gate; it just won't populate the field.
|
||||||
|
func TestGateApproval_PassesPubKeyHex(t *testing.T) {
|
||||||
|
app := &recordingApprover{respond: nil}
|
||||||
|
srv := gateTestServer(true, app)
|
||||||
|
conn := newGateConn(t)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
pub := make([]byte, 32)
|
||||||
|
for i := range pub {
|
||||||
|
pub[i] = byte(i)
|
||||||
|
}
|
||||||
|
header := &connectionHeader{mode: ModeAttach, clientStatic: pub}
|
||||||
|
allowed, _ := srv.gateApproval(conn, header, srv.log)
|
||||||
|
assert.True(t, allowed)
|
||||||
|
assert.Equal(t, hex.EncodeToString(pub), app.lastIn.PeerPubKey)
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ type session struct {
|
|||||||
serverH int
|
serverH int
|
||||||
desktopName string
|
desktopName string
|
||||||
log *log.Entry
|
log *log.Entry
|
||||||
|
// viewOnly drops KeyEvent / PointerEvent (legacy + QEMU + extended)
|
||||||
|
// without invoking the injector when the user approved the
|
||||||
|
// connection in view-only mode. The bytes are still consumed off the
|
||||||
|
// wire so the protocol stays in sync.
|
||||||
|
viewOnly bool
|
||||||
|
|
||||||
writeMu sync.Mutex
|
writeMu sync.Mutex
|
||||||
// encMu guards the negotiated pixel format and encoding state below.
|
// encMu guards the negotiated pixel format and encoding state below.
|
||||||
@@ -161,6 +166,15 @@ func (s *session) serve() {
|
|||||||
}
|
}
|
||||||
s.log.Infof("client connected: %s", s.addr())
|
s.log.Infof("client connected: %s", s.addr())
|
||||||
|
|
||||||
|
// View-only clients can't move the pointer, so default to compositing
|
||||||
|
// the host cursor into the framebuffer. The client can still send
|
||||||
|
// ShowRemoteCursor to turn it off.
|
||||||
|
if s.viewOnly {
|
||||||
|
s.encMu.Lock()
|
||||||
|
s.showRemoteCursor = true
|
||||||
|
s.encMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// On any exit path (clean disconnect, transport error, panic) release
|
// On any exit path (clean disconnect, transport error, panic) release
|
||||||
// modifier keys and mouse buttons so the host doesn't end up with
|
// modifier keys and mouse buttons so the host doesn't end up with
|
||||||
// Shift/Ctrl/Alt or a mouse button stuck because the client dropped
|
// Shift/Ctrl/Alt or a mouse button stuck because the client dropped
|
||||||
@@ -226,8 +240,10 @@ func (s *session) handshake() error {
|
|||||||
// mode, username) that precedes the RFB handshake; the protocol-level
|
// mode, username) that precedes the RFB handshake; the protocol-level
|
||||||
// password scheme is not supported.
|
// password scheme is not supported.
|
||||||
func (s *session) sendSecurityTypes() error {
|
func (s *session) sendSecurityTypes() error {
|
||||||
_, err := s.conn.Write([]byte{1, secNone})
|
if _, err := s.conn.Write([]byte{1, secNone}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) handleSecurity(secType byte) error {
|
func (s *session) handleSecurity(secType byte) error {
|
||||||
@@ -237,11 +253,20 @@ func (s *session) handleSecurity(secType byte) error {
|
|||||||
return binary.Write(s.conn, binary.BigEndian, uint32(0))
|
return binary.Write(s.conn, binary.BigEndian, uint32(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewOnlyDesktopNamePrefix tags the RFB desktop name when the host
|
||||||
|
// approved the connection in view-only mode, so a NetBird-aware client
|
||||||
|
// can switch its UI into read-only state. NUL framing guarantees no
|
||||||
|
// collision with a user-set name.
|
||||||
|
const ViewOnlyDesktopNamePrefix = "\x00NB-VIEW-ONLY\x00"
|
||||||
|
|
||||||
func (s *session) sendServerInit() error {
|
func (s *session) sendServerInit() error {
|
||||||
desktop := s.desktopName
|
desktop := s.desktopName
|
||||||
if desktop == "" {
|
if desktop == "" {
|
||||||
desktop = "NetBird VNC"
|
desktop = "NetBird VNC"
|
||||||
}
|
}
|
||||||
|
if s.viewOnly {
|
||||||
|
desktop = ViewOnlyDesktopNamePrefix + desktop
|
||||||
|
}
|
||||||
name := []byte(desktop)
|
name := []byte(desktop)
|
||||||
buf := make([]byte, 0, 4+16+4+len(name))
|
buf := make([]byte, 0, 4+16+4+len(name))
|
||||||
|
|
||||||
@@ -259,8 +284,10 @@ func (s *session) sendServerInit() error {
|
|||||||
)
|
)
|
||||||
buf = append(buf, name...)
|
buf = append(buf, name...)
|
||||||
|
|
||||||
_, err := s.conn.Write(buf)
|
if _, err := s.conn.Write(buf); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) messageLoop() error {
|
func (s *session) messageLoop() error {
|
||||||
@@ -536,8 +563,10 @@ func (s *session) SendDesktopName(name string) error {
|
|||||||
if _, err := s.conn.Write(header); err != nil {
|
if _, err := s.conn.Write(header); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err := s.conn.Write(body)
|
if _, err := s.conn.Write(body); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) handleKeyEvent() error {
|
func (s *session) handleKeyEvent() error {
|
||||||
@@ -545,6 +574,9 @@ func (s *session) handleKeyEvent() error {
|
|||||||
if _, err := io.ReadFull(s.conn, data[:]); err != nil {
|
if _, err := io.ReadFull(s.conn, data[:]); err != nil {
|
||||||
return fmt.Errorf("read KeyEvent: %w", err)
|
return fmt.Errorf("read KeyEvent: %w", err)
|
||||||
}
|
}
|
||||||
|
if s.viewOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
down := data[0] == 1
|
down := data[0] == 1
|
||||||
keysym := binary.BigEndian.Uint32(data[3:7])
|
keysym := binary.BigEndian.Uint32(data[3:7])
|
||||||
s.injector.InjectKey(keysym, down)
|
s.injector.InjectKey(keysym, down)
|
||||||
@@ -565,6 +597,9 @@ func (s *session) handleQEMUMessage() error {
|
|||||||
s.log.Tracef("ignoring QEMU subtype %d", subtype)
|
s.log.Tracef("ignoring QEMU subtype %d", subtype)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if s.viewOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
down := binary.BigEndian.Uint16(data[1:3]) != 0
|
down := binary.BigEndian.Uint16(data[1:3]) != 0
|
||||||
keysym := binary.BigEndian.Uint32(data[3:7])
|
keysym := binary.BigEndian.Uint32(data[3:7])
|
||||||
scancode := binary.BigEndian.Uint32(data[7:11])
|
scancode := binary.BigEndian.Uint32(data[7:11])
|
||||||
@@ -598,6 +633,9 @@ func (s *session) handlePointerEvent() error {
|
|||||||
s.lastPointerX = x
|
s.lastPointerX = x
|
||||||
s.lastPointerY = y
|
s.lastPointerY = y
|
||||||
s.pointerMu.Unlock()
|
s.pointerMu.Unlock()
|
||||||
|
if s.viewOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
s.injector.InjectPointer(mask, x, y, s.serverW, s.serverH)
|
s.injector.InjectPointer(mask, x, y, s.serverW, s.serverH)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,16 +58,14 @@ func NewSessionKey() (string, []byte, error) {
|
|||||||
return id, kp.Public, nil
|
return id, kp.Public, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// consumeSessionKey atomically retrieves and removes the keypair for id.
|
// lookupSessionKey returns the keypair for id. Keys stay live for the
|
||||||
// A session handle is single-use; combining lookup and delete under one
|
// WASM lifetime so the same session handle can drive multiple VNC
|
||||||
// critical section prevents concurrent callers from observing the same key.
|
// connections (reconnect, multiple peers, etc.). The handle is just an
|
||||||
func consumeSessionKey(id string) (noise.DHKey, bool) {
|
// opaque map key; the private half never leaves wasm.
|
||||||
|
func lookupSessionKey(id string) (noise.DHKey, bool) {
|
||||||
sessionKeyStore.mu.Lock()
|
sessionKeyStore.mu.Lock()
|
||||||
defer sessionKeyStore.mu.Unlock()
|
defer sessionKeyStore.mu.Unlock()
|
||||||
kp, ok := sessionKeyStore.keys[id]
|
kp, ok := sessionKeyStore.keys[id]
|
||||||
if ok {
|
|
||||||
delete(sessionKeyStore.keys, id)
|
|
||||||
}
|
|
||||||
return kp, ok
|
return kp, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +185,7 @@ func (p *VNCProxy) CreateProxy(req ProxyRequest) js.Value {
|
|||||||
height: height,
|
height: height,
|
||||||
}
|
}
|
||||||
if req.KeySessionID != "" {
|
if req.KeySessionID != "" {
|
||||||
kp, ok := consumeSessionKey(req.KeySessionID)
|
kp, ok := lookupSessionKey(req.KeySessionID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return rejectedPromise("unknown VNC session id")
|
return rejectedPromise("unknown VNC session id")
|
||||||
}
|
}
|
||||||
@@ -217,11 +215,11 @@ func decodePeerPubKey(b64 string) ([]byte, error) {
|
|||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// rejectedPromise returns a resolved Promise carrying msg as an error
|
// rejectedPromise returns a rejected Promise carrying msg as the
|
||||||
// string, mirroring how CreateProxy reports earlier validation failures.
|
// reason. Callers in JS see this via `await ...` throwing.
|
||||||
func rejectedPromise(msg string) js.Value {
|
func rejectedPromise(msg string) js.Value {
|
||||||
promise := js.Global().Get("Promise")
|
promise := js.Global().Get("Promise")
|
||||||
return promise.Call("resolve", js.ValueOf(msg))
|
return promise.Call("reject", js.ValueOf(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// newProxyPromise wraps the JS Promise creation + executor lifecycle so
|
// newProxyPromise wraps the JS Promise creation + executor lifecycle so
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
|
integrationsConfig "github.com/netbirdio/management-integrations/integrations/config"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/ssh/auth"
|
auth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
|
||||||
@@ -223,8 +223,9 @@ func buildSessionPubKeysProto(ctx context.Context, in []types.VNCSessionPubKey)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out = append(out, &proto.SessionPubKey{
|
out = append(out, &proto.SessionPubKey{
|
||||||
PubKey: pub,
|
PubKey: pub,
|
||||||
UserIdHash: hash[:],
|
UserIdHash: hash[:],
|
||||||
|
DisplayName: e.DisplayName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
||||||
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
||||||
"github.com/netbirdio/netbird/management/server/types"
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
|
"github.com/netbirdio/netbird/shared/auth"
|
||||||
"github.com/netbirdio/netbird/shared/management/http/api"
|
"github.com/netbirdio/netbird/shared/management/http/api"
|
||||||
"github.com/netbirdio/netbird/shared/management/http/util"
|
"github.com/netbirdio/netbird/shared/management/http/util"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
@@ -525,6 +526,7 @@ func (h *Handler) CreateTemporaryAccess(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
policy.Rules[0].SessionPubKey = pubKey
|
policy.Rules[0].SessionPubKey = pubKey
|
||||||
|
policy.Rules[0].SessionDisplayName = h.displayNameForUser(r.Context(), userAuth)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.accountManager.SavePolicy(r.Context(), userAuth.AccountId, userAuth.UserId, policy, true)
|
_, err = h.accountManager.SavePolicy(r.Context(), userAuth.AccountId, userAuth.UserId, policy, true)
|
||||||
@@ -744,3 +746,30 @@ func peerIPv6String(peer *nbpeer.Peer) *string {
|
|||||||
s := peer.IPv6.String()
|
s := peer.IPv6.String()
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// displayNameForUser returns a human-readable label for the requesting
|
||||||
|
// user suitable for a VNC approval prompt. Tries the IdP-resolved
|
||||||
|
// UserInfo first (carries name / email management caches from the
|
||||||
|
// identity provider) and falls through to JWT claims, then user id.
|
||||||
|
// Errors from the lookup don't fail the request; we just degrade.
|
||||||
|
func (h *Handler) displayNameForUser(ctx context.Context, u auth.UserAuth) string {
|
||||||
|
if info, err := h.accountManager.GetCurrentUserInfo(ctx, u); err == nil && info != nil {
|
||||||
|
switch {
|
||||||
|
case info.UserInfo != nil && info.UserInfo.Name != "":
|
||||||
|
return info.UserInfo.Name
|
||||||
|
case info.UserInfo != nil && info.UserInfo.Email != "":
|
||||||
|
return info.UserInfo.Email
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
log.WithContext(ctx).Debugf("display name: GetCurrentUserInfo: %v", err)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case u.PreferredName != "":
|
||||||
|
return u.PreferredName
|
||||||
|
case u.Name != "":
|
||||||
|
return u.Name
|
||||||
|
case u.Email != "":
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
return u.UserId
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/ssh/auth"
|
auth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
proxydomain "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain"
|
proxydomain "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/ssh/auth"
|
auth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/ssh/auth"
|
auth "github.com/netbirdio/netbird/shared/sessionauth"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +28,9 @@ type VNCSessionPubKey struct {
|
|||||||
PubKey string
|
PubKey string
|
||||||
// UserID is the unhashed user identity the pubkey authenticates as.
|
// UserID is the unhashed user identity the pubkey authenticates as.
|
||||||
UserID string
|
UserID string
|
||||||
|
// DisplayName is a human-readable label for UserID, used by the host
|
||||||
|
// peer's approval prompt. Empty when not provided.
|
||||||
|
DisplayName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ruleAuthCallbacks lets Account and NetworkMapComponents share the per-rule
|
// ruleAuthCallbacks lets Account and NetworkMapComponents share the per-rule
|
||||||
@@ -83,8 +86,9 @@ func (cb ruleAuthCallbacks) handleVNCRule(rule *PolicyRule, peerInSources, peerI
|
|||||||
cb.collectVNCUsers(rule, state.vncAuthorizedUsers)
|
cb.collectVNCUsers(rule, state.vncAuthorizedUsers)
|
||||||
if peerInDestinations && rule.SessionPubKey != "" && rule.AuthorizedUser != "" {
|
if peerInDestinations && rule.SessionPubKey != "" && rule.AuthorizedUser != "" {
|
||||||
state.vncSessionPubKeys = append(state.vncSessionPubKeys, VNCSessionPubKey{
|
state.vncSessionPubKeys = append(state.vncSessionPubKeys, VNCSessionPubKey{
|
||||||
PubKey: rule.SessionPubKey,
|
PubKey: rule.SessionPubKey,
|
||||||
UserID: rule.AuthorizedUser,
|
UserID: rule.AuthorizedUser,
|
||||||
|
DisplayName: rule.SessionDisplayName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,13 @@ type PolicyRule struct {
|
|||||||
// AuthorizedUser when the rule was created via temporary-access for a
|
// AuthorizedUser when the rule was created via temporary-access for a
|
||||||
// VNC scope; empty otherwise.
|
// VNC scope; empty otherwise.
|
||||||
SessionPubKey string
|
SessionPubKey string
|
||||||
|
|
||||||
|
// SessionDisplayName is a human-readable label for the user the
|
||||||
|
// SessionPubKey was issued to (typically display name, falling back
|
||||||
|
// to email or user id). The daemon surfaces it on the host's
|
||||||
|
// per-connection approval prompt so the user being asked can
|
||||||
|
// recognise who is requesting access.
|
||||||
|
SessionDisplayName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of a policy rule
|
// Copy returns a copy of a policy rule
|
||||||
@@ -116,6 +123,7 @@ func (pm *PolicyRule) Copy() *PolicyRule {
|
|||||||
AuthorizedGroups: make(map[string][]string, len(pm.AuthorizedGroups)),
|
AuthorizedGroups: make(map[string][]string, len(pm.AuthorizedGroups)),
|
||||||
AuthorizedUser: pm.AuthorizedUser,
|
AuthorizedUser: pm.AuthorizedUser,
|
||||||
SessionPubKey: pm.SessionPubKey,
|
SessionPubKey: pm.SessionPubKey,
|
||||||
|
SessionDisplayName: pm.SessionDisplayName,
|
||||||
}
|
}
|
||||||
copy(rule.Destinations, pm.Destinations)
|
copy(rule.Destinations, pm.Destinations)
|
||||||
copy(rule.Sources, pm.Sources)
|
copy(rule.Sources, pm.Sources)
|
||||||
@@ -144,7 +152,8 @@ func (pm *PolicyRule) Equal(other *PolicyRule) bool {
|
|||||||
pm.SourceResource != other.SourceResource ||
|
pm.SourceResource != other.SourceResource ||
|
||||||
pm.DestinationResource != other.DestinationResource ||
|
pm.DestinationResource != other.DestinationResource ||
|
||||||
pm.AuthorizedUser != other.AuthorizedUser ||
|
pm.AuthorizedUser != other.AuthorizedUser ||
|
||||||
pm.SessionPubKey != other.SessionPubKey {
|
pm.SessionPubKey != other.SessionPubKey ||
|
||||||
|
pm.SessionDisplayName != other.SessionDisplayName {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2761,6 +2761,12 @@ type SessionPubKey struct {
|
|||||||
// UserIDHash is the BLAKE2b-128 hash of the user ID this session
|
// UserIDHash is the BLAKE2b-128 hash of the user ID this session
|
||||||
// belongs to, matching the entries in VNCAuth.AuthorizedUsers.
|
// belongs to, matching the entries in VNCAuth.AuthorizedUsers.
|
||||||
UserIdHash []byte `protobuf:"bytes,2,opt,name=user_id_hash,json=userIdHash,proto3" json:"user_id_hash,omitempty"`
|
UserIdHash []byte `protobuf:"bytes,2,opt,name=user_id_hash,json=userIdHash,proto3" json:"user_id_hash,omitempty"`
|
||||||
|
// DisplayName is a human-readable label for the user this session was
|
||||||
|
// issued to (typically the IDP display name, falling back to email).
|
||||||
|
// Used by the host peer's UI in the per-connection approval prompt so
|
||||||
|
// the user being asked can recognise the requester. May be empty when
|
||||||
|
// the management server has no user record for the session.
|
||||||
|
DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SessionPubKey) Reset() {
|
func (x *SessionPubKey) Reset() {
|
||||||
@@ -2809,6 +2815,13 @@ func (x *SessionPubKey) GetUserIdHash() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SessionPubKey) GetDisplayName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.DisplayName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// RemotePeerConfig represents a configuration of a remote peer.
|
// RemotePeerConfig represents a configuration of a remote peer.
|
||||||
// The properties are used to configure WireGuard Peers sections
|
// The properties are used to configure WireGuard Peers sections
|
||||||
type RemotePeerConfig struct {
|
type RemotePeerConfig struct {
|
||||||
@@ -5091,346 +5104,348 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6d,
|
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6d,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61,
|
0x65, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61,
|
||||||
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4a, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69,
|
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6d, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69,
|
||||||
0x6f, 0x6e, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f,
|
0x6f, 0x6e, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f,
|
||||||
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65,
|
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65,
|
||||||
0x79, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x68, 0x61, 0x73,
|
0x79, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x68, 0x61, 0x73,
|
||||||
0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x48,
|
0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x48,
|
||||||
0x61, 0x73, 0x68, 0x22, 0xbb, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65,
|
0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75,
|
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c,
|
||||||
0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75,
|
0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74,
|
||||||
0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49,
|
0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77,
|
||||||
0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65,
|
0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77,
|
||||||
0x64, 0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
|
||||||
0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c,
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09,
|
0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f,
|
||||||
0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64,
|
0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x22, 0x0a,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20,
|
0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e,
|
||||||
0x6e, 0x22, 0x7e, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e,
|
0x12, 0x22, 0x0a, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||||
0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01,
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72,
|
||||||
0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c,
|
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18,
|
||||||
0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x09,
|
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
||||||
0x6a, 0x77, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02,
|
||||||
0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4a, 0x57, 0x54,
|
0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12,
|
||||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x6a, 0x77, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x33, 0x0a, 0x09, 0x6a, 0x77, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01,
|
||||||
0x67, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75,
|
0x4a, 0x57, 0x54, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x6a, 0x77, 0x74, 0x43, 0x6f,
|
||||||
0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75,
|
0x6e, 0x66, 0x69, 0x67, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75,
|
||||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12,
|
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52,
|
||||||
0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63,
|
||||||
0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44,
|
|
||||||
0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52,
|
|
||||||
0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f,
|
|
||||||
0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28,
|
|
||||||
0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50,
|
|
||||||
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50,
|
|
||||||
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a,
|
|
||||||
0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, 0x53,
|
|
||||||
0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74,
|
|
||||||
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65,
|
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74,
|
|
||||||
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x42,
|
|
||||||
0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
|
||||||
0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
|
|
||||||
0x69, 0x67, 0x22, 0xbc, 0x03, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43,
|
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
|
|
||||||
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
|
|
||||||
0x44, 0x12, 0x26, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65,
|
|
||||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x43, 0x6c, 0x69,
|
|
||||||
0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d,
|
|
||||||
0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
|
||||||
0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a,
|
|
||||||
0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f,
|
|
||||||
0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63,
|
|
||||||
0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a,
|
|
||||||
0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06,
|
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f,
|
|
||||||
0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65,
|
|
||||||
0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55,
|
|
||||||
0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, 0x74,
|
|
||||||
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69,
|
|
||||||
0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
|
|
||||||
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12,
|
|
||||||
0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18,
|
|
||||||
0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55,
|
|
||||||
0x52, 0x4c, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72,
|
|
||||||
0x6f, 0x6d, 0x70, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52,
|
|
||||||
0x12, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4c, 0x6f,
|
|
||||||
0x67, 0x69, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x6c, 0x61, 0x67,
|
|
||||||
0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x6c, 0x61,
|
|
||||||
0x67, 0x22, 0x93, 0x02, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49,
|
|
||||||
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e,
|
|
||||||
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65,
|
|
||||||
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
|
|
||||||
0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77,
|
|
||||||
0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18,
|
|
||||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d,
|
|
||||||
0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74,
|
|
||||||
0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64,
|
|
||||||
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72,
|
|
||||||
0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d,
|
|
||||||
0x61, 0x69, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61,
|
|
||||||
0x69, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
|
||||||
0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x6f, 0x75, 0x74,
|
|
||||||
0x65, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x75, 0x74, 0x6f, 0x41, 0x70, 0x70,
|
|
||||||
0x6c, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x75,
|
|
||||||
0x74, 0x6f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x22, 0xde, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43,
|
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
|
||||||
0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, 0x65,
|
|
||||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x4e,
|
|
||||||
0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18,
|
|
||||||
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
|
||||||
0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f,
|
|
||||||
0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72,
|
|
||||||
0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f,
|
|
||||||
0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e,
|
|
||||||
0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x28,
|
|
||||||
0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x18,
|
|
||||||
0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0d, 0x46, 0x6f, 0x72, 0x77, 0x61,
|
|
||||||
0x72, 0x64, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xb8, 0x01, 0x0a, 0x0a, 0x43, 0x75, 0x73,
|
|
||||||
0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
|
||||||
0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
|
||||||
0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
|
|
||||||
0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69,
|
|
||||||
0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f,
|
|
||||||
0x72, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d,
|
|
||||||
0x61, 0x69, 0x6e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
|
|
||||||
0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x44,
|
|
||||||
0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x4e, 0x6f, 0x6e, 0x41, 0x75,
|
|
||||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
|
||||||
0x08, 0x52, 0x10, 0x4e, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x61, 0x74,
|
|
||||||
0x69, 0x76, 0x65, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63,
|
|
||||||
0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18,
|
|
||||||
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43,
|
|
||||||
0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73,
|
|
||||||
0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03,
|
|
||||||
0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61,
|
|
||||||
0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a,
|
|
||||||
0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
|
|
||||||
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65,
|
|
||||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61,
|
|
||||||
0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72,
|
|
||||||
0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03,
|
|
||||||
0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53,
|
|
||||||
0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62,
|
|
||||||
0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63,
|
|
||||||
0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22,
|
|
||||||
0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a,
|
|
||||||
0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a,
|
|
||||||
0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e,
|
|
||||||
0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20,
|
|
||||||
0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xfb, 0x02, 0x0a, 0x0c, 0x46, 0x69,
|
|
||||||
0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x06, 0x50, 0x65,
|
|
||||||
0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06,
|
|
||||||
0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x37, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
|
|
||||||
0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63,
|
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
|
||||||
0x2e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
|
||||||
0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c,
|
|
||||||
0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
|
||||||
0x34, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28,
|
|
||||||
0x0e, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52,
|
|
||||||
0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f,
|
|
||||||
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x30, 0x0a, 0x08, 0x50, 0x6f, 0x72,
|
|
||||||
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61,
|
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66,
|
|
||||||
0x6f, 0x52, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x50,
|
|
||||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x50,
|
|
||||||
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f,
|
|
||||||
0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
|
||||||
0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12,
|
|
||||||
0x26, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65,
|
|
||||||
0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50,
|
|
||||||
0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x22, 0x38, 0x0a, 0x0e, 0x4e, 0x65, 0x74, 0x77, 0x6f,
|
|
||||||
0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x65, 0x74,
|
|
||||||
0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74, 0x49, 0x50, 0x12,
|
|
||||||
0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61,
|
|
||||||
0x63, 0x22, 0x1e, 0x0a, 0x06, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x46,
|
|
||||||
0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x46, 0x69, 0x6c, 0x65,
|
|
||||||
0x73, 0x22, 0x96, 0x01, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14,
|
|
||||||
0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x04,
|
|
||||||
0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20,
|
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
|
||||||
0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48,
|
|
||||||
0x00, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x1a, 0x2f, 0x0a, 0x05, 0x52, 0x61, 0x6e, 0x67,
|
|
||||||
0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
|
||||||
0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02,
|
|
||||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x70, 0x6f, 0x72,
|
|
||||||
0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x87, 0x03, 0x0a, 0x11, 0x52,
|
|
||||||
0x6f, 0x75, 0x74, 0x65, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65,
|
|
||||||
0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x73,
|
|
||||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x61,
|
|
||||||
0x6e, 0x67, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02,
|
|
||||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
|
||||||
0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63,
|
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
|
|
||||||
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69,
|
|
||||||
0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
|
||||||
0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
|
||||||
0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x30, 0x0a, 0x08,
|
|
||||||
0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
|
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74,
|
|
||||||
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c,
|
|
||||||
0x0a, 0x09, 0x69, 0x73, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28,
|
|
||||||
0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x12, 0x18, 0x0a, 0x07,
|
|
||||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64,
|
|
||||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
|
||||||
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e,
|
|
||||||
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1a,
|
|
||||||
0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c,
|
|
||||||
0x52, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x52, 0x6f,
|
|
||||||
0x75, 0x74, 0x65, 0x49, 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x52, 0x6f, 0x75,
|
|
||||||
0x74, 0x65, 0x49, 0x44, 0x22, 0xf2, 0x01, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
|
|
||||||
0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3e, 0x0a,
|
|
||||||
0x0f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x72, 0x74,
|
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x64, 0x65,
|
|
||||||
0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x2c, 0x0a,
|
|
||||||
0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65,
|
|
||||||
0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c,
|
|
||||||
0x61, 0x74, 0x65, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x74,
|
|
||||||
0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20,
|
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
|
||||||
0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
|
||||||
0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x8b, 0x02, 0x0a, 0x14, 0x45, 0x78,
|
|
||||||
0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
|
||||||
0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
|
||||||
0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
|
||||||
0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74,
|
|
||||||
0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x10,
|
|
||||||
0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x69, 0x6e,
|
|
||||||
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b,
|
|
||||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28,
|
|
||||||
0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x16, 0x0a,
|
|
||||||
0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64,
|
|
||||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x72,
|
|
||||||
0x65, 0x66, 0x69, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65,
|
|
||||||
0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e,
|
|
||||||
0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x69, 0x73,
|
|
||||||
0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xa1, 0x01, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f,
|
|
||||||
0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
|
||||||
0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
|
|
||||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f,
|
|
||||||
0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69,
|
|
||||||
0x63, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
|
||||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2c, 0x0a,
|
|
||||||
0x12, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67,
|
|
||||||
0x6e, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x6f, 0x72, 0x74, 0x41,
|
|
||||||
0x75, 0x74, 0x6f, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x22, 0x2c, 0x0a, 0x12, 0x52,
|
|
||||||
0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
|
||||||
0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x6e,
|
|
||||||
0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
|
||||||
0x22, 0x2b, 0x0a, 0x11, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65,
|
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x14, 0x0a,
|
|
||||||
0x12, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
|
||||||
0x6e, 0x73, 0x65, 0x2a, 0x3a, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
|
||||||
0x12, 0x12, 0x0a, 0x0e, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74,
|
|
||||||
0x75, 0x73, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65,
|
|
||||||
0x64, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x02, 0x2a,
|
|
||||||
0x6c, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74,
|
|
||||||
0x79, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
|
|
||||||
0x69, 0x74, 0x79, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c,
|
|
||||||
0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x53, 0x6f,
|
|
||||||
0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x10, 0x01, 0x12, 0x1d,
|
|
||||||
0x0a, 0x19, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,
|
|
||||||
0x49, 0x50, 0x76, 0x36, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x10, 0x02, 0x2a, 0x4c, 0x0a,
|
|
||||||
0x0c, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a,
|
|
||||||
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c,
|
|
||||||
0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03,
|
|
||||||
0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x12,
|
|
||||||
0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x05, 0x2a, 0x20, 0x0a, 0x0d, 0x52,
|
|
||||||
0x75, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02,
|
|
||||||
0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x22, 0x0a,
|
|
||||||
0x0a, 0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41,
|
|
||||||
0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10,
|
|
||||||
0x01, 0x2a, 0x63, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x63, 0x6f, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48, 0x54,
|
|
||||||
0x54, 0x50, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x48,
|
|
||||||
0x54, 0x54, 0x50, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45,
|
|
||||||
0x5f, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45,
|
|
||||||
0x5f, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45,
|
|
||||||
0x5f, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x32, 0xfd, 0x06, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67,
|
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05,
|
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
|
||||||
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
|
||||||
0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
|
||||||
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
|
||||||
0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61,
|
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
|
||||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
|
||||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47,
|
|
||||||
0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61,
|
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d,
|
|
||||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76,
|
|
||||||
0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
|
||||||
0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d,
|
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
|
||||||
0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70,
|
|
||||||
0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63,
|
|
||||||
0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c,
|
0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c,
|
||||||
0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01,
|
||||||
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
|
||||||
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
|
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64,
|
||||||
0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e,
|
||||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d,
|
0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x79,
|
0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06,
|
||||||
0x6e, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45,
|
||||||
0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67,
|
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f,
|
||||||
0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||||
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43,
|
||||||
0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x1c, 0x2e,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43,
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xbc, 0x03, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64,
|
||||||
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61,
|
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65,
|
||||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65,
|
||||||
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12,
|
0x6e, 0x74, 0x49, 0x44, 0x12, 0x26, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65,
|
||||||
0x4c, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12,
|
0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c,
|
||||||
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06,
|
||||||
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f,
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65,
|
||||||
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a,
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65,
|
||||||
0x0b, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d,
|
0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65,
|
||||||
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,
|
||||||
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0a, 0x53, 0x74,
|
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e,
|
||||||
0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18,
|
||||||
|
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a,
|
||||||
|
0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
|
||||||
|
0x52, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15,
|
||||||
|
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64,
|
||||||
|
0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74,
|
||||||
|
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69,
|
||||||
|
0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52,
|
||||||
|
0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65,
|
||||||
|
0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c,
|
||||||
|
0x65, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x0b, 0x20, 0x01,
|
||||||
|
0x28, 0x08, 0x52, 0x12, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6d, 0x70,
|
||||||
|
0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46,
|
||||||
|
0x6c, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||||
|
0x46, 0x6c, 0x61, 0x67, 0x22, 0x93, 0x02, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e,
|
||||||
|
0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18,
|
||||||
|
0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77,
|
||||||
|
0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e,
|
||||||
|
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65,
|
||||||
|
0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16,
|
||||||
|
0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
||||||
|
0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65,
|
||||||
|
0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71,
|
||||||
|
0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18,
|
||||||
|
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07,
|
||||||
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44,
|
||||||
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x52, 0x6f,
|
||||||
|
0x75, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x52,
|
||||||
|
0x6f, 0x75, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x75, 0x74, 0x6f,
|
||||||
|
0x41, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b, 0x69,
|
||||||
|
0x70, 0x41, 0x75, 0x74, 0x6f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x22, 0xde, 0x01, 0x0a, 0x09, 0x44,
|
||||||
|
0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76,
|
||||||
|
0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
|
0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47,
|
||||||
|
0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75,
|
||||||
|
0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
|
0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||||
|
0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f,
|
||||||
|
0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
||||||
|
0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65,
|
||||||
|
0x73, 0x12, 0x28, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x72, 0x50, 0x6f,
|
||||||
|
0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0d, 0x46, 0x6f,
|
||||||
|
0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xb8, 0x01, 0x0a, 0x0a,
|
||||||
|
0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f,
|
||||||
|
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61,
|
||||||
|
0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20,
|
||||||
|
0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
|
0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52,
|
||||||
|
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
|
||||||
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03,
|
||||||
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61,
|
||||||
|
0x69, 0x6e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x4e, 0x6f,
|
||||||
|
0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x04,
|
||||||
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x4e, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
|
||||||
|
0x74, 0x61, 0x74, 0x69, 0x76, 0x65, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65,
|
||||||
|
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79,
|
||||||
|
0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14,
|
||||||
|
0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43,
|
||||||
|
0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
|
0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18,
|
||||||
|
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a,
|
||||||
|
0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70,
|
||||||
|
0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18,
|
||||||
|
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
|
0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e,
|
||||||
|
0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72,
|
||||||
|
0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69,
|
||||||
|
0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18,
|
||||||
|
0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32,
|
||||||
|
0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45,
|
||||||
|
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65,
|
||||||
|
0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c,
|
||||||
|
0x65, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
|
0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50,
|
||||||
|
0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||||
|
0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74,
|
||||||
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xfb, 0x02, 0x0a,
|
||||||
|
0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1a, 0x0a,
|
||||||
|
0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18,
|
||||||
|
0x01, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x37, 0x0a, 0x09, 0x44, 0x69, 0x72,
|
||||||
|
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x69,
|
||||||
|
0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69,
|
||||||
|
0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
|
||||||
|
0x28, 0x0e, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
|
0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69,
|
||||||
|
0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04,
|
||||||
|
0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08,
|
||||||
|
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74,
|
||||||
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x30, 0x0a, 0x08,
|
||||||
|
0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
|
||||||
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74,
|
||||||
|
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a,
|
||||||
|
0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c,
|
||||||
|
0x52, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x75,
|
||||||
|
0x73, 0x74, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01,
|
||||||
|
0x28, 0x0d, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||||
|
0x6f, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66,
|
||||||
|
0x69, 0x78, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72,
|
||||||
|
0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x22, 0x38, 0x0a, 0x0e, 0x4e, 0x65,
|
||||||
|
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05,
|
||||||
|
0x6e, 0x65, 0x74, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x65, 0x74,
|
||||||
|
0x49, 0x50, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x03, 0x6d, 0x61, 0x63, 0x22, 0x1e, 0x0a, 0x06, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x14,
|
||||||
|
0x0a, 0x05, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x46,
|
||||||
|
0x69, 0x6c, 0x65, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66,
|
||||||
|
0x6f, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48,
|
||||||
|
0x00, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65,
|
||||||
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
|
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x52, 0x61, 0x6e,
|
||||||
|
0x67, 0x65, 0x48, 0x00, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x1a, 0x2f, 0x0a, 0x05, 0x52,
|
||||||
|
0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e,
|
||||||
|
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x42, 0x0f, 0x0a, 0x0d,
|
||||||
|
0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x87, 0x03,
|
||||||
|
0x0a, 0x11, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52,
|
||||||
|
0x75, 0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x61, 0x6e,
|
||||||
|
0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||||
|
0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
||||||
|
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||||
|
0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69,
|
||||||
|
0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65,
|
||||||
|
0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x08, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12,
|
||||||
|
0x30, 0x0a, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||||
|
0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50,
|
||||||
|
0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66,
|
||||||
|
0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x73, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x18, 0x06,
|
||||||
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x12,
|
||||||
|
0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09,
|
||||||
|
0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x75, 0x73,
|
||||||
|
0x74, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28,
|
||||||
|
0x0d, 0x52, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||||
|
0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x18, 0x09, 0x20,
|
||||||
|
0x01, 0x28, 0x0c, 0x52, 0x08, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x44, 0x12, 0x18, 0x0a,
|
||||||
|
0x07, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||||
|
0x52, 0x6f, 0x75, 0x74, 0x65, 0x49, 0x44, 0x22, 0xf2, 0x01, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77,
|
||||||
|
0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||||
|
0x12, 0x3e, 0x0a, 0x0f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50,
|
||||||
|
0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52,
|
||||||
|
0x0f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x72, 0x74,
|
||||||
|
0x12, 0x2c, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x41, 0x64,
|
||||||
|
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x74, 0x72, 0x61,
|
||||||
|
0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c,
|
||||||
|
0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74,
|
||||||
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
|
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0e, 0x74, 0x72,
|
||||||
|
0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x8b, 0x02, 0x0a,
|
||||||
|
0x14, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x08, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
|
||||||
|
0x6c, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
||||||
|
0x70, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18,
|
||||||
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12,
|
||||||
|
0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x05,
|
||||||
|
0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73,
|
||||||
|
0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
|
0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e,
|
||||||
|
0x61, 0x6d, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x69, 0x73,
|
||||||
|
0x74, 0x65, 0x6e, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a,
|
||||||
|
0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xa1, 0x01, 0x0a, 0x15, 0x45,
|
||||||
|
0x78, 0x70, 0x6f, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76,
|
||||||
|
0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||||
|
0x63, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65,
|
||||||
|
0x72, 0x76, 0x69, 0x63, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61,
|
||||||
|
0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
|
0x12, 0x2c, 0x0a, 0x12, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x61, 0x73,
|
||||||
|
0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x6f,
|
||||||
|
0x72, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x22, 0x2c,
|
||||||
|
0x0a, 0x12, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x15, 0x0a, 0x13,
|
||||||
|
0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
|
0x6e, 0x73, 0x65, 0x22, 0x2b, 0x0a, 0x11, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73,
|
||||||
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61,
|
||||||
|
0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
|
0x22, 0x14, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x3a, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61,
|
||||||
|
0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65,
|
||||||
|
0x65, 0x64, 0x65, 0x64, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64,
|
||||||
|
0x10, 0x02, 0x2a, 0x6c, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69,
|
||||||
|
0x6c, 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61,
|
||||||
|
0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12,
|
||||||
|
0x20, 0x0a, 0x1c, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74,
|
||||||
|
0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x10,
|
||||||
|
0x01, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x65, 0x65, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c,
|
||||||
|
0x69, 0x74, 0x79, 0x49, 0x50, 0x76, 0x36, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x10, 0x02,
|
||||||
|
0x2a, 0x4c, 0x0a, 0x0c, 0x52, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||||
|
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a,
|
||||||
|
0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12,
|
||||||
|
0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50,
|
||||||
|
0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x05, 0x2a, 0x20,
|
||||||
|
0x0a, 0x0d, 0x52, 0x75, 0x6c, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
||||||
|
0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01,
|
||||||
|
0x2a, 0x22, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a,
|
||||||
|
0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52,
|
||||||
|
0x4f, 0x50, 0x10, 0x01, 0x2a, 0x63, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x50, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x45, 0x58, 0x50, 0x4f, 0x53, 0x45,
|
||||||
|
0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x50, 0x4f, 0x53,
|
||||||
|
0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50,
|
||||||
|
0x4f, 0x53, 0x45, 0x5f, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50,
|
||||||
|
0x4f, 0x53, 0x45, 0x5f, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58, 0x50,
|
||||||
|
0x4f, 0x53, 0x45, 0x5f, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x32, 0xfd, 0x06, 0x0a, 0x11, 0x4d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
||||||
|
0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
||||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c,
|
||||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
|
||||||
|
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
||||||
|
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42,
|
||||||
|
0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11,
|
||||||
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||||
|
0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53,
|
||||||
|
0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
|
0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12,
|
||||||
|
0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70,
|
||||||
|
0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
|
0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65,
|
||||||
|
0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||||
|
0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
|
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||||
|
0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
|
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||||
|
0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75,
|
||||||
|
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12,
|
||||||
|
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
||||||
|
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
|
||||||
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
||||||
|
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a,
|
||||||
|
0x08, 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
||||||
|
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06,
|
||||||
|
0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
|
0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
|
0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x03, 0x4a, 0x6f, 0x62,
|
||||||
|
0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
||||||
|
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c,
|
||||||
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
|
||||||
|
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01,
|
||||||
|
0x30, 0x01, 0x12, 0x4c, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x78, 0x70, 0x6f,
|
||||||
|
0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||||
|
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||||
|
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
||||||
|
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
|
||||||
|
0x12, 0x4b, 0x0a, 0x0b, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12,
|
||||||
|
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
||||||
|
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
|
||||||
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79,
|
||||||
|
0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a,
|
||||||
|
0x0a, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
||||||
|
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||||
|
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
||||||
|
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -453,6 +453,13 @@ message SessionPubKey {
|
|||||||
// UserIDHash is the BLAKE2b-128 hash of the user ID this session
|
// UserIDHash is the BLAKE2b-128 hash of the user ID this session
|
||||||
// belongs to, matching the entries in VNCAuth.AuthorizedUsers.
|
// belongs to, matching the entries in VNCAuth.AuthorizedUsers.
|
||||||
bytes user_id_hash = 2;
|
bytes user_id_hash = 2;
|
||||||
|
|
||||||
|
// DisplayName is a human-readable label for the user this session was
|
||||||
|
// issued to (typically the IDP display name, falling back to email).
|
||||||
|
// Used by the host peer's UI in the per-connection approval prompt so
|
||||||
|
// the user being asked can recognise the requester. May be empty when
|
||||||
|
// the management server has no user record for the session.
|
||||||
|
string display_name = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemotePeerConfig represents a configuration of a remote peer.
|
// RemotePeerConfig represents a configuration of a remote peer.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package auth
|
package sessionauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -43,6 +43,11 @@ type Authorizer struct {
|
|||||||
// Populated from management's temporary-access flow; used by VNC to
|
// Populated from management's temporary-access flow; used by VNC to
|
||||||
// authenticate via the Noise_IK handshake.
|
// authenticate via the Noise_IK handshake.
|
||||||
sessionPubKeys map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash
|
sessionPubKeys map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash
|
||||||
|
// sessionDisplayNames mirrors sessionPubKeys with the optional
|
||||||
|
// human-readable display name management associated with each
|
||||||
|
// session key. Used by the per-connection UI approval prompt; not
|
||||||
|
// consulted by any authorization decision.
|
||||||
|
sessionDisplayNames map[[sessionPubKeyLen]byte]string
|
||||||
|
|
||||||
// mu protects the list of users
|
// mu protects the list of users
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@@ -66,10 +71,13 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SessionPubKey is a single ephemeral-key entry: the 32-byte X25519
|
// SessionPubKey is a single ephemeral-key entry: the 32-byte X25519
|
||||||
// static public key plus the hashed user identity it authenticates as.
|
// static public key plus the hashed user identity it authenticates as,
|
||||||
|
// optionally plus a human-readable display name for the UI approval
|
||||||
|
// prompt to identify the requester.
|
||||||
type SessionPubKey struct {
|
type SessionPubKey struct {
|
||||||
PubKey []byte
|
PubKey []byte
|
||||||
UserIDHash sshuserhash.UserIDHash
|
UserIDHash sshuserhash.UserIDHash
|
||||||
|
DisplayName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthorizer creates a new SSH authorizer with empty configuration
|
// NewAuthorizer creates a new SSH authorizer with empty configuration
|
||||||
@@ -77,7 +85,8 @@ func NewAuthorizer() *Authorizer {
|
|||||||
a := &Authorizer{
|
a := &Authorizer{
|
||||||
userIDClaim: DefaultUserIDClaim,
|
userIDClaim: DefaultUserIDClaim,
|
||||||
machineUsers: make(map[string][]uint32),
|
machineUsers: make(map[string][]uint32),
|
||||||
sessionPubKeys: make(map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash),
|
sessionPubKeys: make(map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash),
|
||||||
|
sessionDisplayNames: make(map[[sessionPubKeyLen]byte]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
return a
|
return a
|
||||||
@@ -94,6 +103,7 @@ func (a *Authorizer) Update(config *Config) {
|
|||||||
a.authorizedUsers = []sshuserhash.UserIDHash{}
|
a.authorizedUsers = []sshuserhash.UserIDHash{}
|
||||||
a.machineUsers = make(map[string][]uint32)
|
a.machineUsers = make(map[string][]uint32)
|
||||||
a.sessionPubKeys = make(map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash)
|
a.sessionPubKeys = make(map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash)
|
||||||
|
a.sessionDisplayNames = make(map[[sessionPubKeyLen]byte]string)
|
||||||
log.Info("SSH authorization cleared")
|
log.Info("SSH authorization cleared")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -117,6 +127,7 @@ func (a *Authorizer) Update(config *Config) {
|
|||||||
a.machineUsers = machineUsers
|
a.machineUsers = machineUsers
|
||||||
|
|
||||||
sessionPubKeys := make(map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash, len(config.SessionPubKeys))
|
sessionPubKeys := make(map[[sessionPubKeyLen]byte]sshuserhash.UserIDHash, len(config.SessionPubKeys))
|
||||||
|
sessionDisplayNames := make(map[[sessionPubKeyLen]byte]string, len(config.SessionPubKeys))
|
||||||
conflicted := make(map[[sessionPubKeyLen]byte]struct{})
|
conflicted := make(map[[sessionPubKeyLen]byte]struct{})
|
||||||
for _, e := range config.SessionPubKeys {
|
for _, e := range config.SessionPubKeys {
|
||||||
if len(e.PubKey) != sessionPubKeyLen {
|
if len(e.PubKey) != sessionPubKeyLen {
|
||||||
@@ -130,12 +141,17 @@ func (a *Authorizer) Update(config *Config) {
|
|||||||
if existing, ok := sessionPubKeys[key]; ok && existing != e.UserIDHash {
|
if existing, ok := sessionPubKeys[key]; ok && existing != e.UserIDHash {
|
||||||
log.Warnf("SSH auth: session pubkey bound to conflicting user hashes; dropping binding")
|
log.Warnf("SSH auth: session pubkey bound to conflicting user hashes; dropping binding")
|
||||||
delete(sessionPubKeys, key)
|
delete(sessionPubKeys, key)
|
||||||
|
delete(sessionDisplayNames, key)
|
||||||
conflicted[key] = struct{}{}
|
conflicted[key] = struct{}{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sessionPubKeys[key] = e.UserIDHash
|
sessionPubKeys[key] = e.UserIDHash
|
||||||
|
if e.DisplayName != "" {
|
||||||
|
sessionDisplayNames[key] = e.DisplayName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a.sessionPubKeys = sessionPubKeys
|
a.sessionPubKeys = sessionPubKeys
|
||||||
|
a.sessionDisplayNames = sessionDisplayNames
|
||||||
|
|
||||||
log.Debugf("SSH auth: updated with %d authorized users, %d machine user mappings, %d session pubkeys",
|
log.Debugf("SSH auth: updated with %d authorized users, %d machine user mappings, %d session pubkeys",
|
||||||
len(config.AuthorizedUsers), len(machineUsers), len(sessionPubKeys))
|
len(config.AuthorizedUsers), len(machineUsers), len(sessionPubKeys))
|
||||||
@@ -217,6 +233,22 @@ func (a *Authorizer) LookupSessionKey(pubKey []byte) (sshuserhash.UserIDHash, er
|
|||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupSessionDisplayName returns the human-readable display name
|
||||||
|
// management associated with a session pubkey, or empty string when none
|
||||||
|
// is recorded. Never returns an error: a missing/unknown key reports as
|
||||||
|
// "" and the caller falls back to other identifiers.
|
||||||
|
func (a *Authorizer) LookupSessionDisplayName(pubKey []byte) string {
|
||||||
|
if len(pubKey) != sessionPubKeyLen {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var key [sessionPubKeyLen]byte
|
||||||
|
copy(key[:], pubKey)
|
||||||
|
a.mu.RLock()
|
||||||
|
name := a.sessionDisplayNames[key]
|
||||||
|
a.mu.RUnlock()
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
// AuthorizeOSUserBySessionKey resolves the OS-user mapping for a session
|
// AuthorizeOSUserBySessionKey resolves the OS-user mapping for a session
|
||||||
// key. Mirrors Authorize but skips the JWT-hash step since the key has
|
// key. Mirrors Authorize but skips the JWT-hash step since the key has
|
||||||
// already been verified and the user identity hash is in hand.
|
// already been verified and the user identity hash is in hand.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package auth
|
package sessionauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
Reference in New Issue
Block a user