mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-12 20:26:19 -04:00
Compare commits
1 Commits
crowdsec-s
...
pcp-suppor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b287ee966 |
@@ -199,11 +199,9 @@ func runForDuration(cmd *cobra.Command, args []string) error {
|
||||
cmd.Println("Log level set to trace.")
|
||||
}
|
||||
|
||||
needsRestoreUp := false
|
||||
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
||||
cmd.PrintErrf("Failed to bring service down: %v\n", status.Convert(err).Message())
|
||||
} else {
|
||||
needsRestoreUp = !stateWasDown
|
||||
cmd.Println("netbird down")
|
||||
}
|
||||
|
||||
@@ -219,7 +217,6 @@ func runForDuration(cmd *cobra.Command, args []string) error {
|
||||
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
|
||||
cmd.PrintErrf("Failed to bring service up: %v\n", status.Convert(err).Message())
|
||||
} else {
|
||||
needsRestoreUp = false
|
||||
cmd.Println("netbird up")
|
||||
}
|
||||
|
||||
@@ -267,14 +264,6 @@ func runForDuration(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
|
||||
}
|
||||
|
||||
if needsRestoreUp {
|
||||
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
|
||||
cmd.PrintErrf("Failed to restore service up state: %v\n", status.Convert(err).Message())
|
||||
} else {
|
||||
cmd.Println("netbird up (restored)")
|
||||
}
|
||||
}
|
||||
|
||||
if stateWasDown {
|
||||
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
||||
cmd.PrintErrf("Failed to restore service down state: %v\n", status.Convert(err).Message())
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
"github.com/netbirdio/netbird/client/anonymize"
|
||||
"github.com/netbirdio/netbird/client/configs"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/installer"
|
||||
@@ -53,7 +52,6 @@ resolved_domains.txt: Anonymized resolved domain IP addresses from the status re
|
||||
config.txt: Anonymized configuration information of the NetBird client.
|
||||
network_map.json: Anonymized sync response containing peer configurations, routes, DNS settings, and firewall rules.
|
||||
state.json: Anonymized client state dump containing netbird states for the active profile.
|
||||
service_params.json: Sanitized service install parameters (service.json). Sensitive environment variable values are masked. Only present when service.json exists.
|
||||
metrics.txt: Buffered client metrics in InfluxDB line protocol format. Only present when metrics collection is enabled. Peer identifiers are anonymized.
|
||||
mutex.prof: Mutex profiling information.
|
||||
goroutine.prof: Goroutine profiling information.
|
||||
@@ -361,10 +359,6 @@ func (g *BundleGenerator) createArchive() error {
|
||||
log.Errorf("failed to add corrupted state files to debug bundle: %v", err)
|
||||
}
|
||||
|
||||
if err := g.addServiceParams(); err != nil {
|
||||
log.Errorf("failed to add service params to debug bundle: %v", err)
|
||||
}
|
||||
|
||||
if err := g.addMetrics(); err != nil {
|
||||
log.Errorf("failed to add metrics to debug bundle: %v", err)
|
||||
}
|
||||
@@ -494,90 +488,6 @@ func (g *BundleGenerator) addConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
serviceParamsFile = "service.json"
|
||||
serviceParamsBundle = "service_params.json"
|
||||
maskedValue = "***"
|
||||
envVarPrefix = "NB_"
|
||||
jsonKeyManagementURL = "management_url"
|
||||
jsonKeyServiceEnv = "service_env_vars"
|
||||
)
|
||||
|
||||
var sensitiveEnvSubstrings = []string{"key", "token", "secret", "password", "credential"}
|
||||
|
||||
// addServiceParams reads the service.json file and adds a sanitized version to the bundle.
|
||||
// Non-NB_ env vars and vars with sensitive names are masked. Other NB_ values are anonymized.
|
||||
func (g *BundleGenerator) addServiceParams() error {
|
||||
path := filepath.Join(configs.StateDir, serviceParamsFile)
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("read service params: %w", err)
|
||||
}
|
||||
|
||||
var params map[string]any
|
||||
if err := json.Unmarshal(data, ¶ms); err != nil {
|
||||
return fmt.Errorf("parse service params: %w", err)
|
||||
}
|
||||
|
||||
if g.anonymize {
|
||||
if mgmtURL, ok := params[jsonKeyManagementURL].(string); ok && mgmtURL != "" {
|
||||
params[jsonKeyManagementURL] = g.anonymizer.AnonymizeURI(mgmtURL)
|
||||
}
|
||||
}
|
||||
|
||||
g.sanitizeServiceEnvVars(params)
|
||||
|
||||
sanitizedData, err := json.MarshalIndent(params, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal sanitized service params: %w", err)
|
||||
}
|
||||
|
||||
if err := g.addFileToZip(bytes.NewReader(sanitizedData), serviceParamsBundle); err != nil {
|
||||
return fmt.Errorf("add service params to zip: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sanitizeServiceEnvVars masks or anonymizes env var values in service params.
|
||||
// Non-NB_ vars and vars with sensitive names (key, token, etc.) are fully masked.
|
||||
// Other NB_ var values are passed through the anonymizer when anonymization is enabled.
|
||||
func (g *BundleGenerator) sanitizeServiceEnvVars(params map[string]any) {
|
||||
envVars, ok := params[jsonKeyServiceEnv].(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
sanitized := make(map[string]any, len(envVars))
|
||||
for k, v := range envVars {
|
||||
val, _ := v.(string)
|
||||
switch {
|
||||
case !strings.HasPrefix(k, envVarPrefix) || isSensitiveEnvVar(k):
|
||||
sanitized[k] = maskedValue
|
||||
case g.anonymize:
|
||||
sanitized[k] = g.anonymizer.AnonymizeString(val)
|
||||
default:
|
||||
sanitized[k] = val
|
||||
}
|
||||
}
|
||||
params[jsonKeyServiceEnv] = sanitized
|
||||
}
|
||||
|
||||
// isSensitiveEnvVar returns true for env var names that may contain secrets.
|
||||
func isSensitiveEnvVar(key string) bool {
|
||||
lower := strings.ToLower(key)
|
||||
for _, s := range sensitiveEnvSubstrings {
|
||||
if strings.Contains(lower, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *BundleGenerator) addCommonConfigFields(configContent *strings.Builder) {
|
||||
configContent.WriteString("NetBird Client Configuration:\n\n")
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -14,7 +10,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/client/anonymize"
|
||||
"github.com/netbirdio/netbird/client/configs"
|
||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
)
|
||||
|
||||
@@ -425,226 +420,6 @@ func TestAnonymizeNetworkMap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSensitiveEnvVar(t *testing.T) {
|
||||
tests := []struct {
|
||||
key string
|
||||
sensitive bool
|
||||
}{
|
||||
{"NB_SETUP_KEY", true},
|
||||
{"NB_API_TOKEN", true},
|
||||
{"NB_CLIENT_SECRET", true},
|
||||
{"NB_PASSWORD", true},
|
||||
{"NB_CREDENTIAL", true},
|
||||
{"NB_LOG_LEVEL", false},
|
||||
{"NB_MANAGEMENT_URL", false},
|
||||
{"NB_HOSTNAME", false},
|
||||
{"HOME", false},
|
||||
{"PATH", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.key, func(t *testing.T) {
|
||||
assert.Equal(t, tt.sensitive, isSensitiveEnvVar(tt.key))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeServiceEnvVars(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
anonymize bool
|
||||
input map[string]any
|
||||
check func(t *testing.T, params map[string]any)
|
||||
}{
|
||||
{
|
||||
name: "no env vars key",
|
||||
anonymize: false,
|
||||
input: map[string]any{"management_url": "https://mgmt.example.com"},
|
||||
check: func(t *testing.T, params map[string]any) {
|
||||
t.Helper()
|
||||
assert.Equal(t, "https://mgmt.example.com", params["management_url"], "non-env fields should be untouched")
|
||||
_, ok := params[jsonKeyServiceEnv]
|
||||
assert.False(t, ok, "service_env_vars should not be added")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-NB vars are masked",
|
||||
anonymize: false,
|
||||
input: map[string]any{
|
||||
jsonKeyServiceEnv: map[string]any{
|
||||
"HOME": "/root",
|
||||
"PATH": "/usr/bin",
|
||||
"NB_LOG_LEVEL": "debug",
|
||||
},
|
||||
},
|
||||
check: func(t *testing.T, params map[string]any) {
|
||||
t.Helper()
|
||||
env := params[jsonKeyServiceEnv].(map[string]any)
|
||||
assert.Equal(t, maskedValue, env["HOME"], "non-NB_ var should be masked")
|
||||
assert.Equal(t, maskedValue, env["PATH"], "non-NB_ var should be masked")
|
||||
assert.Equal(t, "debug", env["NB_LOG_LEVEL"], "safe NB_ var should pass through")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sensitive NB vars are masked",
|
||||
anonymize: false,
|
||||
input: map[string]any{
|
||||
jsonKeyServiceEnv: map[string]any{
|
||||
"NB_SETUP_KEY": "abc123",
|
||||
"NB_API_TOKEN": "tok_xyz",
|
||||
"NB_LOG_LEVEL": "info",
|
||||
},
|
||||
},
|
||||
check: func(t *testing.T, params map[string]any) {
|
||||
t.Helper()
|
||||
env := params[jsonKeyServiceEnv].(map[string]any)
|
||||
assert.Equal(t, maskedValue, env["NB_SETUP_KEY"], "sensitive NB_ var should be masked")
|
||||
assert.Equal(t, maskedValue, env["NB_API_TOKEN"], "sensitive NB_ var should be masked")
|
||||
assert.Equal(t, "info", env["NB_LOG_LEVEL"], "safe NB_ var should pass through")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "safe NB vars anonymized when anonymize is true",
|
||||
anonymize: true,
|
||||
input: map[string]any{
|
||||
jsonKeyServiceEnv: map[string]any{
|
||||
"NB_MANAGEMENT_URL": "https://mgmt.example.com:443",
|
||||
"NB_LOG_LEVEL": "debug",
|
||||
"NB_SETUP_KEY": "secret",
|
||||
"SOME_OTHER": "val",
|
||||
},
|
||||
},
|
||||
check: func(t *testing.T, params map[string]any) {
|
||||
t.Helper()
|
||||
env := params[jsonKeyServiceEnv].(map[string]any)
|
||||
// Safe NB_ values should be anonymized (not the original, not masked)
|
||||
mgmtVal := env["NB_MANAGEMENT_URL"].(string)
|
||||
assert.NotEqual(t, "https://mgmt.example.com:443", mgmtVal, "should be anonymized")
|
||||
assert.NotEqual(t, maskedValue, mgmtVal, "should not be masked")
|
||||
|
||||
logVal := env["NB_LOG_LEVEL"].(string)
|
||||
assert.NotEqual(t, maskedValue, logVal, "safe NB_ var should not be masked")
|
||||
|
||||
// Sensitive and non-NB_ still masked
|
||||
assert.Equal(t, maskedValue, env["NB_SETUP_KEY"])
|
||||
assert.Equal(t, maskedValue, env["SOME_OTHER"])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses())
|
||||
g := &BundleGenerator{
|
||||
anonymize: tt.anonymize,
|
||||
anonymizer: anonymizer,
|
||||
}
|
||||
g.sanitizeServiceEnvVars(tt.input)
|
||||
tt.check(t, tt.input)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddServiceParams(t *testing.T) {
|
||||
t.Run("missing service.json returns nil", func(t *testing.T) {
|
||||
g := &BundleGenerator{
|
||||
anonymizer: anonymize.NewAnonymizer(anonymize.DefaultAddresses()),
|
||||
}
|
||||
|
||||
origStateDir := configs.StateDir
|
||||
configs.StateDir = t.TempDir()
|
||||
t.Cleanup(func() { configs.StateDir = origStateDir })
|
||||
|
||||
err := g.addServiceParams()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("management_url anonymized when anonymize is true", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
origStateDir := configs.StateDir
|
||||
configs.StateDir = dir
|
||||
t.Cleanup(func() { configs.StateDir = origStateDir })
|
||||
|
||||
input := map[string]any{
|
||||
jsonKeyManagementURL: "https://api.example.com:443",
|
||||
jsonKeyServiceEnv: map[string]any{
|
||||
"NB_LOG_LEVEL": "trace",
|
||||
},
|
||||
}
|
||||
data, err := json.Marshal(input)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, serviceParamsFile), data, 0600))
|
||||
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
|
||||
g := &BundleGenerator{
|
||||
anonymize: true,
|
||||
anonymizer: anonymize.NewAnonymizer(anonymize.DefaultAddresses()),
|
||||
archive: zw,
|
||||
}
|
||||
|
||||
require.NoError(t, g.addServiceParams())
|
||||
require.NoError(t, zw.Close())
|
||||
|
||||
zr, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, zr.File, 1)
|
||||
assert.Equal(t, serviceParamsBundle, zr.File[0].Name)
|
||||
|
||||
rc, err := zr.File[0].Open()
|
||||
require.NoError(t, err)
|
||||
defer rc.Close()
|
||||
|
||||
var result map[string]any
|
||||
require.NoError(t, json.NewDecoder(rc).Decode(&result))
|
||||
|
||||
mgmt := result[jsonKeyManagementURL].(string)
|
||||
assert.NotEqual(t, "https://api.example.com:443", mgmt, "management_url should be anonymized")
|
||||
assert.NotEmpty(t, mgmt)
|
||||
|
||||
env := result[jsonKeyServiceEnv].(map[string]any)
|
||||
assert.NotEqual(t, maskedValue, env["NB_LOG_LEVEL"], "safe NB_ var should not be masked")
|
||||
})
|
||||
|
||||
t.Run("management_url preserved when anonymize is false", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
origStateDir := configs.StateDir
|
||||
configs.StateDir = dir
|
||||
t.Cleanup(func() { configs.StateDir = origStateDir })
|
||||
|
||||
input := map[string]any{
|
||||
jsonKeyManagementURL: "https://api.example.com:443",
|
||||
}
|
||||
data, err := json.Marshal(input)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, serviceParamsFile), data, 0600))
|
||||
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
|
||||
g := &BundleGenerator{
|
||||
anonymize: false,
|
||||
anonymizer: anonymize.NewAnonymizer(anonymize.DefaultAddresses()),
|
||||
archive: zw,
|
||||
}
|
||||
|
||||
require.NoError(t, g.addServiceParams())
|
||||
require.NoError(t, zw.Close())
|
||||
|
||||
zr, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
|
||||
require.NoError(t, err)
|
||||
|
||||
rc, err := zr.File[0].Open()
|
||||
require.NoError(t, err)
|
||||
defer rc.Close()
|
||||
|
||||
var result map[string]any
|
||||
require.NoError(t, json.NewDecoder(rc).Decode(&result))
|
||||
|
||||
assert.Equal(t, "https://api.example.com:443", result[jsonKeyManagementURL], "management_url should be preserved")
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to check if IP is in CGNAT range
|
||||
func isInCGNATRange(ip net.IP) bool {
|
||||
cgnat := net.IPNet{
|
||||
|
||||
@@ -8,18 +8,27 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
envDisableNATMapper = "NB_DISABLE_NAT_MAPPER"
|
||||
envDisableNATMapper = "NB_DISABLE_NAT_MAPPER"
|
||||
envDisablePCPHealthCheck = "NB_DISABLE_PCP_HEALTH_CHECK"
|
||||
)
|
||||
|
||||
func isDisabledByEnv() bool {
|
||||
val := os.Getenv(envDisableNATMapper)
|
||||
return parseBoolEnv(envDisableNATMapper)
|
||||
}
|
||||
|
||||
func isHealthCheckDisabled() bool {
|
||||
return parseBoolEnv(envDisablePCPHealthCheck)
|
||||
}
|
||||
|
||||
func parseBoolEnv(key string) bool {
|
||||
val := os.Getenv(key)
|
||||
if val == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
disabled, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse %s: %v", envDisableNATMapper, err)
|
||||
log.Warnf("failed to parse %s: %v", key, err)
|
||||
return false
|
||||
}
|
||||
return disabled
|
||||
|
||||
@@ -11,13 +11,16 @@ import (
|
||||
|
||||
"github.com/libp2p/go-nat"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/portforward/pcp"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMappingTTL = 2 * time.Hour
|
||||
renewalInterval = defaultMappingTTL / 2
|
||||
discoveryTimeout = 10 * time.Second
|
||||
mappingDescription = "NetBird"
|
||||
defaultMappingTTL = 2 * time.Hour
|
||||
renewalInterval = defaultMappingTTL / 2
|
||||
healthCheckInterval = 1 * time.Minute
|
||||
discoveryTimeout = 10 * time.Second
|
||||
mappingDescription = "NetBird"
|
||||
)
|
||||
|
||||
type Mapping struct {
|
||||
@@ -143,7 +146,7 @@ func (m *Manager) setup(ctx context.Context) (nat.NAT, *Mapping, error) {
|
||||
discoverCtx, discoverCancel := context.WithTimeout(ctx, discoveryTimeout)
|
||||
defer discoverCancel()
|
||||
|
||||
gateway, err := nat.DiscoverGateway(discoverCtx)
|
||||
gateway, err := discoverGateway(discoverCtx)
|
||||
if err != nil {
|
||||
log.Infof("NAT gateway discovery failed: %v (port forwarding disabled)", err)
|
||||
return nil, nil, err
|
||||
@@ -171,7 +174,6 @@ func (m *Manager) createMapping(ctx context.Context, gateway nat.NAT) (*Mapping,
|
||||
externalIP, err := gateway.GetExternalAddress()
|
||||
if err != nil {
|
||||
log.Debugf("failed to get external address: %v", err)
|
||||
// todo return with err?
|
||||
}
|
||||
|
||||
mapping := &Mapping{
|
||||
@@ -188,22 +190,67 @@ func (m *Manager) createMapping(ctx context.Context, gateway nat.NAT) (*Mapping,
|
||||
}
|
||||
|
||||
func (m *Manager) renewLoop(ctx context.Context, gateway nat.NAT) {
|
||||
ticker := time.NewTicker(renewalInterval)
|
||||
defer ticker.Stop()
|
||||
renewTicker := time.NewTicker(renewalInterval)
|
||||
healthTicker := time.NewTicker(healthCheckInterval)
|
||||
defer renewTicker.Stop()
|
||||
defer healthTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
case <-renewTicker.C:
|
||||
if err := m.renewMapping(ctx, gateway); err != nil {
|
||||
log.Warnf("failed to renew port mapping: %v", err)
|
||||
continue
|
||||
}
|
||||
case <-healthTicker.C:
|
||||
if m.checkHealthAndRecreate(ctx, gateway) {
|
||||
renewTicker.Reset(renewalInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) checkHealthAndRecreate(ctx context.Context, gateway nat.NAT) bool {
|
||||
if isHealthCheckDisabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
m.mappingLock.Lock()
|
||||
hasMapping := m.mapping != nil
|
||||
m.mappingLock.Unlock()
|
||||
|
||||
if !hasMapping {
|
||||
return false
|
||||
}
|
||||
|
||||
pcpNAT, ok := gateway.(*pcp.NAT)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
epoch, serverRestarted, err := pcpNAT.CheckServerHealth(ctx)
|
||||
if err != nil {
|
||||
log.Debugf("PCP health check failed: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if serverRestarted {
|
||||
log.Warnf("PCP server restart detected (epoch=%d), recreating port mapping", epoch)
|
||||
if err := m.renewMapping(ctx, gateway); err != nil {
|
||||
log.Errorf("failed to recreate port mapping after server restart: %v", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Manager) renewMapping(ctx context.Context, gateway nat.NAT) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
@@ -248,3 +295,4 @@ func (m *Manager) startTearDown(ctx context.Context) {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
408
client/internal/portforward/pcp/client.go
Normal file
408
client/internal/portforward/pcp/client.go
Normal file
@@ -0,0 +1,408 @@
|
||||
package pcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 3 * time.Second
|
||||
responseBufferSize = 128
|
||||
|
||||
// RFC 6887 Section 8.1.1 retry timing
|
||||
initialRetryDelay = 3 * time.Second
|
||||
maxRetryDelay = 1024 * time.Second
|
||||
maxRetries = 4 // 3s + 6s + 12s + 24s = 45s total worst case
|
||||
)
|
||||
|
||||
// Client is a PCP protocol client.
|
||||
// All methods are safe for concurrent use.
|
||||
type Client struct {
|
||||
gateway netip.Addr
|
||||
timeout time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
// localIP caches the resolved local IP address.
|
||||
localIP netip.Addr
|
||||
// lastEpoch is the last observed server epoch value.
|
||||
lastEpoch uint32
|
||||
// epochTime tracks when lastEpoch was received for state loss detection.
|
||||
epochTime time.Time
|
||||
// externalIP caches the external IP from the last successful MAP response.
|
||||
externalIP netip.Addr
|
||||
// epochStateLost is set when epoch indicates server restart.
|
||||
epochStateLost bool
|
||||
}
|
||||
|
||||
// NewClient creates a new PCP client for the gateway at the given IP.
|
||||
func NewClient(gateway net.IP) *Client {
|
||||
addr, ok := netip.AddrFromSlice(gateway)
|
||||
if !ok {
|
||||
log.Debugf("invalid gateway IP: %v", gateway)
|
||||
}
|
||||
return &Client{
|
||||
gateway: addr.Unmap(),
|
||||
timeout: defaultTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWithTimeout creates a new PCP client with a custom timeout.
|
||||
func NewClientWithTimeout(gateway net.IP, timeout time.Duration) *Client {
|
||||
addr, ok := netip.AddrFromSlice(gateway)
|
||||
if !ok {
|
||||
log.Debugf("invalid gateway IP: %v", gateway)
|
||||
}
|
||||
return &Client{
|
||||
gateway: addr.Unmap(),
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// SetLocalIP sets the local IP address to use in PCP requests.
|
||||
func (c *Client) SetLocalIP(ip net.IP) {
|
||||
addr, ok := netip.AddrFromSlice(ip)
|
||||
if !ok {
|
||||
log.Debugf("invalid local IP: %v", ip)
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.localIP = addr.Unmap()
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Gateway returns the gateway IP address.
|
||||
func (c *Client) Gateway() net.IP {
|
||||
return c.gateway.AsSlice()
|
||||
}
|
||||
|
||||
// Announce sends a PCP ANNOUNCE request to discover PCP support.
|
||||
// Returns the server's epoch time on success.
|
||||
func (c *Client) Announce(ctx context.Context) (epoch uint32, err error) {
|
||||
localIP, err := c.getLocalIP()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get local IP: %w", err)
|
||||
}
|
||||
|
||||
req := buildAnnounceRequest(localIP)
|
||||
resp, err := c.sendRequest(ctx, req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("send announce: %w", err)
|
||||
}
|
||||
|
||||
parsed, err := parseResponse(resp)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse announce response: %w", err)
|
||||
}
|
||||
|
||||
if parsed.ResultCode != ResultSuccess {
|
||||
return 0, fmt.Errorf("PCP ANNOUNCE failed: %s", ResultCodeString(parsed.ResultCode))
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
if c.updateEpochLocked(parsed.Epoch) {
|
||||
log.Warnf("PCP server epoch indicates state loss - mappings may need refresh")
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return parsed.Epoch, nil
|
||||
}
|
||||
|
||||
// AddPortMapping requests a port mapping from the PCP server.
|
||||
func (c *Client) AddPortMapping(ctx context.Context, protocol string, internalPort int, lifetime time.Duration) (*MapResponse, error) {
|
||||
return c.addPortMappingWithHint(ctx, protocol, internalPort, internalPort, netip.Addr{}, lifetime)
|
||||
}
|
||||
|
||||
// AddPortMappingWithHint requests a port mapping with suggested external port and IP.
|
||||
// Use lifetime <= 0 to delete a mapping.
|
||||
func (c *Client) AddPortMappingWithHint(ctx context.Context, protocol string, internalPort, suggestedExtPort int, suggestedExtIP net.IP, lifetime time.Duration) (*MapResponse, error) {
|
||||
var extIP netip.Addr
|
||||
if suggestedExtIP != nil {
|
||||
var ok bool
|
||||
extIP, ok = netip.AddrFromSlice(suggestedExtIP)
|
||||
if !ok {
|
||||
log.Debugf("invalid suggested external IP: %v", suggestedExtIP)
|
||||
}
|
||||
extIP = extIP.Unmap()
|
||||
}
|
||||
return c.addPortMappingWithHint(ctx, protocol, internalPort, suggestedExtPort, extIP, lifetime)
|
||||
}
|
||||
|
||||
func (c *Client) addPortMappingWithHint(ctx context.Context, protocol string, internalPort, suggestedExtPort int, suggestedExtIP netip.Addr, lifetime time.Duration) (*MapResponse, error) {
|
||||
localIP, err := c.getLocalIP()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get local IP: %w", err)
|
||||
}
|
||||
|
||||
proto, err := protocolNumber(protocol)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse protocol: %w", err)
|
||||
}
|
||||
|
||||
var nonce [12]byte
|
||||
if _, err := rand.Read(nonce[:]); err != nil {
|
||||
return nil, fmt.Errorf("generate nonce: %w", err)
|
||||
}
|
||||
|
||||
// Convert lifetime to seconds. Lifetime 0 means delete, so only apply
|
||||
// default for positive durations that round to 0 seconds.
|
||||
var lifetimeSec uint32
|
||||
if lifetime > 0 {
|
||||
lifetimeSec = uint32(lifetime.Seconds())
|
||||
if lifetimeSec == 0 {
|
||||
lifetimeSec = DefaultLifetime
|
||||
}
|
||||
}
|
||||
|
||||
req := buildMapRequest(localIP, nonce, proto, uint16(internalPort), uint16(suggestedExtPort), suggestedExtIP, lifetimeSec)
|
||||
|
||||
resp, err := c.sendRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send map request: %w", err)
|
||||
}
|
||||
|
||||
mapResp, err := parseMapResponse(resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse map response: %w", err)
|
||||
}
|
||||
|
||||
if mapResp.Nonce != nonce {
|
||||
return nil, fmt.Errorf("nonce mismatch in response")
|
||||
}
|
||||
|
||||
if mapResp.Protocol != proto {
|
||||
return nil, fmt.Errorf("protocol mismatch: requested %d, got %d", proto, mapResp.Protocol)
|
||||
}
|
||||
if mapResp.InternalPort != uint16(internalPort) {
|
||||
return nil, fmt.Errorf("internal port mismatch: requested %d, got %d", internalPort, mapResp.InternalPort)
|
||||
}
|
||||
|
||||
if mapResp.ResultCode != ResultSuccess {
|
||||
return nil, &Error{
|
||||
Code: mapResp.ResultCode,
|
||||
Message: ResultCodeString(mapResp.ResultCode),
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
if c.updateEpochLocked(mapResp.Epoch) {
|
||||
log.Warnf("PCP server epoch indicates state loss - mappings may need refresh")
|
||||
}
|
||||
c.cacheExternalIPLocked(mapResp.ExternalIP)
|
||||
c.mu.Unlock()
|
||||
return mapResp, nil
|
||||
}
|
||||
|
||||
// DeletePortMapping removes a port mapping by requesting zero lifetime.
|
||||
func (c *Client) DeletePortMapping(ctx context.Context, protocol string, internalPort int) error {
|
||||
if _, err := c.addPortMappingWithHint(ctx, protocol, internalPort, 0, netip.Addr{}, 0); err != nil {
|
||||
var pcpErr *Error
|
||||
if errors.As(err, &pcpErr) && pcpErr.Code == ResultNotAuthorized {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("delete mapping: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExternalAddress returns the external IP address.
|
||||
// First checks for a cached value from previous MAP responses.
|
||||
// If not cached, creates a short-lived mapping to discover the external IP.
|
||||
func (c *Client) GetExternalAddress(ctx context.Context) (net.IP, error) {
|
||||
c.mu.Lock()
|
||||
if c.externalIP.IsValid() {
|
||||
ip := c.externalIP.AsSlice()
|
||||
c.mu.Unlock()
|
||||
return ip, nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
// Use an ephemeral port in the dynamic range (49152-65535).
|
||||
// Port 0 is not valid with UDP/TCP protocols per RFC 6887.
|
||||
ephemeralPort := 49152 + int(uint16(time.Now().UnixNano()))%(65535-49152)
|
||||
|
||||
// Use minimal lifetime (1 second) for discovery.
|
||||
resp, err := c.AddPortMapping(ctx, "udp", ephemeralPort, time.Second)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create temporary mapping: %w", err)
|
||||
}
|
||||
|
||||
if err := c.DeletePortMapping(ctx, "udp", ephemeralPort); err != nil {
|
||||
log.Debugf("cleanup temporary PCP mapping: %v", err)
|
||||
}
|
||||
|
||||
return resp.ExternalIP.AsSlice(), nil
|
||||
}
|
||||
|
||||
// LastEpoch returns the last observed server epoch value.
|
||||
// A decrease in epoch indicates the server may have restarted and mappings may be lost.
|
||||
func (c *Client) LastEpoch() uint32 {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.lastEpoch
|
||||
}
|
||||
|
||||
// EpochStateLost returns true if epoch state loss was detected and clears the flag.
|
||||
func (c *Client) EpochStateLost() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
lost := c.epochStateLost
|
||||
c.epochStateLost = false
|
||||
return lost
|
||||
}
|
||||
|
||||
// updateEpoch updates the epoch tracking and detects potential state loss.
|
||||
// Returns true if state loss was detected (server likely restarted).
|
||||
// Caller must hold c.mu.
|
||||
func (c *Client) updateEpochLocked(newEpoch uint32) bool {
|
||||
now := time.Now()
|
||||
stateLost := false
|
||||
|
||||
// RFC 6887 Section 8.5: Detect invalid epoch indicating server state loss.
|
||||
// client_delta = time since last response
|
||||
// server_delta = epoch change since last response
|
||||
// Invalid if: client_delta+2 < server_delta - server_delta/16
|
||||
// OR: server_delta+2 < client_delta - client_delta/16
|
||||
// The +2 handles quantization, /16 (6.25%) handles clock drift.
|
||||
if !c.epochTime.IsZero() && c.lastEpoch > 0 {
|
||||
clientDelta := uint32(now.Sub(c.epochTime).Seconds())
|
||||
serverDelta := newEpoch - c.lastEpoch
|
||||
|
||||
// Check for epoch going backwards or jumping unexpectedly.
|
||||
// Subtraction is safe: serverDelta/16 is always <= serverDelta.
|
||||
if clientDelta+2 < serverDelta-(serverDelta/16) ||
|
||||
serverDelta+2 < clientDelta-(clientDelta/16) {
|
||||
stateLost = true
|
||||
c.epochStateLost = true
|
||||
}
|
||||
}
|
||||
|
||||
c.lastEpoch = newEpoch
|
||||
c.epochTime = now
|
||||
return stateLost
|
||||
}
|
||||
|
||||
// cacheExternalIP stores the external IP from a successful MAP response.
|
||||
// Caller must hold c.mu.
|
||||
func (c *Client) cacheExternalIPLocked(ip netip.Addr) {
|
||||
if ip.IsValid() && !ip.IsUnspecified() {
|
||||
c.externalIP = ip
|
||||
}
|
||||
}
|
||||
|
||||
// sendRequest sends a PCP request with retries per RFC 6887 Section 8.1.1.
|
||||
func (c *Client) sendRequest(ctx context.Context, req []byte) ([]byte, error) {
|
||||
addr := &net.UDPAddr{IP: c.gateway.AsSlice(), Port: Port}
|
||||
|
||||
var lastErr error
|
||||
delay := initialRetryDelay
|
||||
|
||||
for range maxRetries {
|
||||
resp, err := c.sendOnce(ctx, addr, req)
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
lastErr = err
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
// RFC 6887 Section 8.1.1: RT = (1 + RAND) * MIN(2 * RTprev, MRT)
|
||||
// RAND is random between -0.1 and +0.1
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-time.After(retryDelayWithJitter(delay)):
|
||||
}
|
||||
delay = min(delay*2, maxRetryDelay)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("PCP request failed after %d retries: %w", maxRetries, lastErr)
|
||||
}
|
||||
|
||||
// retryDelayWithJitter applies RFC 6887 jitter: multiply by (1 + RAND) where RAND is [-0.1, +0.1].
|
||||
func retryDelayWithJitter(d time.Duration) time.Duration {
|
||||
var b [1]byte
|
||||
_, _ = rand.Read(b[:])
|
||||
// Convert byte to range [-0.1, +0.1]: (b/255 * 0.2) - 0.1
|
||||
jitter := (float64(b[0])/255.0)*0.2 - 0.1
|
||||
return time.Duration(float64(d) * (1 + jitter))
|
||||
}
|
||||
|
||||
func (c *Client) sendOnce(ctx context.Context, addr *net.UDPAddr, req []byte) ([]byte, error) {
|
||||
// Use ListenUDP instead of DialUDP to validate response source address per RFC 6887 §8.3.
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listen: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Debugf("close UDP connection: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
timeout := c.timeout
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
if remaining := time.Until(deadline); remaining < timeout {
|
||||
timeout = remaining
|
||||
}
|
||||
}
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, fmt.Errorf("set deadline: %w", err)
|
||||
}
|
||||
|
||||
if _, err := conn.WriteToUDP(req, addr); err != nil {
|
||||
return nil, fmt.Errorf("write: %w", err)
|
||||
}
|
||||
|
||||
resp := make([]byte, responseBufferSize)
|
||||
n, from, err := conn.ReadFromUDP(resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read: %w", err)
|
||||
}
|
||||
|
||||
// RFC 6887 §8.3: Validate response came from expected PCP server.
|
||||
if !from.IP.Equal(addr.IP) {
|
||||
return nil, fmt.Errorf("response from unexpected source %s (expected %s)", from.IP, addr.IP)
|
||||
}
|
||||
|
||||
return resp[:n], nil
|
||||
}
|
||||
|
||||
func (c *Client) getLocalIP() (netip.Addr, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.localIP.IsValid() {
|
||||
return netip.Addr{}, fmt.Errorf("local IP not set for gateway %s", c.gateway)
|
||||
}
|
||||
return c.localIP, nil
|
||||
}
|
||||
|
||||
func protocolNumber(protocol string) (uint8, error) {
|
||||
switch protocol {
|
||||
case "udp", "UDP":
|
||||
return ProtoUDP, nil
|
||||
case "tcp", "TCP":
|
||||
return ProtoTCP, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
// Error represents a PCP error response.
|
||||
type Error struct {
|
||||
Code uint8
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("PCP error: %s (%d)", e.Message, e.Code)
|
||||
}
|
||||
187
client/internal/portforward/pcp/client_test.go
Normal file
187
client/internal/portforward/pcp/client_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package pcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAddrConversion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addr netip.Addr
|
||||
}{
|
||||
{"IPv4", netip.MustParseAddr("192.168.1.100")},
|
||||
{"IPv4 loopback", netip.MustParseAddr("127.0.0.1")},
|
||||
{"IPv6", netip.MustParseAddr("2001:db8::1")},
|
||||
{"IPv6 loopback", netip.MustParseAddr("::1")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b16 := addrTo16(tt.addr)
|
||||
|
||||
recovered := addrFrom16(b16)
|
||||
assert.Equal(t, tt.addr, recovered, "address should round-trip")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAnnounceRequest(t *testing.T) {
|
||||
clientIP := netip.MustParseAddr("192.168.1.100")
|
||||
req := buildAnnounceRequest(clientIP)
|
||||
|
||||
require.Len(t, req, headerSize)
|
||||
assert.Equal(t, byte(Version), req[0], "version")
|
||||
assert.Equal(t, byte(OpAnnounce), req[1], "opcode")
|
||||
|
||||
// Check client IP is properly encoded as IPv4-mapped IPv6
|
||||
assert.Equal(t, byte(0xff), req[18], "IPv4-mapped prefix byte 10")
|
||||
assert.Equal(t, byte(0xff), req[19], "IPv4-mapped prefix byte 11")
|
||||
assert.Equal(t, byte(192), req[20], "IP octet 1")
|
||||
assert.Equal(t, byte(168), req[21], "IP octet 2")
|
||||
assert.Equal(t, byte(1), req[22], "IP octet 3")
|
||||
assert.Equal(t, byte(100), req[23], "IP octet 4")
|
||||
}
|
||||
|
||||
func TestBuildMapRequest(t *testing.T) {
|
||||
clientIP := netip.MustParseAddr("192.168.1.100")
|
||||
nonce := [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
||||
req := buildMapRequest(clientIP, nonce, ProtoUDP, 51820, 51820, netip.Addr{}, 3600)
|
||||
|
||||
require.Len(t, req, mapRequestSize)
|
||||
assert.Equal(t, byte(Version), req[0], "version")
|
||||
assert.Equal(t, byte(OpMap), req[1], "opcode")
|
||||
|
||||
// Lifetime at bytes 4-7
|
||||
assert.Equal(t, uint32(3600), (uint32(req[4])<<24)|(uint32(req[5])<<16)|(uint32(req[6])<<8)|uint32(req[7]), "lifetime")
|
||||
|
||||
// Nonce at bytes 24-35
|
||||
assert.Equal(t, nonce[:], req[24:36], "nonce")
|
||||
|
||||
// Protocol at byte 36
|
||||
assert.Equal(t, byte(ProtoUDP), req[36], "protocol")
|
||||
|
||||
// Internal port at bytes 40-41
|
||||
assert.Equal(t, uint16(51820), (uint16(req[40])<<8)|uint16(req[41]), "internal port")
|
||||
|
||||
// External port at bytes 42-43
|
||||
assert.Equal(t, uint16(51820), (uint16(req[42])<<8)|uint16(req[43]), "external port")
|
||||
}
|
||||
|
||||
func TestParseResponse(t *testing.T) {
|
||||
// Construct a valid ANNOUNCE response
|
||||
resp := make([]byte, headerSize)
|
||||
resp[0] = Version
|
||||
resp[1] = OpAnnounce | OpReply
|
||||
// Result code = 0 (success)
|
||||
// Lifetime = 0
|
||||
// Epoch = 12345
|
||||
resp[8] = 0
|
||||
resp[9] = 0
|
||||
resp[10] = 0x30
|
||||
resp[11] = 0x39
|
||||
|
||||
parsed, err := parseResponse(resp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(Version), parsed.Version)
|
||||
assert.Equal(t, uint8(OpAnnounce|OpReply), parsed.Opcode)
|
||||
assert.Equal(t, uint8(ResultSuccess), parsed.ResultCode)
|
||||
assert.Equal(t, uint32(12345), parsed.Epoch)
|
||||
}
|
||||
|
||||
func TestParseResponseErrors(t *testing.T) {
|
||||
t.Run("too short", func(t *testing.T) {
|
||||
_, err := parseResponse([]byte{1, 2, 3})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("wrong version", func(t *testing.T) {
|
||||
resp := make([]byte, headerSize)
|
||||
resp[0] = 1 // Wrong version
|
||||
resp[1] = OpReply
|
||||
_, err := parseResponse(resp)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("missing reply bit", func(t *testing.T) {
|
||||
resp := make([]byte, headerSize)
|
||||
resp[0] = Version
|
||||
resp[1] = OpAnnounce // Missing OpReply bit
|
||||
_, err := parseResponse(resp)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResultCodeString(t *testing.T) {
|
||||
assert.Equal(t, "SUCCESS", ResultCodeString(ResultSuccess))
|
||||
assert.Equal(t, "NOT_AUTHORIZED", ResultCodeString(ResultNotAuthorized))
|
||||
assert.Equal(t, "ADDRESS_MISMATCH", ResultCodeString(ResultAddressMismatch))
|
||||
assert.Contains(t, ResultCodeString(255), "UNKNOWN")
|
||||
}
|
||||
|
||||
func TestProtocolNumber(t *testing.T) {
|
||||
proto, err := protocolNumber("udp")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(ProtoUDP), proto)
|
||||
|
||||
proto, err = protocolNumber("tcp")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(ProtoTCP), proto)
|
||||
|
||||
proto, err = protocolNumber("UDP")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint8(ProtoUDP), proto)
|
||||
|
||||
_, err = protocolNumber("icmp")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestClientCreation(t *testing.T) {
|
||||
gateway := netip.MustParseAddr("192.168.1.1").AsSlice()
|
||||
|
||||
client := NewClient(gateway)
|
||||
assert.Equal(t, net.IP(gateway), client.Gateway())
|
||||
assert.Equal(t, defaultTimeout, client.timeout)
|
||||
|
||||
clientWithTimeout := NewClientWithTimeout(gateway, 5*time.Second)
|
||||
assert.Equal(t, 5*time.Second, clientWithTimeout.timeout)
|
||||
}
|
||||
|
||||
func TestNATType(t *testing.T) {
|
||||
n := NewNAT(netip.MustParseAddr("192.168.1.1").AsSlice(), netip.MustParseAddr("192.168.1.100").AsSlice())
|
||||
assert.Equal(t, "PCP", n.Type())
|
||||
}
|
||||
|
||||
// Integration test - skipped unless PCP_TEST_GATEWAY env is set
|
||||
func TestClientIntegration(t *testing.T) {
|
||||
t.Skip("Integration test - run manually with PCP_TEST_GATEWAY=<gateway-ip>")
|
||||
|
||||
gateway := netip.MustParseAddr("10.0.1.1").AsSlice() // Change to your test gateway
|
||||
localIP := netip.MustParseAddr("10.0.1.100").AsSlice() // Change to your local IP
|
||||
|
||||
client := NewClient(gateway)
|
||||
client.SetLocalIP(localIP)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Test ANNOUNCE
|
||||
epoch, err := client.Announce(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Logf("Server epoch: %d", epoch)
|
||||
|
||||
// Test MAP
|
||||
resp, err := client.AddPortMapping(ctx, "udp", 51820, 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
t.Logf("Mapping: internal=%d external=%d externalIP=%s",
|
||||
resp.InternalPort, resp.ExternalPort, resp.ExternalIP)
|
||||
|
||||
// Cleanup
|
||||
err = client.DeletePortMapping(ctx, "udp", 51820)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
209
client/internal/portforward/pcp/nat.go
Normal file
209
client/internal/portforward/pcp/nat.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package pcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/libp2p/go-nat"
|
||||
"github.com/libp2p/go-netroute"
|
||||
)
|
||||
|
||||
var _ nat.NAT = (*NAT)(nil)
|
||||
|
||||
// NAT implements the go-nat NAT interface using PCP.
|
||||
// Supports dual-stack (IPv4 and IPv6) when available.
|
||||
// All methods are safe for concurrent use.
|
||||
//
|
||||
// TODO: IPv6 pinholes use the local IPv6 address. If the address changes
|
||||
// (e.g., due to SLAAC rotation or network change), the pinhole becomes stale
|
||||
// and needs to be recreated with the new address.
|
||||
type NAT struct {
|
||||
client *Client
|
||||
|
||||
mu sync.RWMutex
|
||||
// client6 is the IPv6 PCP client, nil if IPv6 is unavailable.
|
||||
client6 *Client
|
||||
// localIP6 caches the local IPv6 address used for PCP requests.
|
||||
localIP6 netip.Addr
|
||||
}
|
||||
|
||||
// NewNAT creates a new NAT instance backed by PCP.
|
||||
func NewNAT(gateway, localIP net.IP) *NAT {
|
||||
client := NewClient(gateway)
|
||||
client.SetLocalIP(localIP)
|
||||
return &NAT{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// Type returns "PCP" as the NAT type.
|
||||
func (n *NAT) Type() string {
|
||||
return "PCP"
|
||||
}
|
||||
|
||||
// GetDeviceAddress returns the gateway IP address.
|
||||
func (n *NAT) GetDeviceAddress() (net.IP, error) {
|
||||
return n.client.Gateway(), nil
|
||||
}
|
||||
|
||||
// GetExternalAddress returns the external IP address.
|
||||
func (n *NAT) GetExternalAddress() (net.IP, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
return n.client.GetExternalAddress(ctx)
|
||||
}
|
||||
|
||||
// GetInternalAddress returns the local IP address used to communicate with the gateway.
|
||||
func (n *NAT) GetInternalAddress() (net.IP, error) {
|
||||
addr, err := n.client.getLocalIP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addr.AsSlice(), nil
|
||||
}
|
||||
|
||||
// AddPortMapping creates a port mapping on both IPv4 and IPv6 (if available).
|
||||
func (n *NAT) AddPortMapping(ctx context.Context, protocol string, internalPort int, _ string, timeout time.Duration) (int, error) {
|
||||
resp, err := n.client.AddPortMapping(ctx, protocol, internalPort, timeout)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("add mapping: %w", err)
|
||||
}
|
||||
|
||||
n.mu.RLock()
|
||||
client6 := n.client6
|
||||
localIP6 := n.localIP6
|
||||
n.mu.RUnlock()
|
||||
|
||||
if client6 == nil {
|
||||
return int(resp.ExternalPort), nil
|
||||
}
|
||||
|
||||
if _, err := client6.AddPortMapping(ctx, protocol, internalPort, timeout); err != nil {
|
||||
log.Warnf("IPv6 PCP mapping failed (continuing with IPv4): %v", err)
|
||||
return int(resp.ExternalPort), nil
|
||||
}
|
||||
|
||||
log.Infof("created IPv6 PCP pinhole: %s:%d", localIP6, internalPort)
|
||||
return int(resp.ExternalPort), nil
|
||||
}
|
||||
|
||||
// DeletePortMapping removes a port mapping from both IPv4 and IPv6.
|
||||
func (n *NAT) DeletePortMapping(ctx context.Context, protocol string, internalPort int) error {
|
||||
err := n.client.DeletePortMapping(ctx, protocol, internalPort)
|
||||
|
||||
n.mu.RLock()
|
||||
client6 := n.client6
|
||||
n.mu.RUnlock()
|
||||
|
||||
if client6 != nil {
|
||||
if err6 := client6.DeletePortMapping(ctx, protocol, internalPort); err6 != nil {
|
||||
log.Warnf("IPv6 PCP delete mapping failed: %v", err6)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete mapping: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckServerHealth sends an ANNOUNCE to verify the server is still responsive.
|
||||
// Returns the current epoch and whether the server may have restarted (epoch state loss detected).
|
||||
func (n *NAT) CheckServerHealth(ctx context.Context) (epoch uint32, serverRestarted bool, err error) {
|
||||
epoch, err = n.client.Announce(ctx)
|
||||
if err != nil {
|
||||
return 0, false, fmt.Errorf("announce: %w", err)
|
||||
}
|
||||
return epoch, n.client.EpochStateLost(), nil
|
||||
}
|
||||
|
||||
// DiscoverPCP attempts to discover a PCP-capable gateway.
|
||||
// Returns a NAT interface if PCP is supported, or an error otherwise.
|
||||
// Discovers both IPv4 and IPv6 gateways when available.
|
||||
func DiscoverPCP(ctx context.Context) (nat.NAT, error) {
|
||||
gateway, localIP, err := getDefaultGateway()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get default gateway: %w", err)
|
||||
}
|
||||
|
||||
client := NewClient(gateway)
|
||||
client.SetLocalIP(localIP)
|
||||
if _, err := client.Announce(ctx); err != nil {
|
||||
return nil, fmt.Errorf("PCP announce: %w", err)
|
||||
}
|
||||
|
||||
result := &NAT{client: client}
|
||||
discoverIPv6(ctx, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func discoverIPv6(ctx context.Context, result *NAT) {
|
||||
gateway6, localIP6, err := getDefaultGateway6()
|
||||
if err != nil {
|
||||
log.Debugf("IPv6 gateway discovery failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
client6 := NewClient(gateway6)
|
||||
client6.SetLocalIP(localIP6)
|
||||
if _, err := client6.Announce(ctx); err != nil {
|
||||
log.Debugf("PCP IPv6 announce failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
addr, ok := netip.AddrFromSlice(localIP6)
|
||||
if !ok {
|
||||
log.Debugf("invalid IPv6 local IP: %v", localIP6)
|
||||
return
|
||||
}
|
||||
result.mu.Lock()
|
||||
result.client6 = client6
|
||||
result.localIP6 = addr
|
||||
result.mu.Unlock()
|
||||
log.Debugf("PCP IPv6 gateway discovered: %s (local: %s)", gateway6, localIP6)
|
||||
}
|
||||
|
||||
// getDefaultGateway returns the default IPv4 gateway and local IP using the system routing table.
|
||||
func getDefaultGateway() (gateway net.IP, localIP net.IP, err error) {
|
||||
router, err := netroute.New()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_, gateway, localIP, err = router.Route(net.IPv4zero)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if gateway == nil {
|
||||
return nil, nil, nat.ErrNoNATFound
|
||||
}
|
||||
|
||||
return gateway, localIP, nil
|
||||
}
|
||||
|
||||
// getDefaultGateway6 returns the default IPv6 gateway IP address using the system routing table.
|
||||
func getDefaultGateway6() (gateway net.IP, localIP net.IP, err error) {
|
||||
router, err := netroute.New()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_, gateway, localIP, err = router.Route(net.IPv6zero)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if gateway == nil {
|
||||
return nil, nil, nat.ErrNoNATFound
|
||||
}
|
||||
|
||||
return gateway, localIP, nil
|
||||
}
|
||||
225
client/internal/portforward/pcp/protocol.go
Normal file
225
client/internal/portforward/pcp/protocol.go
Normal file
@@ -0,0 +1,225 @@
|
||||
// Package pcp implements the Port Control Protocol (RFC 6887).
|
||||
//
|
||||
// # Implemented Features
|
||||
//
|
||||
// - ANNOUNCE opcode: Discovers PCP server support
|
||||
// - MAP opcode: Creates/deletes port mappings (IPv4 NAT) and firewall pinholes (IPv6)
|
||||
// - Dual-stack: Simultaneous IPv4 and IPv6 support via separate clients
|
||||
// - Nonce validation: Prevents response spoofing
|
||||
// - Epoch tracking: Detects server restarts per Section 8.5
|
||||
// - RFC-compliant retry timing: 3s initial, exponential backoff to 1024s max (Section 8.1.1)
|
||||
//
|
||||
// # Not Implemented
|
||||
//
|
||||
// - PEER opcode: For outbound peer connections (not needed for inbound NAT traversal)
|
||||
// - THIRD_PARTY option: For managing mappings on behalf of other devices
|
||||
// - PREFER_FAILURE option: Requires exact external port or fail (IPv4 NAT only, not needed for IPv6 pinholing)
|
||||
// - FILTER option: To restrict remote peer addresses
|
||||
//
|
||||
// These optional features are omitted because the primary use case is simple
|
||||
// port forwarding for WireGuard, which only requires MAP with default behavior.
|
||||
package pcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version is the PCP protocol version (RFC 6887).
|
||||
Version = 2
|
||||
|
||||
// Port is the standard PCP server port.
|
||||
Port = 5351
|
||||
|
||||
// DefaultLifetime is the default requested mapping lifetime in seconds.
|
||||
DefaultLifetime = 7200 // 2 hours
|
||||
|
||||
// Header sizes
|
||||
headerSize = 24
|
||||
mapPayloadSize = 36
|
||||
mapRequestSize = headerSize + mapPayloadSize // 60 bytes
|
||||
)
|
||||
|
||||
// Opcodes
|
||||
const (
|
||||
OpAnnounce = 0
|
||||
OpMap = 1
|
||||
OpPeer = 2
|
||||
OpReply = 0x80 // OR'd with opcode in responses
|
||||
)
|
||||
|
||||
// Protocol numbers for MAP requests
|
||||
const (
|
||||
ProtoUDP = 17
|
||||
ProtoTCP = 6
|
||||
)
|
||||
|
||||
// Result codes (RFC 6887 Section 7.4)
|
||||
const (
|
||||
ResultSuccess = 0
|
||||
ResultUnsuppVersion = 1
|
||||
ResultNotAuthorized = 2
|
||||
ResultMalformedRequest = 3
|
||||
ResultUnsuppOpcode = 4
|
||||
ResultUnsuppOption = 5
|
||||
ResultMalformedOption = 6
|
||||
ResultNetworkFailure = 7
|
||||
ResultNoResources = 8
|
||||
ResultUnsuppProtocol = 9
|
||||
ResultUserExQuota = 10
|
||||
ResultCannotProvideExt = 11
|
||||
ResultAddressMismatch = 12
|
||||
ResultExcessiveRemotePeers = 13
|
||||
)
|
||||
|
||||
// ResultCodeString returns a human-readable string for a result code.
|
||||
func ResultCodeString(code uint8) string {
|
||||
switch code {
|
||||
case ResultSuccess:
|
||||
return "SUCCESS"
|
||||
case ResultUnsuppVersion:
|
||||
return "UNSUPP_VERSION"
|
||||
case ResultNotAuthorized:
|
||||
return "NOT_AUTHORIZED"
|
||||
case ResultMalformedRequest:
|
||||
return "MALFORMED_REQUEST"
|
||||
case ResultUnsuppOpcode:
|
||||
return "UNSUPP_OPCODE"
|
||||
case ResultUnsuppOption:
|
||||
return "UNSUPP_OPTION"
|
||||
case ResultMalformedOption:
|
||||
return "MALFORMED_OPTION"
|
||||
case ResultNetworkFailure:
|
||||
return "NETWORK_FAILURE"
|
||||
case ResultNoResources:
|
||||
return "NO_RESOURCES"
|
||||
case ResultUnsuppProtocol:
|
||||
return "UNSUPP_PROTOCOL"
|
||||
case ResultUserExQuota:
|
||||
return "USER_EX_QUOTA"
|
||||
case ResultCannotProvideExt:
|
||||
return "CANNOT_PROVIDE_EXTERNAL"
|
||||
case ResultAddressMismatch:
|
||||
return "ADDRESS_MISMATCH"
|
||||
case ResultExcessiveRemotePeers:
|
||||
return "EXCESSIVE_REMOTE_PEERS"
|
||||
default:
|
||||
return fmt.Sprintf("UNKNOWN(%d)", code)
|
||||
}
|
||||
}
|
||||
|
||||
// Response represents a parsed PCP response header.
|
||||
type Response struct {
|
||||
Version uint8
|
||||
Opcode uint8
|
||||
ResultCode uint8
|
||||
Lifetime uint32
|
||||
Epoch uint32
|
||||
}
|
||||
|
||||
// MapResponse contains the full response to a MAP request.
|
||||
type MapResponse struct {
|
||||
Response
|
||||
Nonce [12]byte
|
||||
Protocol uint8
|
||||
InternalPort uint16
|
||||
ExternalPort uint16
|
||||
ExternalIP netip.Addr
|
||||
}
|
||||
|
||||
// addrTo16 converts an address to its 16-byte IPv4-mapped IPv6 representation.
|
||||
func addrTo16(addr netip.Addr) [16]byte {
|
||||
if addr.Is4() {
|
||||
return netip.AddrFrom4(addr.As4()).As16()
|
||||
}
|
||||
return addr.As16()
|
||||
}
|
||||
|
||||
// addrFrom16 extracts an address from a 16-byte representation, unmapping IPv4.
|
||||
func addrFrom16(b [16]byte) netip.Addr {
|
||||
return netip.AddrFrom16(b).Unmap()
|
||||
}
|
||||
|
||||
// buildAnnounceRequest creates a PCP ANNOUNCE request packet.
|
||||
func buildAnnounceRequest(clientIP netip.Addr) []byte {
|
||||
req := make([]byte, headerSize)
|
||||
req[0] = Version
|
||||
req[1] = OpAnnounce
|
||||
mapped := addrTo16(clientIP)
|
||||
copy(req[8:24], mapped[:])
|
||||
return req
|
||||
}
|
||||
|
||||
// buildMapRequest creates a PCP MAP request packet.
|
||||
func buildMapRequest(clientIP netip.Addr, nonce [12]byte, protocol uint8, internalPort, suggestedExtPort uint16, suggestedExtIP netip.Addr, lifetime uint32) []byte {
|
||||
req := make([]byte, mapRequestSize)
|
||||
|
||||
// Header
|
||||
req[0] = Version
|
||||
req[1] = OpMap
|
||||
binary.BigEndian.PutUint32(req[4:8], lifetime)
|
||||
mapped := addrTo16(clientIP)
|
||||
copy(req[8:24], mapped[:])
|
||||
|
||||
// MAP payload
|
||||
copy(req[24:36], nonce[:])
|
||||
req[36] = protocol
|
||||
binary.BigEndian.PutUint16(req[40:42], internalPort)
|
||||
binary.BigEndian.PutUint16(req[42:44], suggestedExtPort)
|
||||
if suggestedExtIP.IsValid() {
|
||||
extMapped := addrTo16(suggestedExtIP)
|
||||
copy(req[44:60], extMapped[:])
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// parseResponse parses the common PCP response header.
|
||||
func parseResponse(data []byte) (*Response, error) {
|
||||
if len(data) < headerSize {
|
||||
return nil, fmt.Errorf("response too short: %d bytes", len(data))
|
||||
}
|
||||
|
||||
resp := &Response{
|
||||
Version: data[0],
|
||||
Opcode: data[1],
|
||||
ResultCode: data[3], // Byte 2 is reserved, byte 3 is result code (RFC 6887 §7.2)
|
||||
Lifetime: binary.BigEndian.Uint32(data[4:8]),
|
||||
Epoch: binary.BigEndian.Uint32(data[8:12]),
|
||||
}
|
||||
|
||||
if resp.Version != Version {
|
||||
return nil, fmt.Errorf("unsupported PCP version: %d", resp.Version)
|
||||
}
|
||||
|
||||
if resp.Opcode&OpReply == 0 {
|
||||
return nil, fmt.Errorf("response missing reply bit: opcode=0x%02x", resp.Opcode)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// parseMapResponse parses a complete MAP response.
|
||||
func parseMapResponse(data []byte) (*MapResponse, error) {
|
||||
if len(data) < mapRequestSize {
|
||||
return nil, fmt.Errorf("MAP response too short: %d bytes", len(data))
|
||||
}
|
||||
|
||||
resp, err := parseResponse(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse header: %w", err)
|
||||
}
|
||||
|
||||
mapResp := &MapResponse{
|
||||
Response: *resp,
|
||||
Protocol: data[36],
|
||||
InternalPort: binary.BigEndian.Uint16(data[40:42]),
|
||||
ExternalPort: binary.BigEndian.Uint16(data[42:44]),
|
||||
ExternalIP: addrFrom16([16]byte(data[44:60])),
|
||||
}
|
||||
copy(mapResp.Nonce[:], data[24:36])
|
||||
|
||||
return mapResp, nil
|
||||
}
|
||||
@@ -8,11 +8,24 @@ import (
|
||||
|
||||
"github.com/libp2p/go-nat"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/portforward/pcp"
|
||||
)
|
||||
|
||||
// discoverGateway is the function used for NAT gateway discovery.
|
||||
// It can be replaced in tests to avoid real network operations.
|
||||
var discoverGateway = nat.DiscoverGateway
|
||||
// Tries PCP first, then falls back to NAT-PMP/UPnP.
|
||||
var discoverGateway = defaultDiscoverGateway
|
||||
|
||||
func defaultDiscoverGateway(ctx context.Context) (nat.NAT, error) {
|
||||
pcpGateway, err := pcp.DiscoverPCP(ctx)
|
||||
if err == nil {
|
||||
return pcpGateway, nil
|
||||
}
|
||||
log.Debugf("PCP discovery failed: %v, trying NAT-PMP/UPnP", err)
|
||||
|
||||
return nat.DiscoverGateway(ctx)
|
||||
}
|
||||
|
||||
// State is persisted only for crash recovery cleanup
|
||||
type State struct {
|
||||
|
||||
@@ -43,24 +43,18 @@ func GetInfo(ctx context.Context) *Info {
|
||||
|
||||
systemHostname, _ := os.Hostname()
|
||||
|
||||
addrs, err := networkAddresses()
|
||||
if err != nil {
|
||||
log.Warnf("failed to discover network addresses: %s", err)
|
||||
}
|
||||
|
||||
return &Info{
|
||||
GoOS: runtime.GOOS,
|
||||
Kernel: osInfo[0],
|
||||
Platform: runtime.GOARCH,
|
||||
OS: osName,
|
||||
OSVersion: osVersion,
|
||||
Hostname: extractDeviceName(ctx, systemHostname),
|
||||
CPUs: runtime.NumCPU(),
|
||||
NetbirdVersion: version.NetbirdVersion(),
|
||||
UIVersion: extractUserAgent(ctx),
|
||||
KernelVersion: osInfo[1],
|
||||
NetworkAddresses: addrs,
|
||||
Environment: env,
|
||||
GoOS: runtime.GOOS,
|
||||
Kernel: osInfo[0],
|
||||
Platform: runtime.GOARCH,
|
||||
OS: osName,
|
||||
OSVersion: osVersion,
|
||||
Hostname: extractDeviceName(ctx, systemHostname),
|
||||
CPUs: runtime.NumCPU(),
|
||||
NetbirdVersion: version.NetbirdVersion(),
|
||||
UIVersion: extractUserAgent(ctx),
|
||||
KernelVersion: osInfo[1],
|
||||
Environment: env,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,9 @@ import (
|
||||
|
||||
// Initial state for the debug collection
|
||||
type debugInitialState struct {
|
||||
wasDown bool
|
||||
needsRestoreUp bool
|
||||
logLevel proto.LogLevel
|
||||
isLevelTrace bool
|
||||
wasDown bool
|
||||
logLevel proto.LogLevel
|
||||
isLevelTrace bool
|
||||
}
|
||||
|
||||
// Debug collection parameters
|
||||
@@ -372,51 +371,46 @@ func (s *serviceClient) configureServiceForDebug(
|
||||
conn proto.DaemonServiceClient,
|
||||
state *debugInitialState,
|
||||
enablePersistence bool,
|
||||
) {
|
||||
) error {
|
||||
if state.wasDown {
|
||||
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||
log.Warnf("failed to bring service up: %v", err)
|
||||
} else {
|
||||
log.Info("Service brought up for debug")
|
||||
time.Sleep(time.Second * 10)
|
||||
return fmt.Errorf("bring service up: %v", err)
|
||||
}
|
||||
log.Info("Service brought up for debug")
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
|
||||
if !state.isLevelTrace {
|
||||
if _, err := conn.SetLogLevel(s.ctx, &proto.SetLogLevelRequest{Level: proto.LogLevel_TRACE}); err != nil {
|
||||
log.Warnf("failed to set log level to TRACE: %v", err)
|
||||
} else {
|
||||
log.Info("Log level set to TRACE for debug")
|
||||
return fmt.Errorf("set log level to TRACE: %v", err)
|
||||
}
|
||||
log.Info("Log level set to TRACE for debug")
|
||||
}
|
||||
|
||||
if _, err := conn.Down(s.ctx, &proto.DownRequest{}); err != nil {
|
||||
log.Warnf("failed to bring service down: %v", err)
|
||||
} else {
|
||||
state.needsRestoreUp = !state.wasDown
|
||||
time.Sleep(time.Second)
|
||||
return fmt.Errorf("bring service down: %v", err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
if enablePersistence {
|
||||
if _, err := conn.SetSyncResponsePersistence(s.ctx, &proto.SetSyncResponsePersistenceRequest{
|
||||
Enabled: true,
|
||||
}); err != nil {
|
||||
log.Warnf("failed to enable sync response persistence: %v", err)
|
||||
} else {
|
||||
log.Info("Sync response persistence enabled for debug")
|
||||
return fmt.Errorf("enable sync response persistence: %v", err)
|
||||
}
|
||||
log.Info("Sync response persistence enabled for debug")
|
||||
}
|
||||
|
||||
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||
log.Warnf("failed to bring service back up: %v", err)
|
||||
} else {
|
||||
state.needsRestoreUp = false
|
||||
time.Sleep(time.Second * 3)
|
||||
return fmt.Errorf("bring service back up: %v", err)
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
if _, err := conn.StartCPUProfile(s.ctx, &proto.StartCPUProfileRequest{}); err != nil {
|
||||
log.Warnf("failed to start CPU profiling: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceClient) collectDebugData(
|
||||
@@ -430,7 +424,9 @@ func (s *serviceClient) collectDebugData(
|
||||
var wg sync.WaitGroup
|
||||
startProgressTracker(ctx, &wg, params.duration, progress)
|
||||
|
||||
s.configureServiceForDebug(conn, state, params.enablePersistence)
|
||||
if err := s.configureServiceForDebug(conn, state, params.enablePersistence); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
progress.progressBar.Hide()
|
||||
@@ -486,17 +482,9 @@ func (s *serviceClient) createDebugBundleFromCollection(
|
||||
|
||||
// Restore service to original state
|
||||
func (s *serviceClient) restoreServiceState(conn proto.DaemonServiceClient, state *debugInitialState) {
|
||||
if state.needsRestoreUp {
|
||||
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||
log.Warnf("failed to restore up state: %v", err)
|
||||
} else {
|
||||
log.Info("Service state restored to up")
|
||||
}
|
||||
}
|
||||
|
||||
if state.wasDown {
|
||||
if _, err := conn.Down(s.ctx, &proto.DownRequest{}); err != nil {
|
||||
log.Warnf("failed to restore down state: %v", err)
|
||||
log.Errorf("Failed to restore down state: %v", err)
|
||||
} else {
|
||||
log.Info("Service state restored to down")
|
||||
}
|
||||
@@ -504,7 +492,7 @@ func (s *serviceClient) restoreServiceState(conn proto.DaemonServiceClient, stat
|
||||
|
||||
if !state.isLevelTrace {
|
||||
if _, err := conn.SetLogLevel(s.ctx, &proto.SetLogLevelRequest{Level: state.logLevel}); err != nil {
|
||||
log.Warnf("failed to restore log level: %v", err)
|
||||
log.Errorf("Failed to restore log level: %v", err)
|
||||
} else {
|
||||
log.Info("Log level restored to original setting")
|
||||
}
|
||||
|
||||
114
go.mod
114
go.mod
@@ -13,28 +13,28 @@ require (
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.27.6
|
||||
github.com/rs/cors v1.8.0
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.9
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
google.golang.org/grpc v1.79.3
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.7.0
|
||||
fyne.io/systray v1.12.1-0.20260116214250-81f8e1a496f9
|
||||
github.com/awnumar/memguard v0.23.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.6
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.10
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2
|
||||
github.com/c-robinson/iplib v1.0.3
|
||||
github.com/caddyserver/certmagic v0.21.3
|
||||
github.com/cilium/ebpf v0.15.0
|
||||
@@ -42,8 +42,6 @@ require (
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/coreos/go-oidc/v3 v3.14.1
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/crowdsecurity/crowdsec v1.7.7
|
||||
github.com/crowdsecurity/go-cs-bouncer v0.0.21
|
||||
github.com/dexidp/dex v0.0.0-00010101000000-000000000000
|
||||
github.com/dexidp/dex/api/v2 v2.4.0
|
||||
github.com/eko/gocache/lib/v4 v4.2.0
|
||||
@@ -62,7 +60,7 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/libdns/route53 v1.5.0
|
||||
github.com/libp2p/go-nat v0.2.0
|
||||
@@ -105,21 +103,21 @@ require (
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/yusufpapurcu/wmi v1.2.4
|
||||
github.com/zcalusic/sysinfo v1.1.3
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0
|
||||
go.opentelemetry.io/otel v1.42.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.48.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0
|
||||
go.opentelemetry.io/otel/metric v1.42.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0
|
||||
go.uber.org/mock v0.5.2
|
||||
go.uber.org/zap v1.27.0
|
||||
goauthentik.io/api/v3 v3.2023051.3
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/mobile v0.0.0-20251113184115-a159579294ab
|
||||
golang.org/x/mod v0.30.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/mod v0.32.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/term v0.38.0
|
||||
golang.org/x/term v0.40.0
|
||||
golang.org/x/time v0.14.0
|
||||
google.golang.org/api v0.257.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -135,7 +133,7 @@ require (
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/AppsFlyer/go-sundheit v0.6.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
@@ -146,39 +144,36 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/awnumar/memcall v0.4.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect
|
||||
github.com/aws/smithy-go v1.23.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
github.com/beevik/etree v1.6.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/crowdsecurity/go-cs-lib v0.0.25 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v28.0.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fredbi/uri v1.1.1 // indirect
|
||||
github.com/fyne-io/gl-js v0.2.0 // indirect
|
||||
@@ -192,23 +187,11 @@ require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-text/render v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.2.1 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
@@ -229,18 +212,16 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/koron/go-ssdp v0.0.4 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/mdelapenya/tlscert v0.2.0 // indirect
|
||||
@@ -248,7 +229,6 @@ require (
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||
github.com/mholt/acmez/v2 v2.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
@@ -260,8 +240,7 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||
github.com/nxadm/tail v1.4.11 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
@@ -271,42 +250,41 @@ require (
|
||||
github.com/pion/transport/v2 v2.2.4 // indirect
|
||||
github.com/pion/turn/v4 v4.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.6.0 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.5.0 // indirect
|
||||
github.com/rymdport/portal v0.4.2 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.8 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.2.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
github.com/yuin/goldmark v1.7.8 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/image v0.33.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502
|
||||
|
||||
231
go.sum
231
go.sum
@@ -9,8 +9,8 @@ cunicu.li/go-rosenpass v0.4.0 h1:LtPtBgFWY/9emfgC4glKLEqS0MJTylzV6+ChRhiZERw=
|
||||
cunicu.li/go-rosenpass v0.4.0/go.mod h1:MPbjH9nxV4l3vEagKVdFNwHOketqgS5/To1VYJplf/M=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
fyne.io/fyne/v2 v2.7.0 h1:GvZSpE3X0liU/fqstInVvRsaboIVpIWQ4/sfjDGIGGQ=
|
||||
fyne.io/fyne/v2 v2.7.0/go.mod h1:xClVlrhxl7D+LT+BWYmcrW4Nf+dJTvkhnPgji7spAwE=
|
||||
fyne.io/systray v1.12.1-0.20260116214250-81f8e1a496f9 h1:829+77I4TaMrcg9B3wf+gHhdSgoCVEgH2czlPXPbfj4=
|
||||
@@ -40,50 +40,48 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g=
|
||||
github.com/awnumar/memcall v0.4.0/go.mod h1:8xOx1YbfyuCg3Fy6TO8DK0kZUua3V42/goA5Ru47E8w=
|
||||
github.com/awnumar/memguard v0.23.0 h1:sJ3a1/SWlcuKIQ7MV+R9p0Pvo9CWsMbGZvcZQtmc68A=
|
||||
github.com/awnumar/memguard v0.23.0/go.mod h1:olVofBrsPdITtJ2HgxQKrEYEMyIBAIciVG4wNnZhW9M=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.6 h1:a1t8fXY4GT4xjyJExz4knbuoxSCacB5hT/WgtfPyLjo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.6/go.mod h1:5ByscNi7R+ztvOGzeUaIu49vkMk2soq5NaH5PYe33MQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 h1:wbjnrrMnKew78/juW7I2BtKQwa1qlf6EjQgS69uYY14=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6/go.mod h1:AtiqqNrDioJXuUgz3+3T0mBWN7Hro2n9wll2zRUc0ww=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 h1:R0tNFJqfjHL3900cqhXuwQ+1K4G0xc9Yf8EDbFXCKEw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6/go.mod h1:y/7sDdu+aJvPtGXr4xYosdpq9a6T9Z0jkXfugmti0rI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 h1:hncKj/4gR+TPauZgTAsxOxNcvBayhUlYZ6LO/BYiQ30=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6/go.mod h1:OiIh45tp6HdJDDJGnja0mw8ihQGz3VGrUflLqSL0SmM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 h1:LHS1YAIJXJ4K9zS+1d/xa9JAA9sL2QyXIQCQFQW/X08=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6/go.mod h1:c9PCiTEuh0wQID5/KqA32J+HAgZxN9tOGXKCiYJjTZI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 h1:nEXUSAwyUfLTgnc9cxlDWy637qsq4UWwp3sNAfl0Z3Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6/go.mod h1:HGzIULx4Ge3Do2V0FaiYKcyKzOqwrhUZgCI77NisswQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 h1:MmLCRqP4U4Cw9gJ4bNrCG0mWqEtBlmAVleyelcHARMU=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3/go.mod h1:AMPjK2YnRh0YgOID3PqhJA1BRNfXDfGOnSsKHtAe8yA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 h1:ETkfWcXP2KNPLecaDa++5bsQhCRa5M5sLUJa5DWYIIg=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3/go.mod h1:+/3ZTqoYb3Ur7DObD00tarKMLMuKg8iqz5CHEanqTnw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 h1:8OLZnVJPvjnrxEwHFg9hVUof/P4sibH+Ea4KKuqAGSg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1/go.mod h1:27M3BpVi0C02UiQh1w9nsBEit6pLhlaH3NHna6WUbDE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 h1:gKWSTnqudpo8dAxqBqZnDoDWCiEh/40FziUjr/mo6uA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2/go.mod h1:x7+rkNmRoEN1U13A6JE2fXne9EWyJy54o3n6d4mGaXQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 h1:YZPjhyaGzhDQEvsffDEcpycq49nl7fiGcfJTIo8BszI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2/go.mod h1:2dIN8qhQfv37BdUYGgEC8Q3tteM3zFxTI1MLO2O3J3c=
|
||||
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
||||
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 h1:tWUG+4wZqdMl/znThEk9tcCy8tTMxq8dW0JTgamohrY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
|
||||
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE=
|
||||
github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -101,8 +99,6 @@ github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+Y
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||
@@ -122,18 +118,11 @@ github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/crowdsecurity/crowdsec v1.7.7 h1:sduZN763iXsrZodocWDrsR//7nLeffGu+RVkkIsbQkE=
|
||||
github.com/crowdsecurity/crowdsec v1.7.7/go.mod h1:L1HLGPDnBYCcY+yfSFnuBbQ1G9DHEJN9c+Kevv9F+4Q=
|
||||
github.com/crowdsecurity/go-cs-bouncer v0.0.21 h1:arPz0VtdVSaz+auOSfHythzkZVLyy18CzYvYab8UJDU=
|
||||
github.com/crowdsecurity/go-cs-bouncer v0.0.21/go.mod h1:4JiH0XXA4KKnnWThItUpe5+heJHWzsLOSA2IWJqUDBA=
|
||||
github.com/crowdsecurity/go-cs-lib v0.0.25 h1:Ov6VPW9yV+OPsbAIQk1iTkEWhwkpaG0v3lrBzeqjzj4=
|
||||
github.com/crowdsecurity/go-cs-lib v0.0.25/go.mod h1:X0GMJY2CxdA1S09SpuqIKaWQsvRGxXmecUp9cP599dE=
|
||||
github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6 h1:/DS5cDX3FJdl+XaN2D7XAwFpuanTxnp52DBLZAaJKx0=
|
||||
github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dexidp/dex/api/v2 v2.4.0 h1:gNba7n6BKVp8X4Jp24cxYn5rIIGhM6kDOXcZoL6tr9A=
|
||||
github.com/dexidp/dex/api/v2 v2.4.0/go.mod h1:/p550ADvFFh7K95VmhUD+jgm15VdaNnab9td8DHOpyI=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
@@ -142,12 +131,12 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
|
||||
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/eko/gocache/lib/v4 v4.2.0 h1:MNykyi5Xw+5Wu3+PUrvtOCaKSZM1nUSVftbzmeC7Yuw=
|
||||
github.com/eko/gocache/lib/v4 v4.2.0/go.mod h1:7ViVmbU+CzDHzRpmB4SXKyyzyuJ8A3UW3/cszpcqB4M=
|
||||
github.com/eko/gocache/store/go_cache/v4 v4.2.2 h1:tAI9nl6TLoJyKG1ujF0CS0n/IgTEMl+NivxtR5R3/hw=
|
||||
@@ -166,7 +155,6 @@ github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko=
|
||||
github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
|
||||
@@ -199,24 +187,6 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||
github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrYnZg=
|
||||
github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
|
||||
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
|
||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
@@ -233,14 +203,10 @@ github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg
|
||||
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
@@ -264,7 +230,6 @@ github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -272,8 +237,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
@@ -313,8 +276,8 @@ github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PU
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
@@ -356,8 +319,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
@@ -369,8 +330,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
@@ -400,8 +361,6 @@ github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tA
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
@@ -425,8 +384,6 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
@@ -466,13 +423,10 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/okta/okta-sdk-golang/v2 v2.18.0 h1:cfDasMb7CShbZvOrF6n+DnLevWwiHgedWMGJ8M8xKDc=
|
||||
github.com/okta/okta-sdk-golang/v2 v2.18.0/go.mod h1:dz30v3ctAiMb7jpsCngGfQUAEGm1/NsWT92uTbNDQIs=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -493,8 +447,8 @@ github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq5
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs=
|
||||
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
@@ -532,9 +486,8 @@ github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
|
||||
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
@@ -542,10 +495,12 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
|
||||
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||
@@ -556,15 +511,15 @@ github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
|
||||
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
|
||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks=
|
||||
github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM=
|
||||
github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw=
|
||||
github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
|
||||
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970=
|
||||
github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/go-m1cpu v0.2.1 h1:yqRB4fvOge2+FyRXFkXqsyMoqPazv14Yyy+iyccT2E4=
|
||||
github.com/shoenig/go-m1cpu v0.2.1/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w=
|
||||
@@ -573,8 +528,8 @@ github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
|
||||
github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
||||
@@ -623,11 +578,11 @@ github.com/ti-mo/conntrack v0.5.1/go.mod h1:T6NCbkMdVU4qEIgwL0njA6lw/iCAbzchlnwm
|
||||
github.com/ti-mo/netfilter v0.5.2 h1:CTjOwFuNNeZ9QPdRXt1MZFLFUf84cKtiQutNauHWd40=
|
||||
github.com/ti-mo/netfilter v0.5.2/go.mod h1:Btx3AtFiOVdHReTDmP9AE+hlkOcvIy403u7BXXbWZKo=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
@@ -656,12 +611,10 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
@@ -670,8 +623,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZ
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.48.0 h1:sBQe3VNGUjY9IKWQC6z2lNqa5iGbDSxhs60ABwK4y0s=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.48.0/go.mod h1:DtrbMzoZWwQHyrQmCfLam5DZbnmorsGbOtTbYHycU5o=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
@@ -690,8 +643,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
goauthentik.io/api/v3 v3.2023051.3 h1:NebAhD/TeTWNo/9X3/Uj+rM5fG1HaiLOlKTNLQv9Qq4=
|
||||
goauthentik.io/api/v3 v3.2023051.3/go.mod h1:nYECml4jGbp/541hj8GcylKQG1gVBsKppHy4+7G8u4U=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -705,10 +658,10 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
|
||||
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
@@ -723,8 +676,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
@@ -743,8 +696,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
@@ -780,8 +733,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -809,8 +762,8 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -822,8 +775,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -837,8 +790,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -858,8 +811,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
@@ -872,8 +825,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -883,8 +836,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
||||
@@ -182,23 +182,6 @@ read_enable_proxy() {
|
||||
return 0
|
||||
}
|
||||
|
||||
read_enable_crowdsec() {
|
||||
echo "" > /dev/stderr
|
||||
echo "Do you want to enable CrowdSec IP reputation blocking?" > /dev/stderr
|
||||
echo "CrowdSec checks client IPs against a community threat intelligence database" > /dev/stderr
|
||||
echo "and blocks known malicious sources before they reach your services." > /dev/stderr
|
||||
echo "A local CrowdSec LAPI container will be added to your deployment." > /dev/stderr
|
||||
echo -n "Enable CrowdSec? [y/N]: " > /dev/stderr
|
||||
read -r CHOICE < /dev/tty
|
||||
|
||||
if [[ "$CHOICE" =~ ^[Yy]$ ]]; then
|
||||
echo "true"
|
||||
else
|
||||
echo "false"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
read_traefik_acme_email() {
|
||||
echo "" > /dev/stderr
|
||||
echo "Enter your email for Let's Encrypt certificate notifications." > /dev/stderr
|
||||
@@ -314,10 +297,6 @@ initialize_default_values() {
|
||||
# NetBird Proxy configuration
|
||||
ENABLE_PROXY="false"
|
||||
PROXY_TOKEN=""
|
||||
|
||||
# CrowdSec configuration
|
||||
ENABLE_CROWDSEC="false"
|
||||
CROWDSEC_BOUNCER_KEY=""
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -346,9 +325,6 @@ configure_reverse_proxy() {
|
||||
if [[ "$REVERSE_PROXY_TYPE" == "0" ]]; then
|
||||
TRAEFIK_ACME_EMAIL=$(read_traefik_acme_email)
|
||||
ENABLE_PROXY=$(read_enable_proxy)
|
||||
if [[ "$ENABLE_PROXY" == "true" ]]; then
|
||||
ENABLE_CROWDSEC=$(read_enable_crowdsec)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Handle external Traefik-specific prompts (option 1)
|
||||
@@ -378,7 +354,7 @@ check_existing_installation() {
|
||||
echo "Generated files already exist, if you want to reinitialize the environment, please remove them first."
|
||||
echo "You can use the following commands:"
|
||||
echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
|
||||
echo " rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt && rm -rf crowdsec/"
|
||||
echo " rm -f docker-compose.yml dashboard.env config.yaml proxy.env traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt"
|
||||
echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
|
||||
exit 1
|
||||
fi
|
||||
@@ -399,9 +375,6 @@ generate_configuration_files() {
|
||||
echo "NB_PROXY_TOKEN=placeholder" >> proxy.env
|
||||
# TCP ServersTransport for PROXY protocol v2 to the proxy backend
|
||||
render_traefik_dynamic > traefik-dynamic.yaml
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" ]]; then
|
||||
mkdir -p crowdsec/db
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
@@ -444,12 +417,8 @@ start_services_and_show_instructions() {
|
||||
|
||||
if [[ "$ENABLE_PROXY" == "true" ]]; then
|
||||
# Phase 1: Start core services (without proxy)
|
||||
local core_services="traefik dashboard netbird-server"
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" ]]; then
|
||||
core_services="$core_services crowdsec"
|
||||
fi
|
||||
echo "Starting core services..."
|
||||
$DOCKER_COMPOSE_COMMAND up -d $core_services
|
||||
$DOCKER_COMPOSE_COMMAND up -d traefik dashboard netbird-server
|
||||
|
||||
sleep 3
|
||||
wait_management_proxy traefik
|
||||
@@ -469,36 +438,7 @@ start_services_and_show_instructions() {
|
||||
|
||||
echo "Proxy token created successfully."
|
||||
|
||||
# Phase 3: Create CrowdSec bouncer key if enabled
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" ]]; then
|
||||
echo "Registering CrowdSec bouncer..."
|
||||
# Wait for CrowdSec to be healthy (healthcheck handles this via depends_on, but be safe)
|
||||
local cs_retries=0
|
||||
while ! $DOCKER_COMPOSE_COMMAND exec -T crowdsec cscli capi status >/dev/null 2>&1; do
|
||||
cs_retries=$((cs_retries + 1))
|
||||
if [[ $cs_retries -ge 30 ]]; then
|
||||
echo "WARNING: CrowdSec did not become ready. Skipping CrowdSec setup." > /dev/stderr
|
||||
echo "You can register a bouncer manually later with:" > /dev/stderr
|
||||
echo " docker exec netbird-crowdsec cscli bouncers add netbird-proxy -o raw" > /dev/stderr
|
||||
ENABLE_CROWDSEC="false"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" ]]; then
|
||||
CROWDSEC_BOUNCER_KEY=$($DOCKER_COMPOSE_COMMAND exec -T crowdsec \
|
||||
cscli bouncers add netbird-proxy -o raw 2>/dev/null)
|
||||
if [[ -z "$CROWDSEC_BOUNCER_KEY" ]]; then
|
||||
echo "WARNING: Failed to create CrowdSec bouncer key. Skipping CrowdSec setup." > /dev/stderr
|
||||
ENABLE_CROWDSEC="false"
|
||||
else
|
||||
echo "CrowdSec bouncer registered."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate proxy.env with the token (and CrowdSec key if enabled)
|
||||
# Generate proxy.env with the token
|
||||
render_proxy_env > proxy.env
|
||||
|
||||
# Start proxy service
|
||||
@@ -585,25 +525,11 @@ render_docker_compose_traefik_builtin() {
|
||||
# Generate proxy service section and Traefik dynamic config if enabled
|
||||
local proxy_service=""
|
||||
local proxy_volumes=""
|
||||
local crowdsec_service=""
|
||||
local crowdsec_volumes=""
|
||||
local traefik_file_provider=""
|
||||
local traefik_dynamic_volume=""
|
||||
if [[ "$ENABLE_PROXY" == "true" ]]; then
|
||||
traefik_file_provider=' - "--providers.file.filename=/etc/traefik/dynamic.yaml"'
|
||||
traefik_dynamic_volume=" - ./traefik-dynamic.yaml:/etc/traefik/dynamic.yaml:ro"
|
||||
|
||||
local proxy_depends="
|
||||
netbird-server:
|
||||
condition: service_started"
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" ]]; then
|
||||
proxy_depends="
|
||||
netbird-server:
|
||||
condition: service_started
|
||||
crowdsec:
|
||||
condition: service_healthy"
|
||||
fi
|
||||
|
||||
proxy_service="
|
||||
# NetBird Proxy - exposes internal resources to the internet
|
||||
proxy:
|
||||
@@ -613,7 +539,8 @@ render_docker_compose_traefik_builtin() {
|
||||
- 51820:51820/udp
|
||||
restart: unless-stopped
|
||||
networks: [netbird]
|
||||
depends_on:${proxy_depends}
|
||||
depends_on:
|
||||
- netbird-server
|
||||
env_file:
|
||||
- ./proxy.env
|
||||
volumes:
|
||||
@@ -636,36 +563,6 @@ render_docker_compose_traefik_builtin() {
|
||||
"
|
||||
proxy_volumes="
|
||||
netbird_proxy_certs:"
|
||||
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" ]]; then
|
||||
crowdsec_service="
|
||||
# CrowdSec LAPI - IP reputation and community blocklist
|
||||
crowdsec:
|
||||
image: crowdsecurity/crowdsec:latest
|
||||
container_name: netbird-crowdsec
|
||||
restart: unless-stopped
|
||||
networks: [netbird]
|
||||
environment:
|
||||
COLLECTIONS: crowdsecurity/linux
|
||||
volumes:
|
||||
- ./crowdsec:/etc/crowdsec
|
||||
- crowdsec_db:/var/lib/crowdsec/data
|
||||
healthcheck:
|
||||
test: [\"CMD\", \"cscli\", \"capi\", \"status\"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 15
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
logging:
|
||||
driver: \"json-file\"
|
||||
options:
|
||||
max-size: \"500m\"
|
||||
max-file: \"2\"
|
||||
"
|
||||
crowdsec_volumes="
|
||||
crowdsec_db:"
|
||||
fi
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
@@ -778,10 +675,10 @@ $traefik_dynamic_volume
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
${proxy_service}${crowdsec_service}
|
||||
${proxy_service}
|
||||
volumes:
|
||||
netbird_data:
|
||||
netbird_traefik_letsencrypt:${proxy_volumes}${crowdsec_volumes}
|
||||
netbird_traefik_letsencrypt:${proxy_volumes}
|
||||
|
||||
networks:
|
||||
netbird:
|
||||
@@ -886,15 +783,6 @@ NB_PROXY_PROXY_PROTOCOL=true
|
||||
# Trust Traefik's IP for PROXY protocol headers
|
||||
NB_PROXY_TRUSTED_PROXIES=$TRAEFIK_IP
|
||||
EOF
|
||||
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" && -n "$CROWDSEC_BOUNCER_KEY" ]]; then
|
||||
cat <<EOF
|
||||
# CrowdSec IP reputation (LAPI running in crowdsec container)
|
||||
NB_PROXY_CROWDSEC_API_URL=http://crowdsec:8080
|
||||
NB_PROXY_CROWDSEC_API_KEY=$CROWDSEC_BOUNCER_KEY
|
||||
EOF
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1284,17 +1172,6 @@ print_builtin_traefik_instructions() {
|
||||
echo ""
|
||||
echo " *.$NETBIRD_DOMAIN CNAME $NETBIRD_DOMAIN"
|
||||
echo ""
|
||||
if [[ "$ENABLE_CROWDSEC" == "true" ]]; then
|
||||
echo "CrowdSec IP Reputation:"
|
||||
echo " CrowdSec LAPI is running and connected to the community blocklist."
|
||||
echo " The proxy will automatically check client IPs against known threats."
|
||||
echo " Enable CrowdSec per-service in the dashboard under Access Control."
|
||||
echo ""
|
||||
echo " To enroll in CrowdSec Console (optional, for dashboard and premium blocklists):"
|
||||
echo " docker exec netbird-crowdsec cscli console enroll <your-enrollment-key>"
|
||||
echo " Get your enrollment key at: https://app.crowdsec.net"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package accesslogs
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
@@ -38,7 +37,6 @@ type AccessLogEntry struct {
|
||||
BytesUpload int64 `gorm:"index"`
|
||||
BytesDownload int64 `gorm:"index"`
|
||||
Protocol AccessLogProtocol `gorm:"index"`
|
||||
Metadata map[string]string `gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// FromProto creates an AccessLogEntry from a proto.AccessLog
|
||||
@@ -57,7 +55,6 @@ func (a *AccessLogEntry) FromProto(serviceLog *proto.AccessLog) {
|
||||
a.BytesUpload = serviceLog.GetBytesUpload()
|
||||
a.BytesDownload = serviceLog.GetBytesDownload()
|
||||
a.Protocol = AccessLogProtocol(serviceLog.GetProtocol())
|
||||
a.Metadata = maps.Clone(serviceLog.GetMetadata())
|
||||
|
||||
if sourceIP := serviceLog.GetSourceIp(); sourceIP != "" {
|
||||
if addr, err := netip.ParseAddr(sourceIP); err == nil {
|
||||
@@ -120,11 +117,6 @@ func (a *AccessLogEntry) ToAPIResponse() *api.ProxyAccessLog {
|
||||
protocol = &p
|
||||
}
|
||||
|
||||
var metadata *map[string]string
|
||||
if len(a.Metadata) > 0 {
|
||||
metadata = &a.Metadata
|
||||
}
|
||||
|
||||
return &api.ProxyAccessLog{
|
||||
Id: a.ID,
|
||||
ServiceId: a.ServiceID,
|
||||
@@ -144,6 +136,5 @@ func (a *AccessLogEntry) ToAPIResponse() *api.ProxyAccessLog {
|
||||
BytesUpload: a.BytesUpload,
|
||||
BytesDownload: a.BytesDownload,
|
||||
Protocol: protocol,
|
||||
Metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ type Domain struct {
|
||||
// RequireSubdomain is populated at query time. When true, the domain
|
||||
// cannot be used bare and a subdomain label must be prepended. Not persisted.
|
||||
RequireSubdomain *bool `gorm:"-"`
|
||||
// SupportsCrowdSec is populated at query time from proxy cluster capabilities.
|
||||
// Not persisted.
|
||||
SupportsCrowdSec *bool `gorm:"-"`
|
||||
}
|
||||
|
||||
// EventMeta returns activity event metadata for a domain
|
||||
|
||||
@@ -48,7 +48,6 @@ func domainToApi(d *domain.Domain) api.ReverseProxyDomain {
|
||||
Validated: d.Validated,
|
||||
SupportsCustomPorts: d.SupportsCustomPorts,
|
||||
RequireSubdomain: d.RequireSubdomain,
|
||||
SupportsCrowdsec: d.SupportsCrowdSec,
|
||||
}
|
||||
if d.TargetCluster != "" {
|
||||
resp.TargetCluster = &d.TargetCluster
|
||||
|
||||
@@ -33,7 +33,6 @@ type proxyManager interface {
|
||||
GetActiveClusterAddresses(ctx context.Context) ([]string, error)
|
||||
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
@@ -91,7 +90,6 @@ func (m Manager) GetDomains(ctx context.Context, accountID, userID string) ([]*d
|
||||
}
|
||||
d.SupportsCustomPorts = m.proxyManager.ClusterSupportsCustomPorts(ctx, cluster)
|
||||
d.RequireSubdomain = m.proxyManager.ClusterRequireSubdomain(ctx, cluster)
|
||||
d.SupportsCrowdSec = m.proxyManager.ClusterSupportsCrowdSec(ctx, cluster)
|
||||
ret = append(ret, d)
|
||||
}
|
||||
|
||||
@@ -107,7 +105,6 @@ func (m Manager) GetDomains(ctx context.Context, accountID, userID string) ([]*d
|
||||
}
|
||||
if d.TargetCluster != "" {
|
||||
cd.SupportsCustomPorts = m.proxyManager.ClusterSupportsCustomPorts(ctx, d.TargetCluster)
|
||||
cd.SupportsCrowdSec = m.proxyManager.ClusterSupportsCrowdSec(ctx, d.TargetCluster)
|
||||
}
|
||||
// Custom domains never require a subdomain by default since
|
||||
// the account owns them and should be able to use the bare domain.
|
||||
|
||||
@@ -18,7 +18,6 @@ type Manager interface {
|
||||
GetActiveClusters(ctx context.Context) ([]Cluster, error)
|
||||
ClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||
ClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||
ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||
CleanupStale(ctx context.Context, inactivityDuration time.Duration) error
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ type store interface {
|
||||
GetActiveProxyClusters(ctx context.Context) ([]proxy.Cluster, error)
|
||||
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||
CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error
|
||||
}
|
||||
|
||||
@@ -139,12 +138,6 @@ func (m Manager) ClusterRequireSubdomain(ctx context.Context, clusterAddr string
|
||||
return m.store.GetClusterRequireSubdomain(ctx, clusterAddr)
|
||||
}
|
||||
|
||||
// ClusterSupportsCrowdSec returns whether all active proxies in the cluster
|
||||
// have CrowdSec configured (unanimous). Returns nil when no proxy has reported capabilities.
|
||||
func (m Manager) ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool {
|
||||
return m.store.GetClusterSupportsCrowdSec(ctx, clusterAddr)
|
||||
}
|
||||
|
||||
// CleanupStale removes proxies that haven't sent heartbeat in the specified duration
|
||||
func (m Manager) CleanupStale(ctx context.Context, inactivityDuration time.Duration) error {
|
||||
if err := m.store.CleanupStaleProxies(ctx, inactivityDuration); err != nil {
|
||||
|
||||
@@ -78,20 +78,6 @@ func (mr *MockManagerMockRecorder) ClusterRequireSubdomain(ctx, clusterAddr inte
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterRequireSubdomain", reflect.TypeOf((*MockManager)(nil).ClusterRequireSubdomain), ctx, clusterAddr)
|
||||
}
|
||||
|
||||
// ClusterSupportsCrowdSec mocks base method.
|
||||
func (m *MockManager) ClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ClusterSupportsCrowdSec", ctx, clusterAddr)
|
||||
ret0, _ := ret[0].(*bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ClusterSupportsCrowdSec indicates an expected call of ClusterSupportsCrowdSec.
|
||||
func (mr *MockManagerMockRecorder) ClusterSupportsCrowdSec(ctx, clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClusterSupportsCrowdSec", reflect.TypeOf((*MockManager)(nil).ClusterSupportsCrowdSec), ctx, clusterAddr)
|
||||
}
|
||||
|
||||
// Connect mocks base method.
|
||||
func (m *MockManager) Connect(ctx context.Context, proxyID, clusterAddress, ipAddress string, capabilities *Capabilities) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -11,8 +11,6 @@ type Capabilities struct {
|
||||
// RequireSubdomain indicates whether a subdomain label is required in
|
||||
// front of the cluster domain.
|
||||
RequireSubdomain *bool
|
||||
// SupportsCrowdsec indicates whether this proxy has CrowdSec configured.
|
||||
SupportsCrowdsec *bool
|
||||
}
|
||||
|
||||
// Proxy represents a reverse proxy instance
|
||||
|
||||
@@ -81,7 +81,6 @@ func setupL4Test(t *testing.T, customPortsSupported *bool) (*Manager, store.Stor
|
||||
mockCaps := proxy.NewMockManager(ctrl)
|
||||
mockCaps.EXPECT().ClusterSupportsCustomPorts(gomock.Any(), testCluster).Return(customPortsSupported).AnyTimes()
|
||||
mockCaps.EXPECT().ClusterRequireSubdomain(gomock.Any(), testCluster).Return((*bool)(nil)).AnyTimes()
|
||||
mockCaps.EXPECT().ClusterSupportsCrowdSec(gomock.Any(), testCluster).Return((*bool)(nil)).AnyTimes()
|
||||
|
||||
accountMgr := &mock_server.MockAccountManager{
|
||||
StoreEventFunc: func(_ context.Context, _, _, _ string, _ activity.ActivityDescriber, _ map[string]any) {},
|
||||
|
||||
@@ -113,7 +113,6 @@ type AccessRestrictions struct {
|
||||
BlockedCIDRs []string `json:"blocked_cidrs,omitempty" gorm:"serializer:json"`
|
||||
AllowedCountries []string `json:"allowed_countries,omitempty" gorm:"serializer:json"`
|
||||
BlockedCountries []string `json:"blocked_countries,omitempty" gorm:"serializer:json"`
|
||||
CrowdSecMode string `json:"crowdsec_mode,omitempty" gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the AccessRestrictions.
|
||||
@@ -123,7 +122,6 @@ func (r AccessRestrictions) Copy() AccessRestrictions {
|
||||
BlockedCIDRs: slices.Clone(r.BlockedCIDRs),
|
||||
AllowedCountries: slices.Clone(r.AllowedCountries),
|
||||
BlockedCountries: slices.Clone(r.BlockedCountries),
|
||||
CrowdSecMode: r.CrowdSecMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,11 +555,7 @@ func (s *Service) FromAPIRequest(req *api.ServiceRequest, accountID string) erro
|
||||
}
|
||||
|
||||
if req.AccessRestrictions != nil {
|
||||
restrictions, err := restrictionsFromAPI(req.AccessRestrictions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Restrictions = restrictions
|
||||
s.Restrictions = restrictionsFromAPI(req.AccessRestrictions)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -637,9 +631,9 @@ func authFromAPI(reqAuth *api.ServiceAuthConfig) AuthConfig {
|
||||
return auth
|
||||
}
|
||||
|
||||
func restrictionsFromAPI(r *api.AccessRestrictions) (AccessRestrictions, error) {
|
||||
func restrictionsFromAPI(r *api.AccessRestrictions) AccessRestrictions {
|
||||
if r == nil {
|
||||
return AccessRestrictions{}, nil
|
||||
return AccessRestrictions{}
|
||||
}
|
||||
var res AccessRestrictions
|
||||
if r.AllowedCidrs != nil {
|
||||
@@ -654,19 +648,11 @@ func restrictionsFromAPI(r *api.AccessRestrictions) (AccessRestrictions, error)
|
||||
if r.BlockedCountries != nil {
|
||||
res.BlockedCountries = *r.BlockedCountries
|
||||
}
|
||||
if r.CrowdsecMode != nil {
|
||||
if !r.CrowdsecMode.Valid() {
|
||||
return AccessRestrictions{}, fmt.Errorf("invalid crowdsec_mode %q", *r.CrowdsecMode)
|
||||
}
|
||||
res.CrowdSecMode = string(*r.CrowdsecMode)
|
||||
}
|
||||
return res, nil
|
||||
return res
|
||||
}
|
||||
|
||||
func restrictionsToAPI(r AccessRestrictions) *api.AccessRestrictions {
|
||||
if len(r.AllowedCIDRs) == 0 && len(r.BlockedCIDRs) == 0 &&
|
||||
len(r.AllowedCountries) == 0 && len(r.BlockedCountries) == 0 &&
|
||||
r.CrowdSecMode == "" {
|
||||
if len(r.AllowedCIDRs) == 0 && len(r.BlockedCIDRs) == 0 && len(r.AllowedCountries) == 0 && len(r.BlockedCountries) == 0 {
|
||||
return nil
|
||||
}
|
||||
res := &api.AccessRestrictions{}
|
||||
@@ -682,17 +668,11 @@ func restrictionsToAPI(r AccessRestrictions) *api.AccessRestrictions {
|
||||
if len(r.BlockedCountries) > 0 {
|
||||
res.BlockedCountries = &r.BlockedCountries
|
||||
}
|
||||
if r.CrowdSecMode != "" {
|
||||
mode := api.AccessRestrictionsCrowdsecMode(r.CrowdSecMode)
|
||||
res.CrowdsecMode = &mode
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func restrictionsToProto(r AccessRestrictions) *proto.AccessRestrictions {
|
||||
if len(r.AllowedCIDRs) == 0 && len(r.BlockedCIDRs) == 0 &&
|
||||
len(r.AllowedCountries) == 0 && len(r.BlockedCountries) == 0 &&
|
||||
r.CrowdSecMode == "" {
|
||||
if len(r.AllowedCIDRs) == 0 && len(r.BlockedCIDRs) == 0 && len(r.AllowedCountries) == 0 && len(r.BlockedCountries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &proto.AccessRestrictions{
|
||||
@@ -700,7 +680,6 @@ func restrictionsToProto(r AccessRestrictions) *proto.AccessRestrictions {
|
||||
BlockedCidrs: r.BlockedCIDRs,
|
||||
AllowedCountries: r.AllowedCountries,
|
||||
BlockedCountries: r.BlockedCountries,
|
||||
CrowdsecMode: r.CrowdSecMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,20 +988,7 @@ const (
|
||||
|
||||
// validateAccessRestrictions validates and normalizes access restriction
|
||||
// entries. Country codes are uppercased in place.
|
||||
func validateCrowdSecMode(mode string) error {
|
||||
switch mode {
|
||||
case "", "off", "enforce", "observe":
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("crowdsec_mode %q is invalid", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func validateAccessRestrictions(r *AccessRestrictions) error {
|
||||
if err := validateCrowdSecMode(r.CrowdSecMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(r.AllowedCIDRs) > maxCIDREntries {
|
||||
return fmt.Errorf("allowed_cidrs: exceeds maximum of %d entries", maxCIDREntries)
|
||||
}
|
||||
@@ -1036,37 +1002,35 @@ func validateAccessRestrictions(r *AccessRestrictions) error {
|
||||
return fmt.Errorf("blocked_countries: exceeds maximum of %d entries", maxCountryEntries)
|
||||
}
|
||||
|
||||
if err := validateCIDRList("allowed_cidrs", r.AllowedCIDRs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateCIDRList("blocked_cidrs", r.BlockedCIDRs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := normalizeCountryList("allowed_countries", r.AllowedCountries); err != nil {
|
||||
return err
|
||||
}
|
||||
return normalizeCountryList("blocked_countries", r.BlockedCountries)
|
||||
}
|
||||
|
||||
func validateCIDRList(field string, cidrs []string) error {
|
||||
for i, raw := range cidrs {
|
||||
for i, raw := range r.AllowedCIDRs {
|
||||
prefix, err := netip.ParsePrefix(raw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s[%d]: %w", field, i, err)
|
||||
return fmt.Errorf("allowed_cidrs[%d]: %w", i, err)
|
||||
}
|
||||
if prefix != prefix.Masked() {
|
||||
return fmt.Errorf("%s[%d]: %q has host bits set, use %s instead", field, i, raw, prefix.Masked())
|
||||
return fmt.Errorf("allowed_cidrs[%d]: %q has host bits set, use %s instead", i, raw, prefix.Masked())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeCountryList(field string, codes []string) error {
|
||||
for i, code := range codes {
|
||||
if len(code) != 2 {
|
||||
return fmt.Errorf("%s[%d]: %q must be a 2-letter ISO 3166-1 alpha-2 code", field, i, code)
|
||||
for i, raw := range r.BlockedCIDRs {
|
||||
prefix, err := netip.ParsePrefix(raw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("blocked_cidrs[%d]: %w", i, err)
|
||||
}
|
||||
codes[i] = strings.ToUpper(code)
|
||||
if prefix != prefix.Masked() {
|
||||
return fmt.Errorf("blocked_cidrs[%d]: %q has host bits set, use %s instead", i, raw, prefix.Masked())
|
||||
}
|
||||
}
|
||||
for i, code := range r.AllowedCountries {
|
||||
if len(code) != 2 {
|
||||
return fmt.Errorf("allowed_countries[%d]: %q must be a 2-letter ISO 3166-1 alpha-2 code", i, code)
|
||||
}
|
||||
r.AllowedCountries[i] = strings.ToUpper(code)
|
||||
}
|
||||
for i, code := range r.BlockedCountries {
|
||||
if len(code) != 2 {
|
||||
return fmt.Errorf("blocked_countries[%d]: %q must be a 2-letter ISO 3166-1 alpha-2 code", i, code)
|
||||
}
|
||||
r.BlockedCountries[i] = strings.ToUpper(code)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -188,7 +188,6 @@ func (s *ProxyServiceServer) GetMappingUpdate(req *proto.GetMappingUpdateRequest
|
||||
caps = &proxy.Capabilities{
|
||||
SupportsCustomPorts: c.SupportsCustomPorts,
|
||||
RequireSubdomain: c.RequireSubdomain,
|
||||
SupportsCrowdsec: c.SupportsCrowdsec,
|
||||
}
|
||||
}
|
||||
if err := s.proxyManager.Connect(ctx, proxyID, proxyAddress, peerInfo, caps); err != nil {
|
||||
|
||||
@@ -5499,7 +5499,6 @@ const proxyActiveThreshold = 2 * time.Minute
|
||||
var validCapabilityColumns = map[string]struct{}{
|
||||
"supports_custom_ports": {},
|
||||
"require_subdomain": {},
|
||||
"supports_crowdsec": {},
|
||||
}
|
||||
|
||||
// GetClusterSupportsCustomPorts returns whether any active proxy in the cluster
|
||||
@@ -5514,59 +5513,6 @@ func (s *SqlStore) GetClusterRequireSubdomain(ctx context.Context, clusterAddr s
|
||||
return s.getClusterCapability(ctx, clusterAddr, "require_subdomain")
|
||||
}
|
||||
|
||||
// GetClusterSupportsCrowdSec returns whether all active proxies in the cluster
|
||||
// have CrowdSec configured. Returns nil when no proxy reported the capability.
|
||||
// Unlike other capabilities that use ANY-true (for rolling upgrades), CrowdSec
|
||||
// requires unanimous support: a single unconfigured proxy would let requests
|
||||
// bypass reputation checks.
|
||||
func (s *SqlStore) GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool {
|
||||
return s.getClusterUnanimousCapability(ctx, clusterAddr, "supports_crowdsec")
|
||||
}
|
||||
|
||||
// getClusterUnanimousCapability returns an aggregated boolean capability
|
||||
// requiring all active proxies in the cluster to report true.
|
||||
func (s *SqlStore) getClusterUnanimousCapability(ctx context.Context, clusterAddr, column string) *bool {
|
||||
if _, ok := validCapabilityColumns[column]; !ok {
|
||||
log.WithContext(ctx).Errorf("invalid capability column: %s", column)
|
||||
return nil
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Total int64
|
||||
Reported int64
|
||||
AllTrue bool
|
||||
}
|
||||
|
||||
// All active proxies must have reported the capability (no NULLs) and all
|
||||
// must report true. A single unreported or false proxy means the cluster
|
||||
// does not unanimously support the capability.
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&proxy.Proxy{}).
|
||||
Select("COUNT(*) AS total, "+
|
||||
"COUNT(CASE WHEN "+column+" IS NOT NULL THEN 1 END) AS reported, "+
|
||||
"COUNT(*) > 0 AND COUNT(*) = COUNT(CASE WHEN "+column+" = true THEN 1 END) AS all_true").
|
||||
Where("cluster_address = ? AND status = ? AND last_seen > ?",
|
||||
clusterAddr, "connected", time.Now().Add(-proxyActiveThreshold)).
|
||||
Scan(&result).Error
|
||||
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("query cluster capability %s for %s: %v", column, clusterAddr, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if result.Total == 0 || result.Reported == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If any proxy has not reported (NULL), we can't confirm unanimous support.
|
||||
if result.Reported < result.Total {
|
||||
v := false
|
||||
return &v
|
||||
}
|
||||
|
||||
return &result.AllTrue
|
||||
}
|
||||
|
||||
// getClusterCapability returns an aggregated boolean capability for the given
|
||||
// cluster. It checks active (connected, recently seen) proxies and returns:
|
||||
// - *true if any proxy in the cluster has the capability set to true,
|
||||
|
||||
@@ -289,7 +289,6 @@ type Store interface {
|
||||
GetActiveProxyClusters(ctx context.Context) ([]proxy.Cluster, error)
|
||||
GetClusterSupportsCustomPorts(ctx context.Context, clusterAddr string) *bool
|
||||
GetClusterRequireSubdomain(ctx context.Context, clusterAddr string) *bool
|
||||
GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool
|
||||
CleanupStaleProxies(ctx context.Context, inactivityDuration time.Duration) error
|
||||
|
||||
GetCustomDomainsCounts(ctx context.Context) (total int64, validated int64, err error)
|
||||
|
||||
@@ -165,19 +165,6 @@ func (mr *MockStoreMockRecorder) CleanupStaleProxies(ctx, inactivityDuration int
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupStaleProxies", reflect.TypeOf((*MockStore)(nil).CleanupStaleProxies), ctx, inactivityDuration)
|
||||
}
|
||||
|
||||
// GetClusterSupportsCrowdSec mocks base method.
|
||||
func (m *MockStore) GetClusterSupportsCrowdSec(ctx context.Context, clusterAddr string) *bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetClusterSupportsCrowdSec", ctx, clusterAddr)
|
||||
ret0, _ := ret[0].(*bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetClusterSupportsCrowdSec indicates an expected call of GetClusterSupportsCrowdSec.
|
||||
func (mr *MockStoreMockRecorder) GetClusterSupportsCrowdSec(ctx, clusterAddr interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterSupportsCrowdSec", reflect.TypeOf((*MockStore)(nil).GetClusterSupportsCrowdSec), ctx, clusterAddr)
|
||||
}
|
||||
// Close mocks base method.
|
||||
func (m *MockStore) Close(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -35,7 +35,7 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
logLevel string
|
||||
logLevel string
|
||||
debugLogs bool
|
||||
mgmtAddr string
|
||||
addr string
|
||||
@@ -64,8 +64,6 @@ var (
|
||||
supportsCustomPorts bool
|
||||
requireSubdomain bool
|
||||
geoDataDir string
|
||||
crowdsecAPIURL string
|
||||
crowdsecAPIKey string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -108,8 +106,6 @@ func init() {
|
||||
rootCmd.Flags().DurationVar(&maxDialTimeout, "max-dial-timeout", envDurationOrDefault("NB_PROXY_MAX_DIAL_TIMEOUT", 0), "Cap per-service backend dial timeout (0 = no cap)")
|
||||
rootCmd.Flags().DurationVar(&maxSessionIdleTimeout, "max-session-idle-timeout", envDurationOrDefault("NB_PROXY_MAX_SESSION_IDLE_TIMEOUT", 0), "Cap per-service session idle timeout (0 = no cap)")
|
||||
rootCmd.Flags().StringVar(&geoDataDir, "geo-data-dir", envStringOrDefault("NB_PROXY_GEO_DATA_DIR", "/var/lib/netbird/geolocation"), "Directory for the GeoLite2 MMDB file (auto-downloaded if missing)")
|
||||
rootCmd.Flags().StringVar(&crowdsecAPIURL, "crowdsec-api-url", envStringOrDefault("NB_PROXY_CROWDSEC_API_URL", ""), "CrowdSec LAPI URL for IP reputation checks")
|
||||
rootCmd.Flags().StringVar(&crowdsecAPIKey, "crowdsec-api-key", envStringOrDefault("NB_PROXY_CROWDSEC_API_KEY", ""), "CrowdSec bouncer API key")
|
||||
}
|
||||
|
||||
// Execute runs the root command.
|
||||
@@ -191,8 +187,6 @@ func runServer(cmd *cobra.Command, args []string) error {
|
||||
MaxDialTimeout: maxDialTimeout,
|
||||
MaxSessionIdleTimeout: maxSessionIdleTimeout,
|
||||
GeoDataDir: geoDataDir,
|
||||
CrowdSecAPIURL: crowdsecAPIURL,
|
||||
CrowdSecAPIKey: crowdsecAPIKey,
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
@@ -126,7 +126,6 @@ type logEntry struct {
|
||||
BytesUpload int64
|
||||
BytesDownload int64
|
||||
Protocol Protocol
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// Protocol identifies the transport protocol of an access log entry.
|
||||
@@ -151,10 +150,8 @@ type L4Entry struct {
|
||||
BytesDownload int64
|
||||
// DenyReason, when non-empty, indicates the connection was denied.
|
||||
// Values match the HTTP auth mechanism strings: "ip_restricted",
|
||||
// "country_restricted", "geo_unavailable", "crowdsec_ban", etc.
|
||||
// "country_restricted", "geo_unavailable".
|
||||
DenyReason string
|
||||
// Metadata carries extra context about the connection (e.g. CrowdSec verdict).
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// LogL4 sends an access log entry for a layer-4 connection (TCP or UDP).
|
||||
@@ -170,7 +167,6 @@ func (l *Logger) LogL4(entry L4Entry) {
|
||||
DurationMs: entry.DurationMs,
|
||||
BytesUpload: entry.BytesUpload,
|
||||
BytesDownload: entry.BytesDownload,
|
||||
Metadata: entry.Metadata,
|
||||
}
|
||||
if entry.DenyReason != "" {
|
||||
if !l.allowDenyLog(entry.ServiceID, entry.DenyReason) {
|
||||
@@ -262,7 +258,6 @@ func (l *Logger) log(entry logEntry) {
|
||||
BytesUpload: entry.BytesUpload,
|
||||
BytesDownload: entry.BytesDownload,
|
||||
Protocol: string(entry.Protocol),
|
||||
Metadata: entry.Metadata,
|
||||
},
|
||||
}); err != nil {
|
||||
l.logger.WithFields(log.Fields{
|
||||
|
||||
@@ -82,7 +82,6 @@ func (l *Logger) Middleware(next http.Handler) http.Handler {
|
||||
BytesUpload: bytesUpload,
|
||||
BytesDownload: bytesDownload,
|
||||
Protocol: ProtocolHTTP,
|
||||
Metadata: capturedData.GetMetadata(),
|
||||
}
|
||||
l.logger.Debugf("response: request_id=%s method=%s host=%s path=%s status=%d duration=%dms source=%s origin=%s service=%s account=%s",
|
||||
requestID, r.Method, host, r.URL.Path, sw.status, duration.Milliseconds(), sourceIp, capturedData.GetOrigin(), capturedData.GetServiceID(), capturedData.GetAccountID())
|
||||
|
||||
@@ -167,20 +167,6 @@ func (mw *Middleware) checkIPRestrictions(w http.ResponseWriter, r *http.Request
|
||||
return true
|
||||
}
|
||||
|
||||
if verdict.IsCrowdSec() {
|
||||
if cd := proxy.CapturedDataFromContext(r.Context()); cd != nil {
|
||||
cd.SetMetadata("crowdsec_verdict", verdict.String())
|
||||
if config.IPRestrictions.IsObserveOnly(verdict) {
|
||||
cd.SetMetadata("crowdsec_mode", "observe")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.IPRestrictions.IsObserveOnly(verdict) {
|
||||
mw.logger.Debugf("CrowdSec observe: would block %s for %s (%s)", clientIP, r.Host, verdict)
|
||||
return true
|
||||
}
|
||||
|
||||
reason := verdict.String()
|
||||
mw.blockIPRestriction(r, reason)
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
|
||||
@@ -669,7 +669,7 @@ func TestCheckIPRestrictions_UnparseableAddress(t *testing.T) {
|
||||
mw := NewMiddleware(log.StandardLogger(), nil, nil)
|
||||
|
||||
err := mw.AddDomain("example.com", nil, "", 0, "acc1", "svc1",
|
||||
restrict.ParseFilter(restrict.FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}}))
|
||||
restrict.ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := mw.Protect(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -705,7 +705,7 @@ func TestCheckIPRestrictions_UsesCapturedDataClientIP(t *testing.T) {
|
||||
mw := NewMiddleware(log.StandardLogger(), nil, nil)
|
||||
|
||||
err := mw.AddDomain("example.com", nil, "", 0, "acc1", "svc1",
|
||||
restrict.ParseFilter(restrict.FilterConfig{AllowedCIDRs: []string{"203.0.113.0/24"}}))
|
||||
restrict.ParseFilter([]string{"203.0.113.0/24"}, nil, nil, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := mw.Protect(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -746,7 +746,7 @@ func TestCheckIPRestrictions_NilGeoWithCountryRules(t *testing.T) {
|
||||
mw := NewMiddleware(log.StandardLogger(), nil, nil)
|
||||
|
||||
err := mw.AddDomain("example.com", nil, "", 0, "acc1", "svc1",
|
||||
restrict.ParseFilter(restrict.FilterConfig{AllowedCountries: []string{"US"}}))
|
||||
restrict.ParseFilter(nil, nil, []string{"US"}, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
handler := mw.Protect(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
// Package crowdsec provides a CrowdSec stream bouncer that maintains a local
|
||||
// decision cache for IP reputation checks.
|
||||
package crowdsec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
csbouncer "github.com/crowdsecurity/go-cs-bouncer"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/proxy/internal/restrict"
|
||||
)
|
||||
|
||||
// Bouncer wraps a CrowdSec StreamBouncer, maintaining a local cache of
|
||||
// active decisions for fast IP lookups. It implements restrict.CrowdSecChecker.
|
||||
type Bouncer struct {
|
||||
mu sync.RWMutex
|
||||
ips map[netip.Addr]*restrict.CrowdSecDecision
|
||||
prefixes map[netip.Prefix]*restrict.CrowdSecDecision
|
||||
ready atomic.Bool
|
||||
|
||||
apiURL string
|
||||
apiKey string
|
||||
tickerInterval time.Duration
|
||||
logger *log.Entry
|
||||
|
||||
// lifeMu protects cancel and done from concurrent Start/Stop calls.
|
||||
lifeMu sync.Mutex
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// compile-time check
|
||||
var _ restrict.CrowdSecChecker = (*Bouncer)(nil)
|
||||
|
||||
// NewBouncer creates a bouncer but does not start the stream.
|
||||
func NewBouncer(apiURL, apiKey string, logger *log.Entry) *Bouncer {
|
||||
return &Bouncer{
|
||||
apiURL: apiURL,
|
||||
apiKey: apiKey,
|
||||
logger: logger,
|
||||
ips: make(map[netip.Addr]*restrict.CrowdSecDecision),
|
||||
prefixes: make(map[netip.Prefix]*restrict.CrowdSecDecision),
|
||||
}
|
||||
}
|
||||
|
||||
// Start launches the background goroutine that streams decisions from the
|
||||
// CrowdSec LAPI. The stream runs until Stop is called or ctx is cancelled.
|
||||
func (b *Bouncer) Start(ctx context.Context) error {
|
||||
interval := b.tickerInterval
|
||||
if interval == 0 {
|
||||
interval = 10 * time.Second
|
||||
}
|
||||
stream := &csbouncer.StreamBouncer{
|
||||
APIKey: b.apiKey,
|
||||
APIUrl: b.apiURL,
|
||||
TickerInterval: interval.String(),
|
||||
UserAgent: "netbird-proxy/1.0",
|
||||
Scopes: []string{"ip", "range"},
|
||||
RetryInitialConnect: true,
|
||||
}
|
||||
|
||||
b.logger.Infof("connecting to CrowdSec LAPI at %s", b.apiURL)
|
||||
|
||||
if err := stream.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset state from any previous run.
|
||||
b.mu.Lock()
|
||||
b.ips = make(map[netip.Addr]*restrict.CrowdSecDecision)
|
||||
b.prefixes = make(map[netip.Prefix]*restrict.CrowdSecDecision)
|
||||
b.mu.Unlock()
|
||||
b.ready.Store(false)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
done := make(chan struct{})
|
||||
|
||||
b.lifeMu.Lock()
|
||||
if b.cancel != nil {
|
||||
b.lifeMu.Unlock()
|
||||
cancel()
|
||||
return errors.New("bouncer already started")
|
||||
}
|
||||
b.cancel = cancel
|
||||
b.done = done
|
||||
b.lifeMu.Unlock()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := stream.Run(ctx); err != nil && ctx.Err() == nil {
|
||||
b.logger.Errorf("CrowdSec stream ended: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
b.consumeStream(ctx, stream)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop cancels the stream and waits for all goroutines to finish.
|
||||
func (b *Bouncer) Stop() {
|
||||
b.lifeMu.Lock()
|
||||
cancel := b.cancel
|
||||
done := b.done
|
||||
b.cancel = nil
|
||||
b.lifeMu.Unlock()
|
||||
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
// Ready returns true after the first batch of decisions has been processed.
|
||||
func (b *Bouncer) Ready() bool {
|
||||
return b.ready.Load()
|
||||
}
|
||||
|
||||
// CheckIP looks up addr in the local decision cache. Returns nil if no
|
||||
// active decision exists for the address.
|
||||
//
|
||||
// Prefix lookups are O(1): instead of scanning all stored prefixes, we
|
||||
// probe the map for every possible containing prefix of the address
|
||||
// (at most 33 for IPv4, 129 for IPv6).
|
||||
func (b *Bouncer) CheckIP(addr netip.Addr) *restrict.CrowdSecDecision {
|
||||
addr = addr.Unmap()
|
||||
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
|
||||
if d, ok := b.ips[addr]; ok {
|
||||
return d
|
||||
}
|
||||
|
||||
maxBits := 32
|
||||
if addr.Is6() {
|
||||
maxBits = 128
|
||||
}
|
||||
// Walk from most-specific to least-specific prefix so the narrowest
|
||||
// matching decision wins when ranges overlap.
|
||||
for bits := maxBits; bits >= 0; bits-- {
|
||||
prefix := netip.PrefixFrom(addr, bits).Masked()
|
||||
if d, ok := b.prefixes[prefix]; ok {
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bouncer) consumeStream(ctx context.Context, stream *csbouncer.StreamBouncer) {
|
||||
first := true
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case resp, ok := <-stream.Stream:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.applyDeleted(resp.Deleted)
|
||||
b.applyNew(resp.New)
|
||||
b.mu.Unlock()
|
||||
|
||||
if first {
|
||||
b.ready.Store(true)
|
||||
b.logger.Info("CrowdSec bouncer synced initial decisions")
|
||||
first = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bouncer) applyDeleted(decisions []*models.Decision) {
|
||||
for _, d := range decisions {
|
||||
if d.Value == nil || d.Scope == nil {
|
||||
continue
|
||||
}
|
||||
value := *d.Value
|
||||
|
||||
if strings.ToLower(*d.Scope) == "range" || strings.Contains(value, "/") {
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
b.logger.Debugf("skip unparsable CrowdSec range deletion %q: %v", value, err)
|
||||
continue
|
||||
}
|
||||
prefix = normalizePrefix(prefix)
|
||||
delete(b.prefixes, prefix)
|
||||
} else {
|
||||
addr, err := netip.ParseAddr(value)
|
||||
if err != nil {
|
||||
b.logger.Debugf("skip unparsable CrowdSec IP deletion %q: %v", value, err)
|
||||
continue
|
||||
}
|
||||
delete(b.ips, addr.Unmap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bouncer) applyNew(decisions []*models.Decision) {
|
||||
for _, d := range decisions {
|
||||
if d.Value == nil || d.Type == nil || d.Scope == nil {
|
||||
continue
|
||||
}
|
||||
dec := &restrict.CrowdSecDecision{Type: restrict.DecisionType(*d.Type)}
|
||||
value := *d.Value
|
||||
|
||||
if strings.ToLower(*d.Scope) == "range" || strings.Contains(value, "/") {
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
b.logger.Debugf("skip unparsable CrowdSec range %q: %v", value, err)
|
||||
continue
|
||||
}
|
||||
prefix = normalizePrefix(prefix)
|
||||
b.prefixes[prefix] = dec
|
||||
} else {
|
||||
addr, err := netip.ParseAddr(value)
|
||||
if err != nil {
|
||||
b.logger.Debugf("skip unparsable CrowdSec IP %q: %v", value, err)
|
||||
continue
|
||||
}
|
||||
b.ips[addr.Unmap()] = dec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// normalizePrefix unmaps v4-mapped-v6 addresses and zeros host bits so
|
||||
// the prefix is a valid map key that matches CheckIP's probe logic.
|
||||
func normalizePrefix(p netip.Prefix) netip.Prefix {
|
||||
return netip.PrefixFrom(p.Addr().Unmap(), p.Bits()).Masked()
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
package crowdsec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/proxy/internal/restrict"
|
||||
)
|
||||
|
||||
func TestBouncer_CheckIP_Empty(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.ready.Store(true)
|
||||
|
||||
assert.Nil(t, b.CheckIP(netip.MustParseAddr("1.2.3.4")))
|
||||
}
|
||||
|
||||
func TestBouncer_CheckIP_ExactMatch(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.ready.Store(true)
|
||||
b.ips[netip.MustParseAddr("10.0.0.1")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
|
||||
d := b.CheckIP(netip.MustParseAddr("10.0.0.1"))
|
||||
require.NotNil(t, d)
|
||||
assert.Equal(t, restrict.DecisionBan, d.Type)
|
||||
|
||||
assert.Nil(t, b.CheckIP(netip.MustParseAddr("10.0.0.2")))
|
||||
}
|
||||
|
||||
func TestBouncer_CheckIP_PrefixMatch(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.ready.Store(true)
|
||||
b.prefixes[netip.MustParsePrefix("192.168.1.0/24")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
|
||||
d := b.CheckIP(netip.MustParseAddr("192.168.1.100"))
|
||||
require.NotNil(t, d)
|
||||
assert.Equal(t, restrict.DecisionBan, d.Type)
|
||||
|
||||
assert.Nil(t, b.CheckIP(netip.MustParseAddr("192.168.2.1")))
|
||||
}
|
||||
|
||||
func TestBouncer_CheckIP_UnmapsV4InV6(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.ready.Store(true)
|
||||
b.ips[netip.MustParseAddr("10.0.0.1")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
|
||||
d := b.CheckIP(netip.MustParseAddr("::ffff:10.0.0.1"))
|
||||
require.NotNil(t, d)
|
||||
assert.Equal(t, restrict.DecisionBan, d.Type)
|
||||
}
|
||||
|
||||
func TestBouncer_Ready(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
assert.False(t, b.Ready())
|
||||
|
||||
b.ready.Store(true)
|
||||
assert.True(t, b.Ready())
|
||||
}
|
||||
|
||||
func TestBouncer_CheckIP_ExactBeforePrefix(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.ready.Store(true)
|
||||
b.ips[netip.MustParseAddr("10.0.0.1")] = &restrict.CrowdSecDecision{Type: restrict.DecisionCaptcha}
|
||||
b.prefixes[netip.MustParsePrefix("10.0.0.0/8")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
|
||||
d := b.CheckIP(netip.MustParseAddr("10.0.0.1"))
|
||||
require.NotNil(t, d)
|
||||
assert.Equal(t, restrict.DecisionCaptcha, d.Type)
|
||||
|
||||
d2 := b.CheckIP(netip.MustParseAddr("10.0.0.2"))
|
||||
require.NotNil(t, d2)
|
||||
assert.Equal(t, restrict.DecisionBan, d2.Type)
|
||||
}
|
||||
|
||||
func TestBouncer_ApplyNew_IP(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
|
||||
b.applyNew(makeDecisions(
|
||||
decision{scope: "ip", value: "1.2.3.4", dtype: "ban", scenario: "test/brute"},
|
||||
decision{scope: "ip", value: "5.6.7.8", dtype: "captcha", scenario: "test/crawl"},
|
||||
))
|
||||
|
||||
require.Len(t, b.ips, 2)
|
||||
assert.Equal(t, restrict.DecisionBan, b.ips[netip.MustParseAddr("1.2.3.4")].Type)
|
||||
assert.Equal(t, restrict.DecisionCaptcha, b.ips[netip.MustParseAddr("5.6.7.8")].Type)
|
||||
}
|
||||
|
||||
func TestBouncer_ApplyNew_Range(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
|
||||
b.applyNew(makeDecisions(
|
||||
decision{scope: "range", value: "10.0.0.0/8", dtype: "ban"},
|
||||
))
|
||||
|
||||
require.Len(t, b.prefixes, 1)
|
||||
assert.NotNil(t, b.prefixes[netip.MustParsePrefix("10.0.0.0/8")])
|
||||
}
|
||||
|
||||
func TestBouncer_ApplyDeleted_IP(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.ips[netip.MustParseAddr("1.2.3.4")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
b.ips[netip.MustParseAddr("5.6.7.8")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
|
||||
b.applyDeleted(makeDecisions(
|
||||
decision{scope: "ip", value: "1.2.3.4", dtype: "ban"},
|
||||
))
|
||||
|
||||
assert.Len(t, b.ips, 1)
|
||||
assert.Nil(t, b.ips[netip.MustParseAddr("1.2.3.4")])
|
||||
assert.NotNil(t, b.ips[netip.MustParseAddr("5.6.7.8")])
|
||||
}
|
||||
|
||||
func TestBouncer_ApplyDeleted_Range(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.prefixes[netip.MustParsePrefix("10.0.0.0/8")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
b.prefixes[netip.MustParsePrefix("192.168.0.0/16")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
|
||||
b.applyDeleted(makeDecisions(
|
||||
decision{scope: "range", value: "10.0.0.0/8", dtype: "ban"},
|
||||
))
|
||||
|
||||
require.Len(t, b.prefixes, 1)
|
||||
assert.NotNil(t, b.prefixes[netip.MustParsePrefix("192.168.0.0/16")])
|
||||
}
|
||||
|
||||
func TestBouncer_ApplyNew_OverwritesExisting(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
b.ips[netip.MustParseAddr("1.2.3.4")] = &restrict.CrowdSecDecision{Type: restrict.DecisionBan}
|
||||
|
||||
b.applyNew(makeDecisions(
|
||||
decision{scope: "ip", value: "1.2.3.4", dtype: "captcha"},
|
||||
))
|
||||
|
||||
assert.Equal(t, restrict.DecisionCaptcha, b.ips[netip.MustParseAddr("1.2.3.4")].Type)
|
||||
}
|
||||
|
||||
func TestBouncer_ApplyNew_SkipsInvalid(t *testing.T) {
|
||||
b := newTestBouncer()
|
||||
|
||||
b.applyNew(makeDecisions(
|
||||
decision{scope: "ip", value: "not-an-ip", dtype: "ban"},
|
||||
decision{scope: "range", value: "also-not-valid", dtype: "ban"},
|
||||
))
|
||||
|
||||
assert.Empty(t, b.ips)
|
||||
assert.Empty(t, b.prefixes)
|
||||
}
|
||||
|
||||
// TestBouncer_StreamIntegration tests the full flow: fake LAPI → StreamBouncer → Bouncer cache → CheckIP.
|
||||
func TestBouncer_StreamIntegration(t *testing.T) {
|
||||
lapi := newFakeLAPI()
|
||||
ts := httptest.NewServer(lapi)
|
||||
defer ts.Close()
|
||||
|
||||
// Seed the LAPI with initial decisions.
|
||||
lapi.setDecisions(
|
||||
decision{scope: "ip", value: "1.2.3.4", dtype: "ban", scenario: "crowdsecurity/ssh-bf"},
|
||||
decision{scope: "range", value: "10.0.0.0/8", dtype: "ban", scenario: "crowdsecurity/http-probing"},
|
||||
decision{scope: "ip", value: "5.5.5.5", dtype: "captcha", scenario: "crowdsecurity/http-crawl"},
|
||||
)
|
||||
|
||||
b := NewBouncer(ts.URL, "test-key", log.NewEntry(log.StandardLogger()))
|
||||
b.tickerInterval = 200 * time.Millisecond
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
require.NoError(t, b.Start(ctx))
|
||||
defer b.Stop()
|
||||
|
||||
// Wait for initial sync.
|
||||
require.Eventually(t, b.Ready, 5*time.Second, 50*time.Millisecond, "bouncer should become ready")
|
||||
|
||||
// Verify decisions are cached.
|
||||
d := b.CheckIP(netip.MustParseAddr("1.2.3.4"))
|
||||
require.NotNil(t, d, "1.2.3.4 should be banned")
|
||||
assert.Equal(t, restrict.DecisionBan, d.Type)
|
||||
|
||||
d2 := b.CheckIP(netip.MustParseAddr("10.1.2.3"))
|
||||
require.NotNil(t, d2, "10.1.2.3 should match range ban")
|
||||
assert.Equal(t, restrict.DecisionBan, d2.Type)
|
||||
|
||||
d3 := b.CheckIP(netip.MustParseAddr("5.5.5.5"))
|
||||
require.NotNil(t, d3, "5.5.5.5 should have captcha")
|
||||
assert.Equal(t, restrict.DecisionCaptcha, d3.Type)
|
||||
|
||||
assert.Nil(t, b.CheckIP(netip.MustParseAddr("9.9.9.9")), "unknown IP should be nil")
|
||||
|
||||
// Simulate a delta update: delete one IP, add a new one.
|
||||
lapi.setDelta(
|
||||
[]decision{{scope: "ip", value: "1.2.3.4", dtype: "ban"}},
|
||||
[]decision{{scope: "ip", value: "2.3.4.5", dtype: "throttle", scenario: "crowdsecurity/http-flood"}},
|
||||
)
|
||||
|
||||
// Wait for the delta to be picked up.
|
||||
require.Eventually(t, func() bool {
|
||||
return b.CheckIP(netip.MustParseAddr("2.3.4.5")) != nil
|
||||
}, 5*time.Second, 50*time.Millisecond, "new decision should appear")
|
||||
|
||||
assert.Nil(t, b.CheckIP(netip.MustParseAddr("1.2.3.4")), "deleted decision should be gone")
|
||||
|
||||
d4 := b.CheckIP(netip.MustParseAddr("2.3.4.5"))
|
||||
require.NotNil(t, d4)
|
||||
assert.Equal(t, restrict.DecisionThrottle, d4.Type)
|
||||
|
||||
// Range ban should still be active.
|
||||
assert.NotNil(t, b.CheckIP(netip.MustParseAddr("10.99.99.99")))
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func newTestBouncer() *Bouncer {
|
||||
return &Bouncer{
|
||||
ips: make(map[netip.Addr]*restrict.CrowdSecDecision),
|
||||
prefixes: make(map[netip.Prefix]*restrict.CrowdSecDecision),
|
||||
logger: log.NewEntry(log.StandardLogger()),
|
||||
}
|
||||
}
|
||||
|
||||
type decision struct {
|
||||
scope string
|
||||
value string
|
||||
dtype string
|
||||
scenario string
|
||||
}
|
||||
|
||||
func makeDecisions(decs ...decision) []*models.Decision {
|
||||
out := make([]*models.Decision, len(decs))
|
||||
for i, d := range decs {
|
||||
out[i] = &models.Decision{
|
||||
Scope: strPtr(d.scope),
|
||||
Value: strPtr(d.value),
|
||||
Type: strPtr(d.dtype),
|
||||
Scenario: strPtr(d.scenario),
|
||||
Duration: strPtr("1h"),
|
||||
Origin: strPtr("cscli"),
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
// fakeLAPI is a minimal fake CrowdSec LAPI that serves /v1/decisions/stream.
|
||||
type fakeLAPI struct {
|
||||
mu sync.Mutex
|
||||
initial []decision
|
||||
newDelta []decision
|
||||
delDelta []decision
|
||||
served bool // true after the initial snapshot has been served
|
||||
}
|
||||
|
||||
func newFakeLAPI() *fakeLAPI {
|
||||
return &fakeLAPI{}
|
||||
}
|
||||
|
||||
func (f *fakeLAPI) setDecisions(decs ...decision) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.initial = decs
|
||||
f.served = false
|
||||
}
|
||||
|
||||
func (f *fakeLAPI) setDelta(deleted, added []decision) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.delDelta = deleted
|
||||
f.newDelta = added
|
||||
}
|
||||
|
||||
func (f *fakeLAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/decisions/stream" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
resp := streamResponse{}
|
||||
|
||||
if !f.served {
|
||||
for _, d := range f.initial {
|
||||
resp.New = append(resp.New, toLAPIDecision(d))
|
||||
}
|
||||
f.served = true
|
||||
} else {
|
||||
for _, d := range f.delDelta {
|
||||
resp.Deleted = append(resp.Deleted, toLAPIDecision(d))
|
||||
}
|
||||
for _, d := range f.newDelta {
|
||||
resp.New = append(resp.New, toLAPIDecision(d))
|
||||
}
|
||||
// Clear delta after serving once.
|
||||
f.delDelta = nil
|
||||
f.newDelta = nil
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp) //nolint:errcheck
|
||||
}
|
||||
|
||||
// streamResponse mirrors the CrowdSec LAPI /v1/decisions/stream JSON structure.
|
||||
type streamResponse struct {
|
||||
New []*lapiDecision `json:"new"`
|
||||
Deleted []*lapiDecision `json:"deleted"`
|
||||
}
|
||||
|
||||
type lapiDecision struct {
|
||||
Duration *string `json:"duration"`
|
||||
Origin *string `json:"origin"`
|
||||
Scenario *string `json:"scenario"`
|
||||
Scope *string `json:"scope"`
|
||||
Type *string `json:"type"`
|
||||
Value *string `json:"value"`
|
||||
}
|
||||
|
||||
func toLAPIDecision(d decision) *lapiDecision {
|
||||
return &lapiDecision{
|
||||
Duration: strPtr("1h"),
|
||||
Origin: strPtr("cscli"),
|
||||
Scenario: strPtr(d.scenario),
|
||||
Scope: strPtr(d.scope),
|
||||
Type: strPtr(d.dtype),
|
||||
Value: strPtr(d.value),
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package crowdsec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/proxy/internal/types"
|
||||
)
|
||||
|
||||
// Registry manages a single shared Bouncer instance with reference counting.
|
||||
// The bouncer starts when the first service acquires it and stops when the
|
||||
// last service releases it.
|
||||
type Registry struct {
|
||||
mu sync.Mutex
|
||||
bouncer *Bouncer
|
||||
refs map[types.ServiceID]struct{}
|
||||
apiURL string
|
||||
apiKey string
|
||||
logger *log.Entry
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// NewRegistry creates a registry. The bouncer is not started until Acquire is called.
|
||||
func NewRegistry(apiURL, apiKey string, logger *log.Entry) *Registry {
|
||||
return &Registry{
|
||||
apiURL: apiURL,
|
||||
apiKey: apiKey,
|
||||
logger: logger,
|
||||
refs: make(map[types.ServiceID]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Available returns true when the LAPI URL and API key are configured.
|
||||
func (r *Registry) Available() bool {
|
||||
return r.apiURL != "" && r.apiKey != ""
|
||||
}
|
||||
|
||||
// Acquire registers svcID as a consumer and starts the bouncer if this is the
|
||||
// first consumer. Returns the shared Bouncer (which implements the restrict
|
||||
// package's CrowdSecChecker interface). Returns nil if not Available.
|
||||
func (r *Registry) Acquire(svcID types.ServiceID) *Bouncer {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if !r.Available() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, exists := r.refs[svcID]; exists {
|
||||
return r.bouncer
|
||||
}
|
||||
|
||||
if r.bouncer == nil {
|
||||
r.startLocked()
|
||||
}
|
||||
|
||||
// startLocked may fail, leaving r.bouncer nil.
|
||||
if r.bouncer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.refs[svcID] = struct{}{}
|
||||
return r.bouncer
|
||||
}
|
||||
|
||||
// Release removes svcID as a consumer. Stops the bouncer when the last
|
||||
// consumer releases.
|
||||
func (r *Registry) Release(svcID types.ServiceID) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
delete(r.refs, svcID)
|
||||
|
||||
if len(r.refs) == 0 && r.bouncer != nil {
|
||||
r.stopLocked()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) startLocked() {
|
||||
b := NewBouncer(r.apiURL, r.apiKey, r.logger)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
r.cancel = cancel
|
||||
|
||||
if err := b.Start(ctx); err != nil {
|
||||
r.logger.Errorf("failed to start CrowdSec bouncer: %v", err)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
r.bouncer = b
|
||||
r.logger.Info("CrowdSec bouncer started")
|
||||
}
|
||||
|
||||
func (r *Registry) stopLocked() {
|
||||
r.bouncer.Stop()
|
||||
r.cancel()
|
||||
r.bouncer = nil
|
||||
r.cancel = nil
|
||||
r.logger.Info("CrowdSec bouncer stopped")
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package crowdsec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/netbirdio/netbird/proxy/internal/types"
|
||||
)
|
||||
|
||||
func TestRegistry_Available(t *testing.T) {
|
||||
r := NewRegistry("http://localhost:8080/", "test-key", log.NewEntry(log.StandardLogger()))
|
||||
assert.True(t, r.Available())
|
||||
|
||||
r2 := NewRegistry("", "", log.NewEntry(log.StandardLogger()))
|
||||
assert.False(t, r2.Available())
|
||||
|
||||
r3 := NewRegistry("http://localhost:8080/", "", log.NewEntry(log.StandardLogger()))
|
||||
assert.False(t, r3.Available())
|
||||
}
|
||||
|
||||
func TestRegistry_Acquire_NotAvailable(t *testing.T) {
|
||||
r := NewRegistry("", "", log.NewEntry(log.StandardLogger()))
|
||||
b := r.Acquire("svc-1")
|
||||
assert.Nil(t, b)
|
||||
}
|
||||
|
||||
func TestRegistry_Acquire_Idempotent(t *testing.T) {
|
||||
r := newTestRegistry()
|
||||
|
||||
b1 := r.Acquire("svc-1")
|
||||
// Can't start without a real LAPI, but we can verify the ref tracking.
|
||||
// The bouncer will be nil because Start fails, but the ref is tracked.
|
||||
_ = b1
|
||||
|
||||
assert.Len(t, r.refs, 1)
|
||||
|
||||
// Second acquire of same service should not add another ref.
|
||||
r.Acquire("svc-1")
|
||||
assert.Len(t, r.refs, 1)
|
||||
}
|
||||
|
||||
func TestRegistry_Release_Removes(t *testing.T) {
|
||||
r := newTestRegistry()
|
||||
r.refs[types.ServiceID("svc-1")] = struct{}{}
|
||||
|
||||
r.Release("svc-1")
|
||||
assert.Empty(t, r.refs)
|
||||
}
|
||||
|
||||
func TestRegistry_Release_Noop(t *testing.T) {
|
||||
r := newTestRegistry()
|
||||
// Releasing a service that was never acquired should not panic.
|
||||
r.Release("nonexistent")
|
||||
assert.Empty(t, r.refs)
|
||||
}
|
||||
|
||||
func newTestRegistry() *Registry {
|
||||
return &Registry{
|
||||
apiURL: "http://localhost:8080/",
|
||||
apiKey: "test-key",
|
||||
logger: log.NewEntry(log.StandardLogger()),
|
||||
refs: make(map[types.ServiceID]struct{}),
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
@@ -53,7 +52,6 @@ type CapturedData struct {
|
||||
clientIP netip.Addr
|
||||
userID string
|
||||
authMethod string
|
||||
metadata map[string]string
|
||||
}
|
||||
|
||||
// NewCapturedData creates a CapturedData with the given request ID.
|
||||
@@ -152,23 +150,6 @@ func (c *CapturedData) GetAuthMethod() string {
|
||||
return c.authMethod
|
||||
}
|
||||
|
||||
// SetMetadata sets a key-value pair in the metadata map.
|
||||
func (c *CapturedData) SetMetadata(key, value string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.metadata == nil {
|
||||
c.metadata = make(map[string]string)
|
||||
}
|
||||
c.metadata[key] = value
|
||||
}
|
||||
|
||||
// GetMetadata returns a copy of the metadata map.
|
||||
func (c *CapturedData) GetMetadata() map[string]string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return maps.Clone(c.metadata)
|
||||
}
|
||||
|
||||
// WithCapturedData adds a CapturedData struct to the context.
|
||||
func WithCapturedData(ctx context.Context, data *CapturedData) context.Context {
|
||||
return context.WithValue(ctx, capturedDataKey, data)
|
||||
|
||||
@@ -12,44 +12,12 @@ import (
|
||||
"github.com/netbirdio/netbird/proxy/internal/geolocation"
|
||||
)
|
||||
|
||||
// defaultLogger is used when no logger is provided to ParseFilter.
|
||||
var defaultLogger = log.NewEntry(log.StandardLogger())
|
||||
|
||||
// GeoResolver resolves an IP address to geographic information.
|
||||
type GeoResolver interface {
|
||||
LookupAddr(addr netip.Addr) geolocation.Result
|
||||
Available() bool
|
||||
}
|
||||
|
||||
// DecisionType is the type of CrowdSec remediation action.
|
||||
type DecisionType string
|
||||
|
||||
const (
|
||||
DecisionBan DecisionType = "ban"
|
||||
DecisionCaptcha DecisionType = "captcha"
|
||||
DecisionThrottle DecisionType = "throttle"
|
||||
)
|
||||
|
||||
// CrowdSecDecision holds the type of a CrowdSec decision.
|
||||
type CrowdSecDecision struct {
|
||||
Type DecisionType
|
||||
}
|
||||
|
||||
// CrowdSecChecker queries CrowdSec decisions for an IP address.
|
||||
type CrowdSecChecker interface {
|
||||
CheckIP(addr netip.Addr) *CrowdSecDecision
|
||||
Ready() bool
|
||||
}
|
||||
|
||||
// CrowdSecMode is the per-service enforcement mode.
|
||||
type CrowdSecMode string
|
||||
|
||||
const (
|
||||
CrowdSecOff CrowdSecMode = ""
|
||||
CrowdSecEnforce CrowdSecMode = "enforce"
|
||||
CrowdSecObserve CrowdSecMode = "observe"
|
||||
)
|
||||
|
||||
// Filter evaluates IP restrictions. CIDR checks are performed first
|
||||
// (cheap), followed by country lookups (more expensive) only when needed.
|
||||
type Filter struct {
|
||||
@@ -57,55 +25,32 @@ type Filter struct {
|
||||
BlockedCIDRs []netip.Prefix
|
||||
AllowedCountries []string
|
||||
BlockedCountries []string
|
||||
CrowdSec CrowdSecChecker
|
||||
CrowdSecMode CrowdSecMode
|
||||
}
|
||||
|
||||
// FilterConfig holds the raw configuration for building a Filter.
|
||||
type FilterConfig struct {
|
||||
AllowedCIDRs []string
|
||||
BlockedCIDRs []string
|
||||
AllowedCountries []string
|
||||
BlockedCountries []string
|
||||
CrowdSec CrowdSecChecker
|
||||
CrowdSecMode CrowdSecMode
|
||||
Logger *log.Entry
|
||||
}
|
||||
|
||||
// ParseFilter builds a Filter from the config. Returns nil if no restrictions
|
||||
// are configured.
|
||||
func ParseFilter(cfg FilterConfig) *Filter {
|
||||
hasCS := cfg.CrowdSecMode == CrowdSecEnforce || cfg.CrowdSecMode == CrowdSecObserve
|
||||
if len(cfg.AllowedCIDRs) == 0 && len(cfg.BlockedCIDRs) == 0 &&
|
||||
len(cfg.AllowedCountries) == 0 && len(cfg.BlockedCountries) == 0 && !hasCS {
|
||||
// ParseFilter builds a Filter from the raw string slices. Returns nil
|
||||
// if all slices are empty.
|
||||
func ParseFilter(allowedCIDRs, blockedCIDRs, allowedCountries, blockedCountries []string) *Filter {
|
||||
if len(allowedCIDRs) == 0 && len(blockedCIDRs) == 0 &&
|
||||
len(allowedCountries) == 0 && len(blockedCountries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger := cfg.Logger
|
||||
if logger == nil {
|
||||
logger = defaultLogger
|
||||
}
|
||||
|
||||
f := &Filter{
|
||||
AllowedCountries: normalizeCountryCodes(cfg.AllowedCountries),
|
||||
BlockedCountries: normalizeCountryCodes(cfg.BlockedCountries),
|
||||
AllowedCountries: normalizeCountryCodes(allowedCountries),
|
||||
BlockedCountries: normalizeCountryCodes(blockedCountries),
|
||||
}
|
||||
if hasCS {
|
||||
f.CrowdSec = cfg.CrowdSec
|
||||
f.CrowdSecMode = cfg.CrowdSecMode
|
||||
}
|
||||
for _, cidr := range cfg.AllowedCIDRs {
|
||||
for _, cidr := range allowedCIDRs {
|
||||
prefix, err := netip.ParsePrefix(cidr)
|
||||
if err != nil {
|
||||
logger.Warnf("skip invalid allowed CIDR %q: %v", cidr, err)
|
||||
log.Warnf("skip invalid allowed CIDR %q: %v", cidr, err)
|
||||
continue
|
||||
}
|
||||
f.AllowedCIDRs = append(f.AllowedCIDRs, prefix.Masked())
|
||||
}
|
||||
for _, cidr := range cfg.BlockedCIDRs {
|
||||
for _, cidr := range blockedCIDRs {
|
||||
prefix, err := netip.ParsePrefix(cidr)
|
||||
if err != nil {
|
||||
logger.Warnf("skip invalid blocked CIDR %q: %v", cidr, err)
|
||||
log.Warnf("skip invalid blocked CIDR %q: %v", cidr, err)
|
||||
continue
|
||||
}
|
||||
f.BlockedCIDRs = append(f.BlockedCIDRs, prefix.Masked())
|
||||
@@ -137,15 +82,6 @@ const (
|
||||
// DenyGeoUnavailable indicates that country restrictions are configured
|
||||
// but the geo lookup is unavailable.
|
||||
DenyGeoUnavailable
|
||||
// DenyCrowdSecBan indicates a CrowdSec "ban" decision.
|
||||
DenyCrowdSecBan
|
||||
// DenyCrowdSecCaptcha indicates a CrowdSec "captcha" decision.
|
||||
DenyCrowdSecCaptcha
|
||||
// DenyCrowdSecThrottle indicates a CrowdSec "throttle" decision.
|
||||
DenyCrowdSecThrottle
|
||||
// DenyCrowdSecUnavailable indicates enforce mode but the bouncer has not
|
||||
// completed its initial sync.
|
||||
DenyCrowdSecUnavailable
|
||||
)
|
||||
|
||||
// String returns the deny reason string matching the HTTP auth mechanism names.
|
||||
@@ -159,42 +95,14 @@ func (v Verdict) String() string {
|
||||
return "country_restricted"
|
||||
case DenyGeoUnavailable:
|
||||
return "geo_unavailable"
|
||||
case DenyCrowdSecBan:
|
||||
return "crowdsec_ban"
|
||||
case DenyCrowdSecCaptcha:
|
||||
return "crowdsec_captcha"
|
||||
case DenyCrowdSecThrottle:
|
||||
return "crowdsec_throttle"
|
||||
case DenyCrowdSecUnavailable:
|
||||
return "crowdsec_unavailable"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// IsCrowdSec returns true when the verdict originates from a CrowdSec check.
|
||||
func (v Verdict) IsCrowdSec() bool {
|
||||
switch v {
|
||||
case DenyCrowdSecBan, DenyCrowdSecCaptcha, DenyCrowdSecThrottle, DenyCrowdSecUnavailable:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsObserveOnly returns true when v is a CrowdSec verdict and the filter is in
|
||||
// observe mode. Callers should log the verdict but not block the request.
|
||||
func (f *Filter) IsObserveOnly(v Verdict) bool {
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
return v.IsCrowdSec() && f.CrowdSecMode == CrowdSecObserve
|
||||
}
|
||||
|
||||
// Check evaluates whether addr is permitted. CIDR rules are evaluated
|
||||
// first because they are O(n) prefix comparisons. Country rules run
|
||||
// only when CIDR checks pass and require a geo lookup. CrowdSec checks
|
||||
// run last.
|
||||
// only when CIDR checks pass and require a geo lookup.
|
||||
func (f *Filter) Check(addr netip.Addr, geo GeoResolver) Verdict {
|
||||
if f == nil {
|
||||
return Allow
|
||||
@@ -207,10 +115,7 @@ func (f *Filter) Check(addr netip.Addr, geo GeoResolver) Verdict {
|
||||
if v := f.checkCIDR(addr); v != Allow {
|
||||
return v
|
||||
}
|
||||
if v := f.checkCountry(addr, geo); v != Allow {
|
||||
return v
|
||||
}
|
||||
return f.checkCrowdSec(addr)
|
||||
return f.checkCountry(addr, geo)
|
||||
}
|
||||
|
||||
func (f *Filter) checkCIDR(addr netip.Addr) Verdict {
|
||||
@@ -268,48 +173,11 @@ func (f *Filter) checkCountry(addr netip.Addr, geo GeoResolver) Verdict {
|
||||
return Allow
|
||||
}
|
||||
|
||||
func (f *Filter) checkCrowdSec(addr netip.Addr) Verdict {
|
||||
if f.CrowdSecMode == CrowdSecOff {
|
||||
return Allow
|
||||
}
|
||||
|
||||
// Checker nil with enforce means CrowdSec was requested but the proxy
|
||||
// has no LAPI configured. Fail-closed.
|
||||
if f.CrowdSec == nil {
|
||||
if f.CrowdSecMode == CrowdSecEnforce {
|
||||
return DenyCrowdSecUnavailable
|
||||
}
|
||||
return Allow
|
||||
}
|
||||
|
||||
if !f.CrowdSec.Ready() {
|
||||
if f.CrowdSecMode == CrowdSecEnforce {
|
||||
return DenyCrowdSecUnavailable
|
||||
}
|
||||
return Allow
|
||||
}
|
||||
|
||||
d := f.CrowdSec.CheckIP(addr)
|
||||
if d == nil {
|
||||
return Allow
|
||||
}
|
||||
|
||||
switch d.Type {
|
||||
case DecisionCaptcha:
|
||||
return DenyCrowdSecCaptcha
|
||||
case DecisionThrottle:
|
||||
return DenyCrowdSecThrottle
|
||||
default:
|
||||
return DenyCrowdSecBan
|
||||
}
|
||||
}
|
||||
|
||||
// HasRestrictions returns true if any restriction rules are configured.
|
||||
func (f *Filter) HasRestrictions() bool {
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
return len(f.AllowedCIDRs) > 0 || len(f.BlockedCIDRs) > 0 ||
|
||||
len(f.AllowedCountries) > 0 || len(f.BlockedCountries) > 0 ||
|
||||
f.CrowdSecMode == CrowdSecEnforce || f.CrowdSecMode == CrowdSecObserve
|
||||
len(f.AllowedCountries) > 0 || len(f.BlockedCountries) > 0
|
||||
}
|
||||
|
||||
@@ -29,21 +29,21 @@ func TestFilter_Check_NilFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_Check_AllowedCIDR(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}})
|
||||
f := ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil)
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("10.1.2.3"), nil))
|
||||
assert.Equal(t, DenyCIDR, f.Check(netip.MustParseAddr("192.168.1.1"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_Check_BlockedCIDR(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{BlockedCIDRs: []string{"10.0.0.0/8"}})
|
||||
f := ParseFilter(nil, []string{"10.0.0.0/8"}, nil, nil)
|
||||
|
||||
assert.Equal(t, DenyCIDR, f.Check(netip.MustParseAddr("10.1.2.3"), nil))
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("192.168.1.1"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_Check_AllowedAndBlockedCIDR(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}, BlockedCIDRs: []string{"10.1.0.0/16"}})
|
||||
f := ParseFilter([]string{"10.0.0.0/8"}, []string{"10.1.0.0/16"}, nil, nil)
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("10.2.3.4"), nil), "allowed by allowlist, not in blocklist")
|
||||
assert.Equal(t, DenyCIDR, f.Check(netip.MustParseAddr("10.1.2.3"), nil), "allowed by allowlist but in blocklist")
|
||||
@@ -56,7 +56,7 @@ func TestFilter_Check_AllowedCountry(t *testing.T) {
|
||||
"2.2.2.2": "DE",
|
||||
"3.3.3.3": "CN",
|
||||
})
|
||||
f := ParseFilter(FilterConfig{AllowedCountries: []string{"US", "DE"}})
|
||||
f := ParseFilter(nil, nil, []string{"US", "DE"}, nil)
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("1.1.1.1"), geo), "US in allowlist")
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("2.2.2.2"), geo), "DE in allowlist")
|
||||
@@ -69,7 +69,7 @@ func TestFilter_Check_BlockedCountry(t *testing.T) {
|
||||
"2.2.2.2": "RU",
|
||||
"3.3.3.3": "US",
|
||||
})
|
||||
f := ParseFilter(FilterConfig{BlockedCountries: []string{"CN", "RU"}})
|
||||
f := ParseFilter(nil, nil, nil, []string{"CN", "RU"})
|
||||
|
||||
assert.Equal(t, DenyCountry, f.Check(netip.MustParseAddr("1.1.1.1"), geo), "CN in blocklist")
|
||||
assert.Equal(t, DenyCountry, f.Check(netip.MustParseAddr("2.2.2.2"), geo), "RU in blocklist")
|
||||
@@ -83,7 +83,7 @@ func TestFilter_Check_AllowedAndBlockedCountry(t *testing.T) {
|
||||
"3.3.3.3": "CN",
|
||||
})
|
||||
// Allow US and DE, but block DE explicitly.
|
||||
f := ParseFilter(FilterConfig{AllowedCountries: []string{"US", "DE"}, BlockedCountries: []string{"DE"}})
|
||||
f := ParseFilter(nil, nil, []string{"US", "DE"}, []string{"DE"})
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("1.1.1.1"), geo), "US allowed and not blocked")
|
||||
assert.Equal(t, DenyCountry, f.Check(netip.MustParseAddr("2.2.2.2"), geo), "DE allowed but also blocked, block wins")
|
||||
@@ -94,7 +94,7 @@ func TestFilter_Check_UnknownCountryWithAllowlist(t *testing.T) {
|
||||
geo := newMockGeo(map[string]string{
|
||||
"1.1.1.1": "US",
|
||||
})
|
||||
f := ParseFilter(FilterConfig{AllowedCountries: []string{"US"}})
|
||||
f := ParseFilter(nil, nil, []string{"US"}, nil)
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("1.1.1.1"), geo), "known US in allowlist")
|
||||
assert.Equal(t, DenyCountry, f.Check(netip.MustParseAddr("9.9.9.9"), geo), "unknown country denied when allowlist is active")
|
||||
@@ -104,34 +104,34 @@ func TestFilter_Check_UnknownCountryWithBlocklistOnly(t *testing.T) {
|
||||
geo := newMockGeo(map[string]string{
|
||||
"1.1.1.1": "CN",
|
||||
})
|
||||
f := ParseFilter(FilterConfig{BlockedCountries: []string{"CN"}})
|
||||
f := ParseFilter(nil, nil, nil, []string{"CN"})
|
||||
|
||||
assert.Equal(t, DenyCountry, f.Check(netip.MustParseAddr("1.1.1.1"), geo), "known CN in blocklist")
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("9.9.9.9"), geo), "unknown country allowed when only blocklist is active")
|
||||
}
|
||||
|
||||
func TestFilter_Check_CountryWithoutGeo(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCountries: []string{"US"}})
|
||||
f := ParseFilter(nil, nil, []string{"US"}, nil)
|
||||
assert.Equal(t, DenyGeoUnavailable, f.Check(netip.MustParseAddr("1.2.3.4"), nil), "nil geo with country allowlist")
|
||||
}
|
||||
|
||||
func TestFilter_Check_CountryBlocklistWithoutGeo(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{BlockedCountries: []string{"CN"}})
|
||||
f := ParseFilter(nil, nil, nil, []string{"CN"})
|
||||
assert.Equal(t, DenyGeoUnavailable, f.Check(netip.MustParseAddr("1.2.3.4"), nil), "nil geo with country blocklist")
|
||||
}
|
||||
|
||||
func TestFilter_Check_GeoUnavailable(t *testing.T) {
|
||||
geo := &unavailableGeo{}
|
||||
|
||||
f := ParseFilter(FilterConfig{AllowedCountries: []string{"US"}})
|
||||
f := ParseFilter(nil, nil, []string{"US"}, nil)
|
||||
assert.Equal(t, DenyGeoUnavailable, f.Check(netip.MustParseAddr("1.2.3.4"), geo), "unavailable geo with country allowlist")
|
||||
|
||||
f2 := ParseFilter(FilterConfig{BlockedCountries: []string{"CN"}})
|
||||
f2 := ParseFilter(nil, nil, nil, []string{"CN"})
|
||||
assert.Equal(t, DenyGeoUnavailable, f2.Check(netip.MustParseAddr("1.2.3.4"), geo), "unavailable geo with country blocklist")
|
||||
}
|
||||
|
||||
func TestFilter_Check_CIDROnlySkipsGeo(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}})
|
||||
f := ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil)
|
||||
|
||||
// CIDR-only filter should never touch geo, so nil geo is fine.
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("10.1.2.3"), nil))
|
||||
@@ -143,7 +143,7 @@ func TestFilter_Check_CIDRAllowThenCountryBlock(t *testing.T) {
|
||||
"10.1.2.3": "CN",
|
||||
"10.2.3.4": "US",
|
||||
})
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}, BlockedCountries: []string{"CN"}})
|
||||
f := ParseFilter([]string{"10.0.0.0/8"}, nil, nil, []string{"CN"})
|
||||
|
||||
assert.Equal(t, DenyCountry, f.Check(netip.MustParseAddr("10.1.2.3"), geo), "CIDR allowed but country blocked")
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("10.2.3.4"), geo), "CIDR allowed and country not blocked")
|
||||
@@ -151,12 +151,12 @@ func TestFilter_Check_CIDRAllowThenCountryBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseFilter_Empty(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{})
|
||||
f := ParseFilter(nil, nil, nil, nil)
|
||||
assert.Nil(t, f)
|
||||
}
|
||||
|
||||
func TestParseFilter_InvalidCIDR(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"invalid", "10.0.0.0/8"}})
|
||||
f := ParseFilter([]string{"invalid", "10.0.0.0/8"}, nil, nil, nil)
|
||||
|
||||
assert.NotNil(t, f)
|
||||
assert.Len(t, f.AllowedCIDRs, 1, "invalid CIDR should be skipped")
|
||||
@@ -166,12 +166,12 @@ func TestParseFilter_InvalidCIDR(t *testing.T) {
|
||||
func TestFilter_HasRestrictions(t *testing.T) {
|
||||
assert.False(t, (*Filter)(nil).HasRestrictions())
|
||||
assert.False(t, (&Filter{}).HasRestrictions())
|
||||
assert.True(t, ParseFilter(FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}}).HasRestrictions())
|
||||
assert.True(t, ParseFilter(FilterConfig{AllowedCountries: []string{"US"}}).HasRestrictions())
|
||||
assert.True(t, ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil).HasRestrictions())
|
||||
assert.True(t, ParseFilter(nil, nil, []string{"US"}, nil).HasRestrictions())
|
||||
}
|
||||
|
||||
func TestFilter_Check_IPv6CIDR(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"2001:db8::/32"}})
|
||||
f := ParseFilter([]string{"2001:db8::/32"}, nil, nil, nil)
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("2001:db8::1"), nil), "v6 addr in v6 allowlist")
|
||||
assert.Equal(t, DenyCIDR, f.Check(netip.MustParseAddr("2001:db9::1"), nil), "v6 addr not in v6 allowlist")
|
||||
@@ -179,7 +179,7 @@ func TestFilter_Check_IPv6CIDR(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_Check_IPv4MappedIPv6(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}})
|
||||
f := ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil)
|
||||
|
||||
// A v4-mapped-v6 address like ::ffff:10.1.2.3 must match a v4 CIDR.
|
||||
v4mapped := netip.MustParseAddr("::ffff:10.1.2.3")
|
||||
@@ -191,7 +191,7 @@ func TestFilter_Check_IPv4MappedIPv6(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFilter_Check_MixedV4V6CIDRs(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8", "2001:db8::/32"}})
|
||||
f := ParseFilter([]string{"10.0.0.0/8", "2001:db8::/32"}, nil, nil, nil)
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("10.1.2.3"), nil), "v4 in v4 CIDR")
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("2001:db8::1"), nil), "v6 in v6 CIDR")
|
||||
@@ -202,7 +202,7 @@ func TestFilter_Check_MixedV4V6CIDRs(t *testing.T) {
|
||||
|
||||
func TestParseFilter_CanonicalizesNonMaskedCIDR(t *testing.T) {
|
||||
// 1.1.1.1/24 has host bits set; ParseFilter should canonicalize to 1.1.1.0/24.
|
||||
f := ParseFilter(FilterConfig{AllowedCIDRs: []string{"1.1.1.1/24"}})
|
||||
f := ParseFilter([]string{"1.1.1.1/24"}, nil, nil, nil)
|
||||
assert.Equal(t, netip.MustParsePrefix("1.1.1.0/24"), f.AllowedCIDRs[0])
|
||||
|
||||
// Verify it still matches correctly.
|
||||
@@ -264,7 +264,7 @@ func TestFilter_Check_CountryCodeCaseInsensitive(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f := ParseFilter(FilterConfig{AllowedCountries: tc.allowedCountries, BlockedCountries: tc.blockedCountries})
|
||||
f := ParseFilter(nil, nil, tc.allowedCountries, tc.blockedCountries)
|
||||
got := f.Check(netip.MustParseAddr(tc.addr), geo)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
@@ -275,252 +275,4 @@ func TestFilter_Check_CountryCodeCaseInsensitive(t *testing.T) {
|
||||
type unavailableGeo struct{}
|
||||
|
||||
func (u *unavailableGeo) LookupAddr(_ netip.Addr) geolocation.Result { return geolocation.Result{} }
|
||||
func (u *unavailableGeo) Available() bool { return false }
|
||||
|
||||
// mockCrowdSec is a test implementation of CrowdSecChecker.
|
||||
type mockCrowdSec struct {
|
||||
decisions map[string]*CrowdSecDecision
|
||||
ready bool
|
||||
}
|
||||
|
||||
func (m *mockCrowdSec) CheckIP(addr netip.Addr) *CrowdSecDecision {
|
||||
return m.decisions[addr.Unmap().String()]
|
||||
}
|
||||
|
||||
func (m *mockCrowdSec) Ready() bool { return m.ready }
|
||||
|
||||
func TestFilter_CrowdSec_Enforce_Ban(t *testing.T) {
|
||||
cs := &mockCrowdSec{
|
||||
decisions: map[string]*CrowdSecDecision{"1.2.3.4": {Type: DecisionBan}},
|
||||
ready: true,
|
||||
}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecEnforce})
|
||||
|
||||
assert.Equal(t, DenyCrowdSecBan, f.Check(netip.MustParseAddr("1.2.3.4"), nil))
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("5.6.7.8"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Enforce_Captcha(t *testing.T) {
|
||||
cs := &mockCrowdSec{
|
||||
decisions: map[string]*CrowdSecDecision{"1.2.3.4": {Type: DecisionCaptcha}},
|
||||
ready: true,
|
||||
}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecEnforce})
|
||||
|
||||
assert.Equal(t, DenyCrowdSecCaptcha, f.Check(netip.MustParseAddr("1.2.3.4"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Enforce_Throttle(t *testing.T) {
|
||||
cs := &mockCrowdSec{
|
||||
decisions: map[string]*CrowdSecDecision{"1.2.3.4": {Type: DecisionThrottle}},
|
||||
ready: true,
|
||||
}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecEnforce})
|
||||
|
||||
assert.Equal(t, DenyCrowdSecThrottle, f.Check(netip.MustParseAddr("1.2.3.4"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Observe_DoesNotBlock(t *testing.T) {
|
||||
cs := &mockCrowdSec{
|
||||
decisions: map[string]*CrowdSecDecision{"1.2.3.4": {Type: DecisionBan}},
|
||||
ready: true,
|
||||
}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecObserve})
|
||||
|
||||
verdict := f.Check(netip.MustParseAddr("1.2.3.4"), nil)
|
||||
assert.Equal(t, DenyCrowdSecBan, verdict, "verdict should be ban")
|
||||
assert.True(t, f.IsObserveOnly(verdict), "should be observe-only")
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Enforce_NotReady(t *testing.T) {
|
||||
cs := &mockCrowdSec{ready: false}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecEnforce})
|
||||
|
||||
assert.Equal(t, DenyCrowdSecUnavailable, f.Check(netip.MustParseAddr("1.2.3.4"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Observe_NotReady_Allows(t *testing.T) {
|
||||
cs := &mockCrowdSec{ready: false}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecObserve})
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("1.2.3.4"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Off(t *testing.T) {
|
||||
cs := &mockCrowdSec{
|
||||
decisions: map[string]*CrowdSecDecision{"1.2.3.4": {Type: DecisionBan}},
|
||||
ready: true,
|
||||
}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecOff})
|
||||
|
||||
// CrowdSecOff means the filter is nil (no restrictions).
|
||||
assert.Nil(t, f)
|
||||
}
|
||||
|
||||
func TestFilter_IsObserveOnly(t *testing.T) {
|
||||
f := &Filter{CrowdSecMode: CrowdSecObserve}
|
||||
assert.True(t, f.IsObserveOnly(DenyCrowdSecBan))
|
||||
assert.True(t, f.IsObserveOnly(DenyCrowdSecCaptcha))
|
||||
assert.True(t, f.IsObserveOnly(DenyCrowdSecThrottle))
|
||||
assert.True(t, f.IsObserveOnly(DenyCrowdSecUnavailable))
|
||||
assert.False(t, f.IsObserveOnly(DenyCIDR))
|
||||
assert.False(t, f.IsObserveOnly(Allow))
|
||||
|
||||
f2 := &Filter{CrowdSecMode: CrowdSecEnforce}
|
||||
assert.False(t, f2.IsObserveOnly(DenyCrowdSecBan))
|
||||
}
|
||||
|
||||
// TestFilter_LayerInteraction exercises the evaluation order across all three
|
||||
// restriction layers: CIDR -> Country -> CrowdSec. Each layer can only further
|
||||
// restrict; no layer can relax a denial from an earlier layer.
|
||||
//
|
||||
// Layer order | Behavior
|
||||
// ---------------|-------------------------------------------------------
|
||||
// 1. CIDR | Allowlist narrows to specific ranges, blocklist removes
|
||||
// | specific ranges. Deny here → stop, CrowdSec never runs.
|
||||
// 2. Country | Allowlist/blocklist by geo. Deny here → stop.
|
||||
// 3. CrowdSec | IP reputation. Can block IPs that passed layers 1-2.
|
||||
// | Observe mode: verdict returned but caller doesn't block.
|
||||
func TestFilter_LayerInteraction(t *testing.T) {
|
||||
bannedIP := "10.1.2.3"
|
||||
cleanIP := "10.2.3.4"
|
||||
outsideIP := "192.168.1.1"
|
||||
|
||||
cs := &mockCrowdSec{
|
||||
decisions: map[string]*CrowdSecDecision{bannedIP: {Type: DecisionBan}},
|
||||
ready: true,
|
||||
}
|
||||
geo := newMockGeo(map[string]string{
|
||||
bannedIP: "US",
|
||||
cleanIP: "US",
|
||||
outsideIP: "CN",
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config FilterConfig
|
||||
addr string
|
||||
want Verdict
|
||||
}{
|
||||
// CIDR allowlist + CrowdSec enforce: CrowdSec blocks inside allowed range
|
||||
{
|
||||
name: "allowed CIDR + CrowdSec banned",
|
||||
config: FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}, CrowdSec: cs, CrowdSecMode: CrowdSecEnforce},
|
||||
addr: bannedIP,
|
||||
want: DenyCrowdSecBan,
|
||||
},
|
||||
{
|
||||
name: "allowed CIDR + CrowdSec clean",
|
||||
config: FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}, CrowdSec: cs, CrowdSecMode: CrowdSecEnforce},
|
||||
addr: cleanIP,
|
||||
want: Allow,
|
||||
},
|
||||
{
|
||||
name: "CIDR deny stops before CrowdSec",
|
||||
config: FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}, CrowdSec: cs, CrowdSecMode: CrowdSecEnforce},
|
||||
addr: outsideIP,
|
||||
want: DenyCIDR,
|
||||
},
|
||||
|
||||
// CIDR blocklist + CrowdSec enforce: blocklist blocks first, CrowdSec blocks remaining
|
||||
{
|
||||
name: "blocked CIDR stops before CrowdSec",
|
||||
config: FilterConfig{BlockedCIDRs: []string{"10.1.0.0/16"}, CrowdSec: cs, CrowdSecMode: CrowdSecEnforce},
|
||||
addr: bannedIP,
|
||||
want: DenyCIDR,
|
||||
},
|
||||
{
|
||||
name: "not in blocklist + CrowdSec clean",
|
||||
config: FilterConfig{BlockedCIDRs: []string{"10.1.0.0/16"}, CrowdSec: cs, CrowdSecMode: CrowdSecEnforce},
|
||||
addr: cleanIP,
|
||||
want: Allow,
|
||||
},
|
||||
|
||||
// Country allowlist + CrowdSec enforce
|
||||
{
|
||||
name: "allowed country + CrowdSec banned",
|
||||
config: FilterConfig{AllowedCountries: []string{"US"}, CrowdSec: cs, CrowdSecMode: CrowdSecEnforce},
|
||||
addr: bannedIP,
|
||||
want: DenyCrowdSecBan,
|
||||
},
|
||||
{
|
||||
name: "country deny stops before CrowdSec",
|
||||
config: FilterConfig{AllowedCountries: []string{"US"}, CrowdSec: cs, CrowdSecMode: CrowdSecEnforce},
|
||||
addr: outsideIP,
|
||||
want: DenyCountry,
|
||||
},
|
||||
|
||||
// All three layers: CIDR allowlist + country blocklist + CrowdSec
|
||||
{
|
||||
name: "all layers: CIDR allow + country allow + CrowdSec ban",
|
||||
config: FilterConfig{
|
||||
AllowedCIDRs: []string{"10.0.0.0/8"},
|
||||
BlockedCountries: []string{"CN"},
|
||||
CrowdSec: cs,
|
||||
CrowdSecMode: CrowdSecEnforce,
|
||||
},
|
||||
addr: bannedIP, // 10.x (CIDR ok), US (country ok), banned (CrowdSec deny)
|
||||
want: DenyCrowdSecBan,
|
||||
},
|
||||
{
|
||||
name: "all layers: CIDR deny short-circuits everything",
|
||||
config: FilterConfig{
|
||||
AllowedCIDRs: []string{"10.0.0.0/8"},
|
||||
BlockedCountries: []string{"CN"},
|
||||
CrowdSec: cs,
|
||||
CrowdSecMode: CrowdSecEnforce,
|
||||
},
|
||||
addr: outsideIP, // 192.x (CIDR deny)
|
||||
want: DenyCIDR,
|
||||
},
|
||||
|
||||
// Observe mode: verdict returned but IsObserveOnly is true
|
||||
{
|
||||
name: "observe mode: CrowdSec banned inside allowed CIDR",
|
||||
config: FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}, CrowdSec: cs, CrowdSecMode: CrowdSecObserve},
|
||||
addr: bannedIP,
|
||||
want: DenyCrowdSecBan, // verdict is ban, caller checks IsObserveOnly
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f := ParseFilter(tc.config)
|
||||
got := f.Check(netip.MustParseAddr(tc.addr), geo)
|
||||
assert.Equal(t, tc.want, got)
|
||||
|
||||
// Verify observe mode flag when applicable.
|
||||
if tc.config.CrowdSecMode == CrowdSecObserve && got.IsCrowdSec() {
|
||||
assert.True(t, f.IsObserveOnly(got), "observe mode verdict should be observe-only")
|
||||
}
|
||||
if tc.config.CrowdSecMode == CrowdSecEnforce && got.IsCrowdSec() {
|
||||
assert.False(t, f.IsObserveOnly(got), "enforce mode verdict should not be observe-only")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Enforce_NilChecker(t *testing.T) {
|
||||
// LAPI not configured: checker is nil but mode is enforce. Must fail closed.
|
||||
f := ParseFilter(FilterConfig{CrowdSec: nil, CrowdSecMode: CrowdSecEnforce})
|
||||
|
||||
assert.Equal(t, DenyCrowdSecUnavailable, f.Check(netip.MustParseAddr("1.2.3.4"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_CrowdSec_Observe_NilChecker(t *testing.T) {
|
||||
// LAPI not configured: checker is nil but mode is observe. Must allow.
|
||||
f := ParseFilter(FilterConfig{CrowdSec: nil, CrowdSecMode: CrowdSecObserve})
|
||||
|
||||
assert.Equal(t, Allow, f.Check(netip.MustParseAddr("1.2.3.4"), nil))
|
||||
}
|
||||
|
||||
func TestFilter_HasRestrictions_CrowdSec(t *testing.T) {
|
||||
cs := &mockCrowdSec{ready: true}
|
||||
f := ParseFilter(FilterConfig{CrowdSec: cs, CrowdSecMode: CrowdSecEnforce})
|
||||
assert.True(t, f.HasRestrictions())
|
||||
|
||||
// Enforce mode without checker (LAPI not configured): still has restrictions
|
||||
// because Check() will fail-closed with DenyCrowdSecUnavailable.
|
||||
f2 := ParseFilter(FilterConfig{CrowdSec: nil, CrowdSecMode: CrowdSecEnforce})
|
||||
assert.True(t, f2.HasRestrictions())
|
||||
}
|
||||
func (u *unavailableGeo) Available() bool { return false }
|
||||
|
||||
@@ -479,14 +479,9 @@ func (r *Router) checkRestrictions(conn net.Conn, route Route) restrict.Verdict
|
||||
// On success (nil error), both conn and backend are closed by the relay.
|
||||
func (r *Router) relayTCP(ctx context.Context, conn net.Conn, sni SNIHost, route Route) error {
|
||||
if verdict := r.checkRestrictions(conn, route); verdict != restrict.Allow {
|
||||
if route.Filter != nil && route.Filter.IsObserveOnly(verdict) {
|
||||
r.logger.Debugf("CrowdSec observe: would block %s for %s (%s)", conn.RemoteAddr(), sni, verdict)
|
||||
r.logL4Deny(route, conn, verdict, true)
|
||||
} else {
|
||||
r.logger.Debugf("connection from %s rejected by access restrictions: %s", conn.RemoteAddr(), verdict)
|
||||
r.logL4Deny(route, conn, verdict, false)
|
||||
return errAccessRestricted
|
||||
}
|
||||
r.logger.Debugf("connection from %s rejected by access restrictions: %s", conn.RemoteAddr(), verdict)
|
||||
r.logL4Deny(route, conn, verdict)
|
||||
return errAccessRestricted
|
||||
}
|
||||
|
||||
svcCtx, err := r.acquireRelay(ctx, route)
|
||||
@@ -615,7 +610,7 @@ func (r *Router) logL4Entry(route Route, conn net.Conn, duration time.Duration,
|
||||
}
|
||||
|
||||
// logL4Deny sends an access log entry for a denied connection.
|
||||
func (r *Router) logL4Deny(route Route, conn net.Conn, verdict restrict.Verdict, observeOnly bool) {
|
||||
func (r *Router) logL4Deny(route Route, conn net.Conn, verdict restrict.Verdict) {
|
||||
r.mu.RLock()
|
||||
al := r.accessLog
|
||||
r.mu.RUnlock()
|
||||
@@ -626,22 +621,14 @@ func (r *Router) logL4Deny(route Route, conn net.Conn, verdict restrict.Verdict,
|
||||
|
||||
sourceIP, _ := addrFromConn(conn)
|
||||
|
||||
entry := accesslog.L4Entry{
|
||||
al.LogL4(accesslog.L4Entry{
|
||||
AccountID: route.AccountID,
|
||||
ServiceID: route.ServiceID,
|
||||
Protocol: route.Protocol,
|
||||
Host: route.Domain,
|
||||
SourceIP: sourceIP,
|
||||
DenyReason: verdict.String(),
|
||||
}
|
||||
if verdict.IsCrowdSec() {
|
||||
entry.Metadata = map[string]string{"crowdsec_verdict": verdict.String()}
|
||||
if observeOnly {
|
||||
entry.Metadata["crowdsec_mode"] = "observe"
|
||||
entry.DenyReason = ""
|
||||
}
|
||||
}
|
||||
al.LogL4(entry)
|
||||
})
|
||||
}
|
||||
|
||||
// getOrCreateServiceCtxLocked returns the context for a service, creating one
|
||||
|
||||
@@ -1686,7 +1686,7 @@ func (f *fakeConn) RemoteAddr() net.Addr { return f.remote }
|
||||
|
||||
func TestCheckRestrictions_UnparseableAddress(t *testing.T) {
|
||||
router := NewPortRouter(log.StandardLogger(), nil)
|
||||
filter := restrict.ParseFilter(restrict.FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}})
|
||||
filter := restrict.ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil)
|
||||
route := Route{Filter: filter}
|
||||
|
||||
conn := &fakeConn{remote: fakeAddr("not-an-ip")}
|
||||
@@ -1695,7 +1695,7 @@ func TestCheckRestrictions_UnparseableAddress(t *testing.T) {
|
||||
|
||||
func TestCheckRestrictions_NilRemoteAddr(t *testing.T) {
|
||||
router := NewPortRouter(log.StandardLogger(), nil)
|
||||
filter := restrict.ParseFilter(restrict.FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}})
|
||||
filter := restrict.ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil)
|
||||
route := Route{Filter: filter}
|
||||
|
||||
conn := &fakeConn{remote: nil}
|
||||
@@ -1704,7 +1704,7 @@ func TestCheckRestrictions_NilRemoteAddr(t *testing.T) {
|
||||
|
||||
func TestCheckRestrictions_AllowedAndDenied(t *testing.T) {
|
||||
router := NewPortRouter(log.StandardLogger(), nil)
|
||||
filter := restrict.ParseFilter(restrict.FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}})
|
||||
filter := restrict.ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil)
|
||||
route := Route{Filter: filter}
|
||||
|
||||
allowed := &fakeConn{remote: &net.TCPAddr{IP: net.IPv4(10, 1, 2, 3), Port: 1234}}
|
||||
@@ -1724,7 +1724,7 @@ func TestCheckRestrictions_NilFilter(t *testing.T) {
|
||||
|
||||
func TestCheckRestrictions_IPv4MappedIPv6(t *testing.T) {
|
||||
router := NewPortRouter(log.StandardLogger(), nil)
|
||||
filter := restrict.ParseFilter(restrict.FilterConfig{AllowedCIDRs: []string{"10.0.0.0/8"}})
|
||||
filter := restrict.ParseFilter([]string{"10.0.0.0/8"}, nil, nil, nil)
|
||||
route := Route{Filter: filter}
|
||||
|
||||
// net.IPv4() returns a 16-byte v4-in-v6 representation internally.
|
||||
|
||||
@@ -336,13 +336,8 @@ func (r *Relay) checkAccessRestrictions(addr net.Addr) error {
|
||||
return fmt.Errorf("parse client address %s for restriction check: %w", addr, err)
|
||||
}
|
||||
if v := r.filter.Check(clientIP, r.geo); v != restrict.Allow {
|
||||
if r.filter.IsObserveOnly(v) {
|
||||
r.logger.Debugf("CrowdSec observe: would block %s (%s)", clientIP, v)
|
||||
r.logDeny(clientIP, v, true)
|
||||
} else {
|
||||
r.logDeny(clientIP, v, false)
|
||||
return fmt.Errorf("access restricted for %s", addr)
|
||||
}
|
||||
r.logDeny(clientIP, v)
|
||||
return fmt.Errorf("access restricted for %s", addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -503,27 +498,19 @@ func (r *Relay) logSessionEnd(sess *session) {
|
||||
}
|
||||
|
||||
// logDeny sends an access log entry for a denied UDP packet.
|
||||
func (r *Relay) logDeny(clientIP netip.Addr, verdict restrict.Verdict, observeOnly bool) {
|
||||
func (r *Relay) logDeny(clientIP netip.Addr, verdict restrict.Verdict) {
|
||||
if r.accessLog == nil {
|
||||
return
|
||||
}
|
||||
|
||||
entry := accesslog.L4Entry{
|
||||
r.accessLog.LogL4(accesslog.L4Entry{
|
||||
AccountID: r.accountID,
|
||||
ServiceID: r.serviceID,
|
||||
Protocol: accesslog.ProtocolUDP,
|
||||
Host: r.domain,
|
||||
SourceIP: clientIP,
|
||||
DenyReason: verdict.String(),
|
||||
}
|
||||
if verdict.IsCrowdSec() {
|
||||
entry.Metadata = map[string]string{"crowdsec_verdict": verdict.String()}
|
||||
if observeOnly {
|
||||
entry.Metadata["crowdsec_mode"] = "observe"
|
||||
entry.DenyReason = ""
|
||||
}
|
||||
}
|
||||
r.accessLog.LogL4(entry)
|
||||
})
|
||||
}
|
||||
|
||||
// Close stops the relay, waits for all session goroutines to exit,
|
||||
|
||||
@@ -228,10 +228,6 @@ func (m *testProxyManager) ClusterRequireSubdomain(_ context.Context, _ string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *testProxyManager) ClusterSupportsCrowdSec(_ context.Context, _ string) *bool {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *testProxyManager) CleanupStale(_ context.Context, _ time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ import (
|
||||
"github.com/netbirdio/netbird/proxy/internal/auth"
|
||||
"github.com/netbirdio/netbird/proxy/internal/certwatch"
|
||||
"github.com/netbirdio/netbird/proxy/internal/conntrack"
|
||||
"github.com/netbirdio/netbird/proxy/internal/crowdsec"
|
||||
"github.com/netbirdio/netbird/proxy/internal/debug"
|
||||
"github.com/netbirdio/netbird/proxy/internal/geolocation"
|
||||
proxygrpc "github.com/netbirdio/netbird/proxy/internal/grpc"
|
||||
@@ -101,13 +100,6 @@ type Server struct {
|
||||
geo restrict.GeoResolver
|
||||
geoRaw *geolocation.Lookup
|
||||
|
||||
// crowdsecRegistry manages the shared CrowdSec bouncer lifecycle.
|
||||
crowdsecRegistry *crowdsec.Registry
|
||||
// crowdsecServices tracks which services have CrowdSec enabled for
|
||||
// proper acquire/release lifecycle management.
|
||||
crowdsecMu sync.Mutex
|
||||
crowdsecServices map[types.ServiceID]bool
|
||||
|
||||
// routerReady is closed once mainRouter is fully initialized.
|
||||
// The mapping worker waits on this before processing updates.
|
||||
routerReady chan struct{}
|
||||
@@ -183,10 +175,6 @@ type Server struct {
|
||||
// GeoDataDir is the directory containing GeoLite2 MMDB files for
|
||||
// country-based access restrictions. Empty disables geo lookups.
|
||||
GeoDataDir string
|
||||
// CrowdSecAPIURL is the CrowdSec LAPI URL. Empty disables CrowdSec.
|
||||
CrowdSecAPIURL string
|
||||
// CrowdSecAPIKey is the CrowdSec bouncer API key. Empty disables CrowdSec.
|
||||
CrowdSecAPIKey string
|
||||
// MaxSessionIdleTimeout caps the per-service session idle timeout.
|
||||
// Zero means no cap (the proxy honors whatever management sends).
|
||||
// Set via NB_PROXY_MAX_SESSION_IDLE_TIMEOUT for shared deployments.
|
||||
@@ -287,9 +275,6 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
|
||||
// management connectivity from the first stream connection.
|
||||
s.healthChecker = health.NewChecker(s.Logger, s.netbird)
|
||||
|
||||
s.crowdsecRegistry = crowdsec.NewRegistry(s.CrowdSecAPIURL, s.CrowdSecAPIKey, log.NewEntry(s.Logger))
|
||||
s.crowdsecServices = make(map[types.ServiceID]bool)
|
||||
|
||||
go s.newManagementMappingWorker(runCtx, s.mgmtClient)
|
||||
|
||||
tlsConfig, err := s.configureTLS(ctx)
|
||||
@@ -778,22 +763,6 @@ func (s *Server) shutdownServices() {
|
||||
s.Logger.Debugf("close geolocation: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
s.shutdownCrowdSec()
|
||||
}
|
||||
|
||||
func (s *Server) shutdownCrowdSec() {
|
||||
if s.crowdsecRegistry == nil {
|
||||
return
|
||||
}
|
||||
s.crowdsecMu.Lock()
|
||||
services := maps.Clone(s.crowdsecServices)
|
||||
maps.Clear(s.crowdsecServices)
|
||||
s.crowdsecMu.Unlock()
|
||||
|
||||
for svcID := range services {
|
||||
s.crowdsecRegistry.Release(svcID)
|
||||
}
|
||||
}
|
||||
|
||||
// resolveDialFunc returns a DialContextFunc that dials through the
|
||||
@@ -947,7 +916,6 @@ func (s *Server) newManagementMappingWorker(ctx context.Context, client proto.Pr
|
||||
s.healthChecker.SetManagementConnected(false)
|
||||
}
|
||||
|
||||
supportsCrowdSec := s.crowdsecRegistry.Available()
|
||||
mappingClient, err := client.GetMappingUpdate(ctx, &proto.GetMappingUpdateRequest{
|
||||
ProxyId: s.ID,
|
||||
Version: s.Version,
|
||||
@@ -956,7 +924,6 @@ func (s *Server) newManagementMappingWorker(ctx context.Context, client proto.Pr
|
||||
Capabilities: &proto.ProxyCapabilities{
|
||||
SupportsCustomPorts: &s.SupportsCustomPorts,
|
||||
RequireSubdomain: &s.RequireSubdomain,
|
||||
SupportsCrowdsec: &supportsCrowdSec,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1192,7 +1159,7 @@ func (s *Server) setupTCPMapping(ctx context.Context, mapping *proto.ProxyMappin
|
||||
ProxyProtocol: s.l4ProxyProtocol(mapping),
|
||||
DialTimeout: s.l4DialTimeout(mapping),
|
||||
SessionIdleTimeout: s.clampIdleTimeout(l4SessionIdleTimeout(mapping)),
|
||||
Filter: s.parseRestrictions(mapping),
|
||||
Filter: parseRestrictions(mapping),
|
||||
})
|
||||
|
||||
s.portMu.Lock()
|
||||
@@ -1267,7 +1234,7 @@ func (s *Server) setupTLSMapping(ctx context.Context, mapping *proto.ProxyMappin
|
||||
ProxyProtocol: s.l4ProxyProtocol(mapping),
|
||||
DialTimeout: s.l4DialTimeout(mapping),
|
||||
SessionIdleTimeout: s.clampIdleTimeout(l4SessionIdleTimeout(mapping)),
|
||||
Filter: s.parseRestrictions(mapping),
|
||||
Filter: parseRestrictions(mapping),
|
||||
})
|
||||
|
||||
if tlsPort != s.mainPort {
|
||||
@@ -1301,51 +1268,12 @@ func (s *Server) serviceKeyForMapping(mapping *proto.ProxyMapping) roundtrip.Ser
|
||||
|
||||
// parseRestrictions converts a proto mapping's access restrictions into
|
||||
// a restrict.Filter. Returns nil if the mapping has no restrictions.
|
||||
func (s *Server) parseRestrictions(mapping *proto.ProxyMapping) *restrict.Filter {
|
||||
func parseRestrictions(mapping *proto.ProxyMapping) *restrict.Filter {
|
||||
r := mapping.GetAccessRestrictions()
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
svcID := types.ServiceID(mapping.GetId())
|
||||
csMode := restrict.CrowdSecMode(r.GetCrowdsecMode())
|
||||
|
||||
var checker restrict.CrowdSecChecker
|
||||
if csMode == restrict.CrowdSecEnforce || csMode == restrict.CrowdSecObserve {
|
||||
if b := s.crowdsecRegistry.Acquire(svcID); b != nil {
|
||||
checker = b
|
||||
s.crowdsecMu.Lock()
|
||||
s.crowdsecServices[svcID] = true
|
||||
s.crowdsecMu.Unlock()
|
||||
} else {
|
||||
s.Logger.Warnf("service %s requests CrowdSec mode %q but proxy has no CrowdSec configured", svcID, csMode)
|
||||
// Keep the mode: restrict.Filter will fail-closed for enforce (DenyCrowdSecUnavailable)
|
||||
// and allow for observe.
|
||||
}
|
||||
}
|
||||
|
||||
return restrict.ParseFilter(restrict.FilterConfig{
|
||||
AllowedCIDRs: r.GetAllowedCidrs(),
|
||||
BlockedCIDRs: r.GetBlockedCidrs(),
|
||||
AllowedCountries: r.GetAllowedCountries(),
|
||||
BlockedCountries: r.GetBlockedCountries(),
|
||||
CrowdSec: checker,
|
||||
CrowdSecMode: csMode,
|
||||
Logger: log.NewEntry(s.Logger),
|
||||
})
|
||||
}
|
||||
|
||||
// releaseCrowdSec releases the CrowdSec bouncer reference for the given
|
||||
// service if it had one.
|
||||
func (s *Server) releaseCrowdSec(svcID types.ServiceID) {
|
||||
s.crowdsecMu.Lock()
|
||||
had := s.crowdsecServices[svcID]
|
||||
delete(s.crowdsecServices, svcID)
|
||||
s.crowdsecMu.Unlock()
|
||||
|
||||
if had {
|
||||
s.crowdsecRegistry.Release(svcID)
|
||||
}
|
||||
return restrict.ParseFilter(r.GetAllowedCidrs(), r.GetBlockedCidrs(), r.GetAllowedCountries(), r.GetBlockedCountries())
|
||||
}
|
||||
|
||||
// warnIfGeoUnavailable logs a warning if the mapping has country restrictions
|
||||
@@ -1460,7 +1388,7 @@ func (s *Server) addUDPRelay(ctx context.Context, mapping *proto.ProxyMapping, t
|
||||
DialTimeout: s.l4DialTimeout(mapping),
|
||||
SessionTTL: s.clampIdleTimeout(l4SessionIdleTimeout(mapping)),
|
||||
AccessLog: s.accessLog,
|
||||
Filter: s.parseRestrictions(mapping),
|
||||
Filter: parseRestrictions(mapping),
|
||||
Geo: s.geo,
|
||||
})
|
||||
relay.SetObserver(s.meter)
|
||||
@@ -1497,7 +1425,7 @@ func (s *Server) updateMapping(ctx context.Context, mapping *proto.ProxyMapping)
|
||||
schemes = append(schemes, auth.NewHeader(s.mgmtClient, svcID, accountID, ha.GetHeader()))
|
||||
}
|
||||
|
||||
ipRestrictions := s.parseRestrictions(mapping)
|
||||
ipRestrictions := parseRestrictions(mapping)
|
||||
s.warnIfGeoUnavailable(mapping.GetDomain(), mapping.GetAccessRestrictions())
|
||||
|
||||
maxSessionAge := time.Duration(mapping.GetAuth().GetMaxSessionAgeSeconds()) * time.Second
|
||||
@@ -1539,7 +1467,6 @@ func (s *Server) removeMapping(ctx context.Context, mapping *proto.ProxyMapping)
|
||||
// removal and in-place modification of mappings.
|
||||
func (s *Server) cleanupMappingRoutes(mapping *proto.ProxyMapping) {
|
||||
svcID := types.ServiceID(mapping.GetId())
|
||||
s.releaseCrowdSec(svcID)
|
||||
host := mapping.GetDomain()
|
||||
|
||||
// HTTP/TLS cleanup (only relevant when a domain is set).
|
||||
|
||||
@@ -2860,11 +2860,6 @@ components:
|
||||
type: string
|
||||
description: "Protocol type: http, tcp, or udp"
|
||||
example: "http"
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: "Extra context about the request (e.g. crowdsec_verdict)"
|
||||
required:
|
||||
- id
|
||||
- service_id
|
||||
@@ -3263,14 +3258,6 @@ components:
|
||||
pattern: '^[a-zA-Z]{2}$'
|
||||
example: "DE"
|
||||
description: ISO 3166-1 alpha-2 country codes to block.
|
||||
crowdsec_mode:
|
||||
type: string
|
||||
enum:
|
||||
- "off"
|
||||
- "enforce"
|
||||
- "observe"
|
||||
default: "off"
|
||||
description: CrowdSec IP reputation mode. Only available when the proxy cluster supports CrowdSec.
|
||||
PasswordAuthConfig:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3374,10 +3361,6 @@ components:
|
||||
type: boolean
|
||||
description: Whether a subdomain label is required in front of this domain. When true, the domain cannot be used bare.
|
||||
example: false
|
||||
supports_crowdsec:
|
||||
type: boolean
|
||||
description: Whether the proxy cluster has CrowdSec configured
|
||||
example: false
|
||||
required:
|
||||
- id
|
||||
- domain
|
||||
|
||||
@@ -17,27 +17,6 @@ const (
|
||||
TokenAuthScopes = "TokenAuth.Scopes"
|
||||
)
|
||||
|
||||
// Defines values for AccessRestrictionsCrowdsecMode.
|
||||
const (
|
||||
AccessRestrictionsCrowdsecModeEnforce AccessRestrictionsCrowdsecMode = "enforce"
|
||||
AccessRestrictionsCrowdsecModeObserve AccessRestrictionsCrowdsecMode = "observe"
|
||||
AccessRestrictionsCrowdsecModeOff AccessRestrictionsCrowdsecMode = "off"
|
||||
)
|
||||
|
||||
// Valid indicates whether the value is a known member of the AccessRestrictionsCrowdsecMode enum.
|
||||
func (e AccessRestrictionsCrowdsecMode) Valid() bool {
|
||||
switch e {
|
||||
case AccessRestrictionsCrowdsecModeEnforce:
|
||||
return true
|
||||
case AccessRestrictionsCrowdsecModeObserve:
|
||||
return true
|
||||
case AccessRestrictionsCrowdsecModeOff:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Defines values for CreateAzureIntegrationRequestHost.
|
||||
const (
|
||||
CreateAzureIntegrationRequestHostMicrosoftCom CreateAzureIntegrationRequestHost = "microsoft.com"
|
||||
@@ -1347,14 +1326,8 @@ type AccessRestrictions struct {
|
||||
|
||||
// BlockedCountries ISO 3166-1 alpha-2 country codes to block.
|
||||
BlockedCountries *[]string `json:"blocked_countries,omitempty"`
|
||||
|
||||
// CrowdsecMode CrowdSec IP reputation mode. Only available when the proxy cluster supports CrowdSec.
|
||||
CrowdsecMode *AccessRestrictionsCrowdsecMode `json:"crowdsec_mode,omitempty"`
|
||||
}
|
||||
|
||||
// AccessRestrictionsCrowdsecMode CrowdSec IP reputation mode. Only available when the proxy cluster supports CrowdSec.
|
||||
type AccessRestrictionsCrowdsecMode string
|
||||
|
||||
// AccessiblePeer defines model for AccessiblePeer.
|
||||
type AccessiblePeer struct {
|
||||
// CityName Commonly used English name of the city
|
||||
@@ -3707,9 +3680,6 @@ type ProxyAccessLog struct {
|
||||
// Id Unique identifier for the access log entry
|
||||
Id string `json:"id"`
|
||||
|
||||
// Metadata Extra context about the request (e.g. crowdsec_verdict)
|
||||
Metadata *map[string]string `json:"metadata,omitempty"`
|
||||
|
||||
// Method HTTP method of the request
|
||||
Method string `json:"method"`
|
||||
|
||||
@@ -3789,9 +3759,6 @@ type ReverseProxyDomain struct {
|
||||
// RequireSubdomain Whether a subdomain label is required in front of this domain. When true, the domain cannot be used bare.
|
||||
RequireSubdomain *bool `json:"require_subdomain,omitempty"`
|
||||
|
||||
// SupportsCrowdsec Whether the proxy cluster has CrowdSec configured
|
||||
SupportsCrowdsec *bool `json:"supports_crowdsec,omitempty"`
|
||||
|
||||
// SupportsCustomPorts Whether the cluster supports binding arbitrary TCP/UDP ports
|
||||
SupportsCustomPorts *bool `json:"supports_custom_ports,omitempty"`
|
||||
|
||||
|
||||
@@ -186,8 +186,6 @@ type ProxyCapabilities struct {
|
||||
// Whether the proxy requires a subdomain label in front of its cluster domain.
|
||||
// When true, accounts cannot use the cluster domain bare.
|
||||
RequireSubdomain *bool `protobuf:"varint,2,opt,name=require_subdomain,json=requireSubdomain,proto3,oneof" json:"require_subdomain,omitempty"`
|
||||
// Whether the proxy has CrowdSec configured and can enforce IP reputation checks.
|
||||
SupportsCrowdsec *bool `protobuf:"varint,3,opt,name=supports_crowdsec,json=supportsCrowdsec,proto3,oneof" json:"supports_crowdsec,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ProxyCapabilities) Reset() {
|
||||
@@ -236,13 +234,6 @@ func (x *ProxyCapabilities) GetRequireSubdomain() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ProxyCapabilities) GetSupportsCrowdsec() bool {
|
||||
if x != nil && x.SupportsCrowdsec != nil {
|
||||
return *x.SupportsCrowdsec
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetMappingUpdateRequest is sent to initialise a mapping stream.
|
||||
type GetMappingUpdateRequest struct {
|
||||
state protoimpl.MessageState
|
||||
@@ -688,8 +679,6 @@ type AccessRestrictions struct {
|
||||
BlockedCidrs []string `protobuf:"bytes,2,rep,name=blocked_cidrs,json=blockedCidrs,proto3" json:"blocked_cidrs,omitempty"`
|
||||
AllowedCountries []string `protobuf:"bytes,3,rep,name=allowed_countries,json=allowedCountries,proto3" json:"allowed_countries,omitempty"`
|
||||
BlockedCountries []string `protobuf:"bytes,4,rep,name=blocked_countries,json=blockedCountries,proto3" json:"blocked_countries,omitempty"`
|
||||
// CrowdSec IP reputation mode: "", "off", "enforce", or "observe".
|
||||
CrowdsecMode string `protobuf:"bytes,5,opt,name=crowdsec_mode,json=crowdsecMode,proto3" json:"crowdsec_mode,omitempty"`
|
||||
}
|
||||
|
||||
func (x *AccessRestrictions) Reset() {
|
||||
@@ -752,13 +741,6 @@ func (x *AccessRestrictions) GetBlockedCountries() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AccessRestrictions) GetCrowdsecMode() string {
|
||||
if x != nil {
|
||||
return x.CrowdsecMode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ProxyMapping struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -1008,8 +990,6 @@ type AccessLog struct {
|
||||
BytesUpload int64 `protobuf:"varint,14,opt,name=bytes_upload,json=bytesUpload,proto3" json:"bytes_upload,omitempty"`
|
||||
BytesDownload int64 `protobuf:"varint,15,opt,name=bytes_download,json=bytesDownload,proto3" json:"bytes_download,omitempty"`
|
||||
Protocol string `protobuf:"bytes,16,opt,name=protocol,proto3" json:"protocol,omitempty"`
|
||||
// Extra key-value metadata for the access log entry (e.g. crowdsec_verdict, scenario).
|
||||
Metadata map[string]string `protobuf:"bytes,17,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (x *AccessLog) Reset() {
|
||||
@@ -1156,13 +1136,6 @@ func (x *AccessLog) GetProtocol() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AccessLog) GetMetadata() map[string]string {
|
||||
if x != nil {
|
||||
return x.Metadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthenticateRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -1979,7 +1952,7 @@ var file_proxy_service_proto_rawDesc = []byte{
|
||||
0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x22, 0xf6, 0x01, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x61, 0x70, 0x61,
|
||||
0x74, 0x6f, 0x22, 0xae, 0x01, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x61, 0x70, 0x61,
|
||||
0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x15, 0x73, 0x75, 0x70, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x73, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x70, 0x6f, 0x72, 0x74,
|
||||
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x13, 0x73, 0x75, 0x70, 0x70, 0x6f,
|
||||
@@ -1987,338 +1960,324 @@ var file_proxy_service_proto_rawDesc = []byte{
|
||||
0x01, 0x12, 0x30, 0x0a, 0x11, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x62,
|
||||
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x10,
|
||||
0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||
0x88, 0x01, 0x01, 0x12, 0x30, 0x0a, 0x11, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f,
|
||||
0x63, 0x72, 0x6f, 0x77, 0x64, 0x73, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02,
|
||||
0x52, 0x10, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x43, 0x72, 0x6f, 0x77, 0x64, 0x73,
|
||||
0x65, 0x63, 0x88, 0x01, 0x01, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x73, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x42,
|
||||
0x14, 0x0a, 0x12, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x64,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x73, 0x5f, 0x63, 0x72, 0x6f, 0x77, 0x64, 0x73, 0x65, 0x63, 0x22, 0xe6, 0x01, 0x0a, 0x17,
|
||||
0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a,
|
||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
|
||||
0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x12, 0x41, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65,
|
||||
0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69,
|
||||
0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69,
|
||||
0x74, 0x69, 0x65, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70,
|
||||
0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x32, 0x0a, 0x07, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x6d, 0x61,
|
||||
0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c,
|
||||
0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x79, 0x6e,
|
||||
0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0xce, 0x03, 0x0a, 0x11, 0x50, 0x61,
|
||||
0x74, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,
|
||||
0x26, 0x0a, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x74, 0x6c, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x69,
|
||||
0x66, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x54, 0x6c,
|
||||
0x73, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x42, 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x3e, 0x0a, 0x0c, 0x70,
|
||||
0x61, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50,
|
||||
0x61, 0x74, 0x68, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x0b,
|
||||
0x70, 0x61, 0x74, 0x68, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0x57, 0x0a, 0x0e, 0x63,
|
||||
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x50, 0x61, 0x74, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73,
|
||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x48, 0x65, 0x61,
|
||||
0x64, 0x65, 0x72, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x70, 0x72,
|
||||
0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x4b, 0x0a, 0x14, 0x73,
|
||||
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x6c,
|
||||
0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74,
|
||||
0x6f, 0x6d, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x72, 0x0a, 0x0b, 0x50, 0x61,
|
||||
0x74, 0x68, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74,
|
||||
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74,
|
||||
0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x47,
|
||||
0x0a, 0x0a, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65,
|
||||
0x61, 0x64, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x61, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x61, 0x73, 0x68,
|
||||
0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x0e, 0x41, 0x75, 0x74, 0x68,
|
||||
0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x17, 0x6d,
|
||||
0x61, 0x78, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x67, 0x65, 0x5f, 0x73,
|
||||
0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x61,
|
||||
0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x67, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e,
|
||||
0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x70, 0x69, 0x6e,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x6f, 0x69, 0x64, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04,
|
||||
0x6f, 0x69, 0x64, 0x63, 0x12, 0x39, 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61,
|
||||
0x75, 0x74, 0x68, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e,
|
||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75,
|
||||
0x74, 0x68, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x73, 0x22,
|
||||
0xdd, 0x01, 0x0a, 0x12, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65,
|
||||
0x64, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x61,
|
||||
0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x69, 0x64, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x62,
|
||||
0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03,
|
||||
0x28, 0x09, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x69, 0x64, 0x72, 0x73,
|
||||
0x12, 0x2b, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x6c, 0x6c,
|
||||
0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2b, 0x0a,
|
||||
0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69,
|
||||
0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65,
|
||||
0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72,
|
||||
0x6f, 0x77, 0x64, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0c, 0x63, 0x72, 0x6f, 0x77, 0x64, 0x73, 0x65, 0x63, 0x4d, 0x6f, 0x64, 0x65, 0x22,
|
||||
0xe6, 0x03, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67,
|
||||
0x12, 0x36, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78,
|
||||
0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79,
|
||||
0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
||||
0x2b, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x4d,
|
||||
0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2e, 0x0a, 0x04, 0x61,
|
||||
0x75, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x12, 0x28, 0x0a, 0x10, 0x70,
|
||||
0x61, 0x73, 0x73, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
|
||||
0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x48, 0x6f, 0x73, 0x74, 0x48,
|
||||
0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65,
|
||||
0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x10, 0x72, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63,
|
||||
0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e,
|
||||
0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6c, 0x69, 0x73,
|
||||
0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x4f, 0x0a, 0x13, 0x61, 0x63, 0x63, 0x65, 0x73,
|
||||
0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0c,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x74,
|
||||
0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3f, 0x0a, 0x14, 0x53, 0x65, 0x6e, 0x64,
|
||||
0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x27, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73,
|
||||
0x73, 0x4c, 0x6f, 0x67, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x65, 0x6e,
|
||||
0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x84, 0x05, 0x0a, 0x09, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67,
|
||||
0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
|
||||
0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x6f,
|
||||
0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x6f, 0x67, 0x49,
|
||||
0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64,
|
||||
0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68,
|
||||
0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75,
|
||||
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68,
|
||||
0x6f, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
|
||||
0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x64,
|
||||
0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,
|
||||
0x69, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
0x49, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x63, 0x68, 0x61,
|
||||
0x6e, 0x69, 0x73, 0x6d, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x68,
|
||||
0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
|
||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
|
||||
0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65,
|
||||
0x73, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x53, 0x75,
|
||||
0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x75,
|
||||
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x79, 0x74,
|
||||
0x65, 0x73, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65,
|
||||
0x73, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12,
|
||||
0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3f, 0x0a, 0x08, 0x6d,
|
||||
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73,
|
||||
0x73, 0x4c, 0x6f, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74,
|
||||
0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d,
|
||||
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
|
||||
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
|
||||
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf8, 0x01, 0x0a, 0x13, 0x41, 0x75,
|
||||
0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
|
||||
0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64,
|
||||
0x12, 0x39, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
|
||||
0x00, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x2a, 0x0a, 0x03, 0x70,
|
||||
0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x48, 0x00, 0x52, 0x03, 0x70, 0x69, 0x6e, 0x12, 0x40, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65,
|
||||
0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
|
||||
0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x68,
|
||||
0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x11, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75,
|
||||
0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65, 0x61,
|
||||
0x64, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
|
||||
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0a, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2d, 0x0a,
|
||||
0x0f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x1e, 0x0a, 0x0a,
|
||||
0x50, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69,
|
||||
0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x69, 0x6e, 0x22, 0x55, 0x0a, 0x14,
|
||||
0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x23,
|
||||
0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x22, 0xf3, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1d,
|
||||
0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2f, 0x0a,
|
||||
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d,
|
||||
0x0a, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x73,
|
||||
0x73, 0x75, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x63, 0x65, 0x72, 0x74,
|
||||
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x12, 0x28, 0x0a,
|
||||
0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x65, 0x72, 0x72, 0x6f,
|
||||
0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x53, 0x65, 0x6e,
|
||||
0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb8, 0x01, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12,
|
||||
0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72,
|
||||
0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x50, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
|
||||
0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
|
||||
0x22, 0x6f, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50,
|
||||
0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73,
|
||||
0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75,
|
||||
0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c,
|
||||
0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42,
|
||||
0x10, 0x0a, 0x0e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x22, 0x65, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4f, 0x49, 0x44, 0x43, 0x55, 0x52, 0x4c, 0x52,
|
||||
0x88, 0x01, 0x01, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x73,
|
||||
0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x42, 0x14, 0x0a,
|
||||
0x12, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x22, 0xe6, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69,
|
||||
0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x19, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f,
|
||||
0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x41, 0x0a, 0x0c, 0x63, 0x61, 0x70,
|
||||
0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f,
|
||||
0x78, 0x79, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x52, 0x0c,
|
||||
0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x82, 0x01, 0x0a,
|
||||
0x18, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x6d, 0x61, 0x70,
|
||||
0x70, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e,
|
||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x61, 0x70,
|
||||
0x70, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x32, 0x0a,
|
||||
0x15, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x63, 0x6f,
|
||||
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e,
|
||||
0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
|
||||
0x65, 0x22, 0xce, 0x03, 0x0a, 0x11, 0x50, 0x61, 0x74, 0x68, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
|
||||
0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x5f,
|
||||
0x74, 0x6c, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x54, 0x6c, 0x73, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12,
|
||||
0x42, 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f,
|
||||
0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x6f, 0x75, 0x74, 0x12, 0x3e, 0x0a, 0x0c, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x77, 0x72,
|
||||
0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x77, 0x72, 0x69,
|
||||
0x74, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x70, 0x61, 0x74, 0x68, 0x52, 0x65, 0x77, 0x72,
|
||||
0x69, 0x74, 0x65, 0x12, 0x57, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x68, 0x65,
|
||||
0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6d, 0x61,
|
||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x54, 0x61, 0x72,
|
||||
0x67, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f,
|
||||
0x6d, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63,
|
||||
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x25, 0x0a, 0x0e,
|
||||
0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x63, 0x6f, 0x6c, 0x12, 0x4b, 0x0a, 0x14, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
|
||||
0x64, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x73, 0x65,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
|
||||
0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
||||
0x38, 0x01, 0x22, 0x72, 0x0a, 0x0b, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e,
|
||||
0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x37, 0x0a,
|
||||
0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x68,
|
||||
0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x47, 0x0a, 0x0a, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
|
||||
0x41, 0x75, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c,
|
||||
0x68, 0x61, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0b, 0x68, 0x61, 0x73, 0x68, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22,
|
||||
0xe5, 0x01, 0x0a, 0x0e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6b, 0x65,
|
||||
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x4b, 0x65, 0x79, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x41, 0x67, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61,
|
||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x70, 0x61,
|
||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x03, 0x70, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6f, 0x69, 0x64, 0x63,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x6f, 0x69, 0x64, 0x63, 0x12, 0x39, 0x0a, 0x0c,
|
||||
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x18, 0x06, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64,
|
||||
0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x73, 0x22, 0xb8, 0x01, 0x0a, 0x12, 0x41, 0x63, 0x63, 0x65,
|
||||
0x73, 0x73, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23,
|
||||
0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x69,
|
||||
0x64, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x63,
|
||||
0x69, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63,
|
||||
0x6b, 0x65, 0x64, 0x43, 0x69, 0x64, 0x72, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x6c, 0x6c, 0x6f,
|
||||
0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20,
|
||||
0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64,
|
||||
0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09,
|
||||
0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69,
|
||||
0x65, 0x73, 0x22, 0xe6, 0x03, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d, 0x61, 0x70, 0x70,
|
||||
0x69, 0x6e, 0x67, 0x12, 0x36, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50,
|
||||
0x72, 0x6f, 0x78, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61,
|
||||
0x69, 0x6e, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x17, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61,
|
||||
0x74, 0x68, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
|
||||
0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x06, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2e,
|
||||
0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x12, 0x28,
|
||||
0x0a, 0x10, 0x70, 0x61, 0x73, 0x73, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x64,
|
||||
0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x48, 0x6f,
|
||||
0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x77, 0x72,
|
||||
0x69, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x73, 0x18, 0x09, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x64, 0x69,
|
||||
0x72, 0x65, 0x63, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x69, 0x73,
|
||||
0x74, 0x65, 0x6e, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a,
|
||||
0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x4f, 0x0a, 0x13, 0x61, 0x63,
|
||||
0x63, 0x65, 0x73, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x74, 0x72,
|
||||
0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x12, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52,
|
||||
0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3f, 0x0a, 0x14, 0x53,
|
||||
0x65, 0x6e, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x63,
|
||||
0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x22, 0x17, 0x0a, 0x15,
|
||||
0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x86, 0x04, 0x0a, 0x09, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
|
||||
0x4c, 0x6f, 0x67, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x15, 0x0a,
|
||||
0x06, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
|
||||
0x6f, 0x67, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75,
|
||||
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74,
|
||||
0x68, 0x6f, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f,
|
||||
0x63, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72,
|
||||
0x63, 0x65, 0x5f, 0x69, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6f, 0x75,
|
||||
0x72, 0x63, 0x65, 0x49, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65,
|
||||
0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61,
|
||||
0x75, 0x74, 0x68, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x12, 0x17, 0x0a, 0x07,
|
||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75,
|
||||
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x73, 0x75,
|
||||
0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x75, 0x74,
|
||||
0x68, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65,
|
||||
0x73, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b,
|
||||
0x62, 0x79, 0x74, 0x65, 0x73, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62,
|
||||
0x79, 0x74, 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0f, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,
|
||||
0x61, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x10,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0xf8,
|
||||
0x01, 0x0a, 0x13, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63,
|
||||
0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64,
|
||||
0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x22, 0x26, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f,
|
||||
0x49, 0x44, 0x43, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c,
|
||||
0x22, 0x55, 0x0a, 0x16, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 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, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8c, 0x01, 0x0a, 0x17, 0x56, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
|
||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
|
||||
0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69,
|
||||
0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x61, 0x73,
|
||||
0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64,
|
||||
0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2a, 0x64, 0x0a, 0x16, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4d,
|
||||
0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
|
||||
0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x50, 0x44,
|
||||
0x41, 0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45,
|
||||
0x44, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x59,
|
||||
0x50, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x44, 0x10, 0x02, 0x2a, 0x46, 0x0a, 0x0f,
|
||||
0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12,
|
||||
0x18, 0x0a, 0x14, 0x50, 0x41, 0x54, 0x48, 0x5f, 0x52, 0x45, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f,
|
||||
0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x41, 0x54,
|
||||
0x48, 0x5f, 0x52, 0x45, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x50, 0x52, 0x45, 0x53, 0x45, 0x52,
|
||||
0x56, 0x45, 0x10, 0x01, 0x2a, 0xc8, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x74,
|
||||
0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54,
|
||||
0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x17,
|
||||
0x0a, 0x13, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41,
|
||||
0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x50, 0x52, 0x4f, 0x58, 0x59,
|
||||
0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x54, 0x55, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x4e,
|
||||
0x4f, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20,
|
||||
0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x45, 0x52,
|
||||
0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47,
|
||||
0x10, 0x03, 0x12, 0x23, 0x0a, 0x1f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54,
|
||||
0x55, 0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x46,
|
||||
0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x52, 0x4f, 0x58, 0x59,
|
||||
0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x32,
|
||||
0xfc, 0x04, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x12, 0x5f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
|
||||
0x12, 0x2a, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x03, 0x70, 0x69, 0x6e, 0x12, 0x40, 0x0a, 0x0b,
|
||||
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48,
|
||||
0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x48, 0x00, 0x52, 0x0a, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x42, 0x09,
|
||||
0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x11, 0x48, 0x65, 0x61,
|
||||
0x64, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21,
|
||||
0x0a, 0x0c, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4e, 0x61,
|
||||
0x6d, 0x65, 0x22, 0x2d, 0x0a, 0x0f, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
|
||||
0x64, 0x22, 0x1e, 0x0a, 0x0a, 0x50, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x10, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, 0x69,
|
||||
0x6e, 0x22, 0x55, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63,
|
||||
0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63,
|
||||
0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xf3, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x6e,
|
||||
0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x49, 0x64, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x73, 0x73, 0x75,
|
||||
0x65, 0x64, 0x12, 0x28, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72,
|
||||
0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e,
|
||||
0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x1a,
|
||||
0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb8, 0x01, 0x0a, 0x16, 0x43,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x69, 0x72,
|
||||
0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65,
|
||||
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61,
|
||||
0x72, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63,
|
||||
0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c,
|
||||
0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x6f, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50,
|
||||
0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0d, 0x65, 0x72,
|
||||
0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x65, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4f, 0x49, 0x44,
|
||||
0x43, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65,
|
||||
0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x6c, 0x22, 0x26, 0x0a,
|
||||
0x12, 0x47, 0x65, 0x74, 0x4f, 0x49, 0x44, 0x43, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x55, 0x0a, 0x16, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
|
||||
0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 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, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
||||
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8c, 0x01, 0x0a,
|
||||
0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x17,
|
||||
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f,
|
||||
0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65,
|
||||
0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64,
|
||||
0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64,
|
||||
0x65, 0x6e, 0x69, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2a, 0x64, 0x0a, 0x16, 0x50,
|
||||
0x72, 0x6f, 0x78, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f,
|
||||
0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x18,
|
||||
0x0a, 0x14, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x4f,
|
||||
0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41,
|
||||
0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x44, 0x10,
|
||||
0x02, 0x2a, 0x46, 0x0a, 0x0f, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65,
|
||||
0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x41, 0x54, 0x48, 0x5f, 0x52, 0x45, 0x57,
|
||||
0x52, 0x49, 0x54, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x19,
|
||||
0x0a, 0x15, 0x50, 0x41, 0x54, 0x48, 0x5f, 0x52, 0x45, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x50,
|
||||
0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x10, 0x01, 0x2a, 0xc8, 0x01, 0x0a, 0x0b, 0x50, 0x72,
|
||||
0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x52, 0x4f,
|
||||
0x58, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e,
|
||||
0x47, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54, 0x41,
|
||||
0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f,
|
||||
0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x54, 0x55, 0x4e,
|
||||
0x4e, 0x45, 0x4c, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10,
|
||||
0x02, 0x12, 0x24, 0x0a, 0x20, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55,
|
||||
0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45,
|
||||
0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x23, 0x0a, 0x1f, 0x50, 0x52, 0x4f, 0x58, 0x59,
|
||||
0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43,
|
||||
0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12,
|
||||
0x50, 0x52, 0x4f, 0x58, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52,
|
||||
0x4f, 0x52, 0x10, 0x05, 0x32, 0xfc, 0x04, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70,
|
||||
0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e,
|
||||
0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30,
|
||||
0x01, 0x12, 0x54, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c,
|
||||
0x6f, 0x67, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x65,
|
||||
0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x10, 0x53, 0x65,
|
||||
0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x23,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x4d,
|
||||
0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x54, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63,
|
||||
0x63, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x67, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c,
|
||||
0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73,
|
||||
0x73, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0c,
|
||||
0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65,
|
||||
0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x5d, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0f, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4f, 0x49, 0x44, 0x43,
|
||||
0x55, 0x52, 0x4c, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x47, 0x65, 0x74, 0x4f, 0x49, 0x44, 0x43, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x47, 0x65, 0x74, 0x4f, 0x49, 0x44, 0x43, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53,
|
||||
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08,
|
||||
0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a,
|
||||
0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65,
|
||||
0x72, 0x12, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x65,
|
||||
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x47, 0x65,
|
||||
0x74, 0x4f, 0x49, 0x44, 0x43, 0x55, 0x52, 0x4c, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x49, 0x44, 0x43, 0x55, 0x52, 0x4c,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x49, 0x44, 0x43, 0x55, 0x52, 0x4c, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0f, 0x56, 0x61, 0x6c, 0x69, 0x64,
|
||||
0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x6d, 0x61, 0x6e,
|
||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65,
|
||||
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23,
|
||||
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -2334,7 +2293,7 @@ func file_proxy_service_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_proxy_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
var file_proxy_service_proto_msgTypes = make([]protoimpl.MessageInfo, 27)
|
||||
var file_proxy_service_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
|
||||
var file_proxy_service_proto_goTypes = []interface{}{
|
||||
(ProxyMappingUpdateType)(0), // 0: management.ProxyMappingUpdateType
|
||||
(PathRewriteMode)(0), // 1: management.PathRewriteMode
|
||||
@@ -2365,18 +2324,17 @@ var file_proxy_service_proto_goTypes = []interface{}{
|
||||
(*ValidateSessionRequest)(nil), // 26: management.ValidateSessionRequest
|
||||
(*ValidateSessionResponse)(nil), // 27: management.ValidateSessionResponse
|
||||
nil, // 28: management.PathTargetOptions.CustomHeadersEntry
|
||||
nil, // 29: management.AccessLog.MetadataEntry
|
||||
(*timestamppb.Timestamp)(nil), // 30: google.protobuf.Timestamp
|
||||
(*durationpb.Duration)(nil), // 31: google.protobuf.Duration
|
||||
(*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp
|
||||
(*durationpb.Duration)(nil), // 30: google.protobuf.Duration
|
||||
}
|
||||
var file_proxy_service_proto_depIdxs = []int32{
|
||||
30, // 0: management.GetMappingUpdateRequest.started_at:type_name -> google.protobuf.Timestamp
|
||||
29, // 0: management.GetMappingUpdateRequest.started_at:type_name -> google.protobuf.Timestamp
|
||||
3, // 1: management.GetMappingUpdateRequest.capabilities:type_name -> management.ProxyCapabilities
|
||||
11, // 2: management.GetMappingUpdateResponse.mapping:type_name -> management.ProxyMapping
|
||||
31, // 3: management.PathTargetOptions.request_timeout:type_name -> google.protobuf.Duration
|
||||
30, // 3: management.PathTargetOptions.request_timeout:type_name -> google.protobuf.Duration
|
||||
1, // 4: management.PathTargetOptions.path_rewrite:type_name -> management.PathRewriteMode
|
||||
28, // 5: management.PathTargetOptions.custom_headers:type_name -> management.PathTargetOptions.CustomHeadersEntry
|
||||
31, // 6: management.PathTargetOptions.session_idle_timeout:type_name -> google.protobuf.Duration
|
||||
30, // 6: management.PathTargetOptions.session_idle_timeout:type_name -> google.protobuf.Duration
|
||||
6, // 7: management.PathMapping.options:type_name -> management.PathTargetOptions
|
||||
8, // 8: management.Authentication.header_auths:type_name -> management.HeaderAuth
|
||||
0, // 9: management.ProxyMapping.type:type_name -> management.ProxyMappingUpdateType
|
||||
@@ -2384,31 +2342,30 @@ var file_proxy_service_proto_depIdxs = []int32{
|
||||
9, // 11: management.ProxyMapping.auth:type_name -> management.Authentication
|
||||
10, // 12: management.ProxyMapping.access_restrictions:type_name -> management.AccessRestrictions
|
||||
14, // 13: management.SendAccessLogRequest.log:type_name -> management.AccessLog
|
||||
30, // 14: management.AccessLog.timestamp:type_name -> google.protobuf.Timestamp
|
||||
29, // 15: management.AccessLog.metadata:type_name -> management.AccessLog.MetadataEntry
|
||||
17, // 16: management.AuthenticateRequest.password:type_name -> management.PasswordRequest
|
||||
18, // 17: management.AuthenticateRequest.pin:type_name -> management.PinRequest
|
||||
16, // 18: management.AuthenticateRequest.header_auth:type_name -> management.HeaderAuthRequest
|
||||
2, // 19: management.SendStatusUpdateRequest.status:type_name -> management.ProxyStatus
|
||||
4, // 20: management.ProxyService.GetMappingUpdate:input_type -> management.GetMappingUpdateRequest
|
||||
12, // 21: management.ProxyService.SendAccessLog:input_type -> management.SendAccessLogRequest
|
||||
15, // 22: management.ProxyService.Authenticate:input_type -> management.AuthenticateRequest
|
||||
20, // 23: management.ProxyService.SendStatusUpdate:input_type -> management.SendStatusUpdateRequest
|
||||
22, // 24: management.ProxyService.CreateProxyPeer:input_type -> management.CreateProxyPeerRequest
|
||||
24, // 25: management.ProxyService.GetOIDCURL:input_type -> management.GetOIDCURLRequest
|
||||
26, // 26: management.ProxyService.ValidateSession:input_type -> management.ValidateSessionRequest
|
||||
5, // 27: management.ProxyService.GetMappingUpdate:output_type -> management.GetMappingUpdateResponse
|
||||
13, // 28: management.ProxyService.SendAccessLog:output_type -> management.SendAccessLogResponse
|
||||
19, // 29: management.ProxyService.Authenticate:output_type -> management.AuthenticateResponse
|
||||
21, // 30: management.ProxyService.SendStatusUpdate:output_type -> management.SendStatusUpdateResponse
|
||||
23, // 31: management.ProxyService.CreateProxyPeer:output_type -> management.CreateProxyPeerResponse
|
||||
25, // 32: management.ProxyService.GetOIDCURL:output_type -> management.GetOIDCURLResponse
|
||||
27, // 33: management.ProxyService.ValidateSession:output_type -> management.ValidateSessionResponse
|
||||
27, // [27:34] is the sub-list for method output_type
|
||||
20, // [20:27] is the sub-list for method input_type
|
||||
20, // [20:20] is the sub-list for extension type_name
|
||||
20, // [20:20] is the sub-list for extension extendee
|
||||
0, // [0:20] is the sub-list for field type_name
|
||||
29, // 14: management.AccessLog.timestamp:type_name -> google.protobuf.Timestamp
|
||||
17, // 15: management.AuthenticateRequest.password:type_name -> management.PasswordRequest
|
||||
18, // 16: management.AuthenticateRequest.pin:type_name -> management.PinRequest
|
||||
16, // 17: management.AuthenticateRequest.header_auth:type_name -> management.HeaderAuthRequest
|
||||
2, // 18: management.SendStatusUpdateRequest.status:type_name -> management.ProxyStatus
|
||||
4, // 19: management.ProxyService.GetMappingUpdate:input_type -> management.GetMappingUpdateRequest
|
||||
12, // 20: management.ProxyService.SendAccessLog:input_type -> management.SendAccessLogRequest
|
||||
15, // 21: management.ProxyService.Authenticate:input_type -> management.AuthenticateRequest
|
||||
20, // 22: management.ProxyService.SendStatusUpdate:input_type -> management.SendStatusUpdateRequest
|
||||
22, // 23: management.ProxyService.CreateProxyPeer:input_type -> management.CreateProxyPeerRequest
|
||||
24, // 24: management.ProxyService.GetOIDCURL:input_type -> management.GetOIDCURLRequest
|
||||
26, // 25: management.ProxyService.ValidateSession:input_type -> management.ValidateSessionRequest
|
||||
5, // 26: management.ProxyService.GetMappingUpdate:output_type -> management.GetMappingUpdateResponse
|
||||
13, // 27: management.ProxyService.SendAccessLog:output_type -> management.SendAccessLogResponse
|
||||
19, // 28: management.ProxyService.Authenticate:output_type -> management.AuthenticateResponse
|
||||
21, // 29: management.ProxyService.SendStatusUpdate:output_type -> management.SendStatusUpdateResponse
|
||||
23, // 30: management.ProxyService.CreateProxyPeer:output_type -> management.CreateProxyPeerResponse
|
||||
25, // 31: management.ProxyService.GetOIDCURL:output_type -> management.GetOIDCURLResponse
|
||||
27, // 32: management.ProxyService.ValidateSession:output_type -> management.ValidateSessionResponse
|
||||
26, // [26:33] is the sub-list for method output_type
|
||||
19, // [19:26] is the sub-list for method input_type
|
||||
19, // [19:19] is the sub-list for extension type_name
|
||||
19, // [19:19] is the sub-list for extension extendee
|
||||
0, // [0:19] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proxy_service_proto_init() }
|
||||
@@ -2732,7 +2689,7 @@ func file_proxy_service_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_proxy_service_proto_rawDesc,
|
||||
NumEnums: 3,
|
||||
NumMessages: 27,
|
||||
NumMessages: 26,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -34,8 +34,6 @@ message ProxyCapabilities {
|
||||
// Whether the proxy requires a subdomain label in front of its cluster domain.
|
||||
// When true, accounts cannot use the cluster domain bare.
|
||||
optional bool require_subdomain = 2;
|
||||
// Whether the proxy has CrowdSec configured and can enforce IP reputation checks.
|
||||
optional bool supports_crowdsec = 3;
|
||||
}
|
||||
|
||||
// GetMappingUpdateRequest is sent to initialise a mapping stream.
|
||||
@@ -106,8 +104,6 @@ message AccessRestrictions {
|
||||
repeated string blocked_cidrs = 2;
|
||||
repeated string allowed_countries = 3;
|
||||
repeated string blocked_countries = 4;
|
||||
// CrowdSec IP reputation mode: "", "off", "enforce", or "observe".
|
||||
string crowdsec_mode = 5;
|
||||
}
|
||||
|
||||
message ProxyMapping {
|
||||
@@ -156,8 +152,6 @@ message AccessLog {
|
||||
int64 bytes_upload = 14;
|
||||
int64 bytes_download = 15;
|
||||
string protocol = 16;
|
||||
// Extra key-value metadata for the access log entry (e.g. crowdsec_verdict, scenario).
|
||||
map<string, string> metadata = 17;
|
||||
}
|
||||
|
||||
message AuthenticateRequest {
|
||||
|
||||
Reference in New Issue
Block a user