config: big rewrite and switch from viper to koanf

Signed-off-by: Steffen Vogel <post@steffenvogel.de>
This commit is contained in:
Steffen Vogel
2022-09-14 03:34:30 +02:00
parent 694ffd45fd
commit 8d96dea17e
28 changed files with 2265 additions and 1324 deletions

View File

@@ -6,6 +6,7 @@ import (
cunicu "github.com/stv0g/cunicu/pkg"
"github.com/stv0g/cunicu/pkg/config"
"github.com/stv0g/cunicu/pkg/rpc"
"github.com/stv0g/cunicu/pkg/util/terminal"
"go.uber.org/zap"
"go.uber.org/zap/zapio"
)
@@ -28,7 +29,7 @@ func init() {
pf := daemonCmd.PersistentFlags()
cfg = config.NewConfig(pf)
cfg = config.New(pf)
daemonCmd.RegisterFlagCompletionFunc("ice-candidate-type", cobra.FixedCompletions([]string{"host", "srflx", "prflx", "relay"}, cobra.ShellCompDirectiveNoFileComp))
daemonCmd.RegisterFlagCompletionFunc("ice-network-type", cobra.FixedCompletions([]string{"udp4", "udp6", "tcp4", "tcp6"}, cobra.ShellCompDirectiveNoFileComp))
@@ -43,19 +44,17 @@ func init() {
}
func daemon(cmd *cobra.Command, args []string) {
if err := cfg.Setup(args); err != nil {
if err := cfg.Load(); err != nil {
logger.Fatal("Failed to parse configuration", zap.Error(err))
}
if logger.Core().Enabled(zap.DebugLevel) {
logger.Debug("Loaded configuration:")
wr := &zapio.Writer{
wr := terminal.NewIndenter(&zapio.Writer{
Log: logger,
Level: zap.DebugLevel,
}
if err := cfg.Dump(wr); err != nil {
logger.Fatal("Failed to dump configuration", zap.Error(err))
}
}, " ")
cfg.Marshal(wr)
}
// Create daemon

View File

@@ -16,41 +16,77 @@ A full overview is available in its [manpage](./usage/md/cunicu_daemon.md).
Alternatively a configuration file can be used for a persistent configuration:
```yaml title="cunicu.yaml"
# An interval at which cunicu will periodically check for added, removed or modified WireGuard interfaces.
watch_interval: 1s
backends:
- grpc://localhost:8080?insecure=true&skip_verify=true
- k8s:///path/to/your/kubeconfig.yaml?namespace=default
# RPC control socket settings
rpc:
socket: /var/run/cunicu.sock
# Start of cunicu daemon will block until its unblocked via the control socket
# Mostly useful for testing automation
wait: false
## Hook callbacks
#
# Hook callback can be used to invoke subprocesses or web-hooks on certain events within cunicu.
hooks:
- type: exec
command: ../../scripts/hook.sh
# Prepend additional arguments
args: []
# Pass JSON object via Stdin to command
stdin: true
# Set environment variables for invocation
env:
COLOR: "1"
- type: web
# URL of the webhook endpoint
url: https://my-webhook-endpoint.com/api/v1/webhook
# HTTP method of request
method: POST
# Pass additional HTTP headers.
headers:
User-Agent: ahoi
Authorization: Bearer XXXXXX
#### Interface settings start here
# The following settings can be overwritten for each interface
# using the 'interfaces' settings (see below).
# The following settings will be used as default.
# WireGuard settings
wireguard:
# Create WireGuard interfaces using bundled wireguard-go Userspace implementation
# This will be the default if there is no WireGuard kernel module present.
userspace: false
# Ignore WireGuard interface which do not match this regular expression
interface_filter: .*
# A list of WireGuard interfaces which should be configured
interfaces:
- wg-vpn
# Port range for ListenPort setting of newly created WireGuard interfaces
# cunīcu will select the first available port in this range.
port:
# cunicu will select the first available port in this range.
listen_port_range:
min: 52820
max: 65535
# Control socket settings
socket:
path: /var/run/cunicu.sock
# Start of cunīcu daemon will block until its unblocked via the control socket
# Mostly useful for testing automation
wait: false
# Synchronize WireGuard interface configurations with wg(8) config-files.
config_sync:
## Config file synchronization
#
# Synchronize local WireGuard interface configuration with wg(8) config-files.
cfgsync:
enabled: false
# Directory where Wireguard configuration files are located.
@@ -58,20 +94,43 @@ config_sync:
# Filenames must match the interface name with a '.conf' suffix.
path: /etc/wireguard
# Watch the configuration files for changes and apply them accordingly.
# Watch the configuration files via inotify(7) for changes and apply them accordingly.
watch: false
# Synchronize WireGuard AllowedIPs with Kernel routing table
route_sync:
## Route Synchronization
#
# Synchronize the kernel routing table with WireGuard's AllowedIPs setting
#
# It checks for routes in the kernel routing table which have a peers link-local address
# as next-hop and adds those routes to the AllowedIPs setting of the respective peer.
#
# In reverse, also networks listed in a peers AllowedIPs setting will be installed as a
# kernel route with the peers link-local address as the routes next-hop.
rtsync:
enabled: true
table: main
table: 254 # See /etc/iproute2/rt_tables for table ids
# Discovery of other WireGuard peers
peer_disc:
# Keep watching the for changes in the kernel routing table via netlink multicast group.
watch: true
## /etc/hosts synchronization
#
# Synchronizes the local /etc/hosts file with host names and link-local IP addresses of connected peers.
hsync:
enabled: true
## Peer discovery
#
# Peer discovery finds new peers within the same community and adds them to the respective interface
pdisc:
enabled: true
# A list of WireGuard public keys which are accepted peers
# If not configured, all peers will be accepted.
whitelist:
- coNsGPwVPdpahc8U+dbbWGzTAdCd6+1BvPIYg10wDCI=
- AOZzBaNsoV7P8vo0D5UmuIJUQ7AjMbHbGt2EA8eAuEc=
@@ -79,34 +138,54 @@ peer_disc:
# A passphrase shared among all peers of the same community
community: "some-common-password"
# Discovery of WireGuard endpoint addressesendpoint_disc:
## Endpoint discovery
#
# Endpoint discovery uses Interactive Connectivity Establishment (ICE) as used by WebRTC to
# gather a list of candidate endpoints and performs connectivity checks to find a suitable
# endpoint address which can be used by WireGuard
epdisc:
enabled: true
# Interactive Connectivity Establishment parameters
# Interactive Connectivity Establishment (ICE) parameters
ice:
# A list of STUN and TURN servers used by ICE
# A list of STUN and TURN servers used by ICE.
urls:
- stun:stun.l.google.com:19302
# Community provided STUN/TURN servers
- grpc://relay.cunicu.li
# Credentials for STUN/TURN servers configured above
# Public STUN servers
- stun:stun3.l.google.com:19302
- stun:relay.webwormhole.io
- stun:stun.sipgate.net
- stun:stun.ekiga.net
- stun:stun.services.mozilla.com
# Caution: OpenRelay servers are located in Ontario, Canada.
# Beware of the latency!
# See also: https://www.metered.ca/tools/openrelay/
# - turn:openrelayproject:openrelayproject@openrelay.metered.ca:80
# - turn:openrelayproject:openrelayproject@openrelay.metered.ca:443
# - turn:openrelayproject:openrelayproject@openrelay.metered.ca:443?transport=tcp
# Credentials for STUN/TURN servers configured above.
username: ""
password: ""
# Allow connections to STUNS/TURNS servers for which
# we cant validate their TLS certificates
# Allow connections to STUNS/TURNS servers for which we can not validate TLS certificates.
insecure_skip_verify: false
# Limit available network and candidate types
network_types: [udp4, udp6, tcp4, tcp6]
candidate_types: [host, srflx, prflx ,relay]
# Limit available network and candidate types.
# network_types: [udp4, udp6, tcp4, tcp6]
# candidate_types: [host, srflx, prflx, relay]
# Regular expression whitelist of interfaces which are used to gather ICE candidates.
interface_filter: .*
# A glob(7) pattern to match interfaces against which are used to gather ICE candidates (e.g. \"eth[0-9]\").
interface_filter: "*"
# Lite agents do not perform connectivity check and only provide host candidates.
lite: false
# Attempt to find candidates via mDNS discovery
# Enable local Multicast DNS discovery.
mdns: false
# Sets the max amount of binding requests the agent will send over a candidate pair for validation or nomination.
@@ -116,26 +195,93 @@ peer_disc:
# SetNAT1To1IPs sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used.
# This is useful when you are host a server using Pion on an AWS EC2 instance which has a private address, behind a 1:1 DNAT with a public IP (e.g. Elastic IP).
# In this case, you can give the public IP address so that Pion will use the public IP address in its candidate instead of the private IP address.
nat_1to1_ips: []
# nat_1to1_ips:
# - 10.10.2.3
# Limit the port range used by ICE
port:
port_range:
# Minimum port for allocation policy for ICE sockets (range: 0-65535)
min: 49152
# Maximum port for allocation policy for ICE sockets (range: 0-65535)
max: 65535
# The check interval controls how often our task loop runs when in the connecting state.
# Interval at which the agent performs candidate checks in the connecting phase
check_interval: 200ms
# Time until an Agent transitions disconnected.
# If the duration is 0, the ICE Agent will never go to disconnected
disconnected_timeout: 5s
# Time until an Agent transitions to failed after disconnected
# If the duration is 0, we will never go to failed.
failed_timeout: 5s
# Time to wait before ICE restart
restart_timeout: 5s
# Determines how often should we send ICE keepalives (should be less then connection timeout above).
# A keepalive interval of 0 means we never send keepalive packets
# Interval between STUN keepalives (should be less then connection timeout above).
# Af the interval is 0, we never send keepalive packets
keepalive_interval: 2s
## Interface specific settings / overwrites.
#
# Most of the top-level settings of this configuration file can be overwritten
# with settings specific to a single or a group of interfaces.
# This includes the following settings (see below):
# - wireguard
# - cfgsync
# - rtsync
# - hsync
# - pdisc
# - epdisc
#
# The keys of this mapping are glob(7) patterns which are matched against the
# interface names.
# Settings are overlayed in the order in which the keys are provided in the
# interface map.
#
# Keys which are not a glob(8) pattern, will be created as new interfaces if
# they do not exist already in the system.
interfaces:
#
\*:
cfgsync:
path: /some/special/wireguard/config-dir/
# A simple interface specific setting
# cunicu will set the private key of interface 'wg0' to the provided value.
wg0:
private_key: kODOmlTNhYbF9htW3uYiE1qKuvBnJKd7MFvaookGd14=
# No settings are overwritten. But since this is not a glob pattern,
# A new interface named 'wg1' will be created if it does not exist yet.
# The same applies to the previous interface 'wg0'
wg1: {}
# Create a new interface using the wireguard-go user-space implementation.
wg2:
wireguard:
userspace: true
# This pattern configuration will be applied to all interfaces which match the pattern.
# This rule will not create any new interfaces.
wg-work-*:
pdisc:
community: "mysecret-pass"
epdisc:
ice:
urls:
- turn:mysecret.turn-server.com
# Multiple patterns are supported and evaluated in the order they a defined in the configuration file.
#
wg-work-external-*:
epdisc:
ice:
network_types: [ udp6 ]
```
## Environment Variables
@@ -146,9 +292,13 @@ All the settings from the configuration file can also be passed via environment
- Prefixing the setting name with `CUNICU_`
- Nested settings are separated by underscores
**Example:** The setting `endpoint_disc.ice.max_binding_requests` can be set by the environment variable `CUNICU_ENDPOINT_DISC_ICE_MAX_BINDING_REQUESTS`
**Example:** The setting `epdisc.ice.max_binding_requests` can be set by the environment variable `CUNICU_ENDPOINT_DISC_ICE_MAX_BINDING_REQUESTS`
**Note:** Setting lists such as `endpoint_disc.ice.urls` or `backends` can currently not be set via environment variables.
## At Runtime
cunīcu's configuration can also be updated at runtime, elevating the need to restart the daemon to avoid interruption of connectivity.
Please have a look at the [`cunicu config`](./usage/md/cunicu_config.md) commands.
## DNS Auto-configuration

View File

@@ -1,28 +1,11 @@
# An interval at which cunicu will periodically check for added, removed or modified WireGuard interfaces.
watch_interval: 1s
backends:
- grpc://localhost:8080?insecure=true&skip_verify=true
- k8s:///path/to/your/kubeconfig.yaml?namespace=default
# WireGuard settings
wireguard:
# Create WireGuard interfaces using bundled wireguard-go Userspace implementation
# This will be the default if there is no WireGuard kernel module present.
userspace: false
# Ignore WireGuard interface which do not match this regular expression
interface_filter: .*
# A list of WireGuard interfaces which should be configured
interfaces:
- wg-vpn
# Port range for ListenPort setting of newly created WireGuard interfaces
# cunicu will select the first available port in this range.
port:
min: 52820
max: 65535
# RPC control socket settings
rpc:
@@ -32,34 +15,10 @@ rpc:
# Mostly useful for testing automation
wait: false
# Synchronize local WireGuard interface configuration with wg(8) config-files.
config_sync:
enabled: false
# Directory where Wireguard configuration files are located.
# We expect the same format as used by wg(8) and wg-quick(8).
# Filenames must match the interface name with a '.conf' suffix.
path: /etc/wireguard
# Watch the configuration files via inotify(7) for changes and apply them accordingly.
watch: false
# Synchronize the kernel routing table with WireGuard's AllowedIPs setting
#
# It checks for routes in the kernel routing table which have a peers link-local address
# as next-hop and adds those routes to the AllowedIPs setting of the respective peer.
## Hook callbacks
#
# In reverse, also networks listed in a peers AllowedIPs setting will be installed as a
# kernel route with the peers link-local address as the routes next-hop.
route_sync:
enabled: true
table: 254 # See /etc/iproute2/rt_tables for table ids
# Keep watching the for changes in the kernel routing table via netlink multicast group.
watch: true
# Pass events to external processes or web hook handlers
# Hook callback can be used to invoke subprocesses or web-hooks on certain events within cunicu.
hooks:
- type: exec
command: ../../scripts/hook.sh
@@ -87,11 +46,73 @@ hooks:
User-Agent: ahoi
Authorization: Bearer XXXXXX
# Discovery of other WireGuard peers
peer_disc:
#### Interface settings start here
# The following settings can be overwritten for each interface
# using the 'interfaces' settings (see below).
# The following settings will be used as default.
# WireGuard settings
wireguard:
# Create WireGuard interfaces using bundled wireguard-go Userspace implementation
# This will be the default if there is no WireGuard kernel module present.
userspace: false
# Port range for ListenPort setting of newly created WireGuard interfaces
# cunicu will select the first available port in this range.
listen_port_range:
min: 52820
max: 65535
## Config file synchronization
#
# Synchronize local WireGuard interface configuration with wg(8) config-files.
cfgsync:
enabled: false
# Directory where Wireguard configuration files are located.
# We expect the same format as used by wg(8) and wg-quick(8).
# Filenames must match the interface name with a '.conf' suffix.
path: /etc/wireguard
# Watch the configuration files via inotify(7) for changes and apply them accordingly.
watch: false
## Route Synchronization
#
# Synchronize the kernel routing table with WireGuard's AllowedIPs setting
#
# It checks for routes in the kernel routing table which have a peers link-local address
# as next-hop and adds those routes to the AllowedIPs setting of the respective peer.
#
# In reverse, also networks listed in a peers AllowedIPs setting will be installed as a
# kernel route with the peers link-local address as the routes next-hop.
rtsync:
enabled: true
table: 254 # See /etc/iproute2/rt_tables for table ids
# Keep watching the for changes in the kernel routing table via netlink multicast group.
watch: true
## /etc/hosts synchronization
#
# Synchronizes the local /etc/hosts file with host names and link-local IP addresses of connected peers.
hsync:
enabled: true
## Peer discovery
#
# Peer discovery finds new peers within the same community and adds them to the respective interface
pdisc:
enabled: true
# A list of WireGuard public keys which are accepted peers
# If not configured, all peers will be accepted.
whitelist:
- coNsGPwVPdpahc8U+dbbWGzTAdCd6+1BvPIYg10wDCI=
- AOZzBaNsoV7P8vo0D5UmuIJUQ7AjMbHbGt2EA8eAuEc=
@@ -99,15 +120,35 @@ peer_disc:
# A passphrase shared among all peers of the same community
community: "some-common-password"
# Discovery of WireGuard endpoint addresses
endpoint_disc:
## Endpoint discovery
#
# Endpoint discovery uses Interactive Connectivity Establishment (ICE) as used by WebRTC to
# gather a list of candidate endpoints and performs connectivity checks to find a suitable
# endpoint address which can be used by WireGuard
epdisc:
enabled: true
# Interactive Connectivity Establishment (ICE) parameters
ice:
# A list of STUN and TURN servers used by ICE.
urls:
- stun:stun.l.google.com:19302
# Community provided STUN/TURN servers
- grpc://relay.cunicu.li
# Public STUN servers
- stun:stun3.l.google.com:19302
- stun:relay.webwormhole.io
- stun:stun.sipgate.net
- stun:stun.ekiga.net
- stun:stun.services.mozilla.com
# Caution: OpenRelay servers are located in Ontario, Canada.
# Beware of the latency!
# See also: https://www.metered.ca/tools/openrelay/
# - turn:openrelayproject:openrelayproject@openrelay.metered.ca:80
# - turn:openrelayproject:openrelayproject@openrelay.metered.ca:443
# - turn:openrelayproject:openrelayproject@openrelay.metered.ca:443?transport=tcp
# Credentials for STUN/TURN servers configured above.
username: ""
@@ -117,11 +158,11 @@ endpoint_disc:
insecure_skip_verify: false
# Limit available network and candidate types.
network_types: [udp4, udp6, tcp4, tcp6]
candidate_types: [host, srflx, prflx ,relay]
# network_types: [udp4, udp6, tcp4, tcp6]
# candidate_types: [host, srflx, prflx, relay]
# Regular expression whitelist of interfaces which are used to gather ICE candidates (e.g. \"eth[0-9]+\").
interface_filter: .*
# A glob(7) pattern to match interfaces against which are used to gather ICE candidates (e.g. \"eth[0-9]\").
interface_filter: "*"
# Lite agents do not perform connectivity check and only provide host candidates.
lite: false
@@ -136,10 +177,11 @@ endpoint_disc:
# SetNAT1To1IPs sets a list of external IP addresses of 1:1 (D)NAT and a candidate type for which the external IP address is used.
# This is useful when you are host a server using Pion on an AWS EC2 instance which has a private address, behind a 1:1 DNAT with a public IP (e.g. Elastic IP).
# In this case, you can give the public IP address so that Pion will use the public IP address in its candidate instead of the private IP address.
nat_1to1_ips: []
# nat_1to1_ips:
# - 10.10.2.3
# Limit the port range used by ICE
port:
port_range:
# Minimum port for allocation policy for ICE sockets (range: 0-65535)
min: 49152
@@ -163,3 +205,62 @@ endpoint_disc:
# Interval between STUN keepalives (should be less then connection timeout above).
# Af the interval is 0, we never send keepalive packets
keepalive_interval: 2s
## Interface specific settings / overwrites.
#
# Most of the top-level settings of this configuration file can be overwritten
# with settings specific to a single or a group of interfaces.
# This includes the following settings (see below):
# - wireguard
# - cfgsync
# - rtsync
# - hsync
# - pdisc
# - epdisc
#
# The keys of this mapping are glob(7) patterns which are matched against the
# interface names.
# Settings are overlayed in the order in which the keys are provided in the
# interface map.
#
# Keys which are not a glob(8) pattern, will be created as new interfaces if
# they do not exist already in the system.
interfaces:
#
\*:
cfgsync:
path: /some/special/wireguard/config-dir/
# A simple interface specific setting
# cunicu will set the private key of interface 'wg0' to the provided value.
wg0:
private_key: kODOmlTNhYbF9htW3uYiE1qKuvBnJKd7MFvaookGd14=
# No settings are overwritten. But since this is not a glob pattern,
# A new interface named 'wg1' will be created if it does not exist yet.
# The same applies to the previous interface 'wg0'
wg1: {}
# Create a new interface using the wireguard-go user-space implementation.
wg2:
wireguard:
userspace: true
# This pattern configuration will be applied to all interfaces which match the pattern.
# This rule will not create any new interfaces.
wg-work-*:
pdisc:
community: "mysecret-pass"
epdisc:
ice:
urls:
- turn:mysecret.turn-server.com
# Multiple patterns are supported and evaluated in the order they a defined in the configuration file.
#
wg-work-external-*:
epdisc:
ice:
network_types: [ udp6 ]

14
go.mod
View File

@@ -9,7 +9,9 @@ require (
github.com/go-logr/logr v1.2.3
github.com/go-logr/zapr v1.2.3
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
github.com/imdario/mergo v0.3.13
github.com/jpillora/backoff v1.0.0
github.com/knadh/koanf v1.4.3
github.com/mitchellh/mapstructure v1.5.0
github.com/pion/ice/v2 v2.2.7
github.com/pion/logging v0.2.2
@@ -17,7 +19,6 @@ require (
github.com/pion/stun v0.3.5
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.12.0
github.com/vishvananda/netlink v1.2.1-beta.2
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.23.0
@@ -51,6 +52,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/frankban/quicktest v1.14.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
@@ -61,23 +63,21 @@ require (
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/genetlink v1.2.0 // indirect
github.com/mdlayher/netlink v1.6.0 // indirect
github.com/mdlayher/socket v0.2.3 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pion/dtls/v2 v2.1.5 // indirect
github.com/pion/mdns v0.0.5 // indirect
github.com/pion/transport v0.13.1 // indirect
@@ -86,10 +86,6 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect

511
go.sum
View File

@@ -1,92 +1,88 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -104,26 +100,19 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -135,18 +124,15 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/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.4/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=
@@ -156,68 +142,102 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/nftables v0.0.0-20220808154552-2eca00135732 h1:csc7dT82JiSLvq4aMyQMIQDL7986NH6Wxf/QrvOj55A=
github.com/google/nftables v0.0.0-20220808154552-2eca00135732/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gopacket/gopacket v0.0.0-20220825100013-b6a42f7f20c5 h1:zIUV6OM0TmAVJEgaJ0e81ZVWaKgk9aDa04xW9MOb3UI=
github.com/gopacket/gopacket v0.0.0-20220825100013-b6a42f7f20c5/go.mod h1:DlRRfaM/QjAu2ADqraIure1Eif0HpNL8hmyVQ+qci5Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
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/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/knadh/koanf v1.4.3 h1:rSJcSH5LSFhvzBRsAYfT3k7eLP0I4UxeZqjtAatk+wc=
github.com/knadh/koanf v1.4.3/go.mod h1:5FAkuykKXZvLqhAbP4peWgM5CTcZmn7L1d27k/a+kfg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
@@ -227,28 +247,52 @@ github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5A
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
github.com/pion/ice/v2 v2.2.7 h1:kG9tux3WdYUSqqqnf+O5zKlpy41PdlvLUBlYJeV2emQ=
@@ -269,40 +313,56 @@ github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -311,11 +371,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stv0g/gont v1.6.3 h1:7cIU2+F7iaGOHN9Mu2QKlKqZ4Dzk98Fu/P9gUPtjNFc=
github.com/stv0g/gont v1.6.3/go.mod h1:Cl7BLHOE7YhWCPURgG0mvYBQBGGaNNQJeXSWNw+DJXs=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
@@ -324,17 +381,12 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
@@ -344,94 +396,53 @@ go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -445,13 +456,7 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWI
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -460,54 +465,45 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -522,72 +518,29 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -602,91 +555,28 @@ golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2s
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b h1:9JncmKXcUwE918my+H6xmjBdhK2jM/UTUNXxhRG1BAk=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b/go.mod h1:yp4gl6zOlnDGOZeWeDfMwQcsdOIQnMdhuPx9mwwWBL4=
gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI=
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
@@ -698,13 +588,14 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -714,10 +605,15 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -727,12 +623,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0=
k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk=
k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY=
@@ -753,14 +644,12 @@ kernel.org/pub/linux/libs/security/libcap/cap v1.2.65 h1:gIDtZoGnKeoIZ7XaKRmljMi
kernel.org/pub/linux/libs/security/libcap/cap v1.2.65/go.mod h1:Fp9fDSuNh1vVDA7sozXklfQ+LxXMpB5/H4hDR8eu+0s=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.65 h1:v2G3aCgEMr8qh4GpOGMukkv92EE7jtY+Uh9mB7cAACk=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.65/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/controller-runtime v0.12.3 h1:FCM8xeY/FI8hoAfh/V4XbbYMY20gElh9yh+A98usMio=
sigs.k8s.io/controller-runtime v0.12.3/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

View File

@@ -1,30 +1,116 @@
package config
import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/pion/ice/v2"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"github.com/stv0g/cunicu/pkg/crypto"
signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling"
)
func (c *Config) AgentConfig() (*ice.AgentConfig, error) {
func (c *InterfaceSettings) AgentURLs(ctx context.Context, pk *crypto.Key) ([]*ice.URL, error) {
iceURLs := []*ice.URL{}
g := errgroup.Group{}
for _, u := range c.EndpointDisc.ICE.URLs {
switch u.Scheme {
case "stun", "stuns", "turn", "turns":
// Extract credentials from URL
// Warning: This is not standardized in RFCs 7064/7065
var creds string
if parts := strings.Split(u.Opaque, "@"); len(parts) > 1 {
creds = parts[0]
u.Opaque = parts[1]
}
iu, err := ice.ParseURL(u.String())
if err != nil {
return nil, fmt.Errorf("failed to parse STUN/TURN URL '%s': %w", u.String(), err)
}
if creds != "" {
parts := strings.Split(creds, ":")
if len(parts) != 2 {
return nil, errors.New("invalid user / password")
}
iu.Username = parts[0]
iu.Password = parts[1]
} else {
iu.Username = c.EndpointDisc.ICE.Username
iu.Password = c.EndpointDisc.ICE.Password
}
iceURLs = append(iceURLs, iu)
case "grpc":
g.Go(func() error {
conn, err := grpc.DialContext(ctx, u.String())
if err != nil {
return fmt.Errorf("failed to connect to gRPC server: %w", err)
}
defer conn.Close()
client := signalingproto.NewTurnServerRegistryClient(conn)
resp, err := client.GetTurnServers(ctx, &signalingproto.GetTurnServersParams{
PublicKey: pk.Bytes(),
})
if err != nil {
return fmt.Errorf("received error from gRPC server: %w", err)
}
for _, svr := range resp.Servers {
u, err := ice.ParseURL(u.String())
if err != nil {
return fmt.Errorf("failed to parse STUN/TURN URL: %w", err)
}
u.Username = svr.Username
u.Password = svr.Password
iceURLs = append(iceURLs, u)
}
return nil
})
default:
return nil, fmt.Errorf("invalid ICE URL scheme: %s", u.Scheme)
}
}
return iceURLs, g.Wait()
}
func (c *InterfaceSettings) AgentConfig(ctx context.Context, peer *crypto.Key) (*ice.AgentConfig, error) {
var err error
cfg := &ice.AgentConfig{
InsecureSkipVerify: c.EndpointDisc.ICE.InsecureSkipVerify,
Lite: c.EndpointDisc.ICE.Lite,
PortMin: uint16(c.EndpointDisc.ICE.Port.Min),
PortMax: uint16(c.EndpointDisc.ICE.Port.Max),
PortMin: uint16(c.EndpointDisc.ICE.PortRange.Min),
PortMax: uint16(c.EndpointDisc.ICE.PortRange.Max),
}
cfg.InterfaceFilter = func(name string) bool {
return c.EndpointDisc.ICE.InterfaceFilter.MatchString(name)
match, err := filepath.Match(c.EndpointDisc.ICE.InterfaceFilter, name)
return err == nil && match
}
// ICE URLs
cfg.Urls = []*ice.URL{}
for _, u := range c.EndpointDisc.ICE.URLs {
p := u.URL
p.Username = c.EndpointDisc.ICE.Username
p.Password = c.EndpointDisc.ICE.Password
cfg.Urls = append(cfg.Urls, &p)
if cfg.Urls, err = c.AgentURLs(ctx, peer); err != nil {
return nil, fmt.Errorf("failed to gather ICE URLs: %w", err)
}
if len(c.EndpointDisc.ICE.NAT1to1IPs) > 0 {

View File

@@ -1,6 +1,8 @@
package config_test
import (
"context"
"github.com/pion/ice/v2"
"github.com/stv0g/cunicu/pkg/config"
@@ -9,110 +11,111 @@ import (
)
var _ = Describe("Agent config", func() {
Context("ICE urls", func() {
It("can parse ICE urls with credentials", func() {
DescribeTable("can parse ICE urls with credentials",
func(urlStr string, exp any) {
cfg, err := config.ParseArgs(
"--url", "stun:server1",
"--url", "turn:server2:1234?transport=tcp",
"--url", urlStr,
"--username", "user1",
"--password", "pass1",
)
Expect(err).To(Succeed())
aCfg, err := cfg.AgentConfig()
Expect(err).To(Succeed())
icfg := cfg.DefaultInterfaceSettings
Expect(aCfg.Urls).To(Equal([]*ice.URL{
{
Scheme: ice.SchemeTypeSTUN,
Host: "server1",
Port: 3478,
Proto: ice.ProtoTypeUDP,
Username: "user1",
Password: "pass1",
},
{
Scheme: ice.SchemeTypeTURN,
Host: "server2",
Port: 1234,
Proto: ice.ProtoTypeTCP,
Username: "user1",
Password: "pass1",
},
}))
})
aCfg, err := icfg.AgentConfig(context.Background(), nil)
switch exp := exp.(type) {
case string:
Expect(err).To(MatchError(exp))
case *ice.URL:
Expect(err).To(Succeed())
Expect(aCfg.Urls).To(ContainElements(exp))
}
},
Entry("url1", "stun:server1", &ice.URL{
Scheme: ice.SchemeTypeSTUN,
Host: "server1",
Port: 3478,
Proto: ice.ProtoTypeUDP,
Username: "user1",
Password: "pass1",
}),
Entry("url2", "turn:server2:1234?transport=tcp", &ice.URL{
Scheme: ice.SchemeTypeTURN,
Host: "server2",
Port: 1234,
Proto: ice.ProtoTypeTCP,
Username: "user1",
Password: "pass1",
}),
Entry("url3", "turn:user3:pass3@server3:1234?transport=tcp", &ice.URL{
Scheme: ice.SchemeTypeTURN,
Host: "server3",
Port: 1234,
Proto: ice.ProtoTypeTCP,
Username: "user3",
Password: "pass3",
}),
Entry("url4", "turn:user3@server3:1234?transport=tcp", "failed to gather ICE URLs: invalid user / password"),
Entry("url5", "http://bla.0l.de", "failed to gather ICE URLs: invalid ICE URL scheme: http"),
Entry("url6", "stun:stun.cunicu.li?transport=tcp", "failed to gather ICE URLs: failed to parse STUN/TURN URL 'stun:stun.cunicu.li?transport=tcp': queries not supported in stun address"),
)
It("can parse relay api ICE urls", Pending, func() {
// TODO
})
Context("Candidate types", func() {
It("can parse multiple candidate types", func() {
cfg, err := config.ParseArgs(
"--ice-candidate-type", "host",
"--ice-candidate-type", "relay",
)
Expect(err).To(Succeed())
It("can parse multiple candidate types", func() {
cfg, err := config.ParseArgs(
"--ice-candidate-type", "host",
"--ice-candidate-type", "relay",
)
Expect(err).To(Succeed())
aCfg, err := cfg.AgentConfig()
Expect(err).To(Succeed())
Expect(aCfg.CandidateTypes).To(ConsistOf(ice.CandidateTypeRelay, ice.CandidateTypeHost))
})
icfg := cfg.DefaultInterfaceSettings
aCfg, err := icfg.AgentConfig(context.Background(), nil)
Expect(err).To(Succeed())
Expect(aCfg.CandidateTypes).To(ConsistOf(ice.CandidateTypeRelay, ice.CandidateTypeHost))
})
Context("Network types", func() {
It("can parse multiple network types", func() {
cfg, err := config.ParseArgs(
"--ice-network-type", "udp4",
"--ice-network-type", "tcp6",
)
Expect(err).To(Succeed())
It("can parse multiple network types when passed as individual command line arguments", func() {
cfg, err := config.ParseArgs(
"--ice-network-type", "udp4",
"--ice-network-type", "tcp6",
)
Expect(err).To(Succeed())
aCfg, err := cfg.AgentConfig()
Expect(err).To(Succeed())
Expect(aCfg.NetworkTypes).To(ConsistOf(ice.NetworkTypeTCP6, ice.NetworkTypeUDP4))
})
icfg := cfg.DefaultInterfaceSettings
aCfg, err := icfg.AgentConfig(context.Background(), nil)
Expect(err).To(Succeed())
Expect(aCfg.NetworkTypes).To(ConsistOf(ice.NetworkTypeTCP6, ice.NetworkTypeUDP4))
})
Context("Comma-separated network types", func() {
It("can parse multiple network types", func() {
cfg, err := config.ParseArgs(
"--ice-network-type", "udp4,tcp6",
)
Expect(err).To(Succeed())
It("can parse multiple network types when passed as comma-separated value", func() {
cfg, err := config.ParseArgs("--ice-network-type", "udp4,tcp6")
Expect(err).To(Succeed())
aCfg, err := cfg.AgentConfig()
Expect(err).To(Succeed())
Expect(aCfg.NetworkTypes).To(ConsistOf(ice.NetworkTypeTCP6, ice.NetworkTypeUDP4))
})
icfg := cfg.DefaultInterfaceSettings
aCfg, err := icfg.AgentConfig(context.Background(), nil)
Expect(err).To(Succeed())
Expect(aCfg.NetworkTypes).To(ConsistOf(ice.NetworkTypeTCP6, ice.NetworkTypeUDP4))
})
Context("Interface filter", func() {
It("can parse an interface filter", func() {
cfg, err := config.ParseArgs("--wg-interface-filter", "wg\\d+")
Expect(err).To(Succeed())
It("has proper default values", func() {
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
Expect(cfg.WireGuard.InterfaceFilter.MatchString("wg0")).To(BeTrue())
Expect(cfg.WireGuard.InterfaceFilter.MatchString("et0")).To(BeFalse())
})
})
icfg := cfg.DefaultInterfaceSettings
Context("Interface filter with invalid regex", func() {
It("can parse an interface filter", func() {
_, err := config.ParseArgs("--wg-interface-filter", "eth(")
Expect(err).To(HaveOccurred())
})
})
aCfg, err := icfg.AgentConfig(context.Background(), nil)
Expect(err).To(Succeed())
Context("default values", func() {
It("has default values", func() {
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
Expect(aCfg.InterfaceFilter("wg1")).To(BeTrue())
Expect(cfg.WireGuard.InterfaceFilter.MatchString("wg1234")).To(BeTrue())
aCfg, err := cfg.AgentConfig()
Expect(err).To(Succeed())
Expect(aCfg.Urls).To(HaveLen(1))
Expect(aCfg.Urls[0].String()).To(Equal(config.DefaultURL))
})
Expect(aCfg.Urls).To(HaveLen(1))
Expect(aCfg.Urls[0].String()).To(Equal(config.DefaultICEURLs[0].String()))
})
})

View File

@@ -2,86 +2,116 @@
package config
import (
"errors"
"fmt"
"mime"
"net"
"net/http"
"io"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/stv0g/cunicu/pkg/util/buildinfo"
"go.uber.org/zap"
"github.com/imdario/mergo"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/posflag"
"github.com/mitchellh/mapstructure"
"github.com/pion/ice/v2"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.uber.org/zap"
)
var (
envPrefix = "CUNICU_"
// Map flags from the flags to to Koanf settings
flagMap = map[string]string{
// Config sync
"cfgsync": "cfgsync.enabled",
"cfgsync-path": "cfgsync.path",
"cfgsync-watch": "cfgsync.watch",
// Host sync
"hsync": "hsync.enabled",
// Route sync
"rtsync": "rtsync.enabled",
"rtsync-table": "rtsync.table",
"backend": "backends",
"watch-interval": "watch_interval",
// Socket
"rpc-socket": "rpc.socket",
"rpc-wait": "rpc.wait",
// WireGuard
"wg-userspace": "wireguard.userspace",
// Endpoint discovery
"epdisc": "epdisc.enabled",
"url": "epdisc.ice.urls",
"username": "epdisc.ice.username",
"password": "epdisc.ice.password",
"ice-candidate-type": "epdisc.ice.candidate_types",
"ice-network-type": "epdisc.ice.network_types",
// Peer discovery
"pdisc": "pdisc.enabled",
"community": "pdisc.community",
}
)
type Config struct {
Settings
*viper.Viper
ConfigFiles []string
Domain string
*Meta
*koanf.Koanf
Runtime *koanf.Koanf
// Settings which are not configurable via configuration file
Files []string
Domains []string
DefaultInterfaceFilter string
InterfaceOrder []string
mu sync.Mutex
flags *pflag.FlagSet
logger *zap.Logger
}
func init() {
mtm := map[string][]string{
"text/yaml": {".yaml", ".yml"},
"text/x-yaml": {".yaml", ".yml"},
"application/toml": {".toml"},
"text/x-ini": {".env", ".ini"},
"text/x-java-properties": {".props"},
}
for typ, exts := range mtm {
for _, ext := range exts {
if err := mime.AddExtensionType(ext, typ); err != nil {
panic(err)
}
}
}
}
// ParseArgs creates a new configuration instance and loads all configuration
//
// Only used for testing.
func ParseArgs(args ...string) (*Config, error) {
c := NewConfig(nil)
c := New(nil)
if err := c.flags.Parse(args); err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse command line flags: %w", err)
}
if err := c.Setup(c.flags.Args()); err != nil {
return nil, err
}
return c, nil
return c, c.Load()
}
func NewConfig(flags *pflag.FlagSet) *Config {
// New creates a new configuration instance.
func New(flags *pflag.FlagSet) *Config {
if flags == nil {
flags = pflag.NewFlagSet("", pflag.ContinueOnError)
}
c := &Config{
Viper: viper.New(),
Koanf: koanf.NewWithConf(koanf.Conf{
Delim: ".",
}),
Runtime: koanf.NewWithConf(koanf.Conf{
Delim: ".",
}),
Meta: Metadata(),
flags: flags,
ConfigFiles: []string{},
}
// Defaults
c.SetDefaults()
// Feature flags
flags.BoolP("host-sync", "H", true, "Enable synchronization of /etc/hosts file")
flags.BoolP("config-sync", "S", true, "Enable synchronization of WireGuard configuration files")
@@ -90,8 +120,8 @@ func NewConfig(flags *pflag.FlagSet) *Config {
flags.BoolP("auto-config", "A", true, "Enable setup of link-local addresses and missing interface options")
// Config flags
flags.StringVarP(&c.Domain, "domain", "D", "", "A DNS `domain` name used for DNS auto-configuration")
flags.StringSliceVarP(&c.ConfigFiles, "config", "c", []string{}, "One or more `filename`s of configuration files")
flags.StringSliceVarP(&c.Domains, "domain", "D", []string{}, "A DNS `domain` name used for DNS auto-configuration")
flags.StringSliceVarP(&c.Files, "config", "c", []string{}, "One or more `filename`s of configuration files")
// Daemon flags
flags.StringSliceP("backend", "b", []string{}, "One or more `URL`s to signaling backends")
@@ -102,15 +132,15 @@ func NewConfig(flags *pflag.FlagSet) *Config {
flags.Bool("rpc-wait", false, "Wait until first client connected to control socket before continuing start")
// WireGuard
flags.StringP("wg-interface-filter", "f", ".*", "A `regex` for filtering WireGuard interfaces (e.g. \"wg-.*\")")
flags.BoolP("wg-userspace", "u", false, "Create new interfaces with userspace WireGuard implementation")
flags.StringVarP(&c.DefaultInterfaceFilter, "interface-filter", "f", "*", "A glob(7) `pattern` for filtering WireGuard interfaces which this daemon will manage (e.g. \"wg*\")")
flags.BoolP("wg-userspace", "u", false, "Use user-space WireGuard implementation for newly created interfaces")
// Config sync
flags.StringP("config-path", "w", "", "The `directory` of WireGuard wg/wg-quick configuration files")
flags.BoolP("config-watch", "W", false, "Watch and synchronize changes to the WireGuard configuration files")
// Route sync
flags.StringP("route-table", "T", "main", "Kernel routing table to use")
flags.IntP("route-table", "T", DefaultRouteTable, "Kernel routing table to use")
// Endpoint discovery
flags.StringSliceP("url", "a", []string{}, "One or more `URL`s of STUN and/or TURN servers")
@@ -123,113 +153,130 @@ func NewConfig(flags *pflag.FlagSet) *Config {
// Peer discovery
flags.StringP("community", "x", "", "A `passphrase` shared with other peers in the same community")
flagMap := map[string]string{
// Config sync
"config-sync": "config_sync.enabled",
"config-path": "config_sync.path",
"config-watch": "config_sync.watch",
// Host sync
"host-sync": "host_sync.enabled",
// Route sync
"route-sync": "route_sync.enabled",
"route-table": "route_sync.table",
"backend": "backends",
"watch-interval": "watch_interval",
// Socket
"rpc-socket": "rpc.socket",
"rpc-wait": "rpc.wait",
// WireGuard
"wg-userspace": "wireguard.userspace",
"wg-interface-filter": "wireguard.interface_filter",
// Endpoint discovery
"endpoint-disc": "endpoint_disc.enabled",
"url": "endpoint_disc.ice.urls",
"username": "endpoint_disc.ice.username",
"password": "endpoint_disc.ice.password",
"ice-candidate-type": "endpoint_disc.ice.candidate_types",
"ice-network-type": "endpoint_disc.ice.network_types",
// Peer discovery
"peer-disc": "peer_disc.enabled",
"community": "peer_disc.community",
}
flags.VisitAll(func(flag *pflag.Flag) {
if newName, ok := flagMap[flag.Name]; ok {
if err := c.BindPFlag(newName, flag); err != nil {
panic(err)
}
}
})
return c
}
func (c *Config) Setup(args []string) error {
// Load loads configuration settings from various sources
//
// Settings are loaded in the following order where the later overwrite the previous settings:
// - defaults
// - dns lookups
// - configuration files
// - environment variables
// - command line flags
func (c *Config) Load() error {
// We cant to this in NewConfig since its called by init()
// at which time the logging system is not initialized yet.
c.logger = zap.L().Named("config")
// First lookup settings via DNS
if c.Domain != "" {
if err := c.Lookup(c.Domain); err != nil {
// Load default settings
if err := c.Koanf.Load(ConfMapProvider(&DefaultSettings), nil); err != nil {
return fmt.Errorf("failed to load default settings: %w", err)
}
c.InterfaceOrder = []string{c.DefaultInterfaceFilter}
// Load settings from DNS lookups
for _, domain := range c.Domains {
p := LookupProvider(domain)
if err := c.Koanf.Load(p, nil); err != nil {
return fmt.Errorf("DNS auto-configuration failed: %w", err)
}
c.Files = append(c.Files, p.Files...)
}
if len(c.ConfigFiles) > 0 {
// Merge config files from the flags.
for _, file := range c.ConfigFiles {
if u, err := url.Parse(file); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
if err := c.MergeRemoteConfig(u); err != nil {
return fmt.Errorf("failed to load remote config: %w", err)
}
// Search for config files
if len(c.Files) == 0 {
searchPaths := []string{"/etc", "/etc/cunicu"}
if homeDir := os.Getenv("HOME"); homeDir != "" {
searchPaths = append(searchPaths,
filepath.Join(homeDir, ".config"),
filepath.Join(homeDir, ".config", "cunicu"),
)
}
c.logger.Debug("Using remote configuration file", zap.Any("url", u))
} else {
c.SetConfigFile(file)
if err := c.MergeInConfig(); err != nil {
return fmt.Errorf("failed to merge configurations: %w", err)
}
c.logger.Debug("Using configuration file", zap.String("file", c.ConfigFileUsed()))
for _, path := range append(searchPaths, ".") {
fn := filepath.Join(path, "cunicu.yaml")
if fi, err := os.Stat(fn); err == nil && !fi.IsDir() {
c.Files = append(c.Files, fn)
}
}
} else {
c.AddConfigPath("/etc")
c.AddConfigPath(filepath.Join("$HOME", ".config"))
c.AddConfigPath(".")
c.SetConfigName("cunicu.yaml")
c.SetConfigType("yaml")
if err := c.MergeInConfig(); err == nil {
c.logger.Debug("Using configuration file", zap.String("file", c.ConfigFileUsed()))
} else if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return fmt.Errorf("failed to merge configurations: %w", err)
c.logger.Debug("Search paths", zap.Strings("files", searchPaths))
}
// Load config files
for _, f := range c.Files {
u, err := url.Parse(f)
if err != nil {
return fmt.Errorf("failed to load config file: invalid URL: %w", err)
}
p := YAMLFileProvider(u)
if err := c.Koanf.Load(p, nil); err != nil {
return fmt.Errorf("failed to load config file: %w", err)
}
c.InterfaceOrder = append(c.InterfaceOrder, p.InterfaceOrder...)
}
c.SetEnvPrefix("cunicu")
c.AutomaticEnv()
c.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if len(args) > 0 {
c.Set("wireguard.interfaces", args)
// Load environment variables
envKeyMap := map[string]string{}
for _, k := range c.Meta.Keys() {
m := strings.ToUpper(k)
e := envPrefix + strings.ReplaceAll(m, ".", "_")
envKeyMap[e] = k
}
return c.Load()
c.Koanf.Load(env.ProviderWithValue(envPrefix, ".", func(e, v string) (string, any) {
k := envKeyMap[e]
if p := strings.Split(v, ","); len(p) > 1 {
return k, p
}
return k, v
}), nil)
c.Koanf.Load(posflag.ProviderWithFlag(c.flags, ".", c.Koanf, func(f *pflag.Flag) (string, any) {
setting, ok := flagMap[f.Name]
if !ok {
return "", nil
}
return setting, posflag.FlagVal(c.flags, f)
}), nil)
intfs := map[string]any{}
// Default settings
if c.DefaultInterfaceFilter != "" && (len(c.flags.Args()) == 0 || c.DefaultInterfaceFilter != "*") {
k := fmt.Sprintf("interfaces.%s", c.DefaultInterfaceFilter)
intfs[k] = Map(DefaultInterfaceSettings)
}
// Add interfaces from command line
for _, i := range c.flags.Args() {
k := fmt.Sprintf("interfaces.%s", i)
intfs[k] = map[string]any{}
}
// Load interfaces
if err := c.Koanf.Load(confmap.Provider(intfs, "."), nil); err != nil {
return fmt.Errorf("failed to load: %w", err)
}
c.logger.Debug("Interface order", zap.Strings("order", c.InterfaceOrder))
return c.Unmarshal()
}
// Check performs plausibility checks on the provided configuration.
func (c *Config) Check() error {
if len(c.EndpointDisc.ICE.URLs) > 0 && len(c.EndpointDisc.ICE.CandidateTypes) > 0 {
if len(c.DefaultInterfaceSettings.EndpointDisc.ICE.URLs) > 0 && len(c.DefaultInterfaceSettings.EndpointDisc.ICE.CandidateTypes) > 0 {
needsURL := false
for _, ct := range c.EndpointDisc.ICE.CandidateTypes {
for _, ct := range c.DefaultInterfaceSettings.EndpointDisc.ICE.CandidateTypes {
if ct.CandidateType == ice.CandidateTypeRelay || ct.CandidateType == ice.CandidateTypeServerReflexive {
needsURL = true
}
@@ -237,132 +284,155 @@ func (c *Config) Check() error {
if !needsURL {
c.logger.Warn("Ignoring supplied ICE URLs as there are no selected candidate types which would use them")
c.EndpointDisc.ICE.URLs = nil
c.DefaultInterfaceSettings.EndpointDisc.ICE.URLs = nil
}
}
if c.DefaultInterfaceSettings.WireGuard.ListenPortRange.Min > c.DefaultInterfaceSettings.WireGuard.ListenPortRange.Max {
return fmt.Errorf("invalid settings: WireGuard minimal listen port (%d) must be smaller or equal than maximal port (%d)",
c.DefaultInterfaceSettings.WireGuard.ListenPortRange.Min,
c.DefaultInterfaceSettings.WireGuard.ListenPortRange.Max,
)
}
return nil
}
func (c *Config) MergeRemoteConfig(url *url.URL) error {
if url.Scheme != "https" {
host, _, err := net.SplitHostPort(url.Host)
if err != nil {
return fmt.Errorf("failed to split host:port: %w", err)
} else if host != "localhost" && host != "127.0.0.1" && host != "::1" && host != "[::1]" {
return errors.New("remote configuration must be provided via HTTPS")
}
}
client := &http.Client{
Timeout: 5 * time.Second,
}
req := &http.Request{
Method: "GET",
URL: url,
Header: http.Header{},
}
req.Header.Set("User-Agent", buildinfo.UserAgent())
resp, err := client.Do(req)
// Update sets multiple settings in the provided map.
// See also Set().
func (c *Config) Update(sets map[string]any) error {
err := c.Runtime.Load(confmap.Provider(sets, "."), nil)
if err != nil {
return fmt.Errorf("failed to fetch %s: %w", url, err)
} else if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to fetch: %s: %s", url, resp.Status)
}
contentType := resp.Header.Get("Content-type")
fileExtension := filepath.Ext(url.Path)
if contentType != "" {
if types, err := mime.ExtensionsByType(contentType); err == nil && types != nil && len(types) > 0 {
fileExtension = types[0][1:] // strip leading dot
}
}
if fileExtension == "" {
return fmt.Errorf("failed to load remote configuration file: failed to determine file-type by mime-type or filename suffix")
}
c.SetConfigType(fileExtension)
return c.MergeConfig(resp.Body)
}
func decodeOption(cfg *mapstructure.DecoderConfig) {
cfg.DecodeHook = mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapstructure.TextUnmarshallerHookFunc(),
hookDecodeHook,
)
cfg.ZeroFields = false
cfg.TagName = "yaml"
}
func (c *Config) Load() error {
if err := c.UnmarshalExact(&c.Settings, decodeOption); err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
c.mu.Lock()
err = c.Koanf.Merge(c.Runtime)
c.mu.Unlock()
if err != nil {
return fmt.Errorf("failed to merge config: %w", err)
}
if err := c.Unmarshal(); err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
return nil
}
// Set sets a single setting to the provided value
// The key should provided in its dot-delimited form.
func (c *Config) Set(key string, value any) error {
return c.Update(map[string]any{key: value})
}
// MarshalRuntime writes the runtime configuration in YAML format to the provided writer.
func (c *Config) MarshalRuntime(wr io.Writer) error {
out, err := c.Runtime.Marshal(yaml.Parser())
if err != nil {
return fmt.Errorf("failed to encode config: %w", err)
}
if _, err := wr.Write(out); err != nil {
return fmt.Errorf("failed to write: %w", err)
}
return nil
}
// Marshal writes the configuration in YAML format to the provided writer.
func (c *Config) Marshal(wr io.Writer) error {
out, err := c.Koanf.Marshal(yaml.Parser())
if err != nil {
return err
}
_, err = wr.Write(out)
return err
}
// Unmarshal populates the settings struct from the Koanf settings
func (c *Config) Unmarshal() error {
if err := c.UnmarshalWithConf("", nil, koanf.UnmarshalConf{
DecoderConfig: decoderConfig(&c.Settings),
}); err != nil {
return fmt.Errorf("failed unmarshal settings: %w", err)
}
isGlobPattern := func(str string) bool {
return strings.ContainsAny(str, "*?[]^")
}
for k, v := range c.Interfaces {
if isGlobPattern(k) {
v.Pattern = k
} else {
v.Name = k
}
c.Interfaces[k] = v
}
return c.Check()
}
// decode takes an input structure and uses reflection to translate it to
// the output structure. output must be a pointer to a map or struct.
func decode(input any, output any) error {
cfg := &mapstructure.DecoderConfig{
Metadata: nil,
Result: output,
}
decodeOption(cfg)
decoder, err := mapstructure.NewDecoder(cfg)
if err != nil {
return err
}
return decoder.Decode(input)
}
func (c *Config) Update(new map[string]any) error {
c.mu.Lock()
defer c.mu.Unlock()
prev := map[string]any{}
for key, value := range new {
prev[key] = c.Viper.Get(key)
c.Viper.Set(key, value)
}
if err := c.Load(); err != nil {
// Restore previous values if failed
for key, value := range prev {
c.Viper.Set(key, value)
// InterfaceSettings returns interface specific settings
// These settings are constructed by merging the settings of
// each interface section which matches the name.
// This behavior is quite similar to the OpenSSH client configuration file.
func (c *Config) InterfaceSettings(name string) (cfg *InterfaceSettings) {
for _, i := range c.InterfaceOrder {
icfg := c.Interfaces[i]
if !icfg.Matches(name) {
continue
}
return err
if cfg == nil {
copy := icfg
cfg = &copy
} else {
mergo.Merge(cfg, icfg,
mergo.WithOverride,
mergo.WithSliceDeepCopy)
}
}
return nil
}
func (c *Config) Set(key string, new any) error {
c.mu.Lock()
defer c.mu.Unlock()
prev := c.Viper.Get(key)
c.Viper.Set(key, new)
if err := c.Load(); err != nil {
// Restore previous value if failed
c.Viper.Set(key, prev)
return err
if cfg != nil {
cfg.Name = name
cfg.Pattern = ""
}
return nil
return cfg
}
// InterfaceFilter checks if the provided interface name is matched by any configuration.
func (c *Config) InterfaceFilter(name string) bool {
for _, icfg := range c.Interfaces {
if icfg.Matches(name) {
return true
}
}
return false
}
// decoderConfig returns the mapstructure DecoderConfig which is used by cunicu
func decoderConfig(result any) *mapstructure.DecoderConfig {
return &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapstructure.StringToIPHookFunc(),
mapstructure.StringToIPNetHookFunc(),
mapstructure.TextUnmarshallerHookFunc(),
hookDecodeHook,
),
IgnoreUntaggedFields: true,
WeaklyTypedInput: true,
ErrorUnused: true,
ZeroFields: false,
Result: result,
TagName: "koanf",
}
}

View File

@@ -4,9 +4,12 @@ import (
"bytes"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/rawbytes"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/ghttp"
@@ -25,239 +28,458 @@ func TestSuite(t *testing.T) {
var _ = test.SetupLogging()
var _ = Describe("parse command line arguments", func() {
It("can parse a boolean argument like wg-userspace", func() {
c, err := config.ParseArgs("--wg-userspace")
var _ = Context("config", func() {
mkTempFile := func(contents string) *os.File {
dir := GinkgoT().TempDir()
fn := filepath.Join(dir, "cunicu.yaml")
file, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0600)
Expect(err).To(Succeed())
Expect(c.WireGuard.Userspace).To(BeTrue())
})
It("can parse multiple backends", func() {
c, err := config.ParseArgs("--backend", "k8s", "--backend", "p2p")
defer file.Close()
_, err = file.Write([]byte(contents))
Expect(err).To(Succeed())
Expect(c.Backends).To(HaveLen(2))
Expect(c.Backends[0].Scheme).To(Equal("k8s"))
Expect(c.Backends[1].Scheme).To(Equal("p2p"))
})
It("parse an interface list", func() {
c, err := config.ParseArgs("wg0", "wg1")
return file
}
Expect(err).To(Succeed())
Expect(c.WireGuard.Interfaces).To(ConsistOf("wg0", "wg1"))
})
Describe("parse command line arguments", func() {
It("can parse a boolean argument like wg-userspace", func() {
cfg, err := config.ParseArgs("--wg-userspace")
Expect(err).To(Succeed())
It("fails on invalid arguments", func() {
_, err := config.ParseArgs("--wrong")
icfg := cfg.DefaultInterfaceSettings
Expect(err).To(HaveOccurred())
})
Expect(icfg.WireGuard.UserSpace).To(BeTrue())
})
It("should not load anything from domains without cunicu auto-configuration", func() {
_, err := config.ParseArgs("-D", "google.com")
It("can parse multiple backends", func() {
cfg, err := config.ParseArgs("--backend", "k8s", "--backend", "p2p")
Expect(err).To(
And(
MatchError(ContainSubstring("DNS auto-configuration failed")),
Or(
MatchError(ContainSubstring("no such host")),
MatchError(ContainSubstring("i/o timeout")),
MatchError(ContainSubstring("DNS name does not exist")), // raised by Windows
),
),
)
})
Expect(err).To(Succeed())
Expect(cfg.Backends).To(HaveLen(2))
Expect(cfg.Backends[0].Scheme).To(Equal("k8s"))
Expect(cfg.Backends[1].Scheme).To(Equal("p2p"))
})
It("should fail when passed an non-existent domain name", func() {
// RFC6761 defines that "invalid" is a special domain name to always be invalid
_, err := config.ParseArgs("-D", "invalid")
It("parse an interface list", func() {
cfg, err := config.ParseArgs("wg0", "wg1")
Expect(err).To(Succeed())
Expect(err).To(HaveOccurred())
})
Expect(cfg.InterfaceFilter("wg0")).To(BeTrue())
Expect(cfg.InterfaceFilter("wg1")).To(BeTrue())
Expect(cfg.InterfaceFilter("wg2")).To(BeFalse())
})
Describe("parse configuration files", func() {
It("parse an interface list with custom filter", func() {
cfg, err := config.ParseArgs("--interface-filter", "wg2", "--", "wg0", "wg1")
Expect(err).To(Succeed())
Context("with a local file", func() {
var cfgFile *os.File
Expect(cfg.InterfaceFilter("wg0")).To(BeTrue())
Expect(cfg.InterfaceFilter("wg1")).To(BeTrue())
Expect(cfg.InterfaceFilter("wg2")).To(BeTrue())
Expect(cfg.InterfaceFilter("wg2")).To(BeTrue())
Expect(cfg.InterfaceFilter("wg3")).To(BeFalse())
Expect(cfg.InterfaceSettings("wg3")).To(BeNil())
})
It("fails on invalid arguments", func() {
_, err := config.ParseArgs("--wrong")
Expect(err).To(MatchError("failed to parse command line flags: unknown flag: --wrong"))
})
It("fails on invalid arguments values", func() {
_, err := config.ParseArgs("--url", ":_")
Expect(err).To(MatchError(And(
HavePrefix("failed unmarshal settings"),
HaveSuffix("missing protocol scheme"),
)))
})
Describe("parse configuration files", func() {
Context("with a local file", func() {
var cfgFile *os.File
Context("file with explicit path", func() {
BeforeEach(func() {
var err error
cfgFile, err = os.CreateTemp("", "cunicu-*.yaml")
Expect(err).To(Succeed())
cfgFile = mkTempFile("watch_interval: 1337s\n")
})
It("can read a single valid local file", func() {
Expect(cfgFile.WriteString("watch_interval: 1337s\n")).To(BeNumerically(">", 0))
Expect(cfgFile.Close()).To(Succeed())
Context("file with explicit path", func() {
It("can read a single valid local file", func() {
cfg, err := config.ParseArgs("--config", cfgFile.Name())
c, err := config.ParseArgs("--config", cfgFile.Name())
Expect(err).To(Succeed())
Expect(cfg.WatchInterval).To(Equal(1337 * time.Second))
})
Expect(err).To(Succeed())
Expect(c.WatchInterval).To(Equal(1337 * time.Second))
Specify("that command line arguments take precedence over settings provided by configuration files", func() {
cfg, err := config.ParseArgs("--config", cfgFile.Name(), "--watch-interval", "1m")
Expect(err).To(Succeed())
Expect(cfg.WatchInterval).To(Equal(time.Minute))
})
})
Specify("that command line arguments take precedence over settings provided by configuration files", func() {
Expect(cfgFile.WriteString("watch_interval: 1337s\n")).To(BeNumerically(">", 0))
Expect(cfgFile.Close()).To(Succeed())
Context("in search path", func() {
var oldHomeDir string
c, err := config.ParseArgs("--config", cfgFile.Name(), "--watch-interval", "1m")
Expect(err).To(Succeed())
Expect(c.WatchInterval).To(Equal(time.Minute))
BeforeEach(func() {
// Move config file into XDG config directory
homeDir := filepath.Dir(cfgFile.Name())
configDir := filepath.Join(homeDir, ".config", "cunicu")
err := os.MkdirAll(configDir, 0755)
Expect(err).To(Succeed())
err = os.Rename(
cfgFile.Name(),
filepath.Join(configDir, "cunicu.yaml"),
)
Expect(err).To(Succeed())
oldHomeDir = os.Getenv("HOME")
err = os.Setenv("HOME", homeDir)
Expect(err).To(Succeed())
})
AfterEach(func() {
err := os.Setenv("HOME", oldHomeDir)
Expect(err).To(Succeed())
})
It("can read a single valid local file", func() {
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
Expect(cfg.WatchInterval).To(Equal(1337 * time.Second))
})
})
})
Context("in search path", func() {
Context("with a remote URL", func() {
var server *ghttp.Server
BeforeEach(func() {
var err error
cfgFile, err = os.OpenFile("cunicu.json", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
Expect(err).To(Succeed())
server = ghttp.NewServer()
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/cunicu.yaml"),
ghttp.RespondWith(http.StatusOK,
"watch_interval: 1337s\n",
http.Header{
"Content-type": []string{"text/yaml"},
}),
),
)
})
It("can read a single valid local file", func() {
Expect(cfgFile.WriteString(`{ "watch_interval": "1337s" }`)).To(BeNumerically(">", 0))
Expect(cfgFile.Close()).To(Succeed())
c, err := config.ParseArgs("--config", cfgFile.Name())
It("can fetch a valid remote configuration file", func() {
cfg, err := config.ParseArgs("--config", server.URL()+"/cunicu.yaml")
Expect(err).To(Succeed())
Expect(c.WatchInterval).To(Equal(1337 * time.Second))
Expect(cfg.WatchInterval).To(BeNumerically("==", 1337*time.Second))
})
AfterEach(func() {
//shut down the server between tests
server.Close()
})
It("fails on loading an non-existent remote file", func() {
_, err := config.ParseArgs("--config", "http://example.com/doesnotexist.yaml")
Expect(err).To(HaveOccurred())
})
})
AfterEach(func() {
os.RemoveAll(cfgFile.Name())
})
})
Describe("non-existent files", func() {
It("fails on loading an non-existent local file", func() {
_, err := config.ParseArgs("--config", "/does-not-exist.yaml")
Context("with a remote URL", func() {
var server *ghttp.Server
Expect(err).To(HaveOccurred())
})
BeforeEach(func() {
server = ghttp.NewServer()
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/cunicu.yaml"),
ghttp.RespondWith(http.StatusOK,
"watch_interval: 1337s\n",
http.Header{
"Content-type": []string{"text/yaml"},
}),
),
)
})
It("fails on loading an non-existent remote file", func() {
_, err := config.ParseArgs("--config", "http://example.com/doesnotexist.yaml")
It("can fetch a valid remote configuration file", func() {
cfg, err := config.ParseArgs("--config", server.URL()+"/cunicu.yaml")
Expect(err).To(Succeed())
Expect(cfg.WatchInterval).To(BeNumerically("==", 1337*time.Second))
})
AfterEach(func() {
//shut down the server between tests
server.Close()
})
It("fails on loading an non-existent remote file", func() {
_, err := config.ParseArgs("--config", "http://example.com/doesnotexist.yaml")
Expect(err).To(HaveOccurred())
})
})
Describe("non-existent files", func() {
It("fails on loading an non-existent local file", func() {
_, err := config.ParseArgs("--config", "/does-not-exist.yaml")
Expect(err).To(HaveOccurred())
})
It("fails on loading an non-existent remote file", func() {
_, err := config.ParseArgs("--config", "http://example.com/doesnotexist.yaml")
Expect(err).To(HaveOccurred())
Expect(err).To(HaveOccurred())
})
})
})
})
})
var _ = Describe("use environment variables", func() {
BeforeEach(func() {
os.Setenv("CUNICU_ENDPOINT_DISC_ICE_CANDIDATE_TYPES", "srflx,relay")
})
It("accepts settings via environment variables", func() {
c, err := config.ParseArgs()
It("can parse an interface filter", func() {
cfg, err := config.ParseArgs("--interface-filter", "wg[0-9]")
Expect(err).To(Succeed())
Expect(c.EndpointDisc.ICE.CandidateTypes).To(ConsistOf(
icex.CandidateType{CandidateType: ice.CandidateTypeServerReflexive},
icex.CandidateType{CandidateType: ice.CandidateTypeRelay},
))
cfg.Marshal(GinkgoWriter)
Expect(cfg.InterfaceFilter("wg0")).To(BeTrue())
Expect(cfg.InterfaceFilter("wg1")).To(BeTrue())
Expect(cfg.InterfaceFilter("et0")).To(BeFalse())
})
It("environment variables are overwritten by command line arguments", func() {
c, err := config.ParseArgs("--ice-candidate-type", "host")
Expect(err).To(Succeed())
Describe("use environment variables", func() {
It("accepts settings via environment variables", func() {
os.Setenv("CUNICU_EPDISC_ICE_CANDIDATE_TYPES", "srflx")
Expect(c.EndpointDisc.ICE.CandidateTypes).To(ConsistOf(
icex.CandidateType{CandidateType: ice.CandidateTypeHost},
))
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
icfg := cfg.DefaultInterfaceSettings
Expect(icfg.EndpointDisc.ICE.CandidateTypes).To(ConsistOf(
icex.CandidateType{CandidateType: ice.CandidateTypeServerReflexive},
))
})
It("accepts multiple settings via environment variables", func() {
os.Setenv("CUNICU_EPDISC_ICE_CANDIDATE_TYPES", "srflx,relay")
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
icfg := cfg.DefaultInterfaceSettings
Expect(icfg.EndpointDisc.ICE.CandidateTypes).To(ConsistOf(
icex.CandidateType{CandidateType: ice.CandidateTypeServerReflexive},
icex.CandidateType{CandidateType: ice.CandidateTypeRelay},
))
})
It("environment variables are overwritten by command line arguments", func() {
cfg, err := config.ParseArgs("--ice-candidate-type", "host")
Expect(err).To(Succeed())
icfg := cfg.DefaultInterfaceSettings
Expect(icfg.EndpointDisc.ICE.CandidateTypes).To(HaveLen(1))
Expect(icfg.EndpointDisc.ICE.CandidateTypes).To(ContainElements(
icex.CandidateType{CandidateType: ice.CandidateTypeHost},
))
})
})
})
var _ = Describe("use proper default options", func() {
var c *config.Config
BeforeEach(func() {
Describe("use proper default settings", func() {
var err error
c, err = config.ParseArgs()
var cfg *config.Config
var icfg *config.InterfaceSettings
Expect(err).To(Succeed())
BeforeEach(func() {
cfg, err = config.ParseArgs()
Expect(err).To(Succeed())
icfg = &cfg.DefaultInterfaceSettings
})
It("should use the standard cunicu signaling backend", func() {
Expect(cfg.Backends).To(HaveLen(1))
Expect(cfg.Backends[0].String()).To(Equal("grpc://signal.cunicu.li"))
})
It("should accept all interfaces", func() {
Expect(cfg.InterfaceFilter("wg12345")).To(BeTrue())
})
It("should have a default STUN URL", func() {
Expect(icfg.EndpointDisc.ICE.URLs).To(HaveLen(1))
Expect(icfg.EndpointDisc.ICE.URLs[0].String()).To(Equal("stun:stun.cunicu.li:3478"))
})
})
It("should have a default STUN URL", func() {
Expect(c.EndpointDisc.ICE.URLs).To(HaveLen(1))
Expect(c.EndpointDisc.ICE.URLs).To(ContainElement(HaveField("Host", "stun.l.google.com")))
Describe("dump", func() {
var cfg1, cfg2 *config.Config
var icfg1, icfg2 *config.InterfaceSettings
BeforeEach(func() {
var err error
args := []string{"--ice-network-type", "udp4,udp6", "--url", "stun:stun.cunicu.de", "wg0"}
cfg1, err = config.ParseArgs(args...)
Expect(err).To(Succeed())
buf := &bytes.Buffer{}
Expect(cfg1.Marshal(buf)).To(Succeed())
cfg2, err = config.ParseArgs(args...)
Expect(err).To(Succeed())
err = cfg2.Koanf.Load(rawbytes.Provider(buf.Bytes()), yaml.Parser())
Expect(err).To(Succeed())
err = cfg2.Load()
Expect(err).To(Succeed())
icfg1 = &cfg1.DefaultInterfaceSettings
icfg2 = &cfg2.DefaultInterfaceSettings
})
It("have equal WireGuard interface lists", func() {
Expect(cfg1.Interfaces).To(Equal(cfg2.Interfaces))
})
It("have equal ICE network types", func() {
Expect(icfg1.EndpointDisc.ICE.NetworkTypes).To(Equal(icfg2.EndpointDisc.ICE.NetworkTypes))
})
It("have equal ICE URLs", func() {
Expect(icfg1.EndpointDisc.ICE.URLs).To(Equal(icfg2.EndpointDisc.ICE.URLs))
})
})
})
var _ = Describe("dump", func() {
var c1, c2 *config.Config
BeforeEach(func() {
var err error
c1, err = config.ParseArgs("--ice-network-type", "udp4,udp6", "--url", "stun:0l.de", "wg0")
It("can parse the example config file", func() {
cfg, err := config.ParseArgs("--config", "../../etc/cunicu.yaml")
Expect(err).To(Succeed())
buf := &bytes.Buffer{}
Expect(c1.Dump(buf)).To(Succeed())
c2 = config.NewConfig(nil)
c2.SetConfigType("yaml")
Expect(c2.MergeConfig(buf)).To(Succeed())
Expect(c2.Load()).To(Succeed())
Expect(cfg.Interfaces["wg0"].PrivateKey.String()).To(Equal("kODOmlTNhYbF9htW3uYiE1qKuvBnJKd7MFvaookGd14="))
})
It("have equal WireGuard interface lists", func() {
Expect(c1.WireGuard.Interfaces).To(Equal(c2.WireGuard.Interfaces))
It("throws an error on an invalid config file path", func() {
_, err := config.ParseArgs("--config", "_:")
Expect(err).To(MatchError(HavePrefix("failed to load config file: invalid URL")))
})
It("have equal ICE network types", func() {
Expect(c1.EndpointDisc.ICE.NetworkTypes).To(Equal(c2.EndpointDisc.ICE.NetworkTypes))
It("throws an error on an invalid config file URL schema", func() {
_, err := config.ParseArgs("--config", "smb://is-not-supported")
Expect(err).To(MatchError(And(
HavePrefix("failed to load config file"),
HaveSuffix("unsupported URL scheme: smb"),
)))
})
It("have equal ICE URLs", func() {
Expect(c1.EndpointDisc.ICE.URLs).To(Equal(c2.EndpointDisc.ICE.URLs))
Describe("matching interface configs", func() {
// TODO
})
Describe("runtime", func() {
It("can set a single setting", func() {
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
Expect(cfg.Get("watch_interval")).To(BeNumerically("==", 1*time.Second))
Expect(cfg.WatchInterval).To(Equal(1 * time.Second))
Expect(cfg.Set("watch_interval", "100s")).To(Succeed())
Expect(cfg.Get("watch_interval")).To(Equal("100s"))
Expect(cfg.WatchInterval).To(Equal(100 * time.Second))
})
It("can update multiple settings", func() {
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
Expect(cfg.Update(map[string]any{
"wireguard.listen_port_range.min": 100,
"wireguard.listen_port_range.max": 200,
})).To(Succeed())
Expect(cfg.DefaultInterfaceSettings.WireGuard.ListenPortRange.Min).To(Equal(100))
Expect(cfg.DefaultInterfaceSettings.WireGuard.ListenPortRange.Max).To(Equal(200))
})
It("fails to update multiple settings which are incorrect", func() {
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
orig := cfg.DefaultInterfaceSettings.WireGuard.ListenPortRange
Expect(cfg.Update(map[string]any{
"wireguard.listen_port_range.min": 200,
"wireguard.listen_port_range.max": 100,
})).To(MatchError(
MatchRegexp(`failed to load config: invalid settings: WireGuard minimal listen port \(\d+\) must be smaller or equal than maximal port \(\d+\)`),
))
Expect(cfg.DefaultInterfaceSettings.WireGuard.ListenPortRange).To(Equal(orig), "Failed update has changed settings")
})
It("can save runtime settings", func() {
cfg, err := config.ParseArgs()
Expect(err).To(Succeed())
Expect(cfg.Set("watch_interval", "100s")).To(Succeed())
buf := &bytes.Buffer{}
Expect(cfg.MarshalRuntime(buf)).To(Succeed())
Expect(buf.Bytes()).To(MatchYAML("watch_interval: 100s"))
})
It("can register handler for changed settings", Pending, func() {
// TODO
})
})
Describe("interface overwrites", func() {
It("single interface", func() {
cfgFile := mkTempFile(`---
interfaces:
wg0:
epdisc:
ice:
restart_timeout: 10s
`)
cfg, err := config.ParseArgs("--config", cfgFile.Name())
Expect(err).To(Succeed())
icfg := cfg.InterfaceSettings("wg0")
Expect(icfg).NotTo(BeNil())
Expect(cfg.DefaultInterfaceSettings.EndpointDisc.ICE.RestartTimeout).To(Equal(5 * time.Second))
Expect(icfg.EndpointDisc.ICE.RestartTimeout).To(Equal(10 * time.Second))
Expect(icfg.Name).To(Equal("wg0"))
Expect(icfg.Pattern).To(BeEmpty())
})
It("single interface and pattern overwrites", func() {
cfgFile := mkTempFile(`---
interfaces:
wg0:
epdisc:
ice:
restart_timeout: 10s
wg-work-*:
epdisc:
ice:
keepalive_interval: 123s
restart_timeout: 20s
wg-*:
epdisc:
ice:
restart_timeout: 30s
`)
cfg, err := config.ParseArgs("--config", cfgFile.Name())
Expect(err).To(Succeed())
Expect(cfg.InterfaceOrder).To(Equal([]string{"*", "wg0", "wg-work-*", "wg-*"}))
icfg1 := cfg.InterfaceSettings("wg-work-seattle")
Expect(icfg1).NotTo(BeNil())
icfg2 := cfg.InterfaceSettings("wg-mobile")
Expect(icfg2).NotTo(BeNil())
icfg3 := cfg.InterfaceSettings("wg0")
Expect(icfg3).NotTo(BeNil())
Expect(icfg1.EndpointDisc.ICE.RestartTimeout).To(Equal(30 * time.Second))
Expect(icfg1.EndpointDisc.ICE.KeepaliveInterval).To(Equal(123 * time.Second))
Expect(icfg2.EndpointDisc.ICE.RestartTimeout).To(Equal(30 * time.Second))
Expect(icfg2.EndpointDisc.ICE.KeepaliveInterval).To(Equal(cfg.DefaultInterfaceSettings.EndpointDisc.ICE.KeepaliveInterval))
Expect(icfg3.EndpointDisc.ICE.RestartTimeout).To(Equal(10 * time.Second))
})
})
})
var _ = It("can parse the example config file", func() {
_, err := config.ParseArgs("--config", "../../etc/cunicu.yaml")
Expect(err).To(Succeed())
})

69
pkg/config/confmap.go Normal file
View File

@@ -0,0 +1,69 @@
package config
import (
"errors"
"reflect"
"strings"
)
type confmapProvider struct {
value any
}
// DefaultsProvider creates a new koanf provider which is very
// similar to koanf's confmap provider but slightly adjusted to
// our needs.
func ConfMapProvider(v any) *confmapProvider {
return &confmapProvider{
value: v,
}
}
func (p *confmapProvider) ReadBytes() ([]byte, error) {
return nil, errors.New("this provider requires no parser")
}
func (p *confmapProvider) Read() (map[string]any, error) {
return Map(p.value), nil
}
func Map(v any) map[string]any {
rv := reflect.ValueOf(v)
return _map(rv).(map[string]any)
}
func _map(v reflect.Value) any {
if v.Kind() == reflect.Pointer {
v = v.Elem()
}
t := v.Type()
if v.Kind() == reflect.Struct {
d := map[string]any{}
for i := 0; i < v.NumField(); i++ {
fv := v.Field(i)
sf := t.Field(i)
if fv.IsValid() && !fv.IsZero() {
if tag, ok := sf.Tag.Lookup("koanf"); ok {
name := strings.Split(tag, ",")[0]
n := _map(fv)
if name != "" {
d[name] = n
} else if m, ok := n.(map[string]any); ok {
for k, v := range m {
d[k] = v
}
}
}
}
}
return d
} else {
return v.Interface()
}
}

View File

@@ -1,10 +1,16 @@
package config
import "github.com/stv0g/cunicu/pkg/wg"
import (
"net/url"
"time"
"github.com/pion/ice/v2"
icex "github.com/stv0g/cunicu/pkg/feat/epdisc/ice"
"github.com/stv0g/cunicu/pkg/wg"
)
const (
DefaultSocketPath = "/var/run/cunicu.sock"
DefaultURL = "stun:stun.l.google.com:19302"
// Ephemeral Port Range (RFC6056 Sect. 2.1)
EphemeralPortMin = (1 << 15) + (1 << 14)
@@ -12,36 +18,94 @@ const (
)
var (
DefaultBackends = []string{"grpc://signal.cunicu.li:443"}
)
DefaultBackends = []BackendURL{
{
URL: url.URL{
Scheme: "grpc",
Host: "signal.cunicu.li",
},
},
}
func (c *Config) SetDefaults() {
c.SetDefault("hooks", []HookSetting{})
c.SetDefault("auto_config.enabled", true)
c.SetDefault("backends", DefaultBackends)
c.SetDefault("config_sync.enabled", true)
c.SetDefault("peer_disc.enabled", true)
c.SetDefault("config_sync.path", wg.ConfigPath)
c.SetDefault("config_sync.watch", false)
c.SetDefault("endpoint_disc.enabled", true)
c.SetDefault("endpoint_disc.ice.check_interval", "200ms")
c.SetDefault("endpoint_disc.ice.disconnected_timeout", "5s")
c.SetDefault("endpoint_disc.ice.failed_timeout", "5s")
c.SetDefault("endpoint_disc.ice.keepalive_interval", "2s")
c.SetDefault("endpoint_disc.ice.max_binding_requests", 7)
c.SetDefault("endpoint_disc.ice.port.max", EphemeralPortMax)
c.SetDefault("endpoint_disc.ice.port.min", EphemeralPortMin)
c.SetDefault("endpoint_disc.ice.restart_timeout", "5s")
c.SetDefault("endpoint_disc.ice.urls", []string{DefaultURL})
c.SetDefault("endpoint_disc.ice.interface_filter", ".*")
c.SetDefault("host_sync.enabled", true)
c.SetDefault("route_sync.enabled", true)
c.SetDefault("route_sync.watch", true)
c.SetDefault("route_sync.table", DefaultRouteTable)
c.SetDefault("rpc.socket", DefaultSocketPath)
c.SetDefault("rpc.wait", false)
c.SetDefault("watch_interval", "1s")
c.SetDefault("wireguard.port.max", EphemeralPortMax)
c.SetDefault("wireguard.port.min", wg.DefaultPort)
c.SetDefault("wireguard.interface_filter", ".*")
}
DefaultICEURLs = []URL{
{url.URL{
Scheme: "stun",
Opaque: "stun.cunicu.li:3478",
}},
// TODO: Use relay API
// {url.URL{
// Scheme: "grpc",
// Host: "relay.cunicu.li:",
// }},
}
DefaultSettings = Settings{
Backends: DefaultBackends,
RPC: RPCSettings{
Socket: DefaultSocketPath,
Wait: false,
},
WatchInterval: 1 * time.Second,
DefaultInterfaceSettings: DefaultInterfaceSettings,
}
DefaultInterfaceSettings = InterfaceSettings{
AutoConfig: AutoConfigSettings{
Enabled: true,
},
ConfigSync: ConfigSyncSettings{
Enabled: true,
Path: wg.ConfigPath,
Watch: false,
},
PeerDisc: PeerDiscoverySettings{
Enabled: true,
},
EndpointDisc: EndpointDiscoverySettings{
Enabled: true,
ICE: ICESettings{
URLs: DefaultICEURLs,
CheckInterval: 200 * time.Millisecond,
DisconnectedTimeout: 5 * time.Second,
FailedTimeout: 5 * time.Second,
RestartTimeout: 5 * time.Second,
InterfaceFilter: "*",
KeepaliveInterval: 2 * time.Second, // TODO: increase
MaxBindingRequests: 7,
PortRange: PortRangeSettings{
Min: EphemeralPortMin,
Max: EphemeralPortMax,
},
CandidateTypes: []icex.CandidateType{
{CandidateType: ice.CandidateTypeHost},
{CandidateType: ice.CandidateTypeServerReflexive},
{CandidateType: ice.CandidateTypePeerReflexive},
{CandidateType: ice.CandidateTypeRelay},
},
NetworkTypes: []icex.NetworkType{
{NetworkType: ice.NetworkTypeUDP4},
{NetworkType: ice.NetworkTypeUDP6},
{NetworkType: ice.NetworkTypeTCP4},
{NetworkType: ice.NetworkTypeTCP6},
},
},
},
HostSync: HostSyncSettings{
Enabled: true,
},
RouteSync: RouteSyncSettings{
Enabled: true,
Watch: true,
Table: DefaultRouteTable,
},
WireGuard: WireGuardSettings{
ListenPortRange: &PortRangeSettings{
Min: wg.DefaultPort,
Max: EphemeralPortMax,
},
},
}
)

138
pkg/config/file.go Normal file
View File

@@ -0,0 +1,138 @@
package config
import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"time"
"github.com/stv0g/cunicu/pkg/util/buildinfo"
"gopkg.in/yaml.v3"
kyaml "github.com/knadh/koanf/parsers/yaml"
)
type fileProvider struct {
InterfaceOrder []string
url *url.URL
}
func YAMLFileProvider(u *url.URL) *fileProvider {
return &fileProvider{
url: u,
}
}
func (p *fileProvider) ReadBytes() ([]byte, error) {
return nil, errors.New("this provider requires no parser")
}
func (p *fileProvider) readBytes() ([]byte, error) {
var err error
var out []byte
switch p.url.Scheme {
case "http", "https":
out, err = p.readBytesRemote()
case "":
out, err = p.readBytesLocal()
default:
err = fmt.Errorf("unsupported URL scheme: %s", p.url.Scheme)
}
if err != nil {
return nil, err
}
p.InterfaceOrder, err = ExtractInterfaceOrder(out)
if err != nil {
return nil, err
}
return out, nil
}
func (p *fileProvider) Read() (map[string]interface{}, error) {
buf, err := p.readBytes()
if err != nil {
return nil, err
}
return kyaml.Parser().Unmarshal(buf)
}
func (p *fileProvider) readBytesLocal() ([]byte, error) {
f, err := os.Open(p.url.Path)
if err != nil {
return nil, fmt.Errorf("failed to open file '%s': %w", p.url.Path, err)
}
defer f.Close()
return io.ReadAll(f)
}
func (p *fileProvider) readBytesRemote() ([]byte, error) {
if p.url.Scheme != "https" {
host, _, err := net.SplitHostPort(p.url.Host)
if err != nil {
return nil, fmt.Errorf("failed to split host:port: %w", err)
} else if host != "localhost" && host != "127.0.0.1" && host != "::1" && host != "[::1]" {
return nil, errors.New("remote configuration must be provided via HTTPS")
}
}
client := &http.Client{
Timeout: 5 * time.Second,
}
req := &http.Request{
Method: "GET",
URL: p.url,
Header: http.Header{},
}
req.Header.Set("User-Agent", buildinfo.UserAgent())
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch %s: %w", p.url, err)
} else if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch: %s: %s", p.url, resp.Status)
}
return io.ReadAll(resp.Body)
}
type stringMapSlice []string
func (keys *stringMapSlice) UnmarshalYAML(v *yaml.Node) error {
if v.Kind != yaml.MappingNode {
return fmt.Errorf("pipeline must contain YAML mapping, has %v", v.Kind)
}
*keys = make([]string, len(v.Content)/2)
for i := 0; i < len(v.Content); i += 2 {
if err := v.Content[i].Decode(&(*keys)[i/2]); err != nil {
return err
}
}
return nil
}
func ExtractInterfaceOrder(buf []byte) ([]string, error) {
var s struct {
Interfaces stringMapSlice `yaml:"interfaces,omitempty"`
}
if err := yaml.Unmarshal(buf, &s); err != nil {
return nil, err
}
return s.Interfaces, nil
}

23
pkg/config/file_test.go Normal file
View File

@@ -0,0 +1,23 @@
package config_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stv0g/cunicu/pkg/config"
)
var _ = It("Can extract interface order", func() {
cfg := `---
interfaces:
f:
c:
b:
a:
d:
e:
`
order, err := config.ExtractInterfaceOrder([]byte(cfg))
Expect(err).To(Succeed())
Expect(order).To(Equal([]string{"f", "c", "b", "a", "d", "e"}))
})

View File

@@ -7,16 +7,12 @@ import (
"github.com/mitchellh/mapstructure"
)
type hookBase struct {
Type string `yaml:"type"`
}
func hookDecodeHook(f, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.Map || t.Name() != "HookSetting" {
return data, nil
}
var base hookBase
var base BaseHookSetting
if err := mapstructure.Decode(data, &base); err != nil {
return nil, err
}
@@ -35,5 +31,10 @@ func hookDecodeHook(f, t reflect.Type, data any) (any, error) {
return nil, fmt.Errorf("unknown hook type: %s", base.Type)
}
return hook, decode(data, hook)
decoder, err := mapstructure.NewDecoder(decoderConfig(hook))
if err != nil {
return nil, err
}
return hook, decoder.Decode(data)
}

View File

@@ -1,35 +1,72 @@
package config
import (
"errors"
"fmt"
"net"
"net/url"
"regexp"
"sync"
"github.com/knadh/koanf/maps"
"github.com/pion/ice/v2"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
)
func (c *Config) Lookup(name string) error {
g := errgroup.Group{}
type lookupProvider struct {
Domain string
g.Go(func() error { return c.lookupTXT(name) })
g.Go(func() error { return c.lookupSRV(name) })
Files []string
settings map[string]any
return g.Wait()
mu sync.Mutex
logger *zap.Logger
}
func (c *Config) lookupTXT(name string) error {
rr, err := net.LookupTXT(name)
func LookupProvider(domain string) *lookupProvider {
logger := zap.L().Named("lookup")
return &lookupProvider{
Domain: domain,
settings: map[string]any{},
logger: logger,
}
}
func (p *lookupProvider) set(key string, value any) {
p.mu.Lock()
defer p.mu.Unlock()
p.settings[key] = value
}
func (p *lookupProvider) ReadBytes() ([]byte, error) {
return nil, errors.New("this provider requires no parser")
}
func (p *lookupProvider) Read() (map[string]any, error) {
g := errgroup.Group{}
g.Go(func() error { return p.lookupTXT() })
g.Go(func() error { return p.lookupSRV() })
if err := g.Wait(); err != nil {
return nil, err
}
return maps.Unflatten(p.settings, "."), nil
}
func (p *lookupProvider) lookupTXT() error {
rr, err := net.LookupTXT(p.Domain)
if err != nil {
return err
}
var re = regexp.MustCompile(`^(?m)cunicu-(.+?)=(.*)$`)
c.logger.Debug("TXT records found", zap.Any("records", rr))
p.logger.Debug("TXT records found", zap.Any("records", rr))
rrs := map[string][]string{}
for _, r := range rr {
@@ -46,42 +83,33 @@ func (c *Config) lookupTXT(name string) error {
}
txtSettingMap := map[string]string{
"community": "peer_disc.community",
"ice-username": "endpoint_disc.ice.username",
"ice-password": "endpoint_disc.ice.password",
"community": "pdisc.community",
"ice-username": "epdisc.ice.username",
"ice-password": "epdisc.ice.password",
}
for txtName, settingName := range txtSettingMap {
if values, ok := rrs[txtName]; ok {
if len(values) > 1 {
c.logger.Warn(fmt.Sprintf("Ignoring TXT record 'cunicu-%s' as there are more than one records with this prefix", txtName))
p.logger.Warn(fmt.Sprintf("Ignoring TXT record 'cunicu-%s' as there are more than one records with this prefix", txtName))
} else {
// We use SetDefault here as we do not want to overwrite user-provided settings with settings gathered via DNS
c.SetDefault(settingName, values[0])
p.set(settingName, values[0])
}
}
}
if backends, ok := rrs["backend"]; ok {
c.Set("backends", backends)
p.set("backends", backends)
}
if configFiles, ok := rrs["config"]; ok {
for _, configFile := range configFiles {
if u, err := url.Parse(configFile); err == nil {
if err := c.MergeRemoteConfig(u); err != nil {
return fmt.Errorf("failed to fetch config file from URL in cunicu-config TXT record: %s", err)
}
} else {
return fmt.Errorf("failed to parse URL of config-file in cunicu-config TXT record: %s", err)
}
}
if files, ok := rrs["config"]; ok {
p.Files = append(p.Files, files...)
}
return nil
}
func (c *Config) lookupSRV(name string) error {
func (p *lookupProvider) lookupSRV() error {
svcs := map[string][]string{
"stun": {"udp"},
"stuns": {"tcp"},
@@ -99,9 +127,9 @@ func (c *Config) lookupSRV(name string) error {
for _, proto := range protos {
reqs++
s := svc
p := proto
q := proto
g.Go(func() error {
us, err := lookupICEUrlSRV(name, s, p)
us, err := lookupICEUrlSRV(p.Domain, s, q)
if err != nil {
return err
}
@@ -121,7 +149,7 @@ func (c *Config) lookupSRV(name string) error {
}
// We use SetDefault here as we do not want to overwrite user-provided settings with settings gathered via DNS
c.SetDefault("endpoint_disc.ice.urls", urls)
p.set("epdisc.ice.urls", urls)
return nil
}

View File

@@ -10,143 +10,166 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/ghttp"
"github.com/pion/ice/v2"
"github.com/stv0g/cunicu/pkg/config"
"github.com/stv0g/cunicu/pkg/util/buildinfo"
icex "github.com/stv0g/cunicu/pkg/feat/epdisc/ice"
)
var _ = Describe("lookup", func() {
var dnsSrv *mockdns.Server
var webSrv *ghttp.Server
It("should not load anything from domains without cunicu auto-configuration", func() {
_, err := config.ParseArgs("-D", "google.com")
var cfgPath = "/cunicu"
BeforeEach(func() {
var err error
webSrv = ghttp.NewServer()
webSrv.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", cfgPath),
ghttp.VerifyHeader(http.Header{
"User-agent": []string{buildinfo.UserAgent()},
}),
ghttp.RespondWith(http.StatusOK, "wireguard: { interfaces: [ wg-test ] }",
http.Header{
"Content-type": []string{"text/yaml"},
}),
Expect(err).To(
And(
MatchError(ContainSubstring("DNS auto-configuration failed")),
Or(
MatchError(ContainSubstring("no such host")),
MatchError(ContainSubstring("i/o timeout")),
MatchError(ContainSubstring("DNS name does not exist")), // raised by Windows
),
),
)
dnsSrv, err = mockdns.NewServerWithLogger(map[string]mockdns.Zone{
"example.com.": {
A: []string{"1.2.3.4"},
TXT: []string{
"cunicu-backend=p2p",
"cunicu-backend=grpc://example.com:8080",
"cunicu-community=my-community-password",
"cunicu-ice-username=user1",
"cunicu-ice-password=pass1",
fmt.Sprintf("cunicu-config=%s%s", webSrv.URL(), cfgPath),
},
},
"_stun._udp.example.com.": {
SRV: []net.SRV{
{
Target: "stun.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_stuns._tcp.example.com.": {
SRV: []net.SRV{
{
Target: "stun.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_turn._udp.example.com.": {
SRV: []net.SRV{
{
Target: "turn.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_turn._tcp.example.com.": {
SRV: []net.SRV{
{
Target: "turn.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_turns._tcp.example.com.": {
SRV: []net.SRV{
{
Target: "turn.example.com.",
Port: 5349,
Priority: 10,
Weight: 0,
},
},
},
}, GinkgoWriter, false)
Expect(err).To(Succeed())
dnsSrv.PatchNet(net.DefaultResolver)
})
AfterEach(func() {
mockdns.UnpatchNet(net.DefaultResolver)
It("should fail when passed an non-existent domain name", func() {
// RFC6761 defines that "invalid" is a special domain name to always be invalid
_, err := config.ParseArgs("-D", "invalid")
err := dnsSrv.Close()
Expect(err).To(Succeed())
Expect(err).To(HaveOccurred())
})
It("check mock dns server", func() {
addr, err := net.ResolveIPAddr("ip", "example.com")
Expect(err).To(Succeed())
Expect(addr.IP.String()).To(Equal("1.2.3.4"))
})
Context("mockup dns", func() {
var dnsSrv *mockdns.Server
var webSrv *ghttp.Server
It("can do DNS auto configuration", Label("dns-auto-config"), func() {
c, err := config.ParseArgs("--domain", "example.com")
var cfgPath = "/cunicu"
Expect(err).To(Succeed())
Expect(c.PeerDisc.Community).To(Equal("my-community-password"))
Expect(c.EndpointDisc.ICE.Username).To(Equal("user1"))
Expect(c.EndpointDisc.ICE.Password).To(Equal("pass1"))
Expect(c.Backends).To(ConsistOf(
config.BackendURL{URL: url.URL{Scheme: "p2p"}},
config.BackendURL{URL: url.URL{Scheme: "grpc", Host: "example.com:8080"}},
))
Expect(c.EndpointDisc.ICE.URLs).To(ConsistOf(
icex.URL{URL: ice.URL{Scheme: ice.SchemeTypeSTUN, Host: "stun.example.com.", Port: 3478, Proto: ice.ProtoTypeUDP}},
icex.URL{URL: ice.URL{Scheme: ice.SchemeTypeTURN, Host: "turn.example.com.", Port: 3478, Proto: ice.ProtoTypeUDP}},
icex.URL{URL: ice.URL{Scheme: ice.SchemeTypeSTUNS, Host: "stun.example.com.", Port: 3478, Proto: ice.ProtoTypeTCP}},
icex.URL{URL: ice.URL{Scheme: ice.SchemeTypeTURNS, Host: "turn.example.com.", Port: 5349, Proto: ice.ProtoTypeTCP}},
icex.URL{URL: ice.URL{Scheme: ice.SchemeTypeTURN, Host: "turn.example.com.", Port: 3478, Proto: ice.ProtoTypeTCP}},
))
Expect(c.WireGuard.Interfaces).To(ContainElement("wg-test"))
BeforeEach(func() {
var err error
c.Dump(GinkgoWriter)
})
webSrv = ghttp.NewServer()
webSrv.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", cfgPath),
ghttp.VerifyHeader(http.Header{
"User-agent": []string{buildinfo.UserAgent()},
}),
ghttp.RespondWith(http.StatusOK, "{ interfaces: { wg-test: { } } }",
http.Header{
"Content-type": []string{"text/yaml"},
}),
),
)
AfterEach(func() {
dnsSrv.Close()
webSrv.Close()
mockdns.UnpatchNet(net.DefaultResolver)
dnsSrv, err = mockdns.NewServerWithLogger(map[string]mockdns.Zone{
"example.com.": {
A: []string{"1.2.3.4"},
TXT: []string{
"cunicu-backend=p2p",
"cunicu-backend=grpc://example.com:8080",
"cunicu-community=my-community-password",
"cunicu-ice-username=user1",
"cunicu-ice-password=pass1",
fmt.Sprintf("cunicu-config=%s%s", webSrv.URL(), cfgPath),
},
},
"_stun._udp.example.com.": {
SRV: []net.SRV{
{
Target: "stun.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_stuns._tcp.example.com.": {
SRV: []net.SRV{
{
Target: "stun.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_turn._udp.example.com.": {
SRV: []net.SRV{
{
Target: "turn.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_turn._tcp.example.com.": {
SRV: []net.SRV{
{
Target: "turn.example.com.",
Port: 3478,
Priority: 10,
Weight: 0,
},
},
},
"_turns._tcp.example.com.": {
SRV: []net.SRV{
{
Target: "turn.example.com.",
Port: 5349,
Priority: 10,
Weight: 0,
},
},
},
}, GinkgoWriter, false)
Expect(err).To(Succeed())
dnsSrv.PatchNet(net.DefaultResolver)
})
AfterEach(func() {
mockdns.UnpatchNet(net.DefaultResolver)
err := dnsSrv.Close()
Expect(err).To(Succeed())
})
It("check mock dns server", func() {
addr, err := net.ResolveIPAddr("ip", "example.com")
Expect(err).To(Succeed())
Expect(addr.IP.String()).To(Equal("1.2.3.4"))
})
It("can do DNS auto configuration", func() {
cfg, err := config.ParseArgs("--domain", "example.com")
Expect(err).To(Succeed())
icfg := cfg.DefaultInterfaceSettings
Expect(icfg.PeerDisc.Community).To(Equal("my-community-password"))
Expect(icfg.EndpointDisc.ICE.Username).To(Equal("user1"))
Expect(icfg.EndpointDisc.ICE.Password).To(Equal("pass1"))
Expect(cfg.Backends).To(ConsistOf(
config.BackendURL{URL: url.URL{Scheme: "p2p"}},
config.BackendURL{URL: url.URL{Scheme: "grpc", Host: "example.com:8080"}},
))
Expect(icfg.EndpointDisc.ICE.URLs).To(ConsistOf(
config.URL{url.URL{Scheme: "stun", Opaque: "stun.example.com.:3478"}},
config.URL{url.URL{Scheme: "stuns", Opaque: "stun.example.com.:3478"}},
config.URL{url.URL{Scheme: "turn", Opaque: "turn.example.com.:3478", RawQuery: "transport=udp"}},
config.URL{url.URL{Scheme: "turn", Opaque: "turn.example.com.:3478", RawQuery: "transport=tcp"}},
config.URL{url.URL{Scheme: "turns", Opaque: "turn.example.com.:5349", RawQuery: "transport=tcp"}},
))
Expect(cfg.Interfaces).To(HaveKey("wg-test"))
cfg.Marshal(GinkgoWriter)
})
AfterEach(func() {
dnsSrv.Close()
webSrv.Close()
mockdns.UnpatchNet(net.DefaultResolver)
})
})
})

90
pkg/config/meta.go Normal file
View File

@@ -0,0 +1,90 @@
package config
import (
"reflect"
"strings"
)
const delim = "."
type ConfigChangedHandler interface {
OnConfigChanged(path string, old, new any)
}
type Meta struct {
Fields map[string]*Meta
Type reflect.Type
OnChangedHandlers []ConfigChangedHandler
}
func Metadata() *Meta {
return metadata(reflect.TypeOf(Settings{}))
}
func metadata(typ reflect.Type) *Meta {
if typ.Kind() == reflect.Pointer {
typ = typ.Elem()
}
m := &Meta{
Type: typ,
}
if typ.Kind() == reflect.Struct {
m.Fields = map[string]*Meta{}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if tag, ok := field.Tag.Lookup("koanf"); ok {
name := strings.Split(tag, ",")[0]
n := metadata(field.Type)
if name != "" {
m.Fields[name] = n
} else {
for k, v := range n.Fields {
m.Fields[k] = v
}
}
}
}
}
return m
}
func (m *Meta) Keys() []string {
keys := []string{}
for k, v := range m.Fields {
if v.Fields == nil {
keys = append(keys, k)
} else {
for _, p := range v.Keys() {
keys = append(keys, k+delim+p)
}
}
}
return keys
}
func (m *Meta) Lookup(path string) *Meta {
parts := strings.Split(path, delim)
return m.lookup(parts)
}
func (m *Meta) lookup(path []string) *Meta {
if len(path) == 0 {
return m
} else {
if m.Fields != nil {
if n, ok := m.Fields[path[0]]; ok {
return n.lookup(path[1:])
}
}
}
return nil
}

36
pkg/config/meta_test.go Normal file
View File

@@ -0,0 +1,36 @@
package config_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stv0g/cunicu/pkg/config"
)
var _ = Context("meta", func() {
var m *config.Meta
BeforeEach(func() {
m = config.Metadata()
Expect(m).NotTo(BeNil())
})
It("can enumerate all keys", func() {
m := config.Metadata()
keys := m.Keys()
Expect(keys).To(ContainElements(
Equal("hsync.enabled"),
Equal("watch_interval"),
Equal("epdisc.ice.disconnected_timeout"),
))
Expect(keys).NotTo(ContainElements(
"epdisc.ice",
))
})
It("can lookup a key", func() {
n := m.Lookup("epdisc.ice")
Expect(n.Fields).To(HaveKey("disconnected_timeout"))
})
})

View File

@@ -1,128 +1,148 @@
package config
import (
"io"
"path/filepath"
"time"
"gopkg.in/yaml.v3"
"github.com/stv0g/cunicu/pkg/crypto"
icex "github.com/stv0g/cunicu/pkg/feat/epdisc/ice"
)
type PortSettings struct {
Min int `yaml:"min,omitempty"`
Max int `yaml:"max,omitempty"`
type PortRangeSettings struct {
Min int `koanf:"min,omitempty"`
Max int `koanf:"max,omitempty"`
}
type ICESettings struct {
URLs []icex.URL `yaml:"urls,omitempty"`
CandidateTypes []icex.CandidateType `yaml:"candidate_types,omitempty"`
NetworkTypes []icex.NetworkType `yaml:"network_types,omitempty"`
NAT1to1IPs []string `yaml:"nat_1to1_ips,omitempty"`
URLs []URL `koanf:"urls,omitempty"`
CandidateTypes []icex.CandidateType `koanf:"candidate_types,omitempty"`
NetworkTypes []icex.NetworkType `koanf:"network_types,omitempty"`
NAT1to1IPs []string `koanf:"nat_1to1_ips,omitempty"`
Port PortSettings `yaml:"port,omitempty"`
PortRange PortRangeSettings `koanf:"port_range,omitempty"`
Lite bool `yaml:"lite,omitempty"`
MDNS bool `yaml:"mdns,omitempty"`
MaxBindingRequests int `yaml:"max_binding_requests,omitempty"`
InsecureSkipVerify bool `yaml:"insecure_skip_verify,omitempty"`
Lite bool `koanf:"lite,omitempty"`
MDNS bool `koanf:"mdns,omitempty"`
MaxBindingRequests int `koanf:"max_binding_requests,omitempty"`
InsecureSkipVerify bool `koanf:"insecure_skip_verify,omitempty"`
InterfaceFilter Regexp `yaml:"interface_filter,omitempty"`
InterfaceFilter string `koanf:"interface_filter,omitempty"`
DisconnectedTimeout time.Duration `yaml:"disconnected_timeout,omitempty"`
FailedTimeout time.Duration `yaml:"failed_timeout,omitempty"`
DisconnectedTimeout time.Duration `koanf:"disconnected_timeout,omitempty"`
FailedTimeout time.Duration `koanf:"failed_timeout,omitempty"`
// KeepaliveInterval used to keep candidates alive
KeepaliveInterval time.Duration `yaml:"keepalive_interval,omitempty"`
KeepaliveInterval time.Duration `koanf:"keepalive_interval,omitempty"`
// CheckInterval is the interval at which the agent performs candidate checks in the connecting phase
CheckInterval time.Duration `yaml:"check_interval,omitempty"`
RestartTimeout time.Duration `yaml:"restart_timeout,omitempty"`
CheckInterval time.Duration `koanf:"check_interval,omitempty"`
RestartTimeout time.Duration `koanf:"restart_timeout,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Username string `koanf:"username,omitempty"`
Password string `koanf:"password,omitempty"`
}
type RPCSettings struct {
Socket string `yaml:"socket,omitempty"`
Wait bool `yaml:"wait,omitempty"`
Socket string `koanf:"socket,omitempty"`
Wait bool `koanf:"wait,omitempty"`
}
type ConfigSyncSettings struct {
Enabled bool `yaml:"enabled,omitempty"`
Path string `yaml:"path,omitempty"`
Watch bool `yaml:"watch,omitempty"`
Enabled bool `koanf:"enabled,omitempty"`
Path string `koanf:"path,omitempty"`
Watch bool `koanf:"watch,omitempty"`
}
type RouteSyncSettings struct {
Enabled bool `yaml:"enabled,omitempty"`
Table int `yaml:"table,omitempty"`
Watch bool `yaml:"watch,omitempty"`
Enabled bool `koanf:"enabled,omitempty"`
Table int `koanf:"table,omitempty"`
Watch bool `koanf:"watch,omitempty"`
}
type WireGuardSettings struct {
Userspace bool `yaml:"userspace,omitempty"`
InterfaceFilter Regexp `yaml:"interface_filter,omitempty"`
Interfaces []string `yaml:"interfaces,omitempty"`
UserSpace bool `koanf:"userspace,omitempty"`
Port PortSettings `yaml:"port,omitempty"`
ListenPort *int `koanf:"listen_port,omitempty"`
ListenPortRange *PortRangeSettings `koanf:"listen_port_range,omitempty"`
}
type AutoConfigSettings struct {
Enabled bool `yaml:"enabled,omitempty"`
Enabled bool `koanf:"enabled,omitempty"`
}
type HostSyncSettings struct {
Enabled bool `yaml:"enabled,omitempty"`
Enabled bool `koanf:"enabled,omitempty"`
}
type PeerDiscoverySettings struct {
Enabled bool `yaml:"enabled,omitempty"`
Enabled bool `koanf:"enabled,omitempty"`
Community string `yaml:"community,omitempty"`
Whitelist []Key `yaml:"whitelist,omitempty"`
Community string `koanf:"community,omitempty"`
Whitelist []crypto.Key `koanf:"whitelist,omitempty"`
}
type EndpointDiscoverySettings struct {
Enabled bool `yaml:"enabled,omitempty"`
Enabled bool `koanf:"enabled,omitempty"`
ICE ICESettings `yaml:"ice,omitempty"`
ICE ICESettings `koanf:"ice,omitempty"`
}
type HookSetting any
type BaseHookSetting struct {
Type string `koanf:"type"`
}
type WebHookSetting struct {
URL URL `yaml:"url"`
Method string `yaml:"method"`
Headers map[string]string `yaml:"headers"`
BaseHookSetting `koanf:",squash"`
URL URL `koanf:"url"`
Method string `koanf:"method"`
Headers map[string]string `koanf:"headers"`
}
type ExecHookSetting struct {
Command string `yaml:"command"`
Args []string `yaml:"args"`
Env map[string]string `yaml:"env"`
Stdin bool `yaml:"stdin"`
BaseHookSetting `koanf:",squash"`
Command string `koanf:"command"`
Args []string `koanf:"args"`
Env map[string]string `koanf:"env"`
Stdin bool `koanf:"stdin"`
}
type InterfaceSettings struct {
Name string
Pattern string
PrivateKey crypto.Key `koanf:"private_key,omitempty"`
WireGuard WireGuardSettings `koanf:"wireguard,omitempty"`
AutoConfig AutoConfigSettings `koanf:"autocfg,omitempty"`
ConfigSync ConfigSyncSettings `koanf:"cfgsync,omitempty"`
RouteSync RouteSyncSettings `koanf:"rtsync,omitempty"`
HostSync HostSyncSettings `koanf:"hsync,omitempty"`
EndpointDisc EndpointDiscoverySettings `koanf:"epdisc,omitempty"`
PeerDisc PeerDiscoverySettings `koanf:"pdisc,omitempty"`
}
func (i *InterfaceSettings) Matches(name string) bool {
if i.Pattern != "" {
if ok, err := filepath.Match(i.Pattern, name); err == nil {
return ok
}
} else if i.Name != "" {
return name == i.Name
}
return false
}
type Settings struct {
WatchInterval time.Duration `yaml:"watch_interval,omitempty"`
WatchInterval time.Duration `koanf:"watch_interval,omitempty"`
RPC RPCSettings `koanf:"rpc,omitempty"`
Backends []BackendURL `koanf:"backends,omitempty"`
Backends []BackendURL `yaml:"backends,omitempty"`
// Hooks are a global setting and not currently not customizable per interface.
Hooks []HookSetting `koanf:"hooks,omitempty"`
RPC RPCSettings `yaml:"rpc,omitempty"`
WireGuard WireGuardSettings `yaml:"wireguard,omitempty"`
AutoConfig AutoConfigSettings `yaml:"auto_config,omitempty"`
ConfigSync ConfigSyncSettings `yaml:"config_sync,omitempty"`
RouteSync RouteSyncSettings `yaml:"route_sync,omitempty"`
HostSync HostSyncSettings `yaml:"host_sync,omitempty"`
Hooks []HookSetting `yaml:"hooks,omitempty"`
EndpointDisc EndpointDiscoverySettings `yaml:"endpoint_disc,omitempty"`
PeerDisc PeerDiscoverySettings `yaml:"peer_disc,omitempty"`
}
func (s *Settings) Dump(wr io.Writer) error {
enc := yaml.NewEncoder(wr)
enc.SetIndent(2)
return enc.Encode(s)
DefaultInterfaceSettings InterfaceSettings `koanf:",squash"`
Interfaces map[string]InterfaceSettings `koanf:"interfaces"`
}

View File

@@ -3,32 +3,11 @@ package config
import (
"fmt"
"net/url"
"regexp"
"strings"
"github.com/stv0g/cunicu/pkg/crypto"
"go.uber.org/zap/zapcore"
)
type Regexp struct {
regexp.Regexp
}
func (r *Regexp) UnmarshalText(text []byte) error {
re, err := regexp.Compile(string(text))
if err != nil {
return err
}
r.Regexp = *re
return nil
}
func (r *Regexp) MarshalText() ([]byte, error) {
return []byte(r.String()), nil
}
type BackendURL struct {
url.URL
}
@@ -74,7 +53,8 @@ func (u *URL) UnmarshalText(text []byte) error {
}
func (u URL) MarshalText() ([]byte, error) {
return []byte(u.String()), nil
s := u.String()
return []byte(s), nil
}
type OutputFormat string
@@ -131,20 +111,3 @@ type Level struct {
func (l *Level) Type() string {
return "string"
}
type Key crypto.Key
func (k *Key) UnmarshalText(text []byte) error {
l, err := crypto.ParseKey(string(text))
if err != nil {
return err
}
*k = Key(l)
return nil
}
func (k Key) MarshalText() ([]byte, error) {
return []byte(crypto.Key(k).String()), nil
}

View File

@@ -2,42 +2,44 @@ package config_test
import (
"net/url"
"regexp"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/spf13/pflag"
"github.com/stv0g/cunicu/pkg/config"
"github.com/stv0g/cunicu/pkg/crypto"
)
var _ = Context("types", func() {
Context("regex", func() {
const re1Str = "[a-z]"
const re2Str = "[a-z"
var re1 = regexp.MustCompile(re1Str)
Context("url", func() {
t := []TableEntry{
Entry("example", "https://example.com", url.URL{
Scheme: "https",
Host: "example.com",
}),
Entry("file", "file:///log.txt", url.URL{
Scheme: "file",
Path: "/log.txt",
}),
}
It("unmarshal", func() {
var re config.Regexp
err := re.UnmarshalText([]byte(re1Str))
DescribeTable("unmarshal", func(urlStr string, url url.URL) {
var u config.BackendURL
err := u.UnmarshalText([]byte(urlStr))
Expect(err).To(Succeed())
Expect(u.URL).To(Equal(url))
}, t)
Expect(re.MatchString("1")).NotTo(BeTrue())
Expect(re.MatchString("a")).To(BeTrue())
})
It("marshal", func() {
re := config.Regexp{*re1}
reStr, err := re.MarshalText()
DescribeTable("marshal", func(urlStr string, url url.URL) {
u := config.URL{url}
m, err := u.MarshalText()
Expect(err).To(Succeed())
Expect(string(reStr)).To(Equal(re1Str), "MarshalText: %s != %s", string(reStr), re1Str)
})
Expect(string(m)).To(BeEquivalentTo(urlStr))
}, t)
It("fails on invalid regex", func() {
var re config.Regexp
err := re.UnmarshalText([]byte(re2Str))
It("fails for invalid urls", func() {
var u config.BackendURL
err := u.UnmarshalText([]byte("-"))
Expect(err).To(HaveOccurred())
})
})
@@ -100,42 +102,4 @@ var _ = Context("types", func() {
})
}
})
Context("key", func() {
var key crypto.Key
var keyStr, brokenKeyStr string
BeforeEach(func() {
var err error
key, err = crypto.GenerateKey()
Expect(err).To(Succeed())
keyStr = key.String()
brokenKeyStr = keyStr[:len(keyStr)-2]
})
It("unmarshal", func() {
var keyCfg config.Key
err := keyCfg.UnmarshalText([]byte(keyStr))
Expect(err).To(Succeed())
Expect(keyCfg).To(BeEquivalentTo(key))
})
It("marshal", func() {
keyCfg := config.Key(key)
keyCfgStr, err := keyCfg.MarshalText()
Expect(err).To(Succeed())
Expect(string(keyCfgStr)).To(Equal(keyStr))
})
It("fails on invalid key", func() {
var k config.Key
err := k.UnmarshalText([]byte(brokenKeyStr))
Expect(err).To(HaveOccurred())
})
})
})

View File

@@ -237,7 +237,7 @@ func (i *Interface) SyncConfig(cfgFilename string) error {
func (i *Interface) Configure(cfg *wg.Config) error {
if err := i.client.ConfigureDevice(i.Name(), cfg.Config); err != nil {
return fmt.Errorf("failed to sync interface config: %s", err)
return fmt.Errorf("failed to synchronize interface configuration: %s", err)
}
// TODO: remove old addresses?

View File

@@ -61,7 +61,7 @@ func NewDaemon(cfg *config.Config) (*Daemon, error) {
}
// Create watcher
if d.Watcher, err = watcher.New(d.Client, cfg.WatchInterval, cfg.WireGuard.InterfaceFilter.Regexp.MatchString); err != nil {
if d.Watcher, err = watcher.New(d.Client, cfg.WatchInterval, cfg.InterfaceFilter); err != nil {
return nil, fmt.Errorf("failed to initialize watcher: %w", err)
}
@@ -80,8 +80,9 @@ func NewDaemon(cfg *config.Config) (*Daemon, error) {
}
// Check if WireGuard interface can be created by the kernel
if !cfg.WireGuard.Userspace {
cfg.WireGuard.Userspace = !wg.KernelModuleExists()
if !cfg.DefaultInterfaceSettings.WireGuard.UserSpace && !wg.KernelModuleExists() {
d.logger.Warn("The system does not have kernel support for WireGuard. Falling back to user-space implementation.")
cfg.DefaultInterfaceSettings.WireGuard.UserSpace = true
}
d.Features, d.EndpointDiscovery = feat.NewFeatures(d.Watcher, d.Config, d.Client, d.Backend)
@@ -201,22 +202,21 @@ func (d *Daemon) CreateDevicesFromArgs() error {
return fmt.Errorf("failed to get existing WireGuard devices: %w", err)
}
for _, devName := range d.Config.WireGuard.Interfaces {
if !d.Config.WireGuard.InterfaceFilter.MatchString(devName) {
return fmt.Errorf("device '%s' is not matched by WireGuard interface filter '%s'",
devName, d.Config.WireGuard.InterfaceFilter.String())
}
for _, intf := range d.Config.Interfaces {
if intf.Name != "" {
if wgdev := devs.GetByName(intf.Name); wgdev != nil {
return fmt.Errorf("device '%s' already exists", intf.Name)
}
if wgdev := devs.GetByName(devName); wgdev != nil {
return fmt.Errorf("device '%s' already exists", devName)
}
intfCfg := d.Config.InterfaceSettings(intf.Name)
dev, err := device.NewDevice(devName, d.Config.WireGuard.Userspace)
if err != nil {
return fmt.Errorf("failed to create WireGuard device: %w", err)
}
dev, err := device.NewDevice(intf.Name, intfCfg.WireGuard.UserSpace)
if err != nil {
return fmt.Errorf("failed to create WireGuard device: %w", err)
}
d.devices = append(d.devices, dev)
d.devices = append(d.devices, dev)
}
}
return nil

View File

@@ -160,7 +160,12 @@ func (a *AutoConfig) fixupInterface(i *core.Interface) error {
if i.ListenPort == 0 {
logger.Warn("Device has no listen port. Setting a random one.")
port, err := util.FindNextPortToListen("udp", a.config.WireGuard.Port.Min, a.config.WireGuard.Port.Max)
icfg := a.config.InterfaceSettings(i.Name())
port, err := util.FindNextPortToListen("udp",
icfg.WireGuard.ListenPortRange.Min,
icfg.WireGuard.ListenPortRange.Max,
)
if err != nil {
return fmt.Errorf("failed set listen port: %w", err)
}

View File

@@ -214,7 +214,9 @@ func (p *Peer) createAgent() error {
p.logger.Info("Creating new agent")
// Prepare ICE agent configuration
acfg, err := p.config.AgentConfig()
icfg := p.config.InterfaceSettings(p.Interface.Name())
pk := p.Interface.PublicKey()
acfg, err := icfg.AgentConfig(context.Background(), &pk)
if err != nil {
return fmt.Errorf("failed to generate ICE agent configuration: %w", err)
}

View File

@@ -28,29 +28,29 @@ func NewFeatures(w *watcher.Watcher, cfg *config.Config, c *wgctrl.Client, b sig
var ep *epdisc.EndpointDiscovery
var feats = []Feature{}
if cfg.AutoConfig.Enabled {
if cfg.DefaultInterfaceSettings.AutoConfig.Enabled {
feats = append(feats, autocfg.New(w, cfg, c))
}
if cfg.ConfigSync.Enabled {
feats = append(feats, cfgsync.New(w, c, cfg.ConfigSync.Path, cfg.ConfigSync.Watch, cfg.WireGuard.Userspace, cfg.WireGuard.InterfaceFilter.MatchString))
if cfg.DefaultInterfaceSettings.ConfigSync.Enabled {
feats = append(feats, cfgsync.New(w, c, cfg.DefaultInterfaceSettings.ConfigSync.Path, cfg.DefaultInterfaceSettings.ConfigSync.Watch, cfg.DefaultInterfaceSettings.WireGuard.UserSpace, cfg.InterfaceFilter))
}
if cfg.RouteSync.Enabled {
feats = append(feats, rtsync.New(w, cfg.RouteSync.Table, cfg.RouteSync.Watch))
if cfg.DefaultInterfaceSettings.RouteSync.Enabled {
feats = append(feats, rtsync.New(w, cfg.DefaultInterfaceSettings.RouteSync.Table, cfg.DefaultInterfaceSettings.RouteSync.Watch))
}
if cfg.HostSync.Enabled {
if cfg.DefaultInterfaceSettings.HostSync.Enabled {
feats = append(feats, hsync.New(w))
}
if cfg.EndpointDisc.Enabled {
if cfg.DefaultInterfaceSettings.EndpointDisc.Enabled {
ep = epdisc.New(w, cfg, c, b)
feats = append(feats, ep)
}
if cfg.PeerDisc.Enabled && cfg.PeerDisc.Community != "" {
feats = append(feats, pdisc.New(w, c, b, cfg.PeerDisc.Community, cfg.PeerDisc.Whitelist))
if cfg.DefaultInterfaceSettings.PeerDisc.Enabled && cfg.DefaultInterfaceSettings.PeerDisc.Community != "" {
feats = append(feats, pdisc.New(w, c, b, cfg.DefaultInterfaceSettings.PeerDisc.Community, cfg.DefaultInterfaceSettings.PeerDisc.Whitelist))
}
if len(cfg.Hooks) > 0 {

View File

@@ -9,7 +9,6 @@ import (
"go.uber.org/zap"
"golang.zx2c4.com/wireguard/wgctrl"
"github.com/stv0g/cunicu/pkg/config"
"github.com/stv0g/cunicu/pkg/core"
"github.com/stv0g/cunicu/pkg/crypto"
"github.com/stv0g/cunicu/pkg/signaling"
@@ -30,7 +29,7 @@ type PeerDiscovery struct {
logger *zap.Logger
}
func New(w *watcher.Watcher, c *wgctrl.Client, b signaling.Backend, community string, whitelist []config.Key) *PeerDiscovery {
func New(w *watcher.Watcher, c *wgctrl.Client, b signaling.Backend, community string, whitelist []crypto.Key) *PeerDiscovery {
pd := &PeerDiscovery{
backend: b,
watcher: w,

View File

@@ -217,9 +217,9 @@ func (s *DaemonServer) GetConfig(ctx context.Context, p *rpcproto.GetConfigParam
settings["log.severity"] = log.Severity.String()
}
for _, key := range s.Config.Viper.AllKeys() {
for key, value := range s.Config.All() {
if match(key) {
settings[key] = fmt.Sprintf("%v", s.Config.Get(key))
settings[key] = fmt.Sprintf("%v", value)
}
}