mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-23 00:41:29 +02:00
Compare commits
148 Commits
fix/lower-
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb77ff4661 | ||
|
|
83b83ccfd2 | ||
|
|
4e75e15ea1 | ||
|
|
06afe64aff | ||
|
|
7acaef1152 | ||
|
|
469be3442d | ||
|
|
d1b6387803 | ||
|
|
820e2feec9 | ||
|
|
e0d086a8a8 | ||
|
|
1f95467b02 | ||
|
|
6553d8ce03 | ||
|
|
f0c829afac | ||
|
|
86f14523e4 | ||
|
|
6cefcbfe5d | ||
|
|
19103031ee | ||
|
|
7369f4bc38 | ||
|
|
d9d275a7ce | ||
|
|
57b85f4f8d | ||
|
|
7ef191903e | ||
|
|
3bd15dd1c4 | ||
|
|
1065e0a6c5 | ||
|
|
d4ff55e6fe | ||
|
|
5625d83c3f | ||
|
|
63f2f51614 | ||
|
|
defdcb631e | ||
|
|
7bf0d04bed | ||
|
|
e4ec1fd757 | ||
|
|
dab50f35d7 | ||
|
|
2d7e797e08 | ||
|
|
c3e8187a47 | ||
|
|
cfac8c4762 | ||
|
|
d9dfae625b | ||
|
|
a9e6742d9a | ||
|
|
931f165c9a | ||
|
|
2803e1669b | ||
|
|
f28a657a1d | ||
|
|
1f949f8cee | ||
|
|
75f5b75bc4 | ||
|
|
48a2f6e69d | ||
|
|
b3715b5fad | ||
|
|
836072098b | ||
|
|
85e991ff78 | ||
|
|
f9845e53a0 | ||
|
|
8845e8fbc7 | ||
|
|
1a5ee744a8 | ||
|
|
765aba2c1c | ||
|
|
7cb81f1d70 | ||
|
|
cea19de667 | ||
|
|
29e5eceb6b | ||
|
|
0f63737330 | ||
|
|
bf518c5fba | ||
|
|
eab6183a8e | ||
|
|
15a7b7629b | ||
|
|
4517da8b3a | ||
|
|
9c0d923124 | ||
|
|
6857734c48 | ||
|
|
d3785dc1fa | ||
|
|
ed82ef7fe4 | ||
|
|
aa55fba5ee | ||
|
|
faeae52329 | ||
|
|
9ae03046e7 | ||
|
|
98aa830831 | ||
|
|
c94c949173 | ||
|
|
183f746158 | ||
|
|
dd0d15c9d4 | ||
|
|
3b019800f8 | ||
|
|
4cd4f88666 | ||
|
|
4d0e16f2d0 | ||
|
|
3fcdb51376 | ||
|
|
c0efce6556 | ||
|
|
f0eb004582 | ||
|
|
0a59f12012 | ||
|
|
745e4f76b1 | ||
|
|
085d072b17 | ||
|
|
d2157bda66 | ||
|
|
0a67f5be1a | ||
|
|
43a8ba97e3 | ||
|
|
17874771cc | ||
|
|
f6ccf6b97a | ||
|
|
6aae797baf | ||
|
|
aca054e51e | ||
|
|
f72e852ccb | ||
|
|
10cee8f46e | ||
|
|
54dc78aab8 | ||
|
|
628673db20 | ||
|
|
eaa31c2dc6 | ||
|
|
25723e9b07 | ||
|
|
3cf4d5758f | ||
|
|
69d8d5aa86 | ||
|
|
7581bbd925 | ||
|
|
4d67d72785 | ||
|
|
4a08f1a1e9 | ||
|
|
bfe60c01ba | ||
|
|
06ceac65de | ||
|
|
6801dcb3f6 | ||
|
|
c7db2c0524 | ||
|
|
4f890ff712 | ||
|
|
f7e6aa9b8f | ||
|
|
81f2330d49 | ||
|
|
0261e15aad | ||
|
|
11de2ec42e | ||
|
|
4d2a25b728 | ||
|
|
2f32e0d8cf | ||
|
|
48310ef99c | ||
|
|
24f71bc68a | ||
|
|
e26e2c3a75 | ||
|
|
a5e664d83d | ||
|
|
d8ab3c1632 | ||
|
|
63b4041e9c | ||
|
|
f7d8d03e55 | ||
|
|
5b86a7f3f2 | ||
|
|
deb8203f06 | ||
|
|
e407fe02c5 | ||
|
|
a7760bf0a7 | ||
|
|
64f949abbb | ||
|
|
38f2a59d1b | ||
|
|
9504012920 | ||
|
|
5e93d117cf | ||
|
|
8c70b7d7ff | ||
|
|
ed8def4d9b | ||
|
|
1e115e3893 | ||
|
|
fed9e587af | ||
|
|
a40d4d2f32 | ||
|
|
15818b72c6 | ||
|
|
0556dc1860 | ||
|
|
2b369cd28f | ||
|
|
9d44a476c6 | ||
|
|
57ddb5f262 | ||
|
|
4ced07dd8d | ||
|
|
3430b81622 | ||
|
|
fd4ad15c83 | ||
|
|
4ff069a102 | ||
|
|
7cc3964a4d | ||
|
|
6d627f1923 | ||
|
|
076ce69a24 | ||
|
|
645a1f31a7 | ||
|
|
b4aa7e50f9 | ||
|
|
173ca25dac | ||
|
|
36b2cd16cc | ||
|
|
0a05f8b4d4 | ||
|
|
e82c0a55a3 | ||
|
|
13eb457132 | ||
|
|
1c9c9ae47e | ||
|
|
9ac5a1ed3f | ||
|
|
d4eaec5cbd | ||
|
|
6ae7a790f2 | ||
|
|
49dfbc82d9 | ||
|
|
57a89cf0cc |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -173,7 +173,7 @@ jobs:
|
||||
retention-days: 3
|
||||
|
||||
release_ui_darwin:
|
||||
runs-on: macos-11
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
||||
|
||||
59
.github/workflows/test-infrastructure-files.yml
vendored
59
.github/workflows/test-infrastructure-files.yml
vendored
@@ -178,34 +178,79 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: run script
|
||||
- name: run script with Zitadel PostgreSQL
|
||||
run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh
|
||||
|
||||
- name: test Caddy file gen
|
||||
- name: test Caddy file gen postgres
|
||||
run: test -f Caddyfile
|
||||
- name: test docker-compose file gen
|
||||
|
||||
- name: test docker-compose file gen postgres
|
||||
run: test -f docker-compose.yml
|
||||
- name: test management.json file gen
|
||||
|
||||
- name: test management.json file gen postgres
|
||||
run: test -f management.json
|
||||
- name: test turnserver.conf file gen
|
||||
|
||||
- name: test turnserver.conf file gen postgres
|
||||
run: |
|
||||
set -x
|
||||
test -f turnserver.conf
|
||||
grep external-ip turnserver.conf
|
||||
- name: test zitadel.env file gen
|
||||
|
||||
- name: test zitadel.env file gen postgres
|
||||
run: test -f zitadel.env
|
||||
- name: test dashboard.env file gen
|
||||
|
||||
- name: test dashboard.env file gen postgres
|
||||
run: test -f dashboard.env
|
||||
|
||||
- name: test zdb.env file gen postgres
|
||||
run: test -f zdb.env
|
||||
|
||||
- name: Postgres run cleanup
|
||||
run: |
|
||||
docker-compose down --volumes --rmi all
|
||||
rm -rf docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json zdb.env
|
||||
|
||||
- name: run script with Zitadel CockroachDB
|
||||
run: bash -x infrastructure_files/getting-started-with-zitadel.sh
|
||||
env:
|
||||
NETBIRD_DOMAIN: use-ip
|
||||
ZITADEL_DATABASE: cockroach
|
||||
|
||||
- name: test Caddy file gen CockroachDB
|
||||
run: test -f Caddyfile
|
||||
|
||||
- name: test docker-compose file gen CockroachDB
|
||||
run: test -f docker-compose.yml
|
||||
|
||||
- name: test management.json file gen CockroachDB
|
||||
run: test -f management.json
|
||||
|
||||
- name: test turnserver.conf file gen CockroachDB
|
||||
run: |
|
||||
set -x
|
||||
test -f turnserver.conf
|
||||
grep external-ip turnserver.conf
|
||||
|
||||
- name: test zitadel.env file gen CockroachDB
|
||||
run: test -f zitadel.env
|
||||
|
||||
- name: test dashboard.env file gen CockroachDB
|
||||
run: test -f dashboard.env
|
||||
|
||||
test-download-geolite2-script:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install jq
|
||||
run: sudo apt-get update && sudo apt-get install -y unzip sqlite3
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: test script
|
||||
run: bash -x infrastructure_files/download-geolite2.sh
|
||||
|
||||
- name: test mmdb file exists
|
||||
run: test -f GeoLite2-City.mmdb
|
||||
|
||||
- name: test geonames file exists
|
||||
run: test -f geonames.db
|
||||
|
||||
@@ -3,8 +3,10 @@ builds:
|
||||
- id: netbird-ui-darwin
|
||||
dir: client/ui
|
||||
binary: netbird-ui
|
||||
env: [CGO_ENABLED=1]
|
||||
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- MACOSX_DEPLOYMENT_TARGET=11.0
|
||||
- MACOS_DEPLOYMENT_TARGET=11.0
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM alpine:3.18.5
|
||||
FROM alpine:3.19
|
||||
RUN apk add --no-cache ca-certificates iptables ip6tables
|
||||
ENV NB_FOREGROUND_MODE=true
|
||||
ENTRYPOINT [ "/usr/local/bin/netbird","up"]
|
||||
|
||||
@@ -76,7 +76,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
store, cleanUp, err := mgmt.NewTestStoreFromJson(config.Datadir)
|
||||
store, cleanUp, err := mgmt.NewTestStoreFromJson(context.Background(), config.Datadir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,13 +87,13 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
iv, _ := integrations.NewIntegratedValidator(eventStore)
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv)
|
||||
iv, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, "")
|
||||
mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -163,7 +163,10 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
SetupCloseHandler(ctx, cancel)
|
||||
|
||||
connectClient := internal.NewConnectClient(ctx, config, peer.NewRecorder(config.ManagementURL.String()))
|
||||
r := peer.NewRecorder(config.ManagementURL.String())
|
||||
r.GetFullStatus()
|
||||
|
||||
connectClient := internal.NewConnectClient(ctx, config, r)
|
||||
return connectClient.Run()
|
||||
}
|
||||
|
||||
|
||||
@@ -74,12 +74,12 @@ func (i *routerManager) InsertRoutingRules(pair firewall.RouterPair) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = i.insertRoutingRule(firewall.NatFormat, tableNat, chainRTNAT, routingFinalNatJump, pair)
|
||||
err = i.addNATRule(firewall.NatFormat, tableNat, chainRTNAT, routingFinalNatJump, pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = i.insertRoutingRule(firewall.InNatFormat, tableNat, chainRTNAT, routingFinalNatJump, firewall.GetInPair(pair))
|
||||
err = i.addNATRule(firewall.InNatFormat, tableNat, chainRTNAT, routingFinalNatJump, firewall.GetInPair(pair))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,6 +101,7 @@ func (i *routerManager) insertRoutingRule(keyFormat, table, chain, jump string,
|
||||
}
|
||||
delete(i.rules, ruleKey)
|
||||
}
|
||||
|
||||
err = i.iptablesClient.Insert(table, chain, 1, rule...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while adding new %s rule for %s: %v", getIptablesRuleType(table), pair.Destination, err)
|
||||
@@ -317,6 +318,13 @@ func (i *routerManager) createChain(table, newChain string) error {
|
||||
return fmt.Errorf("couldn't create chain %s in %s table, error: %v", newChain, table, err)
|
||||
}
|
||||
|
||||
// Add the loopback return rule to the NAT chain
|
||||
loopbackRule := []string{"-o", "lo", "-j", "RETURN"}
|
||||
err = i.iptablesClient.Insert(table, newChain, 1, loopbackRule...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add loopback return rule to %s: %v", chainRTNAT, err)
|
||||
}
|
||||
|
||||
err = i.iptablesClient.Append(table, newChain, "-j", "RETURN")
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't create chain %s default rule, error: %v", newChain, err)
|
||||
@@ -326,6 +334,30 @@ func (i *routerManager) createChain(table, newChain string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// addNATRule appends an iptables rule pair to the nat chain
|
||||
func (i *routerManager) addNATRule(keyFormat, table, chain, jump string, pair firewall.RouterPair) error {
|
||||
ruleKey := firewall.GenKey(keyFormat, pair.ID)
|
||||
rule := genRuleSpec(jump, pair.Source, pair.Destination)
|
||||
existingRule, found := i.rules[ruleKey]
|
||||
if found {
|
||||
err := i.iptablesClient.DeleteIfExists(table, chain, existingRule...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while removing existing NAT rule for %s: %v", pair.Destination, err)
|
||||
}
|
||||
delete(i.rules, ruleKey)
|
||||
}
|
||||
|
||||
// inserting after loopback ignore rule
|
||||
err := i.iptablesClient.Insert(table, chain, 2, rule...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while appending new NAT rule for %s: %v", pair.Destination, err)
|
||||
}
|
||||
|
||||
i.rules[ruleKey] = rule
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// genRuleSpec generates rule specification
|
||||
func genRuleSpec(jump, source, destination string) []string {
|
||||
return []string{"-s", source, "-d", destination, "-j", jump}
|
||||
|
||||
@@ -95,7 +95,7 @@ func (m *Manager) InsertRoutingRules(pair firewall.RouterPair) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
return m.router.InsertRoutingRules(pair)
|
||||
return m.router.AddRoutingRules(pair)
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveRoutingRules(pair firewall.RouterPair) error {
|
||||
|
||||
@@ -22,6 +22,8 @@ const (
|
||||
|
||||
userDataAcceptForwardRuleSrc = "frwacceptsrc"
|
||||
userDataAcceptForwardRuleDst = "frwacceptdst"
|
||||
|
||||
loopbackInterface = "lo\x00"
|
||||
)
|
||||
|
||||
// some presets for building nftable rules
|
||||
@@ -126,6 +128,22 @@ func (r *router) createContainers() error {
|
||||
Type: nftables.ChainTypeNAT,
|
||||
})
|
||||
|
||||
// Add RETURN rule for loopback interface
|
||||
loRule := &nftables.Rule{
|
||||
Table: r.workTable,
|
||||
Chain: r.chains[chainNameRoutingNat],
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte(loopbackInterface),
|
||||
},
|
||||
&expr.Verdict{Kind: expr.VerdictReturn},
|
||||
},
|
||||
}
|
||||
r.conn.InsertRule(loRule)
|
||||
|
||||
err := r.refreshRulesMap()
|
||||
if err != nil {
|
||||
log.Errorf("failed to clean up rules from FORWARD chain: %s", err)
|
||||
@@ -138,28 +156,28 @@ func (r *router) createContainers() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertRoutingRules inserts a nftable rule pair to the forwarding chain and if enabled, to the nat chain
|
||||
func (r *router) InsertRoutingRules(pair manager.RouterPair) error {
|
||||
// AddRoutingRules appends a nftable rule pair to the forwarding chain and if enabled, to the nat chain
|
||||
func (r *router) AddRoutingRules(pair manager.RouterPair) error {
|
||||
err := r.refreshRulesMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.insertRoutingRule(manager.ForwardingFormat, chainNameRouteingFw, pair, false)
|
||||
err = r.addRoutingRule(manager.ForwardingFormat, chainNameRouteingFw, pair, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.insertRoutingRule(manager.InForwardingFormat, chainNameRouteingFw, manager.GetInPair(pair), false)
|
||||
err = r.addRoutingRule(manager.InForwardingFormat, chainNameRouteingFw, manager.GetInPair(pair), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pair.Masquerade {
|
||||
err = r.insertRoutingRule(manager.NatFormat, chainNameRoutingNat, pair, true)
|
||||
err = r.addRoutingRule(manager.NatFormat, chainNameRoutingNat, pair, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.insertRoutingRule(manager.InNatFormat, chainNameRoutingNat, manager.GetInPair(pair), true)
|
||||
err = r.addRoutingRule(manager.InNatFormat, chainNameRoutingNat, manager.GetInPair(pair), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -177,8 +195,8 @@ func (r *router) InsertRoutingRules(pair manager.RouterPair) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// insertRoutingRule inserts a nftable rule to the conn client flush queue
|
||||
func (r *router) insertRoutingRule(format, chainName string, pair manager.RouterPair, isNat bool) error {
|
||||
// addRoutingRule inserts a nftable rule to the conn client flush queue
|
||||
func (r *router) addRoutingRule(format, chainName string, pair manager.RouterPair, isNat bool) error {
|
||||
sourceExp := generateCIDRMatcherExpressions(true, pair.Source)
|
||||
destExp := generateCIDRMatcherExpressions(false, pair.Destination)
|
||||
|
||||
@@ -199,7 +217,7 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router
|
||||
}
|
||||
}
|
||||
|
||||
r.rules[ruleKey] = r.conn.InsertRule(&nftables.Rule{
|
||||
r.rules[ruleKey] = r.conn.AddRule(&nftables.Rule{
|
||||
Table: r.workTable,
|
||||
Chain: r.chains[chainName],
|
||||
Exprs: expression,
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
|
||||
|
||||
require.NoError(t, err, "shouldn't return error")
|
||||
|
||||
err = manager.InsertRoutingRules(testCase.InputPair)
|
||||
err = manager.AddRoutingRules(testCase.InputPair)
|
||||
defer func() {
|
||||
_ = manager.RemoveRoutingRules(testCase.InputPair)
|
||||
}()
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
mgm "github.com/netbirdio/netbird/management/client"
|
||||
mgmProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/relay/auth/hmac"
|
||||
relayClient "github.com/netbirdio/netbird/relay/client"
|
||||
signal "github.com/netbirdio/netbird/signal/client"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
"github.com/netbirdio/netbird/version"
|
||||
@@ -244,6 +246,20 @@ func (c *ConnectClient) run(
|
||||
|
||||
c.statusRecorder.MarkSignalConnected()
|
||||
|
||||
relayURL, token := parseRelayInfo(loginResp)
|
||||
relayManager := relayClient.NewManager(engineCtx, relayURL, myPrivateKey.PublicKey().String())
|
||||
if relayURL != "" {
|
||||
if token != nil {
|
||||
relayManager.UpdateToken(token)
|
||||
}
|
||||
log.Infof("connecting to the Relay service %s", relayURL)
|
||||
if err = relayManager.Serve(); err != nil {
|
||||
log.Error(err)
|
||||
return wrapErr(err)
|
||||
}
|
||||
c.statusRecorder.SetRelayMgr(relayManager)
|
||||
}
|
||||
|
||||
peerConfig := loginResp.GetPeerConfig()
|
||||
|
||||
engineConfig, err := createEngineConfig(myPrivateKey, c.config, peerConfig)
|
||||
@@ -255,7 +271,7 @@ func (c *ConnectClient) run(
|
||||
checks := loginResp.GetChecks()
|
||||
|
||||
c.engineMutex.Lock()
|
||||
c.engine = NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, engineConfig, mobileDependency, c.statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe, checks)
|
||||
c.engine = NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, relayManager, engineConfig, mobileDependency, c.statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe, checks)
|
||||
c.engineMutex.Unlock()
|
||||
|
||||
err = c.engine.Start()
|
||||
@@ -299,6 +315,30 @@ func (c *ConnectClient) run(
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRelayInfo(resp *mgmProto.LoginResponse) (string, *hmac.Token) {
|
||||
// todo remove this
|
||||
if ra := peer.ForcedRelayAddress(); ra != "" {
|
||||
return ra, nil
|
||||
}
|
||||
|
||||
msg := resp.GetWiretrusteeConfig().GetRelay()
|
||||
if msg == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var url string
|
||||
if msg.GetUrls() != nil && len(msg.GetUrls()) > 0 {
|
||||
url = msg.GetUrls()[0]
|
||||
}
|
||||
|
||||
token := &hmac.Token{
|
||||
Payload: msg.GetTokenPayload(),
|
||||
Signature: msg.GetTokenSignature(),
|
||||
}
|
||||
|
||||
return url, token
|
||||
}
|
||||
|
||||
func (c *ConnectClient) Engine() *Engine {
|
||||
var e *Engine
|
||||
c.engineMutex.Lock()
|
||||
|
||||
@@ -78,6 +78,11 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||
}()
|
||||
|
||||
log.WithField("question", r.Question[0]).Trace("received an upstream question")
|
||||
// set the AuthenticatedData flag and the EDNS0 buffer size to 4096 bytes to support larger dns records
|
||||
if r.Extra == nil {
|
||||
r.SetEdns0(4096, false)
|
||||
r.MsgHdr.AuthenticatedData = true
|
||||
}
|
||||
|
||||
select {
|
||||
case <-u.ctx.Done():
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v3"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"github.com/netbirdio/netbird/client/firewall/manager"
|
||||
"github.com/netbirdio/netbird/client/internal/acl"
|
||||
"github.com/netbirdio/netbird/client/internal/dns"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/networkmonitor"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/relay"
|
||||
@@ -39,6 +41,8 @@ import (
|
||||
mgm "github.com/netbirdio/netbird/management/client"
|
||||
"github.com/netbirdio/netbird/management/domain"
|
||||
mgmProto "github.com/netbirdio/netbird/management/proto"
|
||||
auth "github.com/netbirdio/netbird/relay/auth/hmac"
|
||||
relayClient "github.com/netbirdio/netbird/relay/client"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
signal "github.com/netbirdio/netbird/signal/client"
|
||||
sProto "github.com/netbirdio/netbird/signal/proto"
|
||||
@@ -101,7 +105,8 @@ type EngineConfig struct {
|
||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||
type Engine struct {
|
||||
// signal is a Signal Service client
|
||||
signal signal.Client
|
||||
signal signal.Client
|
||||
signaler *peer.Signaler
|
||||
// mgmClient is a Management Service client
|
||||
mgmClient mgm.Client
|
||||
// peerConns is a map that holds all the peers that are known to this peer
|
||||
@@ -122,7 +127,8 @@ type Engine struct {
|
||||
// STUNs is a list of STUN servers used by ICE
|
||||
STUNs []*stun.URI
|
||||
// TURNs is a list of STUN servers used by ICE
|
||||
TURNs []*stun.URI
|
||||
TURNs []*stun.URI
|
||||
StunTurn atomic.Value
|
||||
|
||||
// clientRoutes is the most recent list of clientRoutes received from the Management Service
|
||||
clientRoutes route.HAMap
|
||||
@@ -134,7 +140,7 @@ type Engine struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
wgInterface *iface.WGIface
|
||||
wgInterface iface.IWGIface
|
||||
wgProxyFactory *wgproxy.Factory
|
||||
|
||||
udpMux *bind.UniversalUDPMuxDefault
|
||||
@@ -160,10 +166,10 @@ type Engine struct {
|
||||
relayProbe *Probe
|
||||
wgProbe *Probe
|
||||
|
||||
wgConnWorker sync.WaitGroup
|
||||
|
||||
// checks are the client-applied posture checks that need to be evaluated on the client
|
||||
checks []*mgmProto.Checks
|
||||
|
||||
relayManager *relayClient.Manager
|
||||
}
|
||||
|
||||
// Peer is an instance of the Connection Peer
|
||||
@@ -178,6 +184,7 @@ func NewEngine(
|
||||
clientCancel context.CancelFunc,
|
||||
signalClient signal.Client,
|
||||
mgmClient mgm.Client,
|
||||
relayManager *relayClient.Manager,
|
||||
config *EngineConfig,
|
||||
mobileDep MobileDependency,
|
||||
statusRecorder *peer.Status,
|
||||
@@ -188,6 +195,7 @@ func NewEngine(
|
||||
clientCancel,
|
||||
signalClient,
|
||||
mgmClient,
|
||||
relayManager,
|
||||
config,
|
||||
mobileDep,
|
||||
statusRecorder,
|
||||
@@ -205,6 +213,7 @@ func NewEngineWithProbes(
|
||||
clientCancel context.CancelFunc,
|
||||
signalClient signal.Client,
|
||||
mgmClient mgm.Client,
|
||||
relayManager *relayClient.Manager,
|
||||
config *EngineConfig,
|
||||
mobileDep MobileDependency,
|
||||
statusRecorder *peer.Status,
|
||||
@@ -214,12 +223,13 @@ func NewEngineWithProbes(
|
||||
wgProbe *Probe,
|
||||
checks []*mgmProto.Checks,
|
||||
) *Engine {
|
||||
|
||||
return &Engine{
|
||||
clientCtx: clientCtx,
|
||||
clientCancel: clientCancel,
|
||||
signal: signalClient,
|
||||
signaler: peer.NewSignaler(signalClient, config.WgPrivateKey),
|
||||
mgmClient: mgmClient,
|
||||
relayManager: relayManager,
|
||||
peerConns: make(map[string]*peer.Conn),
|
||||
syncMsgMux: &sync.Mutex{},
|
||||
config: config,
|
||||
@@ -265,7 +275,6 @@ func (e *Engine) Stop() error {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
e.close()
|
||||
e.wgConnWorker.Wait()
|
||||
log.Infof("stopped Netbird Engine")
|
||||
return nil
|
||||
}
|
||||
@@ -282,8 +291,6 @@ func (e *Engine) Start() error {
|
||||
}
|
||||
e.ctx, e.cancel = context.WithCancel(e.clientCtx)
|
||||
|
||||
e.wgProxyFactory = wgproxy.NewFactory(e.ctx, e.config.WgPort)
|
||||
|
||||
wgIface, err := e.newWgIface()
|
||||
if err != nil {
|
||||
log.Errorf("failed creating wireguard interface instance %s: [%s]", e.config.WgIfaceName, err)
|
||||
@@ -291,6 +298,9 @@ func (e *Engine) Start() error {
|
||||
}
|
||||
e.wgInterface = wgIface
|
||||
|
||||
userspace := e.wgInterface.IsUserspaceBind()
|
||||
e.wgProxyFactory = wgproxy.NewFactory(e.ctx, userspace, e.config.WgPort)
|
||||
|
||||
if e.config.RosenpassEnabled {
|
||||
log.Infof("rosenpass is enabled")
|
||||
if e.config.RosenpassPermissive {
|
||||
@@ -464,80 +474,42 @@ func (e *Engine) removePeer(peerKey string) error {
|
||||
conn, exists := e.peerConns[peerKey]
|
||||
if exists {
|
||||
delete(e.peerConns, peerKey)
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *peer.ConnectionAlreadyClosedError:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func signalCandidate(candidate ice.Candidate, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client) error {
|
||||
err := s.Send(&sProto.Message{
|
||||
Key: myKey.PublicKey().String(),
|
||||
RemoteKey: remoteKey.String(),
|
||||
Body: &sProto.Body{
|
||||
Type: sProto.Body_CANDIDATE,
|
||||
Payload: candidate.Marshal(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendSignal(message *sProto.Message, s signal.Client) error {
|
||||
return s.Send(message)
|
||||
}
|
||||
|
||||
// SignalOfferAnswer signals either an offer or an answer to remote peer
|
||||
func SignalOfferAnswer(offerAnswer peer.OfferAnswer, myKey wgtypes.Key, remoteKey wgtypes.Key, s signal.Client,
|
||||
isAnswer bool) error {
|
||||
var t sProto.Body_Type
|
||||
if isAnswer {
|
||||
t = sProto.Body_ANSWER
|
||||
} else {
|
||||
t = sProto.Body_OFFER
|
||||
}
|
||||
|
||||
msg, err := signal.MarshalCredential(myKey, offerAnswer.WgListenPort, remoteKey, &signal.Credential{
|
||||
UFrag: offerAnswer.IceCredentials.UFrag,
|
||||
Pwd: offerAnswer.IceCredentials.Pwd,
|
||||
}, t, offerAnswer.RosenpassPubKey, offerAnswer.RosenpassAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Send(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
if update.GetWiretrusteeConfig() != nil {
|
||||
err := e.updateTURNs(update.GetWiretrusteeConfig().GetTurns())
|
||||
wCfg := update.GetWiretrusteeConfig()
|
||||
err := e.updateTURNs(wCfg.GetTurns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.updateSTUNs(update.GetWiretrusteeConfig().GetStuns())
|
||||
err = e.updateSTUNs(wCfg.GetStuns())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var stunTurn []*stun.URI
|
||||
stunTurn = append(stunTurn, e.STUNs...)
|
||||
stunTurn = append(stunTurn, e.TURNs...)
|
||||
e.StunTurn.Store(stunTurn)
|
||||
|
||||
relayMsg := wCfg.GetRelay()
|
||||
if relayMsg != nil {
|
||||
c := &auth.Token{
|
||||
Payload: relayMsg.GetTokenPayload(),
|
||||
Signature: relayMsg.GetTokenSignature(),
|
||||
}
|
||||
e.relayManager.UpdateToken(c)
|
||||
}
|
||||
|
||||
// todo update relay address in the relay manager
|
||||
// todo update signal
|
||||
}
|
||||
|
||||
@@ -933,68 +905,13 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
|
||||
log.Warnf("error adding peer %s to status recorder, got error: %v", peerKey, err)
|
||||
}
|
||||
|
||||
e.wgConnWorker.Add(1)
|
||||
go e.connWorker(conn, peerKey)
|
||||
conn.Open()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
|
||||
defer e.wgConnWorker.Done()
|
||||
for {
|
||||
|
||||
// randomize starting time a bit
|
||||
min := 500
|
||||
max := 2000
|
||||
duration := time.Duration(rand.Intn(max-min)+min) * time.Millisecond
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
return
|
||||
case <-time.After(duration):
|
||||
}
|
||||
|
||||
// if peer has been removed -> give up
|
||||
if !e.peerExists(peerKey) {
|
||||
log.Debugf("peer %s doesn't exist anymore, won't retry connection", peerKey)
|
||||
return
|
||||
}
|
||||
|
||||
if !e.signal.Ready() {
|
||||
log.Infof("signal client isn't ready, skipping connection attempt %s", peerKey)
|
||||
continue
|
||||
}
|
||||
|
||||
// we might have received new STUN and TURN servers meanwhile, so update them
|
||||
e.syncMsgMux.Lock()
|
||||
conn.UpdateStunTurn(append(e.STUNs, e.TURNs...))
|
||||
e.syncMsgMux.Unlock()
|
||||
|
||||
err := conn.Open(e.ctx)
|
||||
if err != nil {
|
||||
log.Debugf("connection to peer %s failed: %v", peerKey, err)
|
||||
var connectionClosedError *peer.ConnectionClosedError
|
||||
switch {
|
||||
case errors.As(err, &connectionClosedError):
|
||||
// conn has been forced to close, so we exit the loop
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) peerExists(peerKey string) bool {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
_, ok := e.peerConns[peerKey]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, error) {
|
||||
log.Debugf("creating peer connection %s", pubKey)
|
||||
var stunTurn []*stun.URI
|
||||
stunTurn = append(stunTurn, e.STUNs...)
|
||||
stunTurn = append(stunTurn, e.TURNs...)
|
||||
|
||||
wgConfig := peer.WgConfig{
|
||||
RemoteKey: pubKey,
|
||||
@@ -1027,52 +944,29 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
|
||||
// randomize connection timeout
|
||||
timeout := time.Duration(rand.Intn(PeerConnectionTimeoutMax-PeerConnectionTimeoutMin)+PeerConnectionTimeoutMin) * time.Millisecond
|
||||
config := peer.ConnConfig{
|
||||
Key: pubKey,
|
||||
LocalKey: e.config.WgPrivateKey.PublicKey().String(),
|
||||
StunTurn: stunTurn,
|
||||
InterfaceBlackList: e.config.IFaceBlackList,
|
||||
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
||||
Timeout: timeout,
|
||||
UDPMux: e.udpMux.UDPMuxDefault,
|
||||
UDPMuxSrflx: e.udpMux,
|
||||
WgConfig: wgConfig,
|
||||
LocalWgPort: e.config.WgPort,
|
||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||
RosenpassPubKey: e.getRosenpassPubKey(),
|
||||
RosenpassAddr: e.getRosenpassAddr(),
|
||||
Key: pubKey,
|
||||
LocalKey: e.config.WgPrivateKey.PublicKey().String(),
|
||||
Timeout: timeout,
|
||||
WgConfig: wgConfig,
|
||||
LocalWgPort: e.config.WgPort,
|
||||
RosenpassPubKey: e.getRosenpassPubKey(),
|
||||
RosenpassAddr: e.getRosenpassAddr(),
|
||||
ICEConfig: peer.ICEConfig{
|
||||
StunTurn: e.StunTurn,
|
||||
InterfaceBlackList: e.config.IFaceBlackList,
|
||||
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
||||
UDPMux: e.udpMux.UDPMuxDefault,
|
||||
UDPMuxSrflx: e.udpMux,
|
||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||
},
|
||||
}
|
||||
|
||||
peerConn, err := peer.NewConn(config, e.statusRecorder, e.wgProxyFactory, e.mobileDep.TunAdapter, e.mobileDep.IFaceDiscover)
|
||||
peerConn, err := peer.NewConn(e.ctx, config, e.statusRecorder, e.wgProxyFactory, e.signaler, e.mobileDep.IFaceDiscover, e.relayManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wgPubKey, err := wgtypes.ParseKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signalOffer := func(offerAnswer peer.OfferAnswer) error {
|
||||
return SignalOfferAnswer(offerAnswer, e.config.WgPrivateKey, wgPubKey, e.signal, false)
|
||||
}
|
||||
|
||||
signalCandidate := func(candidate ice.Candidate) error {
|
||||
return signalCandidate(candidate, e.config.WgPrivateKey, wgPubKey, e.signal)
|
||||
}
|
||||
|
||||
signalAnswer := func(offerAnswer peer.OfferAnswer) error {
|
||||
return SignalOfferAnswer(offerAnswer, e.config.WgPrivateKey, wgPubKey, e.signal, true)
|
||||
}
|
||||
|
||||
peerConn.SetSignalCandidate(signalCandidate)
|
||||
peerConn.SetSignalOffer(signalOffer)
|
||||
peerConn.SetSignalAnswer(signalAnswer)
|
||||
peerConn.SetSendSignalMessage(func(message *sProto.Message) error {
|
||||
return sendSignal(message, e.signal)
|
||||
})
|
||||
|
||||
if e.rpManager != nil {
|
||||
|
||||
peerConn.SetOnConnected(e.rpManager.OnConnected)
|
||||
peerConn.SetOnDisconnected(e.rpManager.OnDisconnected)
|
||||
}
|
||||
@@ -1115,6 +1009,7 @@ func (e *Engine) receiveSignalEvents() {
|
||||
Version: msg.GetBody().GetNetBirdVersion(),
|
||||
RosenpassPubKey: rosenpassPubKey,
|
||||
RosenpassAddr: rosenpassAddr,
|
||||
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
|
||||
})
|
||||
case sProto.Body_ANSWER:
|
||||
remoteCred, err := signal.UnMarshalCredential(msg)
|
||||
@@ -1137,6 +1032,7 @@ func (e *Engine) receiveSignalEvents() {
|
||||
Version: msg.GetBody().GetNetBirdVersion(),
|
||||
RosenpassPubKey: rosenpassPubKey,
|
||||
RosenpassAddr: rosenpassAddr,
|
||||
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
|
||||
})
|
||||
case sProto.Body_CANDIDATE:
|
||||
candidate, err := ice.UnmarshalCandidate(msg.GetBody().Payload)
|
||||
@@ -1145,7 +1041,7 @@ func (e *Engine) receiveSignalEvents() {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.OnRemoteCandidate(candidate, e.GetClientRoutes())
|
||||
go conn.OnRemoteCandidate(candidate, e.GetClientRoutes())
|
||||
case sProto.Body_MODE:
|
||||
}
|
||||
|
||||
@@ -1441,7 +1337,7 @@ func (e *Engine) receiveProbeEvents() {
|
||||
|
||||
for _, peer := range e.peerConns {
|
||||
key := peer.GetKey()
|
||||
wgStats, err := peer.GetConf().WgConfig.WgInterface.GetStats(key)
|
||||
wgStats, err := peer.WgConfig().WgInterface.GetStats(key)
|
||||
if err != nil {
|
||||
log.Debugf("failed to get wg stats for peer %s: %s", key, err)
|
||||
}
|
||||
@@ -1464,6 +1360,15 @@ func (e *Engine) probeTURNs() []relay.ProbeResult {
|
||||
return relay.ProbeAll(e.ctx, relay.ProbeTURN, e.TURNs)
|
||||
}
|
||||
|
||||
func (e *Engine) restartEngine() {
|
||||
if err := e.Stop(); err != nil {
|
||||
log.Errorf("Failed to stop engine: %v", err)
|
||||
}
|
||||
if err := e.Start(); err != nil {
|
||||
log.Errorf("Failed to start engine: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) startNetworkMonitor() {
|
||||
if !e.config.NetworkMonitor {
|
||||
log.Infof("Network monitor is disabled, not starting")
|
||||
@@ -1472,14 +1377,29 @@ func (e *Engine) startNetworkMonitor() {
|
||||
|
||||
e.networkMonitor = networkmonitor.New()
|
||||
go func() {
|
||||
var mu sync.Mutex
|
||||
var debounceTimer *time.Timer
|
||||
|
||||
// Start the network monitor with a callback, Start will block until the monitor is stopped,
|
||||
// a network change is detected, or an error occurs on start up
|
||||
err := e.networkMonitor.Start(e.ctx, func() {
|
||||
log.Infof("Network monitor detected network change, restarting engine")
|
||||
if err := e.Stop(); err != nil {
|
||||
log.Errorf("Failed to stop engine: %v", err)
|
||||
}
|
||||
if err := e.Start(); err != nil {
|
||||
log.Errorf("Failed to start engine: %v", err)
|
||||
// This function is called when a network change is detected
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if debounceTimer != nil {
|
||||
debounceTimer.Stop()
|
||||
}
|
||||
|
||||
// Set a new timer to debounce rapid network changes
|
||||
debounceTimer = time.AfterFunc(1*time.Second, func() {
|
||||
// This function is called after the debounce period
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
log.Infof("Network monitor detected network change, restarting engine")
|
||||
e.restartEngine()
|
||||
})
|
||||
})
|
||||
if err != nil && !errors.Is(err, networkmonitor.ErrStopped) {
|
||||
log.Errorf("Network monitor: %v", err)
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
relayClient "github.com/netbirdio/netbird/relay/client"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
signal "github.com/netbirdio/netbird/signal/client"
|
||||
"github.com/netbirdio/netbird/signal/proto"
|
||||
@@ -57,6 +58,12 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
_ = util.InitLog("debug", "console")
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestEngine_SSH(t *testing.T) {
|
||||
// todo resolve test execution on freebsd
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "freebsd" {
|
||||
@@ -72,13 +79,23 @@ func TestEngine_SSH(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
WgIfaceName: "utun101",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
ServerSSHAllowed: true,
|
||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
||||
relayMgr := relayClient.NewManager(ctx, "", key.PublicKey().String())
|
||||
engine := NewEngine(
|
||||
ctx, cancel,
|
||||
&signal.MockClient{},
|
||||
&mgmt.MockClient{},
|
||||
relayMgr,
|
||||
&EngineConfig{
|
||||
WgIfaceName: "utun101",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
ServerSSHAllowed: true,
|
||||
},
|
||||
MobileDependency{},
|
||||
peer.NewRecorder("https://mgm"),
|
||||
nil,
|
||||
)
|
||||
|
||||
engine.dnsServer = &dns.MockServer{
|
||||
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||
@@ -174,7 +191,7 @@ func TestEngine_SSH(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//time.Sleep(250 * time.Millisecond)
|
||||
// time.Sleep(250 * time.Millisecond)
|
||||
assert.NotNil(t, engine.sshServer)
|
||||
assert.Contains(t, sshPeersRemoved, "MNHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=")
|
||||
|
||||
@@ -207,20 +224,28 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
WgIfaceName: "utun102",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil)
|
||||
newNet, err := stdnet.NewNet()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
relayMgr := relayClient.NewManager(ctx, "", key.PublicKey().String())
|
||||
engine := NewEngine(
|
||||
ctx, cancel,
|
||||
&signal.MockClient{},
|
||||
&mgmt.MockClient{},
|
||||
relayMgr,
|
||||
&EngineConfig{
|
||||
WgIfaceName: "utun102",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
},
|
||||
MobileDependency{},
|
||||
peer.NewRecorder("https://mgm"),
|
||||
nil)
|
||||
|
||||
wgIface := &iface.MockWGIface{
|
||||
RemovePeerFunc: func(peerKey string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
engine.wgInterface = wgIface
|
||||
engine.routeManager = routemanager.NewManager(ctx, key.PublicKey().String(), time.Minute, engine.wgInterface, engine.statusRecorder, nil)
|
||||
engine.dnsServer = &dns.MockServer{
|
||||
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||
@@ -403,8 +428,8 @@ func TestEngine_Sync(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, &EngineConfig{
|
||||
relayMgr := relayClient.NewManager(ctx, "", key.PublicKey().String())
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, relayMgr, &EngineConfig{
|
||||
WgIfaceName: "utun103",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
@@ -563,7 +588,8 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
||||
wgIfaceName := fmt.Sprintf("utun%d", 104+n)
|
||||
wgAddr := fmt.Sprintf("100.66.%d.1/24", n)
|
||||
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
relayMgr := relayClient.NewManager(ctx, "", key.PublicKey().String())
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, relayMgr, &EngineConfig{
|
||||
WgIfaceName: wgIfaceName,
|
||||
WgAddr: wgAddr,
|
||||
WgPrivateKey: key,
|
||||
@@ -733,7 +759,8 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
||||
wgIfaceName := fmt.Sprintf("utun%d", 104+n)
|
||||
wgAddr := fmt.Sprintf("100.66.%d.1/24", n)
|
||||
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
relayMgr := relayClient.NewManager(ctx, "", key.PublicKey().String())
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, relayMgr, &EngineConfig{
|
||||
WgIfaceName: wgIfaceName,
|
||||
WgAddr: wgAddr,
|
||||
WgPrivateKey: key,
|
||||
@@ -1009,7 +1036,8 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
||||
WgPort: wgPort,
|
||||
}
|
||||
|
||||
e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, conf, MobileDependency{}, peer.NewRecorder("https://mgm"), nil), nil
|
||||
relayMgr := relayClient.NewManager(ctx, "", key.PublicKey().String())
|
||||
e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, relayMgr, conf, MobileDependency{}, peer.NewRecorder("https://mgm"), nil), nil
|
||||
e.ctx = ctx
|
||||
return e, err
|
||||
}
|
||||
@@ -1057,7 +1085,7 @@ func startManagement(t *testing.T, dataDir string) (*grpc.Server, string, error)
|
||||
}
|
||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
|
||||
store, cleanUp, err := server.NewTestStoreFromJson(config.Datadir)
|
||||
store, cleanUp, err := server.NewTestStoreFromJson(context.Background(), config.Datadir)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -1068,13 +1096,13 @@ func startManagement(t *testing.T, dataDir string) (*grpc.Server, string, error)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
ia, _ := integrations.NewIntegratedValidator(eventStore)
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, "")
|
||||
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -45,24 +45,6 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
|
||||
msg := (*unix.RtMsghdr)(unsafe.Pointer(&buf[0]))
|
||||
|
||||
switch msg.Type {
|
||||
|
||||
// handle interface state changes
|
||||
case unix.RTM_IFINFO:
|
||||
ifinfo, err := parseInterfaceMessage(buf[:n])
|
||||
if err != nil {
|
||||
log.Errorf("Network monitor: error parsing interface message: %v", err)
|
||||
continue
|
||||
}
|
||||
if msg.Flags&unix.IFF_UP != 0 {
|
||||
continue
|
||||
}
|
||||
if (nexthopv4.Intf == nil || ifinfo.Index != nexthopv4.Intf.Index) && (nexthopv6.Intf == nil || ifinfo.Index != nexthopv6.Intf.Index) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Network monitor: monitored interface (%s) is down.", ifinfo.Name)
|
||||
go callback()
|
||||
|
||||
// handle route changes
|
||||
case unix.RTM_ADD, syscall.RTM_DELETE:
|
||||
route, err := parseRouteMessage(buf[:n])
|
||||
@@ -94,24 +76,6 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
|
||||
}
|
||||
}
|
||||
|
||||
func parseInterfaceMessage(buf []byte) (*route.InterfaceMessage, error) {
|
||||
msgs, err := route.ParseRIB(route.RIBTypeInterface, buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse RIB: %v", err)
|
||||
}
|
||||
|
||||
if len(msgs) != 1 {
|
||||
return nil, fmt.Errorf("unexpected RIB message msgs: %v", msgs)
|
||||
}
|
||||
|
||||
msg, ok := msgs[0].(*route.InterfaceMessage)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected RIB message type: %T", msgs[0])
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func parseRouteMessage(buf []byte) (*systemops.Route, error) {
|
||||
msgs, err := route.ParseRIB(route.RIBTypeRoute, buf)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,14 +19,9 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
|
||||
return errors.New("no interfaces available")
|
||||
}
|
||||
|
||||
linkChan := make(chan netlink.LinkUpdate)
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
if err := netlink.LinkSubscribe(linkChan, done); err != nil {
|
||||
return fmt.Errorf("subscribe to link updates: %v", err)
|
||||
}
|
||||
|
||||
routeChan := make(chan netlink.RouteUpdate)
|
||||
if err := netlink.RouteSubscribe(routeChan, done); err != nil {
|
||||
return fmt.Errorf("subscribe to route updates: %v", err)
|
||||
@@ -38,25 +33,6 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
|
||||
case <-ctx.Done():
|
||||
return ErrStopped
|
||||
|
||||
// handle interface state changes
|
||||
case update := <-linkChan:
|
||||
if (nexthopv4.Intf == nil || update.Index != int32(nexthopv4.Intf.Index)) && (nexthopv6.Intf == nil || update.Index != int32(nexthopv6.Intf.Index)) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch update.Header.Type {
|
||||
case syscall.RTM_DELLINK:
|
||||
log.Infof("Network monitor: monitored interface (%s) is gone", update.Link.Attrs().Name)
|
||||
go callback()
|
||||
return nil
|
||||
case syscall.RTM_NEWLINK:
|
||||
if (update.IfInfomsg.Flags&syscall.IFF_RUNNING) == 0 && update.Link.Attrs().OperState == netlink.OperDown {
|
||||
log.Infof("Network monitor: monitored interface (%s) is down.", update.Link.Attrs().Name)
|
||||
go callback()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// handle route changes
|
||||
case route := <-routeChan:
|
||||
// default route and main table
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -33,12 +34,8 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
|
||||
return fmt.Errorf("get neighbors: %w", err)
|
||||
}
|
||||
|
||||
if n, ok := initialNeighbors[nexthopv4.IP]; ok {
|
||||
neighborv4 = &n
|
||||
}
|
||||
if n, ok := initialNeighbors[nexthopv6.IP]; ok {
|
||||
neighborv6 = &n
|
||||
}
|
||||
neighborv4 = assignNeighbor(nexthopv4, initialNeighbors)
|
||||
neighborv6 = assignNeighbor(nexthopv6, initialNeighbors)
|
||||
}
|
||||
log.Debugf("Network monitor: initial IPv4 neighbor: %v, IPv6 neighbor: %v", neighborv4, neighborv6)
|
||||
|
||||
@@ -58,6 +55,16 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop, ca
|
||||
}
|
||||
}
|
||||
|
||||
func assignNeighbor(nexthop systemops.Nexthop, initialNeighbors map[netip.Addr]systemops.Neighbor) *systemops.Neighbor {
|
||||
if n, ok := initialNeighbors[nexthop.IP]; ok &&
|
||||
n.State != unreachable &&
|
||||
n.State != incomplete &&
|
||||
n.State != tbd {
|
||||
return &n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changed(
|
||||
nexthopv4 systemops.Nexthop,
|
||||
neighborv4 *systemops.Neighbor,
|
||||
@@ -87,37 +94,64 @@ func changed(
|
||||
}
|
||||
|
||||
// routeChanged checks if the default routes still point to our nexthop/interface
|
||||
func routeChanged(nexthop systemops.Nexthop, intf *net.Interface, routes map[netip.Prefix]systemops.Route) bool {
|
||||
func routeChanged(nexthop systemops.Nexthop, intf *net.Interface, routes []systemops.Route) bool {
|
||||
if !nexthop.IP.IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
var unspec netip.Prefix
|
||||
if nexthop.IP.Is6() {
|
||||
unspec = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||
} else {
|
||||
unspec = netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
||||
}
|
||||
unspec := getUnspecifiedPrefix(nexthop.IP)
|
||||
defaultRoutes, foundMatchingRoute := processRoutes(nexthop, intf, routes, unspec)
|
||||
|
||||
if r, ok := routes[unspec]; ok {
|
||||
if r.Nexthop != nexthop.IP || compareIntf(r.Interface, intf) != 0 {
|
||||
oldIntf, newIntf := "<nil>", "<nil>"
|
||||
if intf != nil {
|
||||
oldIntf = intf.Name
|
||||
}
|
||||
if r.Interface != nil {
|
||||
newIntf = r.Interface.Name
|
||||
}
|
||||
log.Infof("network monitor: default route changed: %s from %s (%s) to %s (%s)", r.Destination, nexthop.IP, oldIntf, r.Nexthop, newIntf)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
log.Infof("network monitor: default route is gone")
|
||||
log.Tracef("network monitor: all default routes:\n%s", strings.Join(defaultRoutes, "\n"))
|
||||
|
||||
if !foundMatchingRoute {
|
||||
logRouteChange(nexthop.IP, intf)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getUnspecifiedPrefix(ip netip.Addr) netip.Prefix {
|
||||
if ip.Is6() {
|
||||
return netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||
}
|
||||
return netip.PrefixFrom(netip.IPv4Unspecified(), 0)
|
||||
}
|
||||
|
||||
func processRoutes(nexthop systemops.Nexthop, intf *net.Interface, routes []systemops.Route, unspec netip.Prefix) ([]string, bool) {
|
||||
var defaultRoutes []string
|
||||
foundMatchingRoute := false
|
||||
|
||||
for _, r := range routes {
|
||||
if r.Destination == unspec {
|
||||
routeInfo := formatRouteInfo(r)
|
||||
defaultRoutes = append(defaultRoutes, routeInfo)
|
||||
|
||||
if r.Nexthop == nexthop.IP && compareIntf(r.Interface, intf) == 0 {
|
||||
foundMatchingRoute = true
|
||||
log.Debugf("network monitor: found matching default route: %s", routeInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultRoutes, foundMatchingRoute
|
||||
}
|
||||
|
||||
func formatRouteInfo(r systemops.Route) string {
|
||||
newIntf := "<nil>"
|
||||
if r.Interface != nil {
|
||||
newIntf = r.Interface.Name
|
||||
}
|
||||
return fmt.Sprintf("Nexthop: %s, Interface: %s", r.Nexthop, newIntf)
|
||||
}
|
||||
|
||||
func logRouteChange(ip netip.Addr, intf *net.Interface) {
|
||||
oldIntf := "<nil>"
|
||||
if intf != nil {
|
||||
oldIntf = intf.Name
|
||||
}
|
||||
log.Infof("network monitor: default route for %s (%s) is gone or changed", ip, oldIntf)
|
||||
}
|
||||
|
||||
func neighborChanged(nexthop systemops.Nexthop, neighbor *systemops.Neighbor, neighbors map[netip.Addr]systemops.Neighbor) bool {
|
||||
@@ -127,7 +161,7 @@ func neighborChanged(nexthop systemops.Nexthop, neighbor *systemops.Neighbor, ne
|
||||
|
||||
// TODO: consider non-local nexthops, e.g. on point-to-point interfaces
|
||||
if n, ok := neighbors[nexthop.IP]; ok {
|
||||
if n.State != reachable && n.State != permanent {
|
||||
if n.State == unreachable || n.State == incomplete {
|
||||
log.Infof("network monitor: neighbor %s (%s) is not reachable: %s", neighbor.IPAddress, neighbor.LinkLayerAddress, stateFromInt(n.State))
|
||||
return true
|
||||
} else if n.InterfaceIndex != neighbor.InterfaceIndex {
|
||||
@@ -165,18 +199,13 @@ func getNeighbors() (map[netip.Addr]systemops.Neighbor, error) {
|
||||
return neighbours, nil
|
||||
}
|
||||
|
||||
func getRoutes() (map[netip.Prefix]systemops.Route, error) {
|
||||
func getRoutes() ([]systemops.Route, error) {
|
||||
entries, err := systemops.GetRoutes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get routes: %w", err)
|
||||
}
|
||||
|
||||
routes := make(map[netip.Prefix]systemops.Route, len(entries))
|
||||
for _, entry := range entries {
|
||||
routes[entry.Destination] = entry
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func stateFromInt(state uint8) string {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,25 +2,33 @@ package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/pion/stun/v2"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||
"github.com/netbirdio/netbird/client/internal/wgproxy"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
var connConf = ConnConfig{
|
||||
Key: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
LocalKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
StunTurn: []*stun.URI{},
|
||||
InterfaceBlackList: nil,
|
||||
Timeout: time.Second,
|
||||
LocalWgPort: 51820,
|
||||
Key: "LLHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
LocalKey: "RRHf3Ma6z6mdLbriAJbqhX7+nM/B71lgw2+91q3LfhU=",
|
||||
Timeout: time.Second,
|
||||
LocalWgPort: 51820,
|
||||
ICEConfig: ICEConfig{
|
||||
InterfaceBlackList: nil,
|
||||
},
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
_ = util.InitLog("trace", "console")
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestNewConn_interfaceFilter(t *testing.T) {
|
||||
@@ -36,11 +44,11 @@ func TestNewConn_interfaceFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_GetKey(t *testing.T) {
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), false, connConf.LocalWgPort)
|
||||
defer func() {
|
||||
_ = wgProxyFactory.Free()
|
||||
}()
|
||||
conn, err := NewConn(connConf, nil, wgProxyFactory, nil, nil)
|
||||
conn, err := NewConn(context.Background(), connConf, nil, wgProxyFactory, nil, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -51,11 +59,11 @@ func TestConn_GetKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_OnRemoteOffer(t *testing.T) {
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), false, connConf.LocalWgPort)
|
||||
defer func() {
|
||||
_ = wgProxyFactory.Free()
|
||||
}()
|
||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||
conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -63,7 +71,7 @@ func TestConn_OnRemoteOffer(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
<-conn.remoteOffersCh
|
||||
<-conn.handshaker.remoteOffersCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
@@ -88,11 +96,11 @@ func TestConn_OnRemoteOffer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), false, connConf.LocalWgPort)
|
||||
defer func() {
|
||||
_ = wgProxyFactory.Free()
|
||||
}()
|
||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||
conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -100,7 +108,7 @@ func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
<-conn.remoteAnswerCh
|
||||
<-conn.handshaker.remoteAnswerCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
@@ -124,62 +132,37 @@ func TestConn_OnRemoteAnswer(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
func TestConn_Status(t *testing.T) {
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), false, connConf.LocalWgPort)
|
||||
defer func() {
|
||||
_ = wgProxyFactory.Free()
|
||||
}()
|
||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||
conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tables := []struct {
|
||||
name string
|
||||
status ConnStatus
|
||||
want ConnStatus
|
||||
name string
|
||||
statusIce ConnStatus
|
||||
statusRelay ConnStatus
|
||||
want ConnStatus
|
||||
}{
|
||||
{"StatusConnected", StatusConnected, StatusConnected},
|
||||
{"StatusDisconnected", StatusDisconnected, StatusDisconnected},
|
||||
{"StatusConnecting", StatusConnecting, StatusConnecting},
|
||||
{"StatusConnected", StatusConnected, StatusConnected, StatusConnected},
|
||||
{"StatusDisconnected", StatusDisconnected, StatusDisconnected, StatusDisconnected},
|
||||
{"StatusConnecting", StatusConnecting, StatusConnecting, StatusConnecting},
|
||||
{"StatusConnectingIce", StatusConnecting, StatusDisconnected, StatusConnecting},
|
||||
{"StatusConnectingIceAlternative", StatusConnecting, StatusConnected, StatusConnected},
|
||||
{"StatusConnectingRelay", StatusDisconnected, StatusConnecting, StatusConnecting},
|
||||
{"StatusConnectingRelayAlternative", StatusConnected, StatusConnecting, StatusConnected},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.name, func(t *testing.T) {
|
||||
conn.status = table.status
|
||||
conn.statusICE = table.statusIce
|
||||
conn.statusRelay = table.statusRelay
|
||||
|
||||
got := conn.Status()
|
||||
assert.Equal(t, got, table.want, "they should be equal")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Close(t *testing.T) {
|
||||
wgProxyFactory := wgproxy.NewFactory(context.Background(), connConf.LocalWgPort)
|
||||
defer func() {
|
||||
_ = wgProxyFactory.Free()
|
||||
}()
|
||||
conn, err := NewConn(connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-conn.closeCh
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@ const (
|
||||
envICEForceRelayConn = "NB_ICE_FORCE_RELAY_CONN"
|
||||
)
|
||||
|
||||
func ForcedRelayAddress() string {
|
||||
if envRelay := os.Getenv("NB_RELAY_ADDRESS"); envRelay != "" {
|
||||
return envRelay
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func iceKeepAlive() time.Duration {
|
||||
keepAliveEnv := os.Getenv(envICEKeepAliveIntervalSec)
|
||||
if keepAliveEnv == "" {
|
||||
|
||||
195
client/internal/peer/handshaker.go
Normal file
195
client/internal/peer/handshaker.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSignalIsNotReady = errors.New("signal is not ready")
|
||||
)
|
||||
|
||||
// IceCredentials ICE protocol credentials struct
|
||||
type IceCredentials struct {
|
||||
UFrag string
|
||||
Pwd string
|
||||
}
|
||||
|
||||
// OfferAnswer represents a session establishment offer or answer
|
||||
type OfferAnswer struct {
|
||||
IceCredentials IceCredentials
|
||||
// WgListenPort is a remote WireGuard listen port.
|
||||
// This field is used when establishing a direct WireGuard connection without any proxy.
|
||||
// We can set the remote peer's endpoint with this port.
|
||||
WgListenPort int
|
||||
|
||||
// Version of NetBird Agent
|
||||
Version string
|
||||
// RosenpassPubKey is the Rosenpass public key of the remote peer when receiving this message
|
||||
// This value is the local Rosenpass server public key when sending the message
|
||||
RosenpassPubKey []byte
|
||||
// RosenpassAddr is the Rosenpass server address (IP:port) of the remote peer when receiving this message
|
||||
// This value is the local Rosenpass server address when sending the message
|
||||
RosenpassAddr string
|
||||
|
||||
// relay server address
|
||||
RelaySrvAddress string
|
||||
}
|
||||
|
||||
type HandshakeArgs struct {
|
||||
IceUFrag string
|
||||
IcePwd string
|
||||
RelayAddr string
|
||||
}
|
||||
|
||||
type Handshaker struct {
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
log *log.Entry
|
||||
config ConnConfig
|
||||
signaler *Signaler
|
||||
onNewOfferListeners []func(*OfferAnswer)
|
||||
|
||||
// remoteOffersCh is a channel used to wait for remote credentials to proceed with the connection
|
||||
remoteOffersCh chan OfferAnswer
|
||||
// remoteAnswerCh is a channel used to wait for remote credentials answer (confirmation of our offer) to proceed with the connection
|
||||
remoteAnswerCh chan OfferAnswer
|
||||
|
||||
lastOfferArgs HandshakeArgs
|
||||
}
|
||||
|
||||
func NewHandshaker(ctx context.Context, log *log.Entry, config ConnConfig, signaler *Signaler) *Handshaker {
|
||||
return &Handshaker{
|
||||
ctx: ctx,
|
||||
log: log,
|
||||
config: config,
|
||||
signaler: signaler,
|
||||
remoteOffersCh: make(chan OfferAnswer),
|
||||
remoteAnswerCh: make(chan OfferAnswer),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handshaker) AddOnNewOfferListener(offer func(remoteOfferAnswer *OfferAnswer)) {
|
||||
h.onNewOfferListeners = append(h.onNewOfferListeners, offer)
|
||||
}
|
||||
|
||||
func (h *Handshaker) Listen() {
|
||||
for {
|
||||
h.log.Debugf("wait for remote offer confirmation")
|
||||
remoteOfferAnswer, err := h.waitForRemoteOfferConfirmation()
|
||||
if err != nil {
|
||||
if _, ok := err.(*ConnectionClosedError); ok {
|
||||
h.log.Tracef("stop handshaker")
|
||||
return
|
||||
}
|
||||
h.log.Errorf("failed to received remote offer confirmation: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
h.log.Debugf("received connection confirmation, running version %s and with remote WireGuard listen port %d", remoteOfferAnswer.Version, remoteOfferAnswer.WgListenPort)
|
||||
for _, listener := range h.onNewOfferListeners {
|
||||
go listener(remoteOfferAnswer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handshaker) SendOffer(args HandshakeArgs) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
err := h.sendOffer(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.lastOfferArgs = args
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnRemoteOffer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||
// doesn't block, discards the message if connection wasn't ready
|
||||
func (h *Handshaker) OnRemoteOffer(offer OfferAnswer) bool {
|
||||
// todo remove this if signaling can support relay
|
||||
if ForcedRelayAddress() != "" {
|
||||
offer.RelaySrvAddress = ForcedRelayAddress()
|
||||
}
|
||||
select {
|
||||
case h.remoteOffersCh <- offer:
|
||||
return true
|
||||
default:
|
||||
h.log.Debugf("OnRemoteOffer skipping message because is not ready")
|
||||
// connection might not be ready yet to receive so we ignore the message
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// OnRemoteAnswer handles an offer from the remote peer and returns true if the message was accepted, false otherwise
|
||||
// doesn't block, discards the message if connection wasn't ready
|
||||
func (h *Handshaker) OnRemoteAnswer(answer OfferAnswer) bool {
|
||||
// todo remove this if signaling can support relay
|
||||
if ForcedRelayAddress() != "" {
|
||||
answer.RelaySrvAddress = ForcedRelayAddress()
|
||||
}
|
||||
select {
|
||||
case h.remoteAnswerCh <- answer:
|
||||
return true
|
||||
default:
|
||||
// connection might not be ready yet to receive so we ignore the message
|
||||
h.log.Debugf("OnRemoteAnswer skipping message because is not ready")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handshaker) waitForRemoteOfferConfirmation() (*OfferAnswer, error) {
|
||||
select {
|
||||
case remoteOfferAnswer := <-h.remoteOffersCh:
|
||||
// received confirmation from the remote peer -> ready to proceed
|
||||
err := h.sendAnswer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &remoteOfferAnswer, nil
|
||||
case remoteOfferAnswer := <-h.remoteAnswerCh:
|
||||
return &remoteOfferAnswer, nil
|
||||
case <-h.ctx.Done():
|
||||
// closed externally
|
||||
return nil, NewConnectionClosedError(h.config.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// sendOffer prepares local user credentials and signals them to the remote peer
|
||||
func (h *Handshaker) sendOffer(args HandshakeArgs) error {
|
||||
offer := OfferAnswer{
|
||||
IceCredentials: IceCredentials{args.IceUFrag, args.IcePwd},
|
||||
WgListenPort: h.config.LocalWgPort,
|
||||
Version: version.NetbirdVersion(),
|
||||
RosenpassPubKey: h.config.RosenpassPubKey,
|
||||
RosenpassAddr: h.config.RosenpassAddr,
|
||||
RelaySrvAddress: args.RelayAddr,
|
||||
}
|
||||
|
||||
return h.signaler.SignalOffer(offer, h.config.Key)
|
||||
}
|
||||
|
||||
func (h *Handshaker) sendAnswer() error {
|
||||
h.log.Debugf("sending answer")
|
||||
answer := OfferAnswer{
|
||||
IceCredentials: IceCredentials{h.lastOfferArgs.IceUFrag, h.lastOfferArgs.IcePwd},
|
||||
WgListenPort: h.config.LocalWgPort,
|
||||
Version: version.NetbirdVersion(),
|
||||
RosenpassPubKey: h.config.RosenpassPubKey,
|
||||
RosenpassAddr: h.config.RosenpassAddr,
|
||||
RelaySrvAddress: h.lastOfferArgs.RelayAddr,
|
||||
}
|
||||
err := h.signaler.SignalAnswer(answer, h.config.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
client/internal/peer/signaler.go
Normal file
70
client/internal/peer/signaler.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/pion/ice/v3"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
signal "github.com/netbirdio/netbird/signal/client"
|
||||
sProto "github.com/netbirdio/netbird/signal/proto"
|
||||
)
|
||||
|
||||
type Signaler struct {
|
||||
signal signal.Client
|
||||
wgPrivateKey wgtypes.Key
|
||||
}
|
||||
|
||||
func NewSignaler(signal signal.Client, wgPrivateKey wgtypes.Key) *Signaler {
|
||||
return &Signaler{
|
||||
signal: signal,
|
||||
wgPrivateKey: wgPrivateKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Signaler) SignalOffer(offer OfferAnswer, remoteKey string) error {
|
||||
return s.signalOfferAnswer(offer, remoteKey, sProto.Body_OFFER)
|
||||
}
|
||||
|
||||
func (s *Signaler) SignalAnswer(offer OfferAnswer, remoteKey string) error {
|
||||
return s.signalOfferAnswer(offer, remoteKey, sProto.Body_ANSWER)
|
||||
}
|
||||
|
||||
func (s *Signaler) SignalICECandidate(candidate ice.Candidate, remoteKey string) error {
|
||||
return s.signal.Send(&sProto.Message{
|
||||
Key: s.wgPrivateKey.PublicKey().String(),
|
||||
RemoteKey: remoteKey,
|
||||
Body: &sProto.Body{
|
||||
Type: sProto.Body_CANDIDATE,
|
||||
Payload: candidate.Marshal(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Signaler) Ready() bool {
|
||||
return s.signal.Ready()
|
||||
}
|
||||
|
||||
// SignalOfferAnswer signals either an offer or an answer to remote peer
|
||||
func (s *Signaler) signalOfferAnswer(offerAnswer OfferAnswer, remoteKey string, bodyType sProto.Body_Type) error {
|
||||
msg, err := signal.MarshalCredential(
|
||||
s.wgPrivateKey,
|
||||
offerAnswer.WgListenPort,
|
||||
remoteKey,
|
||||
&signal.Credential{
|
||||
UFrag: offerAnswer.IceCredentials.UFrag,
|
||||
Pwd: offerAnswer.IceCredentials.Pwd,
|
||||
},
|
||||
bodyType,
|
||||
offerAnswer.RosenpassPubKey,
|
||||
offerAnswer.RosenpassAddr,
|
||||
offerAnswer.RelaySrvAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.signal.Send(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal/relay"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/netbirdio/netbird/management/domain"
|
||||
relayClient "github.com/netbirdio/netbird/relay/client"
|
||||
)
|
||||
|
||||
// State contains the latest state of a peer
|
||||
@@ -142,6 +143,8 @@ type Status struct {
|
||||
// Some Peer actions mostly used by in a batch when the network map has been synchronized. In these type of events
|
||||
// set to true this variable and at the end of the processing we will reset it by the FinishPeerListModifications()
|
||||
peerListChangedForNotification bool
|
||||
|
||||
relayMgr *relayClient.Manager
|
||||
}
|
||||
|
||||
// NewRecorder returns a new Status instance
|
||||
@@ -156,6 +159,12 @@ func NewRecorder(mgmAddress string) *Status {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Status) SetRelayMgr(manager *relayClient.Manager) {
|
||||
d.mux.Lock()
|
||||
defer d.mux.Unlock()
|
||||
d.relayMgr = manager
|
||||
}
|
||||
|
||||
// ReplaceOfflinePeers replaces
|
||||
func (d *Status) ReplaceOfflinePeers(replacement []State) {
|
||||
d.mux.Lock()
|
||||
@@ -503,7 +512,28 @@ func (d *Status) GetSignalState() SignalState {
|
||||
}
|
||||
|
||||
func (d *Status) GetRelayStates() []relay.ProbeResult {
|
||||
return d.relayStates
|
||||
if d.relayMgr == nil {
|
||||
return d.relayStates
|
||||
}
|
||||
|
||||
// extend the list of stun, turn servers with relay address
|
||||
relaysState := make([]relay.ProbeResult, len(d.relayStates), len(d.relayStates)+1)
|
||||
copy(relaysState, d.relayStates)
|
||||
|
||||
relayState := relay.ProbeResult{}
|
||||
|
||||
// if the server connection is not established then we will use the general address
|
||||
// in case of connection we will use the instance specific address
|
||||
instanceAddr, err := d.relayMgr.RelayInstanceAddress()
|
||||
if err != nil {
|
||||
relayState.URI = d.relayMgr.ServerURL()
|
||||
relayState.Err = err
|
||||
} else {
|
||||
relayState.URI = instanceAddr
|
||||
}
|
||||
|
||||
relaysState = append(relaysState, relayState)
|
||||
return relaysState
|
||||
}
|
||||
|
||||
func (d *Status) GetDNSStates() []NSGroupState {
|
||||
|
||||
@@ -2,8 +2,8 @@ package peer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -43,7 +43,7 @@ func TestUpdatePeerState(t *testing.T) {
|
||||
status := NewRecorder("https://mgm")
|
||||
peerState := State{
|
||||
PubKey: key,
|
||||
Mux: new(sync.RWMutex),
|
||||
Mux: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
status.peers[key] = peerState
|
||||
@@ -64,7 +64,7 @@ func TestStatus_UpdatePeerFQDN(t *testing.T) {
|
||||
status := NewRecorder("https://mgm")
|
||||
peerState := State{
|
||||
PubKey: key,
|
||||
Mux: new(sync.RWMutex),
|
||||
Mux: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
status.peers[key] = peerState
|
||||
@@ -83,7 +83,7 @@ func TestGetPeerStateChangeNotifierLogic(t *testing.T) {
|
||||
status := NewRecorder("https://mgm")
|
||||
peerState := State{
|
||||
PubKey: key,
|
||||
Mux: new(sync.RWMutex),
|
||||
Mux: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
status.peers[key] = peerState
|
||||
@@ -108,7 +108,7 @@ func TestRemovePeer(t *testing.T) {
|
||||
status := NewRecorder("https://mgm")
|
||||
peerState := State{
|
||||
PubKey: key,
|
||||
Mux: new(sync.RWMutex),
|
||||
Mux: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
status.peers[key] = peerState
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||
)
|
||||
|
||||
func (conn *Conn) newStdNet() (*stdnet.Net, error) {
|
||||
return stdnet.NewNet(conn.config.InterfaceBlackList)
|
||||
func (w *WorkerICE) newStdNet() (*stdnet.Net, error) {
|
||||
return stdnet.NewNet(w.configICE.InterfaceBlackList)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,6 @@ package peer
|
||||
|
||||
import "github.com/netbirdio/netbird/client/internal/stdnet"
|
||||
|
||||
func (conn *Conn) newStdNet() (*stdnet.Net, error) {
|
||||
return stdnet.NewNetWithDiscover(conn.iFaceDiscover, conn.config.InterfaceBlackList)
|
||||
func (w *WorkerICE) newStdNet() (*stdnet.Net, error) {
|
||||
return stdnet.NewNetWithDiscover(w.iFaceDiscover, w.configICE.InterfaceBlackList)
|
||||
}
|
||||
|
||||
461
client/internal/peer/worker_ice.go
Normal file
461
client/internal/peer/worker_ice.go
Normal file
@@ -0,0 +1,461 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pion/ice/v3"
|
||||
"github.com/pion/randutil"
|
||||
"github.com/pion/stun/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/netbirdio/netbird/iface/bind"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
const (
|
||||
iceKeepAliveDefault = 4 * time.Second
|
||||
iceDisconnectedTimeoutDefault = 6 * time.Second
|
||||
// iceRelayAcceptanceMinWaitDefault is the same as in the Pion ICE package
|
||||
iceRelayAcceptanceMinWaitDefault = 2 * time.Second
|
||||
|
||||
lenUFrag = 16
|
||||
lenPwd = 32
|
||||
runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
)
|
||||
|
||||
var (
|
||||
failedTimeout = 6 * time.Second
|
||||
)
|
||||
|
||||
type ICEConfig struct {
|
||||
// StunTurn is a list of STUN and TURN URLs
|
||||
StunTurn atomic.Value // []*stun.URI
|
||||
|
||||
// InterfaceBlackList is a list of machine interfaces that should be filtered out by ICE Candidate gathering
|
||||
// (e.g. if eth0 is in the list, host candidate of this interface won't be used)
|
||||
InterfaceBlackList []string
|
||||
DisableIPv6Discovery bool
|
||||
|
||||
UDPMux ice.UDPMux
|
||||
UDPMuxSrflx ice.UniversalUDPMux
|
||||
|
||||
NATExternalIPs []string
|
||||
}
|
||||
|
||||
type ICEConnInfo struct {
|
||||
RemoteConn net.Conn
|
||||
RosenpassPubKey []byte
|
||||
RosenpassAddr string
|
||||
LocalIceCandidateType string
|
||||
RemoteIceCandidateType string
|
||||
RemoteIceCandidateEndpoint string
|
||||
LocalIceCandidateEndpoint string
|
||||
Direct bool
|
||||
Relayed bool
|
||||
RelayedOnLocal bool
|
||||
}
|
||||
|
||||
type WorkerICECallbacks struct {
|
||||
OnConnReady func(ConnPriority, ICEConnInfo)
|
||||
OnStatusChanged func(ConnStatus)
|
||||
}
|
||||
|
||||
type WorkerICE struct {
|
||||
ctx context.Context
|
||||
log *log.Entry
|
||||
config ConnConfig
|
||||
configICE ICEConfig
|
||||
signaler *Signaler
|
||||
iFaceDiscover stdnet.ExternalIFaceDiscover
|
||||
statusRecorder *Status
|
||||
hasRelayOnLocally bool
|
||||
conn WorkerICECallbacks
|
||||
|
||||
selectedPriority ConnPriority
|
||||
|
||||
agent *ice.Agent
|
||||
muxAgent sync.Mutex
|
||||
|
||||
StunTurn []*stun.URI
|
||||
|
||||
sentExtraSrflx bool
|
||||
|
||||
localUfrag string
|
||||
localPwd string
|
||||
}
|
||||
|
||||
func NewWorkerICE(ctx context.Context, log *log.Entry, config ConnConfig, configICE ICEConfig, signaler *Signaler, ifaceDiscover stdnet.ExternalIFaceDiscover, statusRecorder *Status, hasRelayOnLocally bool, callBacks WorkerICECallbacks) (*WorkerICE, error) {
|
||||
w := &WorkerICE{
|
||||
ctx: ctx,
|
||||
log: log,
|
||||
config: config,
|
||||
configICE: configICE,
|
||||
signaler: signaler,
|
||||
iFaceDiscover: ifaceDiscover,
|
||||
statusRecorder: statusRecorder,
|
||||
hasRelayOnLocally: hasRelayOnLocally,
|
||||
conn: callBacks,
|
||||
}
|
||||
|
||||
localUfrag, localPwd, err := generateICECredentials()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.localUfrag = localUfrag
|
||||
w.localPwd = localPwd
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *WorkerICE) OnNewOffer(remoteOfferAnswer *OfferAnswer) {
|
||||
w.log.Debugf("OnNewOffer for ICE")
|
||||
w.muxAgent.Lock()
|
||||
|
||||
if w.agent != nil {
|
||||
w.log.Debugf("agent already exists, skipping the offer")
|
||||
w.muxAgent.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
var preferredCandidateTypes []ice.CandidateType
|
||||
if w.hasRelayOnLocally && remoteOfferAnswer.RelaySrvAddress != "" {
|
||||
w.selectedPriority = connPriorityICEP2P
|
||||
preferredCandidateTypes = candidateTypesP2P()
|
||||
} else {
|
||||
w.selectedPriority = connPriorityICETurn
|
||||
preferredCandidateTypes = candidateTypes()
|
||||
}
|
||||
|
||||
w.log.Debugf("recreate ICE agent")
|
||||
agentCtx, agentCancel := context.WithCancel(w.ctx)
|
||||
agent, err := w.reCreateAgent(agentCancel, preferredCandidateTypes)
|
||||
if err != nil {
|
||||
w.log.Errorf("failed to recreate ICE Agent: %s", err)
|
||||
w.muxAgent.Unlock()
|
||||
return
|
||||
}
|
||||
w.agent = agent
|
||||
w.muxAgent.Unlock()
|
||||
|
||||
w.log.Debugf("gather candidates")
|
||||
err = w.agent.GatherCandidates()
|
||||
if err != nil {
|
||||
w.log.Debugf("failed to gather candidates: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// will block until connection succeeded
|
||||
// but it won't release if ICE Agent went into Disconnected or Failed state,
|
||||
// so we have to cancel it with the provided context once agent detected a broken connection
|
||||
w.log.Debugf("turn agent dial")
|
||||
remoteConn, err := w.turnAgentDial(agentCtx, remoteOfferAnswer)
|
||||
if err != nil {
|
||||
w.log.Debugf("failed to dial the remote peer: %s", err)
|
||||
return
|
||||
}
|
||||
w.log.Debugf("agent dial succeeded")
|
||||
|
||||
pair, err := w.agent.GetSelectedCandidatePair()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !isRelayCandidate(pair.Local) {
|
||||
// dynamically set remote WireGuard port if other side specified a different one from the default one
|
||||
remoteWgPort := iface.DefaultWgPort
|
||||
if remoteOfferAnswer.WgListenPort != 0 {
|
||||
remoteWgPort = remoteOfferAnswer.WgListenPort
|
||||
}
|
||||
|
||||
// To support old version's with direct mode we attempt to punch an additional role with the remote WireGuard port
|
||||
go w.punchRemoteWGPort(pair, remoteWgPort)
|
||||
}
|
||||
|
||||
ci := ICEConnInfo{
|
||||
RemoteConn: remoteConn,
|
||||
RosenpassPubKey: remoteOfferAnswer.RosenpassPubKey,
|
||||
RosenpassAddr: remoteOfferAnswer.RosenpassAddr,
|
||||
LocalIceCandidateType: pair.Local.Type().String(),
|
||||
RemoteIceCandidateType: pair.Remote.Type().String(),
|
||||
LocalIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Local.Address(), pair.Local.Port()),
|
||||
RemoteIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Remote.Address(), pair.Remote.Port()),
|
||||
Direct: !isRelayCandidate(pair.Local),
|
||||
Relayed: isRelayed(pair),
|
||||
RelayedOnLocal: isRelayCandidate(pair.Local),
|
||||
}
|
||||
w.log.Debugf("on ICE conn read to use ready")
|
||||
go w.conn.OnConnReady(w.selectedPriority, ci)
|
||||
}
|
||||
|
||||
// OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer.
|
||||
func (w *WorkerICE) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HAMap) {
|
||||
w.muxAgent.Lock()
|
||||
defer w.muxAgent.Unlock()
|
||||
w.log.Debugf("OnRemoteCandidate from peer %s -> %s", w.config.Key, candidate.String())
|
||||
if w.agent == nil {
|
||||
w.log.Warnf("ICE Agent is not initialized yet")
|
||||
return
|
||||
}
|
||||
|
||||
if candidateViaRoutes(candidate, haRoutes) {
|
||||
return
|
||||
}
|
||||
|
||||
err := w.agent.AddRemoteCandidate(candidate)
|
||||
if err != nil {
|
||||
w.log.Errorf("error while handling remote candidate")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkerICE) GetLocalUserCredentials() (frag string, pwd string) {
|
||||
w.muxAgent.Lock()
|
||||
defer w.muxAgent.Unlock()
|
||||
return w.localUfrag, w.localPwd
|
||||
}
|
||||
|
||||
func (w *WorkerICE) reCreateAgent(agentCancel context.CancelFunc, relaySupport []ice.CandidateType) (*ice.Agent, error) {
|
||||
transportNet, err := w.newStdNet()
|
||||
if err != nil {
|
||||
w.log.Errorf("failed to create pion's stdnet: %s", err)
|
||||
}
|
||||
|
||||
iceKeepAlive := iceKeepAlive()
|
||||
iceDisconnectedTimeout := iceDisconnectedTimeout()
|
||||
iceRelayAcceptanceMinWait := iceRelayAcceptanceMinWait()
|
||||
|
||||
agentConfig := &ice.AgentConfig{
|
||||
MulticastDNSMode: ice.MulticastDNSModeDisabled,
|
||||
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4, ice.NetworkTypeUDP6},
|
||||
Urls: w.configICE.StunTurn.Load().([]*stun.URI),
|
||||
CandidateTypes: relaySupport,
|
||||
InterfaceFilter: stdnet.InterfaceFilter(w.configICE.InterfaceBlackList),
|
||||
UDPMux: w.configICE.UDPMux,
|
||||
UDPMuxSrflx: w.configICE.UDPMuxSrflx,
|
||||
NAT1To1IPs: w.configICE.NATExternalIPs,
|
||||
Net: transportNet,
|
||||
FailedTimeout: &failedTimeout,
|
||||
DisconnectedTimeout: &iceDisconnectedTimeout,
|
||||
KeepaliveInterval: &iceKeepAlive,
|
||||
RelayAcceptanceMinWait: &iceRelayAcceptanceMinWait,
|
||||
LocalUfrag: w.localUfrag,
|
||||
LocalPwd: w.localPwd,
|
||||
}
|
||||
|
||||
if w.configICE.DisableIPv6Discovery {
|
||||
agentConfig.NetworkTypes = []ice.NetworkType{ice.NetworkTypeUDP4}
|
||||
}
|
||||
|
||||
w.sentExtraSrflx = false
|
||||
agent, err := ice.NewAgent(agentConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = agent.OnCandidate(w.onICECandidate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = agent.OnConnectionStateChange(func(state ice.ConnectionState) {
|
||||
w.log.Debugf("ICE ConnectionState has changed to %s", state.String())
|
||||
if state == ice.ConnectionStateFailed || state == ice.ConnectionStateDisconnected {
|
||||
w.conn.OnStatusChanged(StatusDisconnected)
|
||||
|
||||
w.muxAgent.Lock()
|
||||
agentCancel()
|
||||
_ = agent.Close()
|
||||
w.agent = nil
|
||||
|
||||
w.muxAgent.Unlock()
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = agent.OnSelectedCandidatePairChange(w.onICESelectedCandidatePair)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = agent.OnSuccessfulSelectedPairBindingResponse(func(p *ice.CandidatePair) {
|
||||
err := w.statusRecorder.UpdateLatency(w.config.Key, p.Latency())
|
||||
if err != nil {
|
||||
w.log.Debugf("failed to update latency for peer: %s", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed setting binding response callback: %w", err)
|
||||
}
|
||||
|
||||
return agent, nil
|
||||
}
|
||||
|
||||
func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) {
|
||||
// wait local endpoint configuration
|
||||
time.Sleep(time.Second)
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pair.Remote.Address(), remoteWgPort))
|
||||
if err != nil {
|
||||
w.log.Warnf("got an error while resolving the udp address, err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
mux, ok := w.configICE.UDPMuxSrflx.(*bind.UniversalUDPMuxDefault)
|
||||
if !ok {
|
||||
w.log.Warn("invalid udp mux conversion")
|
||||
return
|
||||
}
|
||||
_, err = mux.GetSharedConn().WriteTo([]byte{0x6e, 0x62}, addr)
|
||||
if err != nil {
|
||||
w.log.Warnf("got an error while sending the punch packet, err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates
|
||||
// and then signals them to the remote peer
|
||||
func (w *WorkerICE) onICECandidate(candidate ice.Candidate) {
|
||||
// nil means candidate gathering has been ended
|
||||
if candidate == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: reported port is incorrect for CandidateTypeHost, makes understanding ICE use via logs confusing as port is ignored
|
||||
w.log.Debugf("discovered local candidate %s", candidate.String())
|
||||
go func() {
|
||||
err := w.signaler.SignalICECandidate(candidate, w.config.Key)
|
||||
if err != nil {
|
||||
w.log.Errorf("failed signaling candidate to the remote peer %s %s", w.config.Key, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !w.shouldSendExtraSrflxCandidate(candidate) {
|
||||
return
|
||||
}
|
||||
|
||||
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
|
||||
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer
|
||||
extraSrflx, err := extraSrflxCandidate(candidate)
|
||||
if err != nil {
|
||||
w.log.Errorf("failed creating extra server reflexive candidate %s", err)
|
||||
return
|
||||
}
|
||||
w.sentExtraSrflx = true
|
||||
|
||||
go func() {
|
||||
err = w.signaler.SignalICECandidate(extraSrflx, w.config.Key)
|
||||
if err != nil {
|
||||
w.log.Errorf("failed signaling the extra server reflexive candidate: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (w *WorkerICE) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
|
||||
w.log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", c1.String(), c2.String(),
|
||||
w.config.Key)
|
||||
}
|
||||
|
||||
func (w *WorkerICE) shouldSendExtraSrflxCandidate(candidate ice.Candidate) bool {
|
||||
if !w.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WorkerICE) turnAgentDial(ctx context.Context, remoteOfferAnswer *OfferAnswer) (*ice.Conn, error) {
|
||||
isControlling := w.config.LocalKey > w.config.Key
|
||||
if isControlling {
|
||||
return w.agent.Dial(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
||||
} else {
|
||||
return w.agent.Accept(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
||||
}
|
||||
}
|
||||
|
||||
func extraSrflxCandidate(candidate ice.Candidate) (*ice.CandidateServerReflexive, error) {
|
||||
relatedAdd := candidate.RelatedAddress()
|
||||
return ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
|
||||
Network: candidate.NetworkType().String(),
|
||||
Address: candidate.Address(),
|
||||
Port: relatedAdd.Port,
|
||||
Component: candidate.Component(),
|
||||
RelAddr: relatedAdd.Address,
|
||||
RelPort: relatedAdd.Port,
|
||||
})
|
||||
}
|
||||
|
||||
func candidateViaRoutes(candidate ice.Candidate, clientRoutes route.HAMap) bool {
|
||||
var routePrefixes []netip.Prefix
|
||||
for _, routes := range clientRoutes {
|
||||
if len(routes) > 0 && routes[0] != nil {
|
||||
routePrefixes = append(routePrefixes, routes[0].Network)
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := netip.ParseAddr(candidate.Address())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to parse IP address %s: %v", candidate.Address(), err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, prefix := range routePrefixes {
|
||||
// default route is
|
||||
if prefix.Bits() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if prefix.Contains(addr) {
|
||||
log.Debugf("Ignoring candidate [%s], its address is part of routed network %s", candidate.String(), prefix)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func candidateTypes() []ice.CandidateType {
|
||||
if hasICEForceRelayConn() {
|
||||
return []ice.CandidateType{ice.CandidateTypeRelay}
|
||||
}
|
||||
// TODO: remove this once we have refactored userspace proxy into the bind package
|
||||
if runtime.GOOS == "ios" {
|
||||
return []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive}
|
||||
}
|
||||
return []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay}
|
||||
}
|
||||
|
||||
func candidateTypesP2P() []ice.CandidateType {
|
||||
return []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive}
|
||||
}
|
||||
|
||||
func isRelayCandidate(candidate ice.Candidate) bool {
|
||||
return candidate.Type() == ice.CandidateTypeRelay
|
||||
}
|
||||
|
||||
func isRelayed(pair *ice.CandidatePair) bool {
|
||||
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func generateICECredentials() (string, string, error) {
|
||||
ufrag, err := randutil.GenerateCryptoRandomString(lenUFrag, runesAlpha)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
pwd, err := randutil.GenerateCryptoRandomString(lenPwd, runesAlpha)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return ufrag, pwd, nil
|
||||
|
||||
}
|
||||
100
client/internal/peer/worker_relay.go
Normal file
100
client/internal/peer/worker_relay.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
relayClient "github.com/netbirdio/netbird/relay/client"
|
||||
)
|
||||
|
||||
type RelayConnInfo struct {
|
||||
relayedConn net.Conn
|
||||
rosenpassPubKey []byte
|
||||
rosenpassAddr string
|
||||
}
|
||||
|
||||
type WorkerRelayCallbacks struct {
|
||||
OnConnReady func(RelayConnInfo)
|
||||
OnDisconnected func()
|
||||
}
|
||||
|
||||
type WorkerRelay struct {
|
||||
ctx context.Context
|
||||
log *log.Entry
|
||||
config ConnConfig
|
||||
relayManager *relayClient.Manager
|
||||
conn WorkerRelayCallbacks
|
||||
}
|
||||
|
||||
func NewWorkerRelay(ctx context.Context, log *log.Entry, config ConnConfig, relayManager *relayClient.Manager, callbacks WorkerRelayCallbacks) *WorkerRelay {
|
||||
return &WorkerRelay{
|
||||
ctx: ctx,
|
||||
log: log,
|
||||
config: config,
|
||||
relayManager: relayManager,
|
||||
conn: callbacks,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkerRelay) OnNewOffer(remoteOfferAnswer *OfferAnswer) {
|
||||
if !w.isRelaySupported(remoteOfferAnswer) {
|
||||
w.log.Infof("Relay is not supported by remote peer")
|
||||
return
|
||||
}
|
||||
|
||||
// the relayManager will return with error in case if the connection has lost with relay server
|
||||
currentRelayAddress, err := w.relayManager.RelayInstanceAddress()
|
||||
if err != nil {
|
||||
w.log.Infof("local Relay connection is lost, skipping connection attempt")
|
||||
return
|
||||
}
|
||||
|
||||
srv := w.preferredRelayServer(currentRelayAddress, remoteOfferAnswer.RelaySrvAddress)
|
||||
|
||||
relayedConn, err := w.relayManager.OpenConn(srv, w.config.Key, w.conn.OnDisconnected)
|
||||
if err != nil {
|
||||
// todo handle all type errors
|
||||
if errors.Is(err, relayClient.ErrConnAlreadyExists) {
|
||||
w.log.Infof("do not need to reopen relay connection")
|
||||
return
|
||||
}
|
||||
w.log.Infof("do not need to reopen relay connection: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.log.Debugf("Relay connection established with %s", srv)
|
||||
go w.conn.OnConnReady(RelayConnInfo{
|
||||
relayedConn: relayedConn,
|
||||
rosenpassPubKey: remoteOfferAnswer.RosenpassPubKey,
|
||||
rosenpassAddr: remoteOfferAnswer.RosenpassAddr,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *WorkerRelay) RelayInstanceAddress() (string, error) {
|
||||
return w.relayManager.RelayInstanceAddress()
|
||||
}
|
||||
|
||||
func (w *WorkerRelay) IsController() bool {
|
||||
return w.config.LocalKey > w.config.Key
|
||||
}
|
||||
|
||||
func (w *WorkerRelay) RelayIsSupportedLocally() bool {
|
||||
return w.relayManager.HasRelayAddress()
|
||||
}
|
||||
|
||||
func (w *WorkerRelay) isRelaySupported(answer *OfferAnswer) bool {
|
||||
if !w.relayManager.HasRelayAddress() {
|
||||
return false
|
||||
}
|
||||
return answer.RelaySrvAddress != ""
|
||||
}
|
||||
|
||||
func (w *WorkerRelay) preferredRelayServer(myRelayAddress, remoteRelayAddress string) string {
|
||||
if w.IsController() {
|
||||
return myRelayAddress
|
||||
}
|
||||
return remoteRelayAddress
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
// ProbeResult holds the info about the result of a relay probe request
|
||||
type ProbeResult struct {
|
||||
URI *stun.URI
|
||||
URI string
|
||||
Err error
|
||||
Addr string
|
||||
}
|
||||
@@ -176,7 +176,7 @@ func ProbeAll(
|
||||
wg.Add(1)
|
||||
go func(res *ProbeResult, stunURI *stun.URI) {
|
||||
defer wg.Done()
|
||||
res.URI = stunURI
|
||||
res.URI = stunURI.String()
|
||||
res.Addr, res.Err = fn(ctx, stunURI)
|
||||
}(&results[i], uri)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ type clientNetwork struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
statusRecorder *peer.Status
|
||||
wgInterface *iface.WGIface
|
||||
wgInterface iface.IWGIface
|
||||
routes map[route.ID]*route.Route
|
||||
routeUpdate chan routesUpdate
|
||||
peerStateUpdate chan struct{}
|
||||
@@ -52,7 +52,7 @@ type clientNetwork struct {
|
||||
updateSerial uint64
|
||||
}
|
||||
|
||||
func newClientNetworkWatcher(ctx context.Context, dnsRouteInterval time.Duration, wgInterface *iface.WGIface, statusRecorder *peer.Status, rt *route.Route, routeRefCounter *refcounter.RouteRefCounter, allowedIPsRefCounter *refcounter.AllowedIPsRefCounter) *clientNetwork {
|
||||
func newClientNetworkWatcher(ctx context.Context, dnsRouteInterval time.Duration, wgInterface iface.IWGIface, statusRecorder *peer.Status, rt *route.Route, routeRefCounter *refcounter.RouteRefCounter, allowedIPsRefCounter *refcounter.AllowedIPsRefCounter) *clientNetwork {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
client := &clientNetwork{
|
||||
|
||||
@@ -23,7 +23,8 @@ import (
|
||||
const (
|
||||
DefaultInterval = time.Minute
|
||||
|
||||
minInterval = 2 * time.Second
|
||||
minInterval = 2 * time.Second
|
||||
failureInterval = 5 * time.Second
|
||||
|
||||
addAllowedIP = "add allowed IP %s: %w"
|
||||
)
|
||||
@@ -160,7 +161,12 @@ func (r *Route) startResolver(ctx context.Context) {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
r.update(ctx)
|
||||
if err := r.update(ctx); err != nil {
|
||||
log.Errorf("Failed to resolve domains for route [%v]: %v", r, err)
|
||||
if interval > failureInterval {
|
||||
ticker.Reset(failureInterval)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -168,17 +174,28 @@ func (r *Route) startResolver(ctx context.Context) {
|
||||
log.Debugf("Stopping dynamic route resolver for domains [%v]", r)
|
||||
return
|
||||
case <-ticker.C:
|
||||
r.update(ctx)
|
||||
if err := r.update(ctx); err != nil {
|
||||
log.Errorf("Failed to resolve domains for route [%v]: %v", r, err)
|
||||
// Use a lower ticker interval if the update fails
|
||||
if interval > failureInterval {
|
||||
ticker.Reset(failureInterval)
|
||||
}
|
||||
} else if interval > failureInterval {
|
||||
// Reset to the original interval if the update succeeds
|
||||
ticker.Reset(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Route) update(ctx context.Context) {
|
||||
func (r *Route) update(ctx context.Context) error {
|
||||
if resolved, err := r.resolveDomains(); err != nil {
|
||||
log.Errorf("Failed to resolve domains for route [%v]: %v", r, err)
|
||||
return fmt.Errorf("resolve domains: %w", err)
|
||||
} else if err := r.updateDynamicRoutes(ctx, resolved); err != nil {
|
||||
log.Errorf("Failed to update dynamic routes for [%v]: %v", r, err)
|
||||
return fmt.Errorf("update dynamic routes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Route) resolveDomains() (domainMap, error) {
|
||||
|
||||
@@ -48,7 +48,7 @@ type DefaultManager struct {
|
||||
serverRouter serverRouter
|
||||
sysOps *systemops.SysOps
|
||||
statusRecorder *peer.Status
|
||||
wgInterface *iface.WGIface
|
||||
wgInterface iface.IWGIface
|
||||
pubKey string
|
||||
notifier *notifier
|
||||
routeRefCounter *refcounter.RouteRefCounter
|
||||
@@ -60,7 +60,7 @@ func NewManager(
|
||||
ctx context.Context,
|
||||
pubKey string,
|
||||
dnsRouteInterval time.Duration,
|
||||
wgInterface *iface.WGIface,
|
||||
wgInterface iface.IWGIface,
|
||||
statusRecorder *peer.Status,
|
||||
initialRoutes []*route.Route,
|
||||
) *DefaultManager {
|
||||
|
||||
@@ -11,6 +11,6 @@ import (
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
)
|
||||
|
||||
func newServerRouter(context.Context, *iface.WGIface, firewall.Manager, *peer.Status) (serverRouter, error) {
|
||||
func newServerRouter(context.Context, iface.IWGIface, firewall.Manager, *peer.Status) (serverRouter, error) {
|
||||
return nil, fmt.Errorf("server route not supported on this os")
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ type defaultServerRouter struct {
|
||||
ctx context.Context
|
||||
routes map[route.ID]*route.Route
|
||||
firewall firewall.Manager
|
||||
wgInterface *iface.WGIface
|
||||
wgInterface iface.IWGIface
|
||||
statusRecorder *peer.Status
|
||||
}
|
||||
|
||||
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface, firewall firewall.Manager, statusRecorder *peer.Status) (serverRouter, error) {
|
||||
func newServerRouter(ctx context.Context, wgInterface iface.IWGIface, firewall firewall.Manager, statusRecorder *peer.Status) (serverRouter, error) {
|
||||
return &defaultServerRouter{
|
||||
ctx: ctx,
|
||||
routes: make(map[route.ID]*route.Route),
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
// Setup configures sysctl settings for RP filtering and source validation.
|
||||
func Setup(wgIface *iface.WGIface) (map[string]int, error) {
|
||||
func Setup(wgIface iface.IWGIface) (map[string]int, error) {
|
||||
keys := map[string]int{}
|
||||
var result *multierror.Error
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ type ExclusionCounter = refcounter.Counter[any, Nexthop]
|
||||
|
||||
type SysOps struct {
|
||||
refCounter *ExclusionCounter
|
||||
wgInterface *iface.WGIface
|
||||
wgInterface iface.IWGIface
|
||||
}
|
||||
|
||||
func NewSysOps(wgInterface *iface.WGIface) *SysOps {
|
||||
func NewSysOps(wgInterface iface.IWGIface) *SysOps {
|
||||
return &SysOps{
|
||||
wgInterface: wgInterface,
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ func (r *SysOps) addRouteForCurrentDefaultGateway(prefix netip.Prefix) error {
|
||||
|
||||
// addRouteToNonVPNIntf adds a new route to the routing table for the given prefix and returns the next hop and interface.
|
||||
// If the next hop or interface is pointing to the VPN interface, it will return the initial values.
|
||||
func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf *iface.WGIface, initialNextHop Nexthop) (Nexthop, error) {
|
||||
func (r *SysOps) addRouteToNonVPNIntf(prefix netip.Prefix, vpnIntf iface.IWGIface, initialNextHop Nexthop) (Nexthop, error) {
|
||||
addr := prefix.Addr()
|
||||
switch {
|
||||
case addr.IsLoopback(),
|
||||
|
||||
@@ -8,9 +8,13 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func NewFactory(ctx context.Context, wgPort int) *Factory {
|
||||
func NewFactory(ctx context.Context, userspace bool, wgPort int) *Factory {
|
||||
f := &Factory{wgPort: wgPort}
|
||||
|
||||
if userspace {
|
||||
return f
|
||||
}
|
||||
|
||||
ebpfProxy := NewWGEBPFProxy(ctx, wgPort)
|
||||
err := ebpfProxy.listen()
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,6 @@ package wgproxy
|
||||
|
||||
import "context"
|
||||
|
||||
func NewFactory(ctx context.Context, wgPort int) *Factory {
|
||||
func NewFactory(ctx context.Context, _ bool, wgPort int) *Factory {
|
||||
return &Factory{wgPort: wgPort}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package wgproxy
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -64,7 +65,6 @@ func (p *WGUserSpaceProxy) Free() error {
|
||||
// proxyToRemote proxies everything from Wireguard to the RemoteKey peer
|
||||
// blocks
|
||||
func (p *WGUserSpaceProxy) proxyToRemote() {
|
||||
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
select {
|
||||
@@ -73,11 +73,17 @@ func (p *WGUserSpaceProxy) proxyToRemote() {
|
||||
default:
|
||||
n, err := p.localConn.Read(buf)
|
||||
if err != nil {
|
||||
log.Debugf("failed to read from wg interface conn: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = p.remoteConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
p.cancel()
|
||||
} else {
|
||||
log.Debugf("failed to write to remote conn: %s", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -96,11 +102,17 @@ func (p *WGUserSpaceProxy) proxyToLocal() {
|
||||
default:
|
||||
n, err := p.remoteConn.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
p.cancel()
|
||||
return
|
||||
}
|
||||
log.Errorf("failed to read from remote conn: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = p.localConn.Write(buf[:n])
|
||||
if err != nil {
|
||||
log.Debugf("failed to write to wg interface conn: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +174,8 @@ func seedFromStatus(a *anonymize.Anonymizer, status *peer.FullStatus) {
|
||||
}
|
||||
|
||||
for _, relay := range status.Relays {
|
||||
if relay.URI != nil {
|
||||
a.AnonymizeURI(relay.URI.String())
|
||||
if relay.URI != "" {
|
||||
a.AnonymizeURI(relay.URI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,7 +745,7 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
||||
|
||||
for _, relayState := range fullStatus.Relays {
|
||||
pbRelayState := &proto.RelayState{
|
||||
URI: relayState.URI.String(),
|
||||
URI: relayState.URI,
|
||||
Available: relayState.Err == nil,
|
||||
}
|
||||
if err := relayState.Err; err != nil {
|
||||
|
||||
@@ -108,7 +108,7 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
||||
return nil, "", err
|
||||
}
|
||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
store, cleanUp, err := server.NewTestStoreFromJson(config.Datadir)
|
||||
store, cleanUp, err := server.NewTestStoreFromJson(context.Background(), config.Datadir)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -119,13 +119,13 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
ia, _ := integrations.NewIntegratedValidator(eventStore)
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, "")
|
||||
mgmtServer, err := server.NewServer(context.Background(), config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -89,5 +89,9 @@ func _getInfo() string {
|
||||
func sysInfo() (serialNumber string, productName string, manufacturer string) {
|
||||
var si sysinfo.SysInfo
|
||||
si.GetSysInfo()
|
||||
return si.Chassis.Serial, si.Product.Name, si.Product.Vendor
|
||||
serial := si.Chassis.Serial
|
||||
if (serial == "Default string" || serial == "") && si.Product.Serial != "" {
|
||||
serial = si.Product.Serial
|
||||
}
|
||||
return serial, si.Product.Name, si.Product.Vendor
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ func main() {
|
||||
log.Errorf("check PID file: %v", err)
|
||||
return
|
||||
}
|
||||
client.setDefaultFonts()
|
||||
systray.Run(client.onTrayReady, client.onTrayExit)
|
||||
}
|
||||
}
|
||||
@@ -876,3 +877,88 @@ func checkPIDFile() error {
|
||||
|
||||
return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0o664) //nolint:gosec
|
||||
}
|
||||
|
||||
func (s *serviceClient) setDefaultFonts() {
|
||||
var (
|
||||
defaultFontPath string
|
||||
)
|
||||
|
||||
//TODO: Linux Multiple Language Support
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
defaultFontPath = "/Library/Fonts/Arial Unicode.ttf"
|
||||
case "windows":
|
||||
fontPath := s.getWindowsFontFilePath()
|
||||
defaultFontPath = fontPath
|
||||
}
|
||||
|
||||
_, err := os.Stat(defaultFontPath)
|
||||
|
||||
if err == nil {
|
||||
os.Setenv("FYNE_FONT", defaultFontPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceClient) getWindowsFontFilePath() (fontPath string) {
|
||||
/*
|
||||
https://learn.microsoft.com/en-us/windows/apps/design/globalizing/loc-international-fonts
|
||||
https://learn.microsoft.com/en-us/typography/fonts/windows_11_font_list
|
||||
*/
|
||||
|
||||
var (
|
||||
fontFolder string = "C:/Windows/Fonts"
|
||||
fontMapping = map[string]string{
|
||||
"default": "Segoeui.ttf",
|
||||
"zh-CN": "Msyh.ttc",
|
||||
"am-ET": "Ebrima.ttf",
|
||||
"nirmala": "Nirmala.ttf",
|
||||
"chr-CHER-US": "Gadugi.ttf",
|
||||
"zh-HK": "Msjh.ttc",
|
||||
"zh-TW": "Msjh.ttc",
|
||||
"ja-JP": "Yugothm.ttc",
|
||||
"km-KH": "Leelawui.ttf",
|
||||
"ko-KR": "Malgun.ttf",
|
||||
"th-TH": "Leelawui.ttf",
|
||||
"ti-ET": "Ebrima.ttf",
|
||||
}
|
||||
nirMalaLang = []string{
|
||||
"as-IN",
|
||||
"bn-BD",
|
||||
"bn-IN",
|
||||
"gu-IN",
|
||||
"hi-IN",
|
||||
"kn-IN",
|
||||
"kok-IN",
|
||||
"ml-IN",
|
||||
"mr-IN",
|
||||
"ne-NP",
|
||||
"or-IN",
|
||||
"pa-IN",
|
||||
"si-LK",
|
||||
"ta-IN",
|
||||
"te-IN",
|
||||
}
|
||||
)
|
||||
cmd := exec.Command("powershell", "-Command", "(Get-Culture).Name")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get Windows default language setting: %v", err)
|
||||
fontPath = path.Join(fontFolder, fontMapping["default"])
|
||||
return
|
||||
}
|
||||
defaultLanguage := strings.TrimSpace(string(output))
|
||||
|
||||
for _, lang := range nirMalaLang {
|
||||
if defaultLanguage == lang {
|
||||
fontPath = path.Join(fontFolder, fontMapping["nirmala"])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if font, ok := fontMapping[defaultLanguage]; ok {
|
||||
fontPath = path.Join(fontFolder, font)
|
||||
} else {
|
||||
fontPath = path.Join(fontFolder, fontMapping["default"])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -17,28 +18,57 @@ import (
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
allRoutesText = "All routes"
|
||||
overlappingRoutesText = "Overlapping routes"
|
||||
exitNodeRoutesText = "Exit-node routes"
|
||||
allRoutes filter = "all"
|
||||
overlappingRoutes filter = "overlapping"
|
||||
exitNodeRoutes filter = "exit-node"
|
||||
getClientFMT = "get client: %v"
|
||||
)
|
||||
|
||||
type filter string
|
||||
|
||||
func (s *serviceClient) showRoutesUI() {
|
||||
s.wRoutes = s.app.NewWindow("NetBird Routes")
|
||||
|
||||
grid := container.New(layout.NewGridLayout(3))
|
||||
go s.updateRoutes(grid)
|
||||
allGrid := container.New(layout.NewGridLayout(3))
|
||||
go s.updateRoutes(allGrid, allRoutes)
|
||||
overlappingGrid := container.New(layout.NewGridLayout(3))
|
||||
exitNodeGrid := container.New(layout.NewGridLayout(3))
|
||||
routeCheckContainer := container.NewVBox()
|
||||
routeCheckContainer.Add(grid)
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem(allRoutesText, allGrid),
|
||||
container.NewTabItem(overlappingRoutesText, overlappingGrid),
|
||||
container.NewTabItem(exitNodeRoutesText, exitNodeGrid),
|
||||
)
|
||||
tabs.OnSelected = func(item *container.TabItem) {
|
||||
s.updateRoutesBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
}
|
||||
tabs.OnUnselected = func(item *container.TabItem) {
|
||||
grid, _ := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
grid.Objects = nil
|
||||
}
|
||||
|
||||
routeCheckContainer.Add(tabs)
|
||||
scrollContainer := container.NewVScroll(routeCheckContainer)
|
||||
scrollContainer.SetMinSize(fyne.NewSize(200, 300))
|
||||
|
||||
buttonBox := container.NewHBox(
|
||||
layout.NewSpacer(),
|
||||
widget.NewButton("Refresh", func() {
|
||||
s.updateRoutes(grid)
|
||||
s.updateRoutesBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
}),
|
||||
widget.NewButton("Select all", func() {
|
||||
s.selectAllRoutes()
|
||||
s.updateRoutes(grid)
|
||||
_, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
s.selectAllFilteredRoutes(f)
|
||||
s.updateRoutesBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
}),
|
||||
widget.NewButton("Deselect All", func() {
|
||||
s.deselectAllRoutes()
|
||||
s.updateRoutes(grid)
|
||||
_, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
s.deselectAllFilteredRoutes(f)
|
||||
s.updateRoutesBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
}),
|
||||
layout.NewSpacer(),
|
||||
)
|
||||
@@ -48,18 +78,12 @@ func (s *serviceClient) showRoutesUI() {
|
||||
s.wRoutes.SetContent(content)
|
||||
s.wRoutes.Show()
|
||||
|
||||
s.startAutoRefresh(5*time.Second, grid)
|
||||
s.startAutoRefresh(10*time.Second, tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||
}
|
||||
|
||||
func (s *serviceClient) updateRoutes(grid *fyne.Container) {
|
||||
routes, err := s.fetchRoutes()
|
||||
if err != nil {
|
||||
log.Errorf("get client: %v", err)
|
||||
s.showError(fmt.Errorf("get client: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serviceClient) updateRoutes(grid *fyne.Container, f filter) {
|
||||
grid.Objects = nil
|
||||
grid.Refresh()
|
||||
idHeader := widget.NewLabelWithStyle(" ID", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
networkHeader := widget.NewLabelWithStyle("Network/Domains", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
resolvedIPsHeader := widget.NewLabelWithStyle("Resolved IPs", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
@@ -67,7 +91,15 @@ func (s *serviceClient) updateRoutes(grid *fyne.Container) {
|
||||
grid.Add(idHeader)
|
||||
grid.Add(networkHeader)
|
||||
grid.Add(resolvedIPsHeader)
|
||||
for _, route := range routes {
|
||||
|
||||
filteredRoutes, err := s.getFilteredRoutes(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sortRoutesByIDs(filteredRoutes)
|
||||
|
||||
for _, route := range filteredRoutes {
|
||||
r := route
|
||||
|
||||
checkBox := widget.NewCheck(r.GetID(), func(checked bool) {
|
||||
@@ -80,35 +112,104 @@ func (s *serviceClient) updateRoutes(grid *fyne.Container) {
|
||||
grid.Add(checkBox)
|
||||
network := r.GetNetwork()
|
||||
domains := r.GetDomains()
|
||||
if len(domains) > 0 {
|
||||
network = strings.Join(domains, ", ")
|
||||
}
|
||||
grid.Add(widget.NewLabel(network))
|
||||
|
||||
if len(domains) > 0 {
|
||||
var resolvedIPsList []string
|
||||
for _, domain := range r.GetDomains() {
|
||||
if ipList, exists := r.GetResolvedIPs()[domain]; exists {
|
||||
resolvedIPsList = append(resolvedIPsList, fmt.Sprintf("%s: %s", domain, strings.Join(ipList.GetIps(), ", ")))
|
||||
}
|
||||
}
|
||||
// TODO: limit width
|
||||
resolvedIPsLabel := widget.NewLabel(strings.Join(resolvedIPsList, ", "))
|
||||
grid.Add(resolvedIPsLabel)
|
||||
} else {
|
||||
if len(domains) == 0 {
|
||||
grid.Add(widget.NewLabel(network))
|
||||
grid.Add(widget.NewLabel(""))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// our selectors are only for display
|
||||
noopFunc := func(_ string) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
domainsSelector := widget.NewSelect(domains, noopFunc)
|
||||
domainsSelector.Selected = domains[0]
|
||||
grid.Add(domainsSelector)
|
||||
|
||||
var resolvedIPsList []string
|
||||
for _, domain := range domains {
|
||||
if ipList, exists := r.GetResolvedIPs()[domain]; exists {
|
||||
resolvedIPsList = append(resolvedIPsList, fmt.Sprintf("%s: %s", domain, strings.Join(ipList.GetIps(), ", ")))
|
||||
}
|
||||
}
|
||||
|
||||
if len(resolvedIPsList) == 0 {
|
||||
grid.Add(widget.NewLabel(""))
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: limit width within the selector display
|
||||
resolvedIPsSelector := widget.NewSelect(resolvedIPsList, noopFunc)
|
||||
resolvedIPsSelector.Selected = resolvedIPsList[0]
|
||||
resolvedIPsSelector.Resize(fyne.NewSize(100, 100))
|
||||
grid.Add(resolvedIPsSelector)
|
||||
}
|
||||
|
||||
s.wRoutes.Content().Refresh()
|
||||
grid.Refresh()
|
||||
}
|
||||
|
||||
func (s *serviceClient) getFilteredRoutes(f filter) ([]*proto.Route, error) {
|
||||
routes, err := s.fetchRoutes()
|
||||
if err != nil {
|
||||
log.Errorf(getClientFMT, err)
|
||||
s.showError(fmt.Errorf(getClientFMT, err))
|
||||
return nil, err
|
||||
}
|
||||
switch f {
|
||||
case overlappingRoutes:
|
||||
return getOverlappingRoutes(routes), nil
|
||||
case exitNodeRoutes:
|
||||
return getExitNodeRoutes(routes), nil
|
||||
default:
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func getOverlappingRoutes(routes []*proto.Route) []*proto.Route {
|
||||
var filteredRoutes []*proto.Route
|
||||
existingRange := make(map[string][]*proto.Route)
|
||||
for _, route := range routes {
|
||||
if len(route.Domains) > 0 {
|
||||
continue
|
||||
}
|
||||
if r, exists := existingRange[route.GetNetwork()]; exists {
|
||||
r = append(r, route)
|
||||
existingRange[route.GetNetwork()] = r
|
||||
} else {
|
||||
existingRange[route.GetNetwork()] = []*proto.Route{route}
|
||||
}
|
||||
}
|
||||
for _, r := range existingRange {
|
||||
if len(r) > 1 {
|
||||
filteredRoutes = append(filteredRoutes, r...)
|
||||
}
|
||||
}
|
||||
return filteredRoutes
|
||||
}
|
||||
|
||||
func getExitNodeRoutes(routes []*proto.Route) []*proto.Route {
|
||||
var filteredRoutes []*proto.Route
|
||||
for _, route := range routes {
|
||||
if route.Network == "0.0.0.0/0" {
|
||||
filteredRoutes = append(filteredRoutes, route)
|
||||
}
|
||||
}
|
||||
return filteredRoutes
|
||||
}
|
||||
|
||||
func sortRoutesByIDs(routes []*proto.Route) {
|
||||
sort.Slice(routes, func(i, j int) bool {
|
||||
return strings.ToLower(routes[i].GetID()) < strings.ToLower(routes[j].GetID())
|
||||
})
|
||||
}
|
||||
|
||||
func (s *serviceClient) fetchRoutes() ([]*proto.Route, error) {
|
||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get client: %v", err)
|
||||
return nil, fmt.Errorf(getClientFMT, err)
|
||||
}
|
||||
|
||||
resp, err := conn.ListRoutes(s.ctx, &proto.ListRoutesRequest{})
|
||||
@@ -122,8 +223,8 @@ func (s *serviceClient) fetchRoutes() ([]*proto.Route, error) {
|
||||
func (s *serviceClient) selectRoute(id string, checked bool) {
|
||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||
if err != nil {
|
||||
log.Errorf("get client: %v", err)
|
||||
s.showError(fmt.Errorf("get client: %v", err))
|
||||
log.Errorf(getClientFMT, err)
|
||||
s.showError(fmt.Errorf(getClientFMT, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -149,16 +250,14 @@ func (s *serviceClient) selectRoute(id string, checked bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceClient) selectAllRoutes() {
|
||||
func (s *serviceClient) selectAllFilteredRoutes(f filter) {
|
||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||
if err != nil {
|
||||
log.Errorf("get client: %v", err)
|
||||
log.Errorf(getClientFMT, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := &proto.SelectRoutesRequest{
|
||||
All: true,
|
||||
}
|
||||
req := s.getRoutesRequest(f, true)
|
||||
if _, err := conn.SelectRoutes(s.ctx, req); err != nil {
|
||||
log.Errorf("failed to select all routes: %v", err)
|
||||
s.showError(fmt.Errorf("failed to select all routes: %v", err))
|
||||
@@ -168,16 +267,14 @@ func (s *serviceClient) selectAllRoutes() {
|
||||
log.Debug("All routes selected")
|
||||
}
|
||||
|
||||
func (s *serviceClient) deselectAllRoutes() {
|
||||
func (s *serviceClient) deselectAllFilteredRoutes(f filter) {
|
||||
conn, err := s.getSrvClient(defaultFailTimeout)
|
||||
if err != nil {
|
||||
log.Errorf("get client: %v", err)
|
||||
log.Errorf(getClientFMT, err)
|
||||
return
|
||||
}
|
||||
|
||||
req := &proto.SelectRoutesRequest{
|
||||
All: true,
|
||||
}
|
||||
req := s.getRoutesRequest(f, false)
|
||||
if _, err := conn.DeselectRoutes(s.ctx, req); err != nil {
|
||||
log.Errorf("failed to deselect all routes: %v", err)
|
||||
s.showError(fmt.Errorf("failed to deselect all routes: %v", err))
|
||||
@@ -187,17 +284,34 @@ func (s *serviceClient) deselectAllRoutes() {
|
||||
log.Debug("All routes deselected")
|
||||
}
|
||||
|
||||
func (s *serviceClient) getRoutesRequest(f filter, appendRoute bool) *proto.SelectRoutesRequest {
|
||||
req := &proto.SelectRoutesRequest{}
|
||||
if f == allRoutes {
|
||||
req.All = true
|
||||
} else {
|
||||
routes, err := s.getFilteredRoutes(f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, route := range routes {
|
||||
req.RouteIDs = append(req.RouteIDs, route.GetID())
|
||||
}
|
||||
req.Append = appendRoute
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (s *serviceClient) showError(err error) {
|
||||
wrappedMessage := wrapText(err.Error(), 50)
|
||||
|
||||
dialog.ShowError(fmt.Errorf("%s", wrappedMessage), s.wRoutes)
|
||||
}
|
||||
|
||||
func (s *serviceClient) startAutoRefresh(interval time.Duration, grid *fyne.Container) {
|
||||
func (s *serviceClient) startAutoRefresh(interval time.Duration, tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
||||
ticker := time.NewTicker(interval)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
s.updateRoutes(grid)
|
||||
s.updateRoutesBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodesGrid)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -206,6 +320,23 @@ func (s *serviceClient) startAutoRefresh(interval time.Duration, grid *fyne.Cont
|
||||
})
|
||||
}
|
||||
|
||||
func (s *serviceClient) updateRoutesBasedOnDisplayTab(tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
||||
grid, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodesGrid)
|
||||
s.wRoutes.Content().Refresh()
|
||||
s.updateRoutes(grid, f)
|
||||
}
|
||||
|
||||
func getGridAndFilterFromTab(tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) (*fyne.Container, filter) {
|
||||
switch tabs.Selected().Text {
|
||||
case overlappingRoutesText:
|
||||
return overlappingGrid, overlappingRoutes
|
||||
case exitNodeRoutesText:
|
||||
return exitNodesGrid, exitNodeRoutes
|
||||
default:
|
||||
return allGrid, allRoutes
|
||||
}
|
||||
}
|
||||
|
||||
// wrapText inserts newlines into the text to ensure that each line is
|
||||
// no longer than 'lineLength' runes.
|
||||
func wrapText(text string, lineLength int) string {
|
||||
|
||||
19
encryption/cert.go
Normal file
19
encryption/cert.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package encryption
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func LoadTLSConfig(certFile, keyFile string) (*tls.Config, error) {
|
||||
serverCert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{serverCert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
NextProtos: []string{
|
||||
"h2", "http/1.1", // enable HTTP/2
|
||||
},
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// CreateCertManager wraps common logic of generating Let's encrypt certificate.
|
||||
func CreateCertManager(datadir string, letsencryptDomain string) (*autocert.Manager, error) {
|
||||
func CreateCertManager(datadir string, letsencryptDomain ...string) (*autocert.Manager, error) {
|
||||
certDir := filepath.Join(datadir, "letsencrypt")
|
||||
|
||||
if _, err := os.Stat(certDir); os.IsNotExist(err) {
|
||||
@@ -24,7 +24,7 @@ func CreateCertManager(datadir string, letsencryptDomain string) (*autocert.Mana
|
||||
certManager := &autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
Cache: autocert.DirCache(certDir),
|
||||
HostPolicy: autocert.HostWhitelist(letsencryptDomain),
|
||||
HostPolicy: autocert.HostWhitelist(letsencryptDomain...),
|
||||
}
|
||||
|
||||
return certManager, nil
|
||||
|
||||
@@ -7,6 +7,18 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/context"
|
||||
)
|
||||
|
||||
type ExecutionContext string
|
||||
|
||||
const (
|
||||
ExecutionContextKey = "executionContext"
|
||||
|
||||
HTTPSource ExecutionContext = "HTTP"
|
||||
GRPCSource ExecutionContext = "GRPC"
|
||||
SystemSource ExecutionContext = "SYSTEM"
|
||||
)
|
||||
|
||||
// ContextHook is a custom hook for add the source information for the entry
|
||||
@@ -30,6 +42,27 @@ func (hook ContextHook) Levels() []logrus.Level {
|
||||
func (hook ContextHook) Fire(entry *logrus.Entry) error {
|
||||
src := hook.parseSrc(entry.Caller.File)
|
||||
entry.Data["source"] = fmt.Sprintf("%s:%v", src, entry.Caller.Line)
|
||||
|
||||
if entry.Context == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
source, ok := entry.Context.Value(ExecutionContextKey).(ExecutionContext)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
entry.Data["context"] = source
|
||||
|
||||
switch source {
|
||||
case HTTPSource:
|
||||
addHTTPFields(entry)
|
||||
case GRPCSource:
|
||||
addGRPCFields(entry)
|
||||
case SystemSource:
|
||||
addSystemFields(entry)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -59,3 +92,42 @@ func (hook ContextHook) parseSrc(filePath string) string {
|
||||
file := path.Base(filePath)
|
||||
return fmt.Sprintf("%s/%s", pkg, file)
|
||||
}
|
||||
|
||||
func addHTTPFields(entry *logrus.Entry) {
|
||||
if ctxReqID, ok := entry.Context.Value(context.RequestIDKey).(string); ok {
|
||||
entry.Data[context.RequestIDKey] = ctxReqID
|
||||
}
|
||||
if ctxAccountID, ok := entry.Context.Value(context.AccountIDKey).(string); ok {
|
||||
entry.Data[context.AccountIDKey] = ctxAccountID
|
||||
}
|
||||
if ctxInitiatorID, ok := entry.Context.Value(context.UserIDKey).(string); ok {
|
||||
entry.Data[context.UserIDKey] = ctxInitiatorID
|
||||
}
|
||||
}
|
||||
|
||||
func addGRPCFields(entry *logrus.Entry) {
|
||||
if ctxReqID, ok := entry.Context.Value(context.RequestIDKey).(string); ok {
|
||||
entry.Data[context.RequestIDKey] = ctxReqID
|
||||
}
|
||||
if ctxAccountID, ok := entry.Context.Value(context.AccountIDKey).(string); ok {
|
||||
entry.Data[context.AccountIDKey] = ctxAccountID
|
||||
}
|
||||
if ctxDeviceID, ok := entry.Context.Value(context.PeerIDKey).(string); ok {
|
||||
entry.Data[context.PeerIDKey] = ctxDeviceID
|
||||
}
|
||||
}
|
||||
|
||||
func addSystemFields(entry *logrus.Entry) {
|
||||
if ctxReqID, ok := entry.Context.Value(context.RequestIDKey).(string); ok {
|
||||
entry.Data[context.RequestIDKey] = ctxReqID
|
||||
}
|
||||
if ctxInitiatorID, ok := entry.Context.Value(context.UserIDKey).(string); ok {
|
||||
entry.Data[context.UserIDKey] = ctxInitiatorID
|
||||
}
|
||||
if ctxAccountID, ok := entry.Context.Value(context.AccountIDKey).(string); ok {
|
||||
entry.Data[context.AccountIDKey] = ctxAccountID
|
||||
}
|
||||
if ctxDeviceID, ok := entry.Context.Value(context.PeerIDKey).(string); ok {
|
||||
entry.Data[context.PeerIDKey] = ctxDeviceID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package formatter
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SetTextFormatter set the text formatter for given logger.
|
||||
func SetTextFormatter(logger *logrus.Logger) {
|
||||
@@ -9,6 +11,13 @@ func SetTextFormatter(logger *logrus.Logger) {
|
||||
logger.AddHook(NewContextHook())
|
||||
}
|
||||
|
||||
// SetJSONFormatter set the JSON formatter for given logger.
|
||||
func SetJSONFormatter(logger *logrus.Logger) {
|
||||
logger.Formatter = &logrus.JSONFormatter{}
|
||||
logger.ReportCaller = true
|
||||
logger.AddHook(NewContextHook())
|
||||
}
|
||||
|
||||
// SetLogcatFormatter set the logcat formatter for given logger.
|
||||
func SetLogcatFormatter(logger *logrus.Logger) {
|
||||
logger.Formatter = NewLogcatFormatter()
|
||||
|
||||
19
go.mod
19
go.mod
@@ -12,7 +12,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.23.0
|
||||
github.com/onsi/gomega v1.27.6
|
||||
github.com/pion/ice/v3 v3.0.2
|
||||
github.com/rs/cors v1.8.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
@@ -44,7 +44,6 @@ require (
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/martian/v3 v3.0.0
|
||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
||||
github.com/gopacket/gopacket v1.1.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
||||
@@ -58,15 +57,17 @@ require (
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/nadoo/ipset v0.5.0
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240524104853-69c6d89826cd
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240703085513-32605f7ffd8e
|
||||
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/randutil v0.1.0
|
||||
github.com/pion/stun/v2 v2.0.0
|
||||
github.com/pion/transport/v3 v3.0.1
|
||||
github.com/pion/turn/v3 v3.0.1
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/quic-go/quic-go v0.45.0
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.4
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
@@ -93,6 +94,7 @@ require (
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/driver/sqlite v1.5.3
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
|
||||
nhooyr.io/websocket v1.8.11
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -134,10 +136,12 @@ require (
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
@@ -163,13 +167,13 @@ require (
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/pegasus-kv/thrift v0.13.0 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.10 // indirect
|
||||
github.com/pion/mdns v0.0.12 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/transport/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -189,9 +193,12 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||
golang.org/x/image v0.10.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||
|
||||
44
go.sum
44
go.sum
@@ -50,6 +50,9 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/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.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
@@ -154,6 +157,8 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -209,10 +214,10 @@ github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
|
||||
github.com/google/martian/v3 v3.0.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-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -247,6 +252,7 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@@ -335,8 +341,8 @@ github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6S
|
||||
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
|
||||
github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e h1:PURA50S8u4mF6RrkYYCAvvPCixhqqEiEy3Ej6avh04c=
|
||||
github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e/go.mod h1:YMLU7qbKfVjmEv7EoZPIVEI+kNYxWCdPK3VS0BU+U4Q=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240524104853-69c6d89826cd h1:IzGGIJMpz07aPs3R6/4sxZv63JoCMddftLpVodUK+Ec=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240524104853-69c6d89826cd/go.mod h1:kxks50DrZnhW+oRTdHOkVOJbcTcyo766am8RBugo+Yc=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240703085513-32605f7ffd8e h1:LYxhAmiEzSldLELHSMVoUnRPq3ztTNQImrD27frrGsI=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240703085513-32605f7ffd8e/go.mod h1:nykwWZnxb+sJz2Z//CEq45CMRWSHllH8pODKRB8eY7Y=
|
||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
|
||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=
|
||||
@@ -356,14 +362,14 @@ github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
|
||||
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@@ -415,6 +421,8 @@ github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+a
|
||||
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
||||
github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=
|
||||
github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=
|
||||
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
|
||||
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so=
|
||||
@@ -462,6 +470,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
@@ -524,6 +533,8 @@ go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2L
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
goauthentik.io/api/v3 v3.2023051.3 h1:NebAhD/TeTWNo/9X3/Uj+rM5fG1HaiLOlKTNLQv9Qq4=
|
||||
goauthentik.io/api/v3 v3.2023051.3/go.mod h1:nYECml4jGbp/541hj8GcylKQG1gVBsKppHy4+7G8u4U=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -542,8 +553,8 @@ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJ
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
|
||||
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -556,6 +567,8 @@ golang.org/x/mod v0.3.0/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/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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=
|
||||
@@ -565,7 +578,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
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-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -610,6 +622,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -655,11 +668,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -678,6 +690,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -772,5 +786,7 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
|
||||
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
|
||||
103
iface/iface_moc.go
Normal file
103
iface/iface_moc.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/netbirdio/netbird/iface/bind"
|
||||
)
|
||||
|
||||
type MockWGIface struct {
|
||||
CreateFunc func() error
|
||||
CreateOnAndroidFunc func(routeRange []string, ip string, domains []string) error
|
||||
IsUserspaceBindFunc func() bool
|
||||
NameFunc func() string
|
||||
AddressFunc func() WGAddress
|
||||
ToInterfaceFunc func() *net.Interface
|
||||
UpFunc func() (*bind.UniversalUDPMuxDefault, error)
|
||||
UpdateAddrFunc func(newAddr string) error
|
||||
UpdatePeerFunc func(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||
RemovePeerFunc func(peerKey string) error
|
||||
AddAllowedIPFunc func(peerKey string, allowedIP string) error
|
||||
RemoveAllowedIPFunc func(peerKey string, allowedIP string) error
|
||||
CloseFunc func() error
|
||||
SetFilterFunc func(filter PacketFilter) error
|
||||
GetFilterFunc func() PacketFilter
|
||||
GetDeviceFunc func() *DeviceWrapper
|
||||
GetStatsFunc func(peerKey string) (WGStats, error)
|
||||
GetInterfaceGUIDStringFunc func() (string, error)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) GetInterfaceGUIDString() (string, error) {
|
||||
return m.GetInterfaceGUIDStringFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) Create() error {
|
||||
return m.CreateFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) CreateOnAndroid(routeRange []string, ip string, domains []string) error {
|
||||
return m.CreateOnAndroidFunc(routeRange, ip, domains)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) IsUserspaceBind() bool {
|
||||
return m.IsUserspaceBindFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) Name() string {
|
||||
return m.NameFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) Address() WGAddress {
|
||||
return m.AddressFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) ToInterface() *net.Interface {
|
||||
return m.ToInterfaceFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
||||
return m.UpFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) UpdateAddr(newAddr string) error {
|
||||
return m.UpdateAddrFunc(newAddr)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||
return m.UpdatePeerFunc(peerKey, allowedIps, keepAlive, endpoint, preSharedKey)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) RemovePeer(peerKey string) error {
|
||||
return m.RemovePeerFunc(peerKey)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) AddAllowedIP(peerKey string, allowedIP string) error {
|
||||
return m.AddAllowedIPFunc(peerKey, allowedIP)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) RemoveAllowedIP(peerKey string, allowedIP string) error {
|
||||
return m.RemoveAllowedIPFunc(peerKey, allowedIP)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) Close() error {
|
||||
return m.CloseFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) SetFilter(filter PacketFilter) error {
|
||||
return m.SetFilterFunc(filter)
|
||||
}
|
||||
|
||||
func (m *MockWGIface) GetFilter() PacketFilter {
|
||||
return m.GetFilterFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) GetDevice() *DeviceWrapper {
|
||||
return m.GetDeviceFunc()
|
||||
}
|
||||
|
||||
func (m *MockWGIface) GetStats(peerKey string) (WGStats, error) {
|
||||
return m.GetStatsFunc(peerKey)
|
||||
}
|
||||
32
iface/iwginterface.go
Normal file
32
iface/iwginterface.go
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build !windows
|
||||
|
||||
package iface
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/netbirdio/netbird/iface/bind"
|
||||
)
|
||||
|
||||
type IWGIface interface {
|
||||
Create() error
|
||||
CreateOnAndroid(routeRange []string, ip string, domains []string) error
|
||||
IsUserspaceBind() bool
|
||||
Name() string
|
||||
Address() WGAddress
|
||||
ToInterface() *net.Interface
|
||||
Up() (*bind.UniversalUDPMuxDefault, error)
|
||||
UpdateAddr(newAddr string) error
|
||||
UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||
RemovePeer(peerKey string) error
|
||||
AddAllowedIP(peerKey string, allowedIP string) error
|
||||
RemoveAllowedIP(peerKey string, allowedIP string) error
|
||||
Close() error
|
||||
SetFilter(filter PacketFilter) error
|
||||
GetFilter() PacketFilter
|
||||
GetDevice() *DeviceWrapper
|
||||
GetStats(peerKey string) (WGStats, error)
|
||||
}
|
||||
31
iface/iwginterface_windows.go
Normal file
31
iface/iwginterface_windows.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package iface
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/netbirdio/netbird/iface/bind"
|
||||
)
|
||||
|
||||
type IWGIface interface {
|
||||
Create() error
|
||||
CreateOnAndroid(routeRange []string, ip string, domains []string) error
|
||||
IsUserspaceBind() bool
|
||||
Name() string
|
||||
Address() WGAddress
|
||||
ToInterface() *net.Interface
|
||||
Up() (*bind.UniversalUDPMuxDefault, error)
|
||||
UpdateAddr(newAddr string) error
|
||||
UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||
RemovePeer(peerKey string) error
|
||||
AddAllowedIP(peerKey string, allowedIP string) error
|
||||
RemoveAllowedIP(peerKey string, allowedIP string) error
|
||||
Close() error
|
||||
SetFilter(filter PacketFilter) error
|
||||
GetFilter() PacketFilter
|
||||
GetDevice() *DeviceWrapper
|
||||
GetStats(peerKey string) (WGStats, error)
|
||||
GetInterfaceGUIDString() (string, error)
|
||||
}
|
||||
@@ -28,7 +28,11 @@ services:
|
||||
- LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
|
||||
volumes:
|
||||
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt/
|
||||
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
# Signal
|
||||
signal:
|
||||
image: netbirdio/signal:$NETBIRD_SIGNAL_TAG
|
||||
@@ -40,6 +44,11 @@ services:
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
|
||||
# Management
|
||||
management:
|
||||
@@ -63,12 +72,16 @@ services:
|
||||
"--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN",
|
||||
"--dns-domain=$NETBIRD_MGMT_DNS_DOMAIN"
|
||||
]
|
||||
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
# Coturn
|
||||
coturn:
|
||||
image: coturn/coturn:$COTURN_TAG
|
||||
restart: unless-stopped
|
||||
domainname: $TURN_DOMAIN
|
||||
#domainname: $TURN_DOMAIN # only needed when TLS is enabled
|
||||
volumes:
|
||||
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
||||
@@ -76,7 +89,11 @@ services:
|
||||
network_mode: host
|
||||
command:
|
||||
- -c /etc/turnserver.conf
|
||||
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
volumes:
|
||||
$MGMT_VOLUMENAME:
|
||||
$SIGNAL_VOLUMENAME:
|
||||
|
||||
@@ -50,7 +50,7 @@ check_jq() {
|
||||
wait_crdb() {
|
||||
set +e
|
||||
while true; do
|
||||
if $DOCKER_COMPOSE_COMMAND exec -T crdb curl -sf -o /dev/null 'http://localhost:8080/health?ready=1'; then
|
||||
if $DOCKER_COMPOSE_COMMAND exec -T zdb curl -sf -o /dev/null 'http://localhost:8080/health?ready=1'; then
|
||||
break
|
||||
fi
|
||||
echo -n " ."
|
||||
@@ -61,14 +61,16 @@ wait_crdb() {
|
||||
}
|
||||
|
||||
init_crdb() {
|
||||
echo -e "\nInitializing Zitadel's CockroachDB\n\n"
|
||||
$DOCKER_COMPOSE_COMMAND up -d crdb
|
||||
echo ""
|
||||
# shellcheck disable=SC2028
|
||||
echo -n "Waiting cockroachDB to become ready "
|
||||
wait_crdb
|
||||
$DOCKER_COMPOSE_COMMAND exec -T crdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/"
|
||||
handle_request_command_status $? "init_crdb failed" ""
|
||||
if [[ $ZITADEL_DATABASE == "cockroach" ]]; then
|
||||
echo -e "\nInitializing Zitadel's CockroachDB\n\n"
|
||||
$DOCKER_COMPOSE_COMMAND up -d zdb
|
||||
echo ""
|
||||
# shellcheck disable=SC2028
|
||||
echo -n "Waiting CockroachDB to become ready"
|
||||
wait_crdb
|
||||
$DOCKER_COMPOSE_COMMAND exec -T zdb /bin/bash -c "cp /cockroach/certs/* /zitadel-certs/ && cockroach cert create-client --overwrite --certs-dir /zitadel-certs/ --ca-key /zitadel-certs/ca.key zitadel_user && chown -R 1000:1000 /zitadel-certs/"
|
||||
handle_request_command_status $? "init_crdb failed" ""
|
||||
fi
|
||||
}
|
||||
|
||||
get_main_ip_address() {
|
||||
@@ -156,7 +158,7 @@ create_new_application() {
|
||||
"'"$BASE_REDIRECT_URL2"'"
|
||||
],
|
||||
"postLogoutRedirectUris": [
|
||||
"'"$LOGOUT_URL"'"
|
||||
"'"$LOGOUT_URL"'"
|
||||
],
|
||||
"RESPONSETypes": [
|
||||
"OIDC_RESPONSE_TYPE_CODE"
|
||||
@@ -461,6 +463,20 @@ initEnvironment() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $ZITADEL_DATABASE == "cockroach" ]]; then
|
||||
echo "Use CockroachDB as Zitadel database."
|
||||
ZDB=$(renderDockerComposeCockroachDB)
|
||||
ZITADEL_DB_ENV=$(renderZitadelCockroachDBEnv)
|
||||
else
|
||||
echo "Use Postgres as default Zitadel database."
|
||||
echo "For using CockroachDB please the environment variable 'export ZITADEL_DATABASE=cockroach'."
|
||||
POSTGRES_ROOT_PASSWORD="$(openssl rand -base64 32 | sed 's/=//g')@"
|
||||
POSTGRES_ZITADEL_PASSWORD="$(openssl rand -base64 32 | sed 's/=//g')@"
|
||||
ZDB=$(renderDockerComposePostgres)
|
||||
ZITADEL_DB_ENV=$(renderZitadelPostgresEnv)
|
||||
renderPostgresEnv > zdb.env
|
||||
fi
|
||||
|
||||
echo Rendering initial files...
|
||||
renderDockerCompose > docker-compose.yml
|
||||
renderCaddyfile > Caddyfile
|
||||
@@ -474,7 +490,7 @@ initEnvironment() {
|
||||
|
||||
init_crdb
|
||||
|
||||
echo -e "\nStarting Zidatel IDP for user management\n\n"
|
||||
echo -e "\nStarting Zitadel IDP for user management\n\n"
|
||||
$DOCKER_COMPOSE_COMMAND up -d caddy zitadel
|
||||
init_zitadel
|
||||
|
||||
@@ -634,15 +650,15 @@ renderManagementJson() {
|
||||
"ExtraConfig": {
|
||||
"ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1"
|
||||
}
|
||||
},
|
||||
"DeviceAuthorizationFlow": {
|
||||
"Provider": "hosted",
|
||||
"ProviderConfig": {
|
||||
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||
"ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||
"Scope": "openid"
|
||||
}
|
||||
},
|
||||
},
|
||||
"DeviceAuthorizationFlow": {
|
||||
"Provider": "hosted",
|
||||
"ProviderConfig": {
|
||||
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||
"ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||
"Scope": "openid"
|
||||
}
|
||||
},
|
||||
"PKCEAuthorizationFlow": {
|
||||
"ProviderConfig": {
|
||||
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
|
||||
@@ -679,16 +695,6 @@ renderZitadelEnv() {
|
||||
cat <<EOF
|
||||
ZITADEL_LOG_LEVEL=debug
|
||||
ZITADEL_MASTERKEY=$ZITADEL_MASTERKEY
|
||||
ZITADEL_DATABASE_COCKROACH_HOST=crdb
|
||||
ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/crdb-certs/ca.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/crdb-certs/client.zitadel_user.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/crdb-certs/client.zitadel_user.key"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/crdb-certs/ca.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/crdb-certs/client.root.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/crdb-certs/client.root.key"
|
||||
ZITADEL_EXTERNALSECURE=$ZITADEL_EXTERNALSECURE
|
||||
ZITADEL_TLS_ENABLED="false"
|
||||
ZITADEL_EXTERNALPORT=$NETBIRD_PORT
|
||||
@@ -698,6 +704,43 @@ ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_SCOPES=openid
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE=$ZIDATE_TOKEN_EXPIRATION_DATE
|
||||
$ZITADEL_DB_ENV
|
||||
EOF
|
||||
}
|
||||
|
||||
renderZitadelCockroachDBEnv() {
|
||||
cat <<EOF
|
||||
ZITADEL_DATABASE_COCKROACH_HOST=zdb
|
||||
ZITADEL_DATABASE_COCKROACH_USER_USERNAME=zitadel_user
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_MODE=verify-full
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT="/zdb-certs/ca.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT="/zdb-certs/client.zitadel_user.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY="/zdb-certs/client.zitadel_user.key"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_MODE=verify-full
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT="/zdb-certs/ca.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT="/zdb-certs/client.root.crt"
|
||||
ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY="/zdb-certs/client.root.key"
|
||||
EOF
|
||||
}
|
||||
|
||||
renderZitadelPostgresEnv() {
|
||||
cat <<EOF
|
||||
ZITADEL_DATABASE_POSTGRES_HOST=zdb
|
||||
ZITADEL_DATABASE_POSTGRES_PORT=5432
|
||||
ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel
|
||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadel
|
||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=$POSTGRES_ZITADEL_PASSWORD
|
||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable
|
||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=root
|
||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=$POSTGRES_ROOT_PASSWORD
|
||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable
|
||||
EOF
|
||||
}
|
||||
|
||||
renderPostgresEnv() {
|
||||
cat <<EOF
|
||||
POSTGRES_USER=root
|
||||
POSTGRES_PASSWORD=$POSTGRES_ROOT_PASSWORD
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -717,18 +760,28 @@ services:
|
||||
volumes:
|
||||
- netbird_caddy_data:/data
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
#UI dashboard
|
||||
# UI dashboard
|
||||
dashboard:
|
||||
image: netbirdio/dashboard:latest
|
||||
restart: unless-stopped
|
||||
networks: [netbird]
|
||||
env_file:
|
||||
- ./dashboard.env
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
# Signal
|
||||
signal:
|
||||
image: netbirdio/signal:latest
|
||||
restart: unless-stopped
|
||||
networks: [netbird]
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
# Management
|
||||
management:
|
||||
image: netbirdio/management:latest
|
||||
@@ -746,52 +799,49 @@ services:
|
||||
"--dns-domain=netbird.selfhosted",
|
||||
"--idp-sign-key-refresh-enabled",
|
||||
]
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
# Coturn, AKA relay server
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
restart: unless-stopped
|
||||
domainname: netbird.relay.selfhosted
|
||||
#domainname: netbird.relay.selfhosted
|
||||
volumes:
|
||||
- ./turnserver.conf:/etc/turnserver.conf:ro
|
||||
network_mode: host
|
||||
command:
|
||||
- -c /etc/turnserver.conf
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
# Zitadel - identity provider
|
||||
zitadel:
|
||||
restart: 'always'
|
||||
networks: [netbird]
|
||||
image: 'ghcr.io/zitadel/zitadel:v2.31.3'
|
||||
image: 'ghcr.io/zitadel/zitadel:v2.54.3'
|
||||
command: 'start-from-init --masterkeyFromEnv --tlsMode $ZITADEL_TLS_MODE'
|
||||
env_file:
|
||||
- ./zitadel.env
|
||||
depends_on:
|
||||
crdb:
|
||||
zdb:
|
||||
condition: 'service_healthy'
|
||||
volumes:
|
||||
- ./machinekey:/machinekey
|
||||
- netbird_zitadel_certs:/crdb-certs:ro
|
||||
# CockroachDB for zitadel
|
||||
crdb:
|
||||
restart: 'always'
|
||||
networks: [netbird]
|
||||
image: 'cockroachdb/cockroach:v22.2.2'
|
||||
command: 'start-single-node --advertise-addr crdb'
|
||||
volumes:
|
||||
- netbird_crdb_data:/cockroach/cockroach-data
|
||||
- netbird_crdb_certs:/cockroach/certs
|
||||
- netbird_zitadel_certs:/zitadel-certs
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
||||
interval: '10s'
|
||||
timeout: '30s'
|
||||
retries: 5
|
||||
start_period: '20s'
|
||||
|
||||
volumes:
|
||||
- netbird_zitadel_certs:/zdb-certs:ro
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
$ZDB
|
||||
netbird_zdb_data:
|
||||
netbird_management:
|
||||
netbird_caddy_data:
|
||||
netbird_crdb_data:
|
||||
netbird_crdb_certs:
|
||||
netbird_zitadel_certs:
|
||||
|
||||
networks:
|
||||
@@ -799,4 +849,59 @@ networks:
|
||||
EOF
|
||||
}
|
||||
|
||||
renderDockerComposeCockroachDB() {
|
||||
cat <<EOF
|
||||
# CockroachDB for Zitadel
|
||||
zdb:
|
||||
restart: 'always'
|
||||
networks: [netbird]
|
||||
image: 'cockroachdb/cockroach:latest-v23.2'
|
||||
command: 'start-single-node --advertise-addr zdb'
|
||||
volumes:
|
||||
- netbird_zdb_data:/cockroach/cockroach-data
|
||||
- netbird_zdb_certs:/cockroach/certs
|
||||
- netbird_zitadel_certs:/zitadel-certs
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
||||
interval: '10s'
|
||||
timeout: '30s'
|
||||
retries: 5
|
||||
start_period: '20s'
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
|
||||
volumes:
|
||||
netbird_zdb_certs:
|
||||
EOF
|
||||
}
|
||||
|
||||
renderDockerComposePostgres() {
|
||||
cat <<EOF
|
||||
# Postgres for Zitadel
|
||||
zdb:
|
||||
restart: 'always'
|
||||
networks: [netbird]
|
||||
image: 'postgres:16-alpine'
|
||||
env_file:
|
||||
- ./zdb.env
|
||||
volumes:
|
||||
- netbird_zdb_data:/var/lib/postgresql/data:rw
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready", "-d", "db_prod"]
|
||||
interval: 5s
|
||||
timeout: 60s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "500m"
|
||||
max-file: "2"
|
||||
volumes:
|
||||
EOF
|
||||
}
|
||||
|
||||
initEnvironment
|
||||
|
||||
@@ -62,7 +62,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
store, cleanUp, err := mgmt.NewTestStoreFromJson(config.Datadir)
|
||||
store, cleanUp, err := mgmt.NewTestStoreFromJson(context.Background(), config.Datadir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -70,13 +70,13 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
ia, _ := integrations.NewIntegratedValidator(eventStore)
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
mgmtServer, err := mgmt.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
turnManager := mgmt.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, "")
|
||||
mgmtServer, err := mgmt.NewServer(context.Background(), config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware/v2"
|
||||
"github.com/miekg/dns"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -35,8 +36,10 @@ import (
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
"github.com/netbirdio/netbird/formatter"
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
nbContext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
httpapi "github.com/netbirdio/netbird/management/server/http"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
@@ -77,6 +80,10 @@ var (
|
||||
Short: "start NetBird Management Server",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
flag.Parse()
|
||||
|
||||
//nolint
|
||||
ctx := context.WithValue(cmd.Context(), formatter.ExecutionContextKey, formatter.SystemSource)
|
||||
|
||||
err := util.InitLog(logLevel, logFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed initializing log %v", err)
|
||||
@@ -85,7 +92,7 @@ var (
|
||||
// detect whether user specified a port
|
||||
userPort := cmd.Flag("port").Changed
|
||||
|
||||
config, err = loadMgmtConfig(mgmtConfig)
|
||||
config, err = loadMgmtConfig(ctx, mgmtConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading provided config file: %s: %v", mgmtConfig, err)
|
||||
}
|
||||
@@ -116,6 +123,11 @@ var (
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
defer cancel()
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, formatter.ExecutionContextKey, formatter.SystemSource)
|
||||
|
||||
err := handleRebrand(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to migrate files %v", err)
|
||||
@@ -131,11 +143,11 @@ var (
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = appMetrics.Expose(mgmtMetricsPort, "/metrics")
|
||||
err = appMetrics.Expose(ctx, mgmtMetricsPort, "/metrics")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store, err := server.NewStore(config.StoreConfig.Engine, config.Datadir, appMetrics)
|
||||
store, err := server.NewStore(ctx, config.StoreConfig.Engine, config.Datadir, appMetrics)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
@@ -143,7 +155,7 @@ var (
|
||||
|
||||
var idpManager idp.Manager
|
||||
if config.IdpManagerConfig != nil {
|
||||
idpManager, err = idp.NewManager(*config.IdpManagerConfig, appMetrics)
|
||||
idpManager, err = idp.NewManager(ctx, *config.IdpManagerConfig, appMetrics)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed retrieving a new idp manager with err: %v", err)
|
||||
}
|
||||
@@ -152,49 +164,49 @@ var (
|
||||
if disableSingleAccMode {
|
||||
mgmtSingleAccModeDomain = ""
|
||||
}
|
||||
eventStore, key, err := integrations.InitEventStore(config.Datadir, config.DataStoreEncryptionKey)
|
||||
eventStore, key, err := integrations.InitEventStore(ctx, config.Datadir, config.DataStoreEncryptionKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize database: %s", err)
|
||||
}
|
||||
|
||||
if config.DataStoreEncryptionKey != key {
|
||||
log.Infof("update config with activity store key")
|
||||
log.WithContext(ctx).Infof("update config with activity store key")
|
||||
config.DataStoreEncryptionKey = key
|
||||
err := updateMgmtConfig(mgmtConfig, config)
|
||||
err := updateMgmtConfig(ctx, mgmtConfig, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write out store encryption key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
geo, err := geolocation.NewGeolocation(config.Datadir)
|
||||
geo, err := geolocation.NewGeolocation(ctx, config.Datadir)
|
||||
if err != nil {
|
||||
log.Warnf("could not initialize geo location service: %v, we proceed without geo support", err)
|
||||
log.WithContext(ctx).Warnf("could not initialize geo location service: %v, we proceed without geo support", err)
|
||||
} else {
|
||||
log.Infof("geo location service has been initialized from %s", config.Datadir)
|
||||
log.WithContext(ctx).Infof("geo location service has been initialized from %s", config.Datadir)
|
||||
}
|
||||
|
||||
integratedPeerValidator, err := integrations.NewIntegratedValidator(eventStore)
|
||||
integratedPeerValidator, err := integrations.NewIntegratedValidator(ctx, eventStore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize integrated peer validator: %v", err)
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
||||
accountManager, err := server.BuildManager(ctx, store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
||||
dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build default manager: %v", err)
|
||||
}
|
||||
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
turnRelayTokenManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig, config.RelayConfig)
|
||||
|
||||
trustedPeers := config.ReverseProxy.TrustedPeers
|
||||
defaultTrustedPeers := []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")}
|
||||
if len(trustedPeers) == 0 || slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers) {
|
||||
log.Warn("TrustedPeers are configured to default value '0.0.0.0/0', '::/0'. This allows connection IP spoofing.")
|
||||
log.WithContext(ctx).Warn("TrustedPeers are configured to default value '0.0.0.0/0', '::/0'. This allows connection IP spoofing.")
|
||||
trustedPeers = defaultTrustedPeers
|
||||
}
|
||||
trustedHTTPProxies := config.ReverseProxy.TrustedHTTPProxies
|
||||
trustedProxiesCount := config.ReverseProxy.TrustedHTTPProxiesCount
|
||||
if len(trustedHTTPProxies) > 0 && trustedProxiesCount > 0 {
|
||||
log.Warn("TrustedHTTPProxies and TrustedHTTPProxiesCount both are configured. " +
|
||||
log.WithContext(ctx).Warn("TrustedHTTPProxies and TrustedHTTPProxiesCount both are configured. " +
|
||||
"This is not recommended way to extract X-Forwarded-For. Consider using one of these options.")
|
||||
}
|
||||
realipOpts := []realip.Option{
|
||||
@@ -206,8 +218,8 @@ var (
|
||||
gRPCOpts := []grpc.ServerOption{
|
||||
grpc.KeepaliveEnforcementPolicy(kaep),
|
||||
grpc.KeepaliveParams(kasp),
|
||||
grpc.ChainUnaryInterceptor(realip.UnaryServerInterceptorOpts(realipOpts...)),
|
||||
grpc.ChainStreamInterceptor(realip.StreamServerInterceptorOpts(realipOpts...)),
|
||||
grpc.ChainUnaryInterceptor(realip.UnaryServerInterceptorOpts(realipOpts...), unaryInterceptor),
|
||||
grpc.ChainStreamInterceptor(realip.StreamServerInterceptorOpts(realipOpts...), streamInterceptor),
|
||||
}
|
||||
|
||||
var certManager *autocert.Manager
|
||||
@@ -224,7 +236,7 @@ var (
|
||||
} else if config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "" {
|
||||
tlsConfig, err = loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
|
||||
if err != nil {
|
||||
log.Errorf("cannot load TLS credentials: %v", err)
|
||||
log.WithContext(ctx).Errorf("cannot load TLS credentials: %v", err)
|
||||
return err
|
||||
}
|
||||
transportCredentials := credentials.NewTLS(tlsConfig)
|
||||
@@ -233,6 +245,7 @@ var (
|
||||
}
|
||||
|
||||
jwtValidator, err := jwtclaims.NewJWTValidator(
|
||||
ctx,
|
||||
config.HttpConfig.AuthIssuer,
|
||||
config.GetAuthAudiences(),
|
||||
config.HttpConfig.AuthKeysLocation,
|
||||
@@ -249,26 +262,24 @@ var (
|
||||
KeysLocation: config.HttpConfig.AuthKeysLocation,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
defer cancel()
|
||||
httpAPIHandler, err := httpapi.APIHandler(ctx, accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg, integratedPeerValidator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
||||
}
|
||||
|
||||
ephemeralManager := server.NewEphemeralManager(store, accountManager)
|
||||
ephemeralManager.LoadInitialPeers()
|
||||
ephemeralManager.LoadInitialPeers(ctx)
|
||||
|
||||
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
|
||||
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, appMetrics, ephemeralManager)
|
||||
srv, err := server.NewServer(ctx, config, accountManager, peersUpdateManager, turnRelayTokenManager, appMetrics, ephemeralManager)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating gRPC API handler: %v", err)
|
||||
}
|
||||
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
||||
|
||||
installationID, err := getInstallationID(store)
|
||||
installationID, err := getInstallationID(ctx, store)
|
||||
if err != nil {
|
||||
log.Errorf("cannot load TLS credentials: %v", err)
|
||||
log.WithContext(ctx).Errorf("cannot load TLS credentials: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -278,18 +289,18 @@ var (
|
||||
idpManager = config.IdpManagerConfig.ManagerType
|
||||
}
|
||||
metricsWorker := metrics.NewWorker(ctx, installationID, store, peersUpdateManager, idpManager)
|
||||
go metricsWorker.Run()
|
||||
go metricsWorker.Run(ctx)
|
||||
}
|
||||
|
||||
var compatListener net.Listener
|
||||
if mgmtPort != ManagementLegacyPort {
|
||||
// The Management gRPC server was running on port 33073 previously. Old agents that are already connected to it
|
||||
// are using port 33073. For compatibility purposes we keep running a 2nd gRPC server on port 33073.
|
||||
compatListener, err = serveGRPC(gRPCAPIHandler, ManagementLegacyPort)
|
||||
compatListener, err = serveGRPC(ctx, gRPCAPIHandler, ManagementLegacyPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
||||
log.WithContext(ctx).Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
||||
}
|
||||
|
||||
rootHandler := handlerFunc(gRPCAPIHandler, httpAPIHandler)
|
||||
@@ -306,8 +317,8 @@ var (
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating TLS listener on port %d: %v", mgmtPort, err)
|
||||
}
|
||||
log.Infof("running HTTP server (LetsEncrypt challenge handler): %s", cml.Addr().String())
|
||||
serveHTTP(cml, certManager.HTTPHandler(nil))
|
||||
log.WithContext(ctx).Infof("running HTTP server (LetsEncrypt challenge handler): %s", cml.Addr().String())
|
||||
serveHTTP(ctx, cml, certManager.HTTPHandler(nil))
|
||||
}
|
||||
} else if tlsConfig != nil {
|
||||
listener, err = tls.Listen("tcp", fmt.Sprintf(":%d", mgmtPort), tlsConfig)
|
||||
@@ -321,14 +332,14 @@ var (
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("management server version %s", version.NetbirdVersion())
|
||||
log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
||||
serveGRPCWithHTTP(listener, rootHandler, tlsEnabled)
|
||||
log.WithContext(ctx).Infof("management server version %s", version.NetbirdVersion())
|
||||
log.WithContext(ctx).Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
||||
serveGRPCWithHTTP(ctx, listener, rootHandler, tlsEnabled)
|
||||
|
||||
SetupCloseHandler()
|
||||
|
||||
<-stopCh
|
||||
integratedPeerValidator.Stop()
|
||||
integratedPeerValidator.Stop(ctx)
|
||||
if geo != nil {
|
||||
_ = geo.Stop()
|
||||
}
|
||||
@@ -339,39 +350,68 @@ var (
|
||||
_ = certManager.Listener().Close()
|
||||
}
|
||||
gRPCAPIHandler.Stop()
|
||||
_ = store.Close()
|
||||
_ = eventStore.Close()
|
||||
log.Infof("stopped Management Service")
|
||||
_ = store.Close(ctx)
|
||||
_ = eventStore.Close(ctx)
|
||||
log.WithContext(ctx).Infof("stopped Management Service")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func notifyStop(msg string) {
|
||||
func unaryInterceptor(
|
||||
ctx context.Context,
|
||||
req interface{},
|
||||
info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler,
|
||||
) (interface{}, error) {
|
||||
reqID := uuid.New().String()
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, formatter.ExecutionContextKey, formatter.GRPCSource)
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, nbContext.RequestIDKey, reqID)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
func streamInterceptor(
|
||||
srv interface{},
|
||||
ss grpc.ServerStream,
|
||||
info *grpc.StreamServerInfo,
|
||||
handler grpc.StreamHandler,
|
||||
) error {
|
||||
reqID := uuid.New().String()
|
||||
wrapped := grpcMiddleware.WrapServerStream(ss)
|
||||
//nolint
|
||||
ctx := context.WithValue(ss.Context(), formatter.ExecutionContextKey, formatter.GRPCSource)
|
||||
//nolint
|
||||
wrapped.WrappedContext = context.WithValue(ctx, nbContext.RequestIDKey, reqID)
|
||||
return handler(srv, wrapped)
|
||||
}
|
||||
|
||||
func notifyStop(ctx context.Context, msg string) {
|
||||
select {
|
||||
case stopCh <- 1:
|
||||
log.Error(msg)
|
||||
log.WithContext(ctx).Error(msg)
|
||||
default:
|
||||
// stop has been already called, nothing to report
|
||||
}
|
||||
}
|
||||
|
||||
func getInstallationID(store server.Store) (string, error) {
|
||||
func getInstallationID(ctx context.Context, store server.Store) (string, error) {
|
||||
installationID := store.GetInstallationID()
|
||||
if installationID != "" {
|
||||
return installationID, nil
|
||||
}
|
||||
|
||||
installationID = strings.ToUpper(uuid.New().String())
|
||||
err := store.SaveInstallationID(installationID)
|
||||
err := store.SaveInstallationID(ctx, installationID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return installationID, nil
|
||||
}
|
||||
|
||||
func serveGRPC(grpcServer *grpc.Server, port int) (net.Listener, error) {
|
||||
func serveGRPC(ctx context.Context, grpcServer *grpc.Server, port int) (net.Listener, error) {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -379,22 +419,22 @@ func serveGRPC(grpcServer *grpc.Server, port int) (net.Listener, error) {
|
||||
go func() {
|
||||
err := grpcServer.Serve(listener)
|
||||
if err != nil {
|
||||
notifyStop(fmt.Sprintf("failed running gRPC server on port %d: %v", port, err))
|
||||
notifyStop(ctx, fmt.Sprintf("failed running gRPC server on port %d: %v", port, err))
|
||||
}
|
||||
}()
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func serveHTTP(httpListener net.Listener, handler http.Handler) {
|
||||
func serveHTTP(ctx context.Context, httpListener net.Listener, handler http.Handler) {
|
||||
go func() {
|
||||
err := http.Serve(httpListener, handler)
|
||||
if err != nil {
|
||||
notifyStop(fmt.Sprintf("failed running HTTP server: %v", err))
|
||||
notifyStop(ctx, fmt.Sprintf("failed running HTTP server: %v", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func serveGRPCWithHTTP(listener net.Listener, handler http.Handler, tlsEnabled bool) {
|
||||
func serveGRPCWithHTTP(ctx context.Context, listener net.Listener, handler http.Handler, tlsEnabled bool) {
|
||||
go func() {
|
||||
var err error
|
||||
if tlsEnabled {
|
||||
@@ -411,7 +451,7 @@ func serveGRPCWithHTTP(listener net.Listener, handler http.Handler, tlsEnabled b
|
||||
if err != nil {
|
||||
select {
|
||||
case stopCh <- 1:
|
||||
log.Errorf("failed to serve HTTP and gRPC server: %v", err)
|
||||
log.WithContext(ctx).Errorf("failed to serve HTTP and gRPC server: %v", err)
|
||||
default:
|
||||
// stop has been already called, nothing to report
|
||||
}
|
||||
@@ -431,7 +471,7 @@ func handlerFunc(gRPCHandler *grpc.Server, httpHandler http.Handler) http.Handle
|
||||
})
|
||||
}
|
||||
|
||||
func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
||||
func loadMgmtConfig(ctx context.Context, mgmtConfigPath string) (*server.Config, error) {
|
||||
loadedConfig := &server.Config{}
|
||||
_, err := util.ReadJson(mgmtConfigPath, loadedConfig)
|
||||
if err != nil {
|
||||
@@ -452,26 +492,26 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
||||
oidcEndpoint := loadedConfig.HttpConfig.OIDCConfigEndpoint
|
||||
if oidcEndpoint != "" {
|
||||
// if OIDCConfigEndpoint is specified, we can load DeviceAuthEndpoint and TokenEndpoint automatically
|
||||
log.Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint)
|
||||
oidcConfig, err := fetchOIDCConfig(oidcEndpoint)
|
||||
log.WithContext(ctx).Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint)
|
||||
oidcConfig, err := fetchOIDCConfig(ctx, oidcEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("loaded OIDC configuration from the provided IDP configuration endpoint: %s", oidcEndpoint)
|
||||
log.WithContext(ctx).Infof("loaded OIDC configuration from the provided IDP configuration endpoint: %s", oidcEndpoint)
|
||||
|
||||
log.Infof("overriding HttpConfig.AuthIssuer with a new value %s, previously configured value: %s",
|
||||
log.WithContext(ctx).Infof("overriding HttpConfig.AuthIssuer with a new value %s, previously configured value: %s",
|
||||
oidcConfig.Issuer, loadedConfig.HttpConfig.AuthIssuer)
|
||||
loadedConfig.HttpConfig.AuthIssuer = oidcConfig.Issuer
|
||||
|
||||
log.Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s",
|
||||
log.WithContext(ctx).Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s",
|
||||
oidcConfig.JwksURI, loadedConfig.HttpConfig.AuthKeysLocation)
|
||||
loadedConfig.HttpConfig.AuthKeysLocation = oidcConfig.JwksURI
|
||||
|
||||
if !(loadedConfig.DeviceAuthorizationFlow == nil || strings.ToLower(loadedConfig.DeviceAuthorizationFlow.Provider) == string(server.NONE)) {
|
||||
log.Infof("overriding DeviceAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
|
||||
log.WithContext(ctx).Infof("overriding DeviceAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
|
||||
oidcConfig.TokenEndpoint, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint)
|
||||
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
|
||||
log.Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s",
|
||||
log.WithContext(ctx).Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s",
|
||||
oidcConfig.DeviceAuthEndpoint, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint)
|
||||
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint = oidcConfig.DeviceAuthEndpoint
|
||||
|
||||
@@ -479,7 +519,7 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s",
|
||||
log.WithContext(ctx).Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s",
|
||||
u.Host, loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Domain)
|
||||
loadedConfig.DeviceAuthorizationFlow.ProviderConfig.Domain = u.Host
|
||||
|
||||
@@ -489,20 +529,24 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
||||
}
|
||||
|
||||
if loadedConfig.PKCEAuthorizationFlow != nil {
|
||||
log.Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
|
||||
log.WithContext(ctx).Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
|
||||
oidcConfig.TokenEndpoint, loadedConfig.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint)
|
||||
loadedConfig.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
|
||||
log.Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s",
|
||||
log.WithContext(ctx).Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s",
|
||||
oidcConfig.AuthorizationEndpoint, loadedConfig.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint)
|
||||
loadedConfig.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint = oidcConfig.AuthorizationEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
if loadedConfig.RelayConfig != nil {
|
||||
log.Infof("Relay address: %v", loadedConfig.RelayConfig.Address)
|
||||
}
|
||||
|
||||
return loadedConfig, err
|
||||
}
|
||||
|
||||
func updateMgmtConfig(path string, config *server.Config) error {
|
||||
return util.DirectWriteJson(path, config)
|
||||
func updateMgmtConfig(ctx context.Context, path string, config *server.Config) error {
|
||||
return util.DirectWriteJson(ctx, path, config)
|
||||
}
|
||||
|
||||
// OIDCConfigResponse used for parsing OIDC config response
|
||||
@@ -515,7 +559,7 @@ type OIDCConfigResponse struct {
|
||||
}
|
||||
|
||||
// fetchOIDCConfig fetches OIDC configuration from the IDP
|
||||
func fetchOIDCConfig(oidcEndpoint string) (OIDCConfigResponse, error) {
|
||||
func fetchOIDCConfig(ctx context.Context, oidcEndpoint string) (OIDCConfigResponse, error) {
|
||||
res, err := http.Get(oidcEndpoint)
|
||||
if err != nil {
|
||||
return OIDCConfigResponse{}, fmt.Errorf("failed fetching OIDC configuration from endpoint %s %v", oidcEndpoint, err)
|
||||
@@ -524,7 +568,7 @@ func fetchOIDCConfig(oidcEndpoint string) (OIDCConfigResponse, error) {
|
||||
defer func() {
|
||||
err := res.Body.Close()
|
||||
if err != nil {
|
||||
log.Debugf("failed closing response body %v", err)
|
||||
log.WithContext(ctx).Debugf("failed closing response body %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
54
management/cmd/management_test.go
Normal file
54
management/cmd/management_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
exampleConfig = `{
|
||||
"RelayConfig": {
|
||||
"Address": "rels://relay.stage.npeer.io"
|
||||
},
|
||||
"HttpConfig": {
|
||||
"AuthAudience": "https://stageapp/",
|
||||
"AuthIssuer": "https://something.eu.auth0.com/",
|
||||
"OIDCConfigEndpoint": "https://something.eu.auth0.com/.well-known/openid-configuration"
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
func Test_loadMgmtConfig(t *testing.T) {
|
||||
tmpFile, err := createConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create config: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := loadMgmtConfig(context.Background(), tmpFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load management config: %s", err)
|
||||
}
|
||||
if cfg.RelayConfig == nil {
|
||||
t.Fatalf("config is nil")
|
||||
}
|
||||
if cfg.RelayConfig.Address == "" {
|
||||
t.Fatalf("relay address is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func createConfig() (string, error) {
|
||||
tmpfile, err := os.CreateTemp("", "config.json")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = tmpfile.Write([]byte(exampleConfig))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tmpfile.Name(), nil
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/netbirdio/netbird/formatter"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
var shortUp = "Migrate JSON file store to SQLite store. Please make a backup of the JSON file before running this command."
|
||||
@@ -26,10 +29,13 @@ var upCmd = &cobra.Command{
|
||||
return fmt.Errorf("failed initializing log %v", err)
|
||||
}
|
||||
|
||||
if err := server.MigrateFileStoreToSqlite(mgmtDataDir); err != nil {
|
||||
//nolint
|
||||
ctx := context.WithValue(cmd.Context(), formatter.ExecutionContextKey, formatter.SystemSource)
|
||||
|
||||
if err := server.MigrateFileStoreToSqlite(ctx, mgmtDataDir); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Migration finished successfully")
|
||||
log.WithContext(ctx).Info("Migration finished successfully")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -177,6 +177,8 @@ message WiretrusteeConfig {
|
||||
|
||||
// a Signal server config
|
||||
HostConfig signal = 3;
|
||||
|
||||
RelayConfig relay = 4;
|
||||
}
|
||||
|
||||
// HostConfig describes connection properties of some server (e.g. STUN, Signal, Management)
|
||||
@@ -193,6 +195,13 @@ message HostConfig {
|
||||
DTLS = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message RelayConfig {
|
||||
repeated string urls = 1;
|
||||
string tokenPayload = 2;
|
||||
string tokenSignature = 3;
|
||||
}
|
||||
|
||||
// ProtectedHostConfig is similar to HostConfig but has additional user and password
|
||||
// Mostly used for TURN servers
|
||||
message ProtectedHostConfig {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
b64 "encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -29,11 +30,11 @@ import (
|
||||
type MocIntegratedValidator struct {
|
||||
}
|
||||
|
||||
func (a MocIntegratedValidator) ValidateExtraSettings(newExtraSettings *account.ExtraSettings, oldExtraSettings *account.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error {
|
||||
func (a MocIntegratedValidator) ValidateExtraSettings(_ context.Context, newExtraSettings *account.ExtraSettings, oldExtraSettings *account.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MocIntegratedValidator) ValidatePeer(update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, error) {
|
||||
func (a MocIntegratedValidator) ValidatePeer(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, error) {
|
||||
return update, nil
|
||||
}
|
||||
func (a MocIntegratedValidator) GetValidatedPeers(accountID string, groups map[string]*group.Group, peers map[string]*nbpeer.Peer, extraSettings *account.ExtraSettings) (map[string]struct{}, error) {
|
||||
@@ -44,15 +45,15 @@ func (a MocIntegratedValidator) GetValidatedPeers(accountID string, groups map[s
|
||||
return validatedPeers, nil
|
||||
}
|
||||
|
||||
func (MocIntegratedValidator) PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer {
|
||||
func (MocIntegratedValidator) PreparePeer(_ context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer {
|
||||
return peer
|
||||
}
|
||||
|
||||
func (MocIntegratedValidator) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool, error) {
|
||||
func (MocIntegratedValidator) IsNotValidPeer(_ context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool, error) {
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
func (MocIntegratedValidator) PeerDeleted(_, _ string) error {
|
||||
func (MocIntegratedValidator) PeerDeleted(_ context.Context, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ func (MocIntegratedValidator) SetPeerInvalidationListener(func(accountID string)
|
||||
|
||||
}
|
||||
|
||||
func (MocIntegratedValidator) Stop() {
|
||||
func (MocIntegratedValidator) Stop(_ context.Context) {
|
||||
}
|
||||
|
||||
func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) {
|
||||
@@ -85,7 +86,7 @@ func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Ac
|
||||
setupKey = key.Key
|
||||
}
|
||||
|
||||
_, _, err := manager.AddPeer(setupKey, userID, peer)
|
||||
_, _, _, err := manager.AddPeer(context.Background(), setupKey, userID, peer)
|
||||
if err != nil {
|
||||
t.Error("expected to add new peer successfully after creating new account, but failed", err)
|
||||
}
|
||||
@@ -395,7 +396,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range tt {
|
||||
account := newAccountWithId("account-1", userID, "netbird.io")
|
||||
account := newAccountWithId(context.Background(), "account-1", userID, "netbird.io")
|
||||
account.UpdateSettings(&testCase.accountSettings)
|
||||
account.Network = network
|
||||
account.Peers = testCase.peers
|
||||
@@ -409,7 +410,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
|
||||
validatedPeers[p] = struct{}{}
|
||||
}
|
||||
|
||||
networkMap := account.GetPeerNetworkMap(testCase.peerID, "netbird.io", validatedPeers)
|
||||
networkMap := account.GetPeerNetworkMap(context.Background(), testCase.peerID, "netbird.io", validatedPeers)
|
||||
assert.Len(t, networkMap.Peers, len(testCase.expectedPeers))
|
||||
assert.Len(t, networkMap.OfflinePeers, len(testCase.expectedOfflinePeers))
|
||||
}
|
||||
@@ -419,7 +420,7 @@ func TestNewAccount(t *testing.T) {
|
||||
domain := "netbird.io"
|
||||
userId := "account_creator"
|
||||
accountID := "account_id"
|
||||
account := newAccountWithId(accountID, userId, domain)
|
||||
account := newAccountWithId(context.Background(), accountID, userId, domain)
|
||||
verifyNewAccountHasDefaultFields(t, account, userId, domain, []string{userId})
|
||||
}
|
||||
|
||||
@@ -430,7 +431,7 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err := manager.GetOrCreateAccountByUser(userID, "")
|
||||
account, err := manager.GetOrCreateAccountByUser(context.Background(), userID, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -439,7 +440,7 @@ func TestAccountManager_GetOrCreateAccountByUser(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccountByUser(userID)
|
||||
account, err = manager.Store.GetAccountByUser(context.Background(), userID)
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userID)
|
||||
return
|
||||
@@ -630,11 +631,11 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
initAccount, err := manager.GetAccountByUserOrAccountID(testCase.inputInitUserParams.UserId, testCase.inputInitUserParams.AccountId, testCase.inputInitUserParams.Domain)
|
||||
initAccount, err := manager.GetAccountByUserOrAccountID(context.Background(), testCase.inputInitUserParams.UserId, testCase.inputInitUserParams.AccountId, testCase.inputInitUserParams.Domain)
|
||||
require.NoError(t, err, "create init user failed")
|
||||
|
||||
if testCase.inputUpdateAttrs {
|
||||
err = manager.updateAccountDomainAttributes(initAccount, jwtclaims.AuthorizationClaims{UserId: testCase.inputInitUserParams.UserId, Domain: testCase.inputInitUserParams.Domain, DomainCategory: testCase.inputInitUserParams.DomainCategory}, true)
|
||||
err = manager.updateAccountDomainAttributes(context.Background(), initAccount, jwtclaims.AuthorizationClaims{UserId: testCase.inputInitUserParams.UserId, Domain: testCase.inputInitUserParams.Domain, DomainCategory: testCase.inputInitUserParams.DomainCategory}, true)
|
||||
require.NoError(t, err, "update init user failed")
|
||||
}
|
||||
|
||||
@@ -642,7 +643,7 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
|
||||
testCase.inputClaims.AccountId = initAccount.Id
|
||||
}
|
||||
|
||||
account, _, err := manager.GetAccountFromToken(testCase.inputClaims)
|
||||
account, _, err := manager.GetAccountFromToken(context.Background(), testCase.inputClaims)
|
||||
require.NoError(t, err, "support function failed")
|
||||
verifyNewAccountHasDefaultFields(t, account, testCase.expectedCreatedBy, testCase.inputClaims.Domain, testCase.expectedUsers)
|
||||
verifyCanAddPeerToAccount(t, manager, account, testCase.expectedCreatedBy)
|
||||
@@ -661,12 +662,12 @@ func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
||||
userId := "user-id"
|
||||
domain := "test.domain"
|
||||
|
||||
initAccount := newAccountWithId("", userId, domain)
|
||||
initAccount := newAccountWithId(context.Background(), "", userId, domain)
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
accountID := initAccount.Id
|
||||
acc, err := manager.GetAccountByUserOrAccountID(userId, accountID, domain)
|
||||
acc, err := manager.GetAccountByUserOrAccountID(context.Background(), userId, accountID, domain)
|
||||
require.NoError(t, err, "create init user failed")
|
||||
// as initAccount was created without account id we have to take the id after account initialization
|
||||
// that happens inside the GetAccountByUserOrAccountID where the id is getting generated
|
||||
@@ -682,18 +683,18 @@ func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("JWT groups disabled", func(t *testing.T) {
|
||||
account, _, err := manager.GetAccountFromToken(claims)
|
||||
account, _, err := manager.GetAccountFromToken(context.Background(), claims)
|
||||
require.NoError(t, err, "get account by token failed")
|
||||
require.Len(t, account.Groups, 1, "only ALL group should exists")
|
||||
})
|
||||
|
||||
t.Run("JWT groups enabled without claim name", func(t *testing.T) {
|
||||
initAccount.Settings.JWTGroupsEnabled = true
|
||||
err := manager.Store.SaveAccount(initAccount)
|
||||
err := manager.Store.SaveAccount(context.Background(), initAccount)
|
||||
require.NoError(t, err, "save account failed")
|
||||
require.Len(t, manager.Store.GetAllAccounts(), 1, "only one account should exist")
|
||||
require.Len(t, manager.Store.GetAllAccounts(context.Background()), 1, "only one account should exist")
|
||||
|
||||
account, _, err := manager.GetAccountFromToken(claims)
|
||||
account, _, err := manager.GetAccountFromToken(context.Background(), claims)
|
||||
require.NoError(t, err, "get account by token failed")
|
||||
require.Len(t, account.Groups, 1, "if group claim is not set no group added from JWT")
|
||||
})
|
||||
@@ -701,11 +702,11 @@ func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
||||
t.Run("JWT groups enabled", func(t *testing.T) {
|
||||
initAccount.Settings.JWTGroupsEnabled = true
|
||||
initAccount.Settings.JWTGroupsClaimName = "idp-groups"
|
||||
err := manager.Store.SaveAccount(initAccount)
|
||||
err := manager.Store.SaveAccount(context.Background(), initAccount)
|
||||
require.NoError(t, err, "save account failed")
|
||||
require.Len(t, manager.Store.GetAllAccounts(), 1, "only one account should exist")
|
||||
require.Len(t, manager.Store.GetAllAccounts(context.Background()), 1, "only one account should exist")
|
||||
|
||||
account, _, err := manager.GetAccountFromToken(claims)
|
||||
account, _, err := manager.GetAccountFromToken(context.Background(), claims)
|
||||
require.NoError(t, err, "get account by token failed")
|
||||
require.Len(t, account.Groups, 3, "groups should be added to the account")
|
||||
|
||||
@@ -728,7 +729,7 @@ func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
||||
|
||||
func TestAccountManager_GetAccountFromPAT(t *testing.T) {
|
||||
store := newStore(t)
|
||||
account := newAccountWithId("account_id", "testuser", "")
|
||||
account := newAccountWithId(context.Background(), "account_id", "testuser", "")
|
||||
|
||||
token := "nbp_9999EUDNdkeusjentDLSJEn1902u84390W6W"
|
||||
hashedToken := sha256.Sum256([]byte(token))
|
||||
@@ -742,7 +743,7 @@ func TestAccountManager_GetAccountFromPAT(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
err := store.SaveAccount(account)
|
||||
err := store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when saving account: %s", err)
|
||||
}
|
||||
@@ -751,7 +752,7 @@ func TestAccountManager_GetAccountFromPAT(t *testing.T) {
|
||||
Store: store,
|
||||
}
|
||||
|
||||
account, user, pat, err := am.GetAccountFromPAT(token)
|
||||
account, user, pat, err := am.GetAccountFromPAT(context.Background(), token)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when getting Account from PAT: %s", err)
|
||||
}
|
||||
@@ -763,7 +764,7 @@ func TestAccountManager_GetAccountFromPAT(t *testing.T) {
|
||||
|
||||
func TestDefaultAccountManager_MarkPATUsed(t *testing.T) {
|
||||
store := newStore(t)
|
||||
account := newAccountWithId("account_id", "testuser", "")
|
||||
account := newAccountWithId(context.Background(), "account_id", "testuser", "")
|
||||
|
||||
token := "nbp_9999EUDNdkeusjentDLSJEn1902u84390W6W"
|
||||
hashedToken := sha256.Sum256([]byte(token))
|
||||
@@ -778,7 +779,7 @@ func TestDefaultAccountManager_MarkPATUsed(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
err := store.SaveAccount(account)
|
||||
err := store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when saving account: %s", err)
|
||||
}
|
||||
@@ -787,12 +788,12 @@ func TestDefaultAccountManager_MarkPATUsed(t *testing.T) {
|
||||
Store: store,
|
||||
}
|
||||
|
||||
err = am.MarkPATUsed("tokenId")
|
||||
err = am.MarkPATUsed(context.Background(), "tokenId")
|
||||
if err != nil {
|
||||
t.Fatalf("Error when marking PAT used: %s", err)
|
||||
}
|
||||
|
||||
account, err = am.Store.GetAccount("account_id")
|
||||
account, err = am.Store.GetAccount(context.Background(), "account_id")
|
||||
if err != nil {
|
||||
t.Fatalf("Error when getting account: %s", err)
|
||||
}
|
||||
@@ -807,7 +808,7 @@ func TestAccountManager_PrivateAccount(t *testing.T) {
|
||||
}
|
||||
|
||||
userId := "test_user"
|
||||
account, err := manager.GetOrCreateAccountByUser(userId, "")
|
||||
account, err := manager.GetOrCreateAccountByUser(context.Background(), userId, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -815,7 +816,7 @@ func TestAccountManager_PrivateAccount(t *testing.T) {
|
||||
t.Fatalf("expected to create an account for a user %s", userId)
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccountByUser(userId)
|
||||
account, err = manager.Store.GetAccountByUser(context.Background(), userId)
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation, no account was found for a user %s", userId)
|
||||
}
|
||||
@@ -834,7 +835,7 @@ func TestAccountManager_SetOrUpdateDomain(t *testing.T) {
|
||||
|
||||
userId := "test_user"
|
||||
domain := "hotmail.com"
|
||||
account, err := manager.GetOrCreateAccountByUser(userId, domain)
|
||||
account, err := manager.GetOrCreateAccountByUser(context.Background(), userId, domain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -848,7 +849,7 @@ func TestAccountManager_SetOrUpdateDomain(t *testing.T) {
|
||||
|
||||
domain = "gmail.com"
|
||||
|
||||
account, err = manager.GetOrCreateAccountByUser(userId, domain)
|
||||
account, err = manager.GetOrCreateAccountByUser(context.Background(), userId, domain)
|
||||
if err != nil {
|
||||
t.Fatalf("got the following error while retrieving existing acc: %v", err)
|
||||
}
|
||||
@@ -871,7 +872,7 @@ func TestAccountManager_GetAccountByUserOrAccountId(t *testing.T) {
|
||||
|
||||
userId := "test_user"
|
||||
|
||||
account, err := manager.GetAccountByUserOrAccountID(userId, "", "")
|
||||
account, err := manager.GetAccountByUserOrAccountID(context.Background(), userId, "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -880,20 +881,20 @@ func TestAccountManager_GetAccountByUserOrAccountId(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = manager.GetAccountByUserOrAccountID("", account.Id, "")
|
||||
_, err = manager.GetAccountByUserOrAccountID(context.Background(), "", account.Id, "")
|
||||
if err != nil {
|
||||
t.Errorf("expected to get existing account after creation using userid, no account was found for a account %s", account.Id)
|
||||
}
|
||||
|
||||
_, err = manager.GetAccountByUserOrAccountID("", "", "")
|
||||
_, err = manager.GetAccountByUserOrAccountID(context.Background(), "", "", "")
|
||||
if err == nil {
|
||||
t.Errorf("expected an error when user and account IDs are empty")
|
||||
}
|
||||
}
|
||||
|
||||
func createAccount(am *DefaultAccountManager, accountID, userID, domain string) (*Account, error) {
|
||||
account := newAccountWithId(accountID, userID, domain)
|
||||
err := am.Store.SaveAccount(account)
|
||||
account := newAccountWithId(context.Background(), accountID, userID, domain)
|
||||
err := am.Store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -915,7 +916,7 @@ func TestAccountManager_GetAccount(t *testing.T) {
|
||||
}
|
||||
|
||||
// AddAccount has been already tested so we can assume it is correct and compare results
|
||||
getAccount, err := manager.Store.GetAccount(account.Id)
|
||||
getAccount, err := manager.Store.GetAccount(context.Background(), account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -952,12 +953,12 @@ func TestAccountManager_DeleteAccount(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = manager.DeleteAccount(account.Id, userId)
|
||||
err = manager.DeleteAccount(context.Background(), account.Id, userId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
getAccount, err := manager.Store.GetAccount(account.Id)
|
||||
getAccount, err := manager.Store.GetAccount(context.Background(), account.Id)
|
||||
if err == nil {
|
||||
t.Fatal(fmt.Errorf("expected to get an error when trying to get deleted account, got %v", getAccount))
|
||||
}
|
||||
@@ -978,7 +979,7 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
|
||||
serial := account.Network.CurrentSerial() // should be 0
|
||||
|
||||
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
setupKey, err := manager.CreateSetupKey(context.Background(), account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
if err != nil {
|
||||
t.Fatal("error creating setup key")
|
||||
return
|
||||
@@ -997,7 +998,7 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
expectedSetupKey := setupKey.Key
|
||||
|
||||
peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||
peer, _, _, err := manager.AddPeer(context.Background(), setupKey.Key, "", &nbpeer.Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
|
||||
})
|
||||
@@ -1006,7 +1007,7 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
account, err = manager.Store.GetAccount(context.Background(), account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -1045,7 +1046,7 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err := manager.GetOrCreateAccountByUser(userID, "netbird.cloud")
|
||||
account, err := manager.GetOrCreateAccountByUser(context.Background(), userID, "netbird.cloud")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1065,7 +1066,7 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
expectedUserID := userID
|
||||
|
||||
peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{
|
||||
peer, _, _, err := manager.AddPeer(context.Background(), "", userID, &nbpeer.Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
|
||||
})
|
||||
@@ -1074,7 +1075,7 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
account, err = manager.Store.GetAccount(context.Background(), account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -1121,7 +1122,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
setupKey, err := manager.CreateSetupKey(context.Background(), account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
if err != nil {
|
||||
t.Fatal("error creating setup key")
|
||||
return
|
||||
@@ -1140,7 +1141,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
expectedPeerKey := key.PublicKey().String()
|
||||
|
||||
peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||
peer, _, _, err := manager.AddPeer(context.Background(), setupKey.Key, "", &nbpeer.Peer{
|
||||
Key: expectedPeerKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
|
||||
})
|
||||
@@ -1156,14 +1157,14 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
peer2 := getPeer()
|
||||
peer3 := getPeer()
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
account, err = manager.Store.GetAccount(context.Background(), account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
updMsg := manager.peersUpdateManager.CreateChannel(peer1.ID)
|
||||
defer manager.peersUpdateManager.CloseChannel(peer1.ID)
|
||||
updMsg := manager.peersUpdateManager.CreateChannel(context.Background(), peer1.ID)
|
||||
defer manager.peersUpdateManager.CloseChannel(context.Background(), peer1.ID)
|
||||
|
||||
group := group.Group{
|
||||
ID: "group-id",
|
||||
@@ -1197,7 +1198,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := manager.SaveGroup(account.Id, userID, &group); err != nil {
|
||||
if err := manager.SaveGroup(context.Background(), account.Id, userID, &group); err != nil {
|
||||
t.Errorf("save group: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -1217,7 +1218,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := manager.DeletePolicy(account.Id, account.Policies[0].ID, userID); err != nil {
|
||||
if err := manager.DeletePolicy(context.Background(), account.Id, account.Policies[0].ID, userID); err != nil {
|
||||
t.Errorf("delete default rule: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -1237,7 +1238,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := manager.SavePolicy(account.Id, userID, &policy); err != nil {
|
||||
if err := manager.SavePolicy(context.Background(), account.Id, userID, &policy); err != nil {
|
||||
t.Errorf("delete default rule: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -1256,7 +1257,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := manager.DeletePeer(account.Id, peer3.ID, userID); err != nil {
|
||||
if err := manager.DeletePeer(context.Background(), account.Id, peer3.ID, userID); err != nil {
|
||||
t.Errorf("delete peer: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -1277,9 +1278,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}()
|
||||
|
||||
// clean policy is pre requirement for delete group
|
||||
_ = manager.DeletePolicy(account.Id, policy.ID, userID)
|
||||
_ = manager.DeletePolicy(context.Background(), account.Id, policy.ID, userID)
|
||||
|
||||
if err := manager.DeleteGroup(account.Id, "", group.ID); err != nil {
|
||||
if err := manager.DeleteGroup(context.Background(), account.Id, "", group.ID); err != nil {
|
||||
t.Errorf("delete group: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -1301,7 +1302,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
setupKey, err := manager.CreateSetupKey(context.Background(), account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false)
|
||||
if err != nil {
|
||||
t.Fatal("error creating setup key")
|
||||
return
|
||||
@@ -1315,7 +1316,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
|
||||
peerKey := key.PublicKey().String()
|
||||
|
||||
peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
|
||||
peer, _, _, err := manager.AddPeer(context.Background(), setupKey.Key, "", &nbpeer.Peer{
|
||||
Key: peerKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: peerKey},
|
||||
})
|
||||
@@ -1324,12 +1325,12 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
err = manager.DeletePeer(account.Id, peerKey, userID)
|
||||
err = manager.DeletePeer(context.Background(), account.Id, peerKey, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
account, err = manager.Store.GetAccount(account.Id)
|
||||
account, err = manager.Store.GetAccount(context.Background(), account.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -1357,7 +1358,7 @@ func getEvent(t *testing.T, accountID string, manager AccountManager, eventType
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("no PeerAddedWithSetupKey event was generated")
|
||||
default:
|
||||
events, err := manager.GetEvents(accountID, userID)
|
||||
events, err := manager.GetEvents(context.Background(), accountID, userID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1389,7 +1390,7 @@ func TestGetUsersFromAccount(t *testing.T) {
|
||||
account.Users[user.Id] = user
|
||||
}
|
||||
|
||||
userInfos, err := manager.GetUsersFromAccount(accountId, "1")
|
||||
userInfos, err := manager.GetUsersFromAccount(context.Background(), accountId, "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1500,7 +1501,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}})
|
||||
routes := account.getRoutesToSync(context.Background(), "peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}})
|
||||
|
||||
assert.Len(t, routes, 2)
|
||||
routeIDs := make(map[route.ID]struct{}, 2)
|
||||
@@ -1510,7 +1511,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
assert.Contains(t, routeIDs, route.ID("route-2"))
|
||||
assert.Contains(t, routeIDs, route.ID("route-3"))
|
||||
|
||||
emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}})
|
||||
emptyRoutes := account.getRoutesToSync(context.Background(), "peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}})
|
||||
|
||||
assert.Len(t, emptyRoutes, 0)
|
||||
}
|
||||
@@ -1645,7 +1646,7 @@ func TestDefaultAccountManager_DefaultAccountSettings(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
account, err := manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to create an account")
|
||||
|
||||
assert.NotNil(t, account.Settings)
|
||||
@@ -1657,23 +1658,23 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
_, err = manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
_, err = manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to create an account")
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
require.NoError(t, err, "unable to generate WireGuard key")
|
||||
peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{
|
||||
peer, _, _, err := manager.AddPeer(context.Background(), "", userID, &nbpeer.Peer{
|
||||
Key: key.PublicKey().String(),
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
|
||||
LoginExpirationEnabled: true,
|
||||
})
|
||||
require.NoError(t, err, "unable to add peer")
|
||||
|
||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
account, err := manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to get the account")
|
||||
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil, account)
|
||||
err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, account)
|
||||
require.NoError(t, err, "unable to mark peer connected")
|
||||
account, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||
account, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
|
||||
PeerLoginExpiration: time.Hour,
|
||||
PeerLoginExpirationEnabled: true,
|
||||
})
|
||||
@@ -1682,10 +1683,10 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
manager.peerLoginExpiry = &MockScheduler{
|
||||
CancelFunc: func(IDs []string) {
|
||||
CancelFunc: func(ctx context.Context, IDs []string) {
|
||||
wg.Done()
|
||||
},
|
||||
ScheduleFunc: func(in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
ScheduleFunc: func(ctx context.Context, in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
wg.Done()
|
||||
},
|
||||
}
|
||||
@@ -1693,11 +1694,11 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
|
||||
// disable expiration first
|
||||
update := peer.Copy()
|
||||
update.LoginExpirationEnabled = false
|
||||
_, err = manager.UpdatePeer(account.Id, userID, update)
|
||||
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, update)
|
||||
require.NoError(t, err, "unable to update peer")
|
||||
// enabling expiration should trigger the routine
|
||||
update.LoginExpirationEnabled = true
|
||||
_, err = manager.UpdatePeer(account.Id, userID, update)
|
||||
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, update)
|
||||
require.NoError(t, err, "unable to update peer")
|
||||
|
||||
failed := waitTimeout(wg, time.Second)
|
||||
@@ -1710,18 +1711,18 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
account, err := manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to create an account")
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
require.NoError(t, err, "unable to generate WireGuard key")
|
||||
_, _, err = manager.AddPeer("", userID, &nbpeer.Peer{
|
||||
_, _, _, err = manager.AddPeer(context.Background(), "", userID, &nbpeer.Peer{
|
||||
Key: key.PublicKey().String(),
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
|
||||
LoginExpirationEnabled: true,
|
||||
})
|
||||
require.NoError(t, err, "unable to add peer")
|
||||
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||
_, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
|
||||
PeerLoginExpiration: time.Hour,
|
||||
PeerLoginExpirationEnabled: true,
|
||||
})
|
||||
@@ -1730,18 +1731,18 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
manager.peerLoginExpiry = &MockScheduler{
|
||||
CancelFunc: func(IDs []string) {
|
||||
CancelFunc: func(ctx context.Context, IDs []string) {
|
||||
wg.Done()
|
||||
},
|
||||
ScheduleFunc: func(in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
ScheduleFunc: func(ctx context.Context, in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
wg.Done()
|
||||
},
|
||||
}
|
||||
|
||||
account, err = manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
account, err = manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to get the account")
|
||||
// when we mark peer as connected, the peer login expiration routine should trigger
|
||||
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil, account)
|
||||
err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, account)
|
||||
require.NoError(t, err, "unable to mark peer connected")
|
||||
|
||||
failed := waitTimeout(wg, time.Second)
|
||||
@@ -1754,35 +1755,35 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
_, err = manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
_, err = manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to create an account")
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
require.NoError(t, err, "unable to generate WireGuard key")
|
||||
_, _, err = manager.AddPeer("", userID, &nbpeer.Peer{
|
||||
_, _, _, err = manager.AddPeer(context.Background(), "", userID, &nbpeer.Peer{
|
||||
Key: key.PublicKey().String(),
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
|
||||
LoginExpirationEnabled: true,
|
||||
})
|
||||
require.NoError(t, err, "unable to add peer")
|
||||
|
||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
account, err := manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to get the account")
|
||||
err = manager.MarkPeerConnected(key.PublicKey().String(), true, nil, account)
|
||||
err = manager.MarkPeerConnected(context.Background(), key.PublicKey().String(), true, nil, account)
|
||||
require.NoError(t, err, "unable to mark peer connected")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
manager.peerLoginExpiry = &MockScheduler{
|
||||
CancelFunc: func(IDs []string) {
|
||||
CancelFunc: func(ctx context.Context, IDs []string) {
|
||||
wg.Done()
|
||||
},
|
||||
ScheduleFunc: func(in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
ScheduleFunc: func(ctx context.Context, in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
wg.Done()
|
||||
},
|
||||
}
|
||||
// enabling PeerLoginExpirationEnabled should trigger the expiration job
|
||||
account, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||
account, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
|
||||
PeerLoginExpiration: time.Hour,
|
||||
PeerLoginExpirationEnabled: true,
|
||||
})
|
||||
@@ -1795,7 +1796,7 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test
|
||||
wg.Add(1)
|
||||
|
||||
// disabling PeerLoginExpirationEnabled should trigger cancel
|
||||
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||
_, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
|
||||
PeerLoginExpiration: time.Hour,
|
||||
PeerLoginExpirationEnabled: false,
|
||||
})
|
||||
@@ -1810,10 +1811,10 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
|
||||
manager, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
account, err := manager.GetAccountByUserOrAccountID(userID, "", "")
|
||||
account, err := manager.GetAccountByUserOrAccountID(context.Background(), userID, "", "")
|
||||
require.NoError(t, err, "unable to create an account")
|
||||
|
||||
updated, err := manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||
updated, err := manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
|
||||
PeerLoginExpiration: time.Hour,
|
||||
PeerLoginExpirationEnabled: false,
|
||||
})
|
||||
@@ -1821,19 +1822,19 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
|
||||
assert.False(t, updated.Settings.PeerLoginExpirationEnabled)
|
||||
assert.Equal(t, updated.Settings.PeerLoginExpiration, time.Hour)
|
||||
|
||||
account, err = manager.GetAccountByUserOrAccountID("", account.Id, "")
|
||||
account, err = manager.GetAccountByUserOrAccountID(context.Background(), "", account.Id, "")
|
||||
require.NoError(t, err, "unable to get account by ID")
|
||||
|
||||
assert.False(t, account.Settings.PeerLoginExpirationEnabled)
|
||||
assert.Equal(t, account.Settings.PeerLoginExpiration, time.Hour)
|
||||
|
||||
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||
_, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
|
||||
PeerLoginExpiration: time.Second,
|
||||
PeerLoginExpirationEnabled: false,
|
||||
})
|
||||
require.Error(t, err, "expecting to fail when providing PeerLoginExpiration less than one hour")
|
||||
|
||||
_, err = manager.UpdateAccountSettings(account.Id, userID, &Settings{
|
||||
_, err = manager.UpdateAccountSettings(context.Background(), account.Id, userID, &Settings{
|
||||
PeerLoginExpiration: time.Hour * 24 * 181,
|
||||
PeerLoginExpirationEnabled: false,
|
||||
})
|
||||
@@ -2294,7 +2295,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
|
||||
manager, err := BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{})
|
||||
manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2305,7 +2306,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
func createStore(t *testing.T) (Store, error) {
|
||||
t.Helper()
|
||||
dataDir := t.TempDir()
|
||||
store, cleanUp, err := NewTestStoreFromJson(dataDir)
|
||||
store, cleanUp, err := NewTestStoreFromJson(context.Background(), dataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -86,7 +87,7 @@ type Store struct {
|
||||
}
|
||||
|
||||
// NewSQLiteStore creates a new Store with an event table if not exists.
|
||||
func NewSQLiteStore(dataDir string, encryptionKey string) (*Store, error) {
|
||||
func NewSQLiteStore(ctx context.Context, dataDir string, encryptionKey string) (*Store, error) {
|
||||
dbFile := filepath.Join(dataDir, eventSinkDB)
|
||||
db, err := sql.Open("sqlite3", dbFile)
|
||||
if err != nil {
|
||||
@@ -111,7 +112,7 @@ func NewSQLiteStore(dataDir string, encryptionKey string) (*Store, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = updateDeletedUsersTable(db)
|
||||
err = updateDeletedUsersTable(ctx, db)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
@@ -153,7 +154,7 @@ func NewSQLiteStore(dataDir string, encryptionKey string) (*Store, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||
func (store *Store) processResult(ctx context.Context, result *sql.Rows) ([]*activity.Event, error) {
|
||||
events := make([]*activity.Event, 0)
|
||||
var cryptErr error
|
||||
for result.Next() {
|
||||
@@ -235,14 +236,14 @@ func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||
}
|
||||
|
||||
if cryptErr != nil {
|
||||
log.Warnf("%s", cryptErr)
|
||||
log.WithContext(ctx).Warnf("%s", cryptErr)
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Get returns "limit" number of events from index ordered descending or ascending by a timestamp
|
||||
func (store *Store) Get(accountID string, offset, limit int, descending bool) ([]*activity.Event, error) {
|
||||
func (store *Store) Get(ctx context.Context, accountID string, offset, limit int, descending bool) ([]*activity.Event, error) {
|
||||
stmt := store.selectDescStatement
|
||||
if !descending {
|
||||
stmt = store.selectAscStatement
|
||||
@@ -254,11 +255,11 @@ func (store *Store) Get(accountID string, offset, limit int, descending bool) ([
|
||||
}
|
||||
|
||||
defer result.Close() //nolint
|
||||
return store.processResult(result)
|
||||
return store.processResult(ctx, result)
|
||||
}
|
||||
|
||||
// Save an event in the SQLite events table end encrypt the "email" element in meta map
|
||||
func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
||||
func (store *Store) Save(_ context.Context, event *activity.Event) (*activity.Event, error) {
|
||||
var jsonMeta string
|
||||
meta, err := store.saveDeletedUserEmailAndNameInEncrypted(event)
|
||||
if err != nil {
|
||||
@@ -317,15 +318,15 @@ func (store *Store) saveDeletedUserEmailAndNameInEncrypted(event *activity.Event
|
||||
}
|
||||
|
||||
// Close the Store
|
||||
func (store *Store) Close() error {
|
||||
func (store *Store) Close(_ context.Context) error {
|
||||
if store.db != nil {
|
||||
return store.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateDeletedUsersTable(db *sql.DB) error {
|
||||
log.Debugf("check deleted_users table version")
|
||||
func updateDeletedUsersTable(ctx context.Context, db *sql.DB) error {
|
||||
log.WithContext(ctx).Debugf("check deleted_users table version")
|
||||
rows, err := db.Query(`PRAGMA table_info(deleted_users);`)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -360,7 +361,7 @@ func updateDeletedUsersTable(db *sql.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("update delted_users table")
|
||||
log.WithContext(ctx).Debugf("update delted_users table")
|
||||
_, err = db.Exec(`ALTER TABLE deleted_users ADD COLUMN name TEXT;`)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -13,17 +14,17 @@ import (
|
||||
func TestNewSQLiteStore(t *testing.T) {
|
||||
dataDir := t.TempDir()
|
||||
key, _ := GenerateKey()
|
||||
store, err := NewSQLiteStore(dataDir, key)
|
||||
store, err := NewSQLiteStore(context.Background(), dataDir, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer store.Close() //nolint
|
||||
defer store.Close(context.Background()) //nolint
|
||||
|
||||
accountID := "account_1"
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = store.Save(&activity.Event{
|
||||
_, err = store.Save(context.Background(), &activity.Event{
|
||||
Timestamp: time.Now().UTC(),
|
||||
Activity: activity.PeerAddedByUser,
|
||||
InitiatorID: "user_" + fmt.Sprint(i),
|
||||
@@ -36,7 +37,7 @@ func TestNewSQLiteStore(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
result, err := store.Get(accountID, 0, 10, false)
|
||||
result, err := store.Get(context.Background(), accountID, 0, 10, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@@ -45,7 +46,7 @@ func TestNewSQLiteStore(t *testing.T) {
|
||||
assert.Len(t, result, 10)
|
||||
assert.True(t, result[0].Timestamp.Before(result[len(result)-1].Timestamp))
|
||||
|
||||
result, err = store.Get(accountID, 0, 5, true)
|
||||
result, err = store.Get(context.Background(), accountID, 0, 5, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package activity
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Store provides an interface to store or stream events.
|
||||
type Store interface {
|
||||
// Save an event in the store
|
||||
Save(event *Event) (*Event, error)
|
||||
Save(ctx context.Context, event *Event) (*Event, error)
|
||||
// Get returns "limit" number of events from the "offset" index ordered descending or ascending by a timestamp
|
||||
Get(accountID string, offset, limit int, descending bool) ([]*Event, error)
|
||||
Get(ctx context.Context, accountID string, offset, limit int, descending bool) ([]*Event, error)
|
||||
// Close the sink flushing events if necessary
|
||||
Close() error
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
// InMemoryEventStore implements the Store interface storing data in-memory
|
||||
@@ -20,7 +23,7 @@ type InMemoryEventStore struct {
|
||||
}
|
||||
|
||||
// Save sets the Event.ID to 1
|
||||
func (store *InMemoryEventStore) Save(event *Event) (*Event, error) {
|
||||
func (store *InMemoryEventStore) Save(_ context.Context, event *Event) (*Event, error) {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
if store.events == nil {
|
||||
@@ -33,7 +36,7 @@ func (store *InMemoryEventStore) Save(event *Event) (*Event, error) {
|
||||
}
|
||||
|
||||
// Get returns a list of ALL events that belong to the given accountID without taking offset, limit and order into consideration
|
||||
func (store *InMemoryEventStore) Get(accountID string, offset, limit int, descending bool) ([]*Event, error) {
|
||||
func (store *InMemoryEventStore) Get(_ context.Context, accountID string, offset, limit int, descending bool) ([]*Event, error) {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
events := make([]*Event, 0)
|
||||
@@ -46,7 +49,7 @@ func (store *InMemoryEventStore) Get(accountID string, offset, limit int, descen
|
||||
}
|
||||
|
||||
// Close cleans up the event list
|
||||
func (store *InMemoryEventStore) Close() error {
|
||||
func (store *InMemoryEventStore) Close(_ context.Context) error {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
store.events = make([]*Event, 0)
|
||||
|
||||
@@ -32,9 +32,10 @@ const (
|
||||
|
||||
// Config of the Management service
|
||||
type Config struct {
|
||||
Stuns []*Host
|
||||
TURNConfig *TURNConfig
|
||||
Signal *Host
|
||||
Stuns []*Host
|
||||
TURNConfig *TURNConfig
|
||||
RelayConfig *RelayConfig
|
||||
Signal *Host
|
||||
|
||||
Datadir string
|
||||
DataStoreEncryptionKey string
|
||||
@@ -71,6 +72,10 @@ type TURNConfig struct {
|
||||
Turns []*Host
|
||||
}
|
||||
|
||||
type RelayConfig struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
// HttpServerConfig is a config of the HTTP Management service server
|
||||
type HttpServerConfig struct {
|
||||
LetsEncryptDomain string
|
||||
|
||||
8
management/server/context/keys.go
Normal file
8
management/server/context/keys.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package context
|
||||
|
||||
const (
|
||||
RequestIDKey = "requestID"
|
||||
AccountIDKey = "accountID"
|
||||
UserIDKey = "userID"
|
||||
PeerIDKey = "peerID"
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
@@ -34,11 +35,11 @@ func (d DNSSettings) Copy() DNSSettings {
|
||||
}
|
||||
|
||||
// GetDNSSettings validates a user role and returns the DNS settings for the provided account ID
|
||||
func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string) (*DNSSettings, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) GetDNSSettings(ctx context.Context, accountID string, userID string) (*DNSSettings, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,11 +57,11 @@ func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string)
|
||||
}
|
||||
|
||||
// SaveDNSSettings validates a user role and updates the account's DNS settings
|
||||
func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) SaveDNSSettings(ctx context.Context, accountID string, userID string, dnsSettingsToSave *DNSSettings) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -89,7 +90,7 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string
|
||||
account.DNSSettings = dnsSettingsToSave.Copy()
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
if err = am.Store.SaveAccount(ctx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -97,17 +98,17 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string
|
||||
for _, id := range addedGroups {
|
||||
group := account.GetGroup(id)
|
||||
meta := map[string]any{"group": group.Name, "group_id": group.ID}
|
||||
am.StoreEvent(userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta)
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.GroupAddedToDisabledManagementGroups, meta)
|
||||
}
|
||||
|
||||
removedGroups := difference(oldSettings.DisabledManagementGroups, dnsSettingsToSave.DisabledManagementGroups)
|
||||
for _, id := range removedGroups {
|
||||
group := account.GetGroup(id)
|
||||
meta := map[string]any{"group": group.Name, "group_id": group.ID}
|
||||
am.StoreEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
|
||||
}
|
||||
|
||||
am.updateAccountPeers(account)
|
||||
am.updateAccountPeers(ctx, account)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -149,9 +150,9 @@ func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
||||
return protoUpdate
|
||||
}
|
||||
|
||||
func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
||||
func getPeersCustomZone(ctx context.Context, account *Account, dnsDomain string) nbdns.CustomZone {
|
||||
if dnsDomain == "" {
|
||||
log.Errorf("no dns domain is set, returning empty zone")
|
||||
log.WithContext(ctx).Errorf("no dns domain is set, returning empty zone")
|
||||
return nbdns.CustomZone{}
|
||||
}
|
||||
|
||||
@@ -161,7 +162,7 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
||||
|
||||
for _, peer := range account.Peers {
|
||||
if peer.DNSLabel == "" {
|
||||
log.Errorf("found a peer with empty dns label. It was probably caused by a invalid character in its name. Peer Name: %s", peer.Name)
|
||||
log.WithContext(ctx).Errorf("found a peer with empty dns label. It was probably caused by a invalid character in its name. Peer Name: %s", peer.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -210,14 +211,14 @@ func peerIsNameserver(peer *nbpeer.Peer, nsGroup *nbdns.NameServerGroup) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func addPeerLabelsToAccount(account *Account, peerLabels lookupMap) {
|
||||
func addPeerLabelsToAccount(ctx context.Context, account *Account, peerLabels lookupMap) {
|
||||
for _, peer := range account.Peers {
|
||||
label, err := getPeerHostLabel(peer.Name, peerLabels)
|
||||
if err != nil {
|
||||
log.Errorf("got an error while generating a peer host label. Peer name %s, error: %v. Trying with the peer's meta hostname", peer.Name, err)
|
||||
log.WithContext(ctx).Errorf("got an error while generating a peer host label. Peer name %s, error: %v. Trying with the peer's meta hostname", peer.Name, err)
|
||||
label, err = getPeerHostLabel(peer.Meta.Hostname, peerLabels)
|
||||
if err != nil {
|
||||
log.Errorf("got another error while generating a peer host label with hostname. Peer hostname %s, error: %v. Skipping", peer.Meta.Hostname, err)
|
||||
log.WithContext(ctx).Errorf("got another error while generating a peer host label with hostname. Peer hostname %s, error: %v. Skipping", peer.Meta.Hostname, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
@@ -35,7 +36,7 @@ func TestGetDNSSettings(t *testing.T) {
|
||||
t.Fatal("failed to init testing account")
|
||||
}
|
||||
|
||||
dnsSettings, err := am.GetDNSSettings(account.Id, dnsAdminUserID)
|
||||
dnsSettings, err := am.GetDNSSettings(context.Background(), account.Id, dnsAdminUserID)
|
||||
if err != nil {
|
||||
t.Fatalf("Got an error when trying to retrieve the DNS settings with an admin user, err: %s", err)
|
||||
}
|
||||
@@ -48,12 +49,12 @@ func TestGetDNSSettings(t *testing.T) {
|
||||
DisabledManagementGroups: []string{group1ID},
|
||||
}
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
err = am.Store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
t.Error("failed to save testing account with new DNS settings")
|
||||
}
|
||||
|
||||
dnsSettings, err = am.GetDNSSettings(account.Id, dnsAdminUserID)
|
||||
dnsSettings, err = am.GetDNSSettings(context.Background(), account.Id, dnsAdminUserID)
|
||||
if err != nil {
|
||||
t.Errorf("Got an error when trying to retrieve the DNS settings with an admin user, err: %s", err)
|
||||
}
|
||||
@@ -62,7 +63,7 @@ func TestGetDNSSettings(t *testing.T) {
|
||||
t.Errorf("DNS settings should have one disabled mgmt group, groups: %s", dnsSettings.DisabledManagementGroups)
|
||||
}
|
||||
|
||||
_, err = am.GetDNSSettings(account.Id, dnsRegularUserID)
|
||||
_, err = am.GetDNSSettings(context.Background(), account.Id, dnsRegularUserID)
|
||||
if err == nil {
|
||||
t.Errorf("An error should be returned when getting the DNS settings with a regular user")
|
||||
}
|
||||
@@ -122,7 +123,7 @@ func TestSaveDNSSettings(t *testing.T) {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
|
||||
err = am.SaveDNSSettings(account.Id, testCase.userID, testCase.inputSettings)
|
||||
err = am.SaveDNSSettings(context.Background(), account.Id, testCase.userID, testCase.inputSettings)
|
||||
if err != nil {
|
||||
if testCase.shouldFail {
|
||||
return
|
||||
@@ -130,7 +131,7 @@ func TestSaveDNSSettings(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
updatedAccount, err := am.Store.GetAccount(account.Id)
|
||||
updatedAccount, err := am.Store.GetAccount(context.Background(), account.Id)
|
||||
if err != nil {
|
||||
t.Errorf("should be able to retrieve updated account, got err: %s", err)
|
||||
}
|
||||
@@ -164,7 +165,7 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
|
||||
newAccountDNSConfig, err := am.GetNetworkMap(peer1.ID)
|
||||
newAccountDNSConfig, err := am.GetNetworkMap(context.Background(), peer1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, newAccountDNSConfig.DNSConfig.CustomZones, 1, "default DNS config should have one custom zone for peers")
|
||||
require.True(t, newAccountDNSConfig.DNSConfig.ServiceEnable, "default DNS config should have local DNS service enabled")
|
||||
@@ -173,14 +174,14 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) {
|
||||
dnsSettings := account.DNSSettings.Copy()
|
||||
dnsSettings.DisabledManagementGroups = append(dnsSettings.DisabledManagementGroups, dnsGroup1ID)
|
||||
account.DNSSettings = dnsSettings
|
||||
err = am.Store.SaveAccount(account)
|
||||
err = am.Store.SaveAccount(context.Background(), account)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedAccountDNSConfig, err := am.GetNetworkMap(peer1.ID)
|
||||
updatedAccountDNSConfig, err := am.GetNetworkMap(context.Background(), peer1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, updatedAccountDNSConfig.DNSConfig.CustomZones, 0, "updated DNS config should have no custom zone when peer belongs to a disabled group")
|
||||
require.False(t, updatedAccountDNSConfig.DNSConfig.ServiceEnable, "updated DNS config should have local DNS service disabled when peer belongs to a disabled group")
|
||||
peer2AccountDNSConfig, err := am.GetNetworkMap(peer2.ID)
|
||||
peer2AccountDNSConfig, err := am.GetNetworkMap(context.Background(), peer2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2AccountDNSConfig.DNSConfig.CustomZones, 1, "DNS config should have one custom zone for peers not in the disabled group")
|
||||
require.True(t, peer2AccountDNSConfig.DNSConfig.ServiceEnable, "DNS config should have DNS service enabled for peers not in the disabled group")
|
||||
@@ -194,13 +195,13 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{})
|
||||
return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{})
|
||||
}
|
||||
|
||||
func createDNSStore(t *testing.T) (Store, error) {
|
||||
t.Helper()
|
||||
dataDir := t.TempDir()
|
||||
store, cleanUp, err := NewTestStoreFromJson(dataDir)
|
||||
store, cleanUp, err := NewTestStoreFromJson(context.Background(), dataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -244,28 +245,28 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
||||
|
||||
domain := "example.com"
|
||||
|
||||
account := newAccountWithId(dnsAccountID, dnsAdminUserID, domain)
|
||||
account := newAccountWithId(context.Background(), dnsAccountID, dnsAdminUserID, domain)
|
||||
|
||||
account.Users[dnsRegularUserID] = &User{
|
||||
Id: dnsRegularUserID,
|
||||
Role: UserRoleUser,
|
||||
}
|
||||
|
||||
err := am.Store.SaveAccount(account)
|
||||
err := am.Store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
savedPeer1, _, err := am.AddPeer("", dnsAdminUserID, peer1)
|
||||
savedPeer1, _, _, err := am.AddPeer(context.Background(), "", dnsAdminUserID, peer1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, err = am.AddPeer("", dnsAdminUserID, peer2)
|
||||
_, _, _, err = am.AddPeer(context.Background(), "", dnsAdminUserID, peer2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
account, err = am.Store.GetAccount(account.Id)
|
||||
account, err = am.Store.GetAccount(context.Background(), account.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -312,10 +313,10 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
||||
Groups: []string{allGroup.ID},
|
||||
}
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
err = am.Store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return am.Store.GetAccount(account.Id)
|
||||
return am.Store.GetAccount(context.Background(), account.Id)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -51,13 +52,15 @@ func NewEphemeralManager(store Store, accountManager AccountManager) *EphemeralM
|
||||
// LoadInitialPeers load from the database the ephemeral type of peers and schedule a cleanup procedure to the head
|
||||
// of the linked list (to the most deprecated peer). At the end of cleanup it schedules the next cleanup to the new
|
||||
// head.
|
||||
func (e *EphemeralManager) LoadInitialPeers() {
|
||||
func (e *EphemeralManager) LoadInitialPeers(ctx context.Context) {
|
||||
e.peersLock.Lock()
|
||||
defer e.peersLock.Unlock()
|
||||
|
||||
e.loadEphemeralPeers()
|
||||
e.loadEphemeralPeers(ctx)
|
||||
if e.headPeer != nil {
|
||||
e.timer = time.AfterFunc(ephemeralLifeTime, e.cleanup)
|
||||
e.timer = time.AfterFunc(ephemeralLifeTime, func() {
|
||||
e.cleanup(ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,12 +76,12 @@ func (e *EphemeralManager) Stop() {
|
||||
|
||||
// OnPeerConnected remove the peer from the linked list of ephemeral peers. Because it has been called when the peer
|
||||
// is active the manager will not delete it while it is active.
|
||||
func (e *EphemeralManager) OnPeerConnected(peer *nbpeer.Peer) {
|
||||
func (e *EphemeralManager) OnPeerConnected(ctx context.Context, peer *nbpeer.Peer) {
|
||||
if !peer.Ephemeral {
|
||||
return
|
||||
}
|
||||
|
||||
log.Tracef("remove peer from ephemeral list: %s", peer.ID)
|
||||
log.WithContext(ctx).Tracef("remove peer from ephemeral list: %s", peer.ID)
|
||||
|
||||
e.peersLock.Lock()
|
||||
defer e.peersLock.Unlock()
|
||||
@@ -94,16 +97,16 @@ func (e *EphemeralManager) OnPeerConnected(peer *nbpeer.Peer) {
|
||||
|
||||
// OnPeerDisconnected add the peer to the linked list of ephemeral peers. Because of the peer
|
||||
// is inactive it will be deleted after the ephemeralLifeTime period.
|
||||
func (e *EphemeralManager) OnPeerDisconnected(peer *nbpeer.Peer) {
|
||||
func (e *EphemeralManager) OnPeerDisconnected(ctx context.Context, peer *nbpeer.Peer) {
|
||||
if !peer.Ephemeral {
|
||||
return
|
||||
}
|
||||
|
||||
log.Tracef("add peer to ephemeral list: %s", peer.ID)
|
||||
log.WithContext(ctx).Tracef("add peer to ephemeral list: %s", peer.ID)
|
||||
|
||||
a, err := e.store.GetAccountByPeerID(peer.ID)
|
||||
a, err := e.store.GetAccountByPeerID(context.Background(), peer.ID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to add peer to ephemeral list: %s", err)
|
||||
log.WithContext(ctx).Errorf("failed to add peer to ephemeral list: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,12 +119,14 @@ func (e *EphemeralManager) OnPeerDisconnected(peer *nbpeer.Peer) {
|
||||
|
||||
e.addPeer(peer.ID, a, newDeadLine())
|
||||
if e.timer == nil {
|
||||
e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), e.cleanup)
|
||||
e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), func() {
|
||||
e.cleanup(ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EphemeralManager) loadEphemeralPeers() {
|
||||
accounts := e.store.GetAllAccounts()
|
||||
func (e *EphemeralManager) loadEphemeralPeers(ctx context.Context) {
|
||||
accounts := e.store.GetAllAccounts(context.Background())
|
||||
t := newDeadLine()
|
||||
count := 0
|
||||
for _, a := range accounts {
|
||||
@@ -132,10 +137,10 @@ func (e *EphemeralManager) loadEphemeralPeers() {
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("loaded ephemeral peer(s): %d", count)
|
||||
log.WithContext(ctx).Debugf("loaded ephemeral peer(s): %d", count)
|
||||
}
|
||||
|
||||
func (e *EphemeralManager) cleanup() {
|
||||
func (e *EphemeralManager) cleanup(ctx context.Context) {
|
||||
log.Tracef("on ephemeral cleanup")
|
||||
deletePeers := make(map[string]*ephemeralPeer)
|
||||
|
||||
@@ -154,7 +159,9 @@ func (e *EphemeralManager) cleanup() {
|
||||
}
|
||||
|
||||
if e.headPeer != nil {
|
||||
e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), e.cleanup)
|
||||
e.timer = time.AfterFunc(e.headPeer.deadline.Sub(timeNow()), func() {
|
||||
e.cleanup(ctx)
|
||||
})
|
||||
} else {
|
||||
e.timer = nil
|
||||
}
|
||||
@@ -162,10 +169,10 @@ func (e *EphemeralManager) cleanup() {
|
||||
e.peersLock.Unlock()
|
||||
|
||||
for id, p := range deletePeers {
|
||||
log.Debugf("delete ephemeral peer: %s", id)
|
||||
err := e.accountManager.DeletePeer(p.account.Id, id, activity.SystemInitiator)
|
||||
log.WithContext(ctx).Debugf("delete ephemeral peer: %s", id)
|
||||
err := e.accountManager.DeletePeer(ctx, p.account.Id, id, activity.SystemInitiator)
|
||||
if err != nil {
|
||||
log.Errorf("failed to delete ephemeral peer: %s", err)
|
||||
log.WithContext(ctx).Errorf("failed to delete ephemeral peer: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -13,11 +14,11 @@ type MockStore struct {
|
||||
account *Account
|
||||
}
|
||||
|
||||
func (s *MockStore) GetAllAccounts() []*Account {
|
||||
func (s *MockStore) GetAllAccounts(_ context.Context) []*Account {
|
||||
return []*Account{s.account}
|
||||
}
|
||||
|
||||
func (s *MockStore) GetAccountByPeerID(peerId string) (*Account, error) {
|
||||
func (s *MockStore) GetAccountByPeerID(_ context.Context, peerId string) (*Account, error) {
|
||||
_, ok := s.account.Peers[peerId]
|
||||
if ok {
|
||||
return s.account, nil
|
||||
@@ -31,7 +32,7 @@ type MocAccountManager struct {
|
||||
store *MockStore
|
||||
}
|
||||
|
||||
func (a MocAccountManager) DeletePeer(accountID, peerID, userID string) error {
|
||||
func (a MocAccountManager) DeletePeer(_ context.Context, accountID, peerID, userID string) error {
|
||||
delete(a.store.account.Peers, peerID)
|
||||
return nil //nolint:nil
|
||||
}
|
||||
@@ -52,9 +53,9 @@ func TestNewManager(t *testing.T) {
|
||||
seedPeers(store, numberOfPeers, numberOfEphemeralPeers)
|
||||
|
||||
mgr := NewEphemeralManager(store, am)
|
||||
mgr.loadEphemeralPeers()
|
||||
mgr.loadEphemeralPeers(context.Background())
|
||||
startTime = startTime.Add(ephemeralLifeTime + 1)
|
||||
mgr.cleanup()
|
||||
mgr.cleanup(context.Background())
|
||||
|
||||
if len(store.account.Peers) != numberOfPeers {
|
||||
t.Errorf("failed to cleanup ephemeral peers, expected: %d, result: %d", numberOfPeers, len(store.account.Peers))
|
||||
@@ -77,11 +78,11 @@ func TestNewManagerPeerConnected(t *testing.T) {
|
||||
seedPeers(store, numberOfPeers, numberOfEphemeralPeers)
|
||||
|
||||
mgr := NewEphemeralManager(store, am)
|
||||
mgr.loadEphemeralPeers()
|
||||
mgr.OnPeerConnected(store.account.Peers["ephemeral_peer_0"])
|
||||
mgr.loadEphemeralPeers(context.Background())
|
||||
mgr.OnPeerConnected(context.Background(), store.account.Peers["ephemeral_peer_0"])
|
||||
|
||||
startTime = startTime.Add(ephemeralLifeTime + 1)
|
||||
mgr.cleanup()
|
||||
mgr.cleanup(context.Background())
|
||||
|
||||
expected := numberOfPeers + 1
|
||||
if len(store.account.Peers) != expected {
|
||||
@@ -105,15 +106,15 @@ func TestNewManagerPeerDisconnected(t *testing.T) {
|
||||
seedPeers(store, numberOfPeers, numberOfEphemeralPeers)
|
||||
|
||||
mgr := NewEphemeralManager(store, am)
|
||||
mgr.loadEphemeralPeers()
|
||||
mgr.loadEphemeralPeers(context.Background())
|
||||
for _, v := range store.account.Peers {
|
||||
mgr.OnPeerConnected(v)
|
||||
mgr.OnPeerConnected(context.Background(), v)
|
||||
|
||||
}
|
||||
mgr.OnPeerDisconnected(store.account.Peers["ephemeral_peer_0"])
|
||||
mgr.OnPeerDisconnected(context.Background(), store.account.Peers["ephemeral_peer_0"])
|
||||
|
||||
startTime = startTime.Add(ephemeralLifeTime + 1)
|
||||
mgr.cleanup()
|
||||
mgr.cleanup(context.Background())
|
||||
|
||||
expected := numberOfPeers + numberOfEphemeralPeers - 1
|
||||
if len(store.account.Peers) != expected {
|
||||
@@ -122,7 +123,7 @@ func TestNewManagerPeerDisconnected(t *testing.T) {
|
||||
}
|
||||
|
||||
func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int) {
|
||||
store.account = newAccountWithId("my account", "", "")
|
||||
store.account = newAccountWithId(context.Background(), "my account", "", "")
|
||||
|
||||
for i := 0; i < numberOfPeers; i++ {
|
||||
peerId := fmt.Sprintf("peer_%d", i)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -11,11 +12,11 @@ import (
|
||||
)
|
||||
|
||||
// GetEvents returns a list of activity events of an account
|
||||
func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activity.Event, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) GetEvents(ctx context.Context, accountID, userID string) ([]*activity.Event, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,7 +30,7 @@ func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activit
|
||||
return nil, status.Errorf(status.PermissionDenied, "only users with admin power can view events")
|
||||
}
|
||||
|
||||
events, err := am.eventStore.Get(accountID, 0, 10000, true)
|
||||
events, err := am.eventStore.Get(ctx, accountID, 0, 10000, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -54,10 +55,10 @@ func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activit
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||
func (am *DefaultAccountManager) StoreEvent(ctx context.Context, initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
|
||||
|
||||
go func() {
|
||||
_, err := am.eventStore.Save(&activity.Event{
|
||||
_, err := am.eventStore.Save(ctx, &activity.Event{
|
||||
Timestamp: time.Now().UTC(),
|
||||
Activity: activityID,
|
||||
InitiatorID: initiatorID,
|
||||
@@ -67,7 +68,7 @@ func (am *DefaultAccountManager) StoreEvent(initiatorID, targetID, accountID str
|
||||
})
|
||||
if err != nil {
|
||||
// todo add metric
|
||||
log.Errorf("received an error while storing an activity event, error: %s", err)
|
||||
log.WithContext(ctx).Errorf("received an error while storing an activity event, error: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,7 +14,7 @@ func generateAndStoreEvents(t *testing.T, manager *DefaultAccountManager, typ ac
|
||||
accountID string, count int) {
|
||||
t.Helper()
|
||||
for i := 0; i < count; i++ {
|
||||
_, err := manager.eventStore.Save(&activity.Event{
|
||||
_, err := manager.eventStore.Save(context.Background(), &activity.Event{
|
||||
Timestamp: time.Now().UTC(),
|
||||
Activity: typ,
|
||||
InitiatorID: initiatorID,
|
||||
@@ -35,32 +36,32 @@ func TestDefaultAccountManager_GetEvents(t *testing.T) {
|
||||
accountID := "accountID"
|
||||
|
||||
t.Run("get empty events list", func(t *testing.T) {
|
||||
events, err := manager.GetEvents(accountID, userID)
|
||||
events, err := manager.GetEvents(context.Background(), accountID, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Len(t, events, 0)
|
||||
_ = manager.eventStore.Close() //nolint
|
||||
_ = manager.eventStore.Close(context.Background()) //nolint
|
||||
})
|
||||
|
||||
t.Run("get events", func(t *testing.T) {
|
||||
generateAndStoreEvents(t, manager, activity.PeerAddedByUser, userID, "peer", accountID, 10)
|
||||
events, err := manager.GetEvents(accountID, userID)
|
||||
events, err := manager.GetEvents(context.Background(), accountID, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Len(t, events, 10)
|
||||
_ = manager.eventStore.Close() //nolint
|
||||
_ = manager.eventStore.Close(context.Background()) //nolint
|
||||
})
|
||||
|
||||
t.Run("get events without duplicates", func(t *testing.T) {
|
||||
generateAndStoreEvents(t, manager, activity.UserJoined, userID, "", accountID, 10)
|
||||
events, err := manager.GetEvents(accountID, userID)
|
||||
events, err := manager.GetEvents(context.Background(), accountID, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Len(t, events, 1)
|
||||
_ = manager.eventStore.Close() //nolint
|
||||
_ = manager.eventStore.Close(context.Background()) //nolint
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -48,8 +49,8 @@ type FileStore struct {
|
||||
type StoredAccount struct{}
|
||||
|
||||
// NewFileStore restores a store from the file located in the datadir
|
||||
func NewFileStore(dataDir string, metrics telemetry.AppMetrics) (*FileStore, error) {
|
||||
fs, err := restore(filepath.Join(dataDir, storeFileName))
|
||||
func NewFileStore(ctx context.Context, dataDir string, metrics telemetry.AppMetrics) (*FileStore, error) {
|
||||
fs, err := restore(ctx, filepath.Join(dataDir, storeFileName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,27 +59,27 @@ func NewFileStore(dataDir string, metrics telemetry.AppMetrics) (*FileStore, err
|
||||
}
|
||||
|
||||
// NewFilestoreFromSqliteStore restores a store from Sqlite and stores to Filestore json in the file located in datadir
|
||||
func NewFilestoreFromSqliteStore(sqlStore *SqlStore, dataDir string, metrics telemetry.AppMetrics) (*FileStore, error) {
|
||||
store, err := NewFileStore(dataDir, metrics)
|
||||
func NewFilestoreFromSqliteStore(ctx context.Context, sqlStore *SqlStore, dataDir string, metrics telemetry.AppMetrics) (*FileStore, error) {
|
||||
store, err := NewFileStore(ctx, dataDir, metrics)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = store.SaveInstallationID(sqlStore.GetInstallationID())
|
||||
err = store.SaveInstallationID(ctx, sqlStore.GetInstallationID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, account := range sqlStore.GetAllAccounts() {
|
||||
for _, account := range sqlStore.GetAllAccounts(ctx) {
|
||||
store.Accounts[account.Id] = account
|
||||
}
|
||||
|
||||
return store, store.persist(store.storeFile)
|
||||
return store, store.persist(ctx, store.storeFile)
|
||||
}
|
||||
|
||||
// restore the state of the store from the file.
|
||||
// Creates a new empty store file if doesn't exist
|
||||
func restore(file string) (*FileStore, error) {
|
||||
func restore(ctx context.Context, file string) (*FileStore, error) {
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
// create a new FileStore if previously didn't exist (e.g. first run)
|
||||
s := &FileStore{
|
||||
@@ -95,7 +96,7 @@ func restore(file string) (*FileStore, error) {
|
||||
storeFile: file,
|
||||
}
|
||||
|
||||
err = s.persist(file)
|
||||
err = s.persist(ctx, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -165,7 +166,7 @@ func restore(file string) (*FileStore, error) {
|
||||
// for data migration. Can be removed once most base will be with labels
|
||||
existingLabels := account.getPeerDNSLabels()
|
||||
if len(existingLabels) != len(account.Peers) {
|
||||
addPeerLabelsToAccount(account, existingLabels)
|
||||
addPeerLabelsToAccount(ctx, account, existingLabels)
|
||||
}
|
||||
|
||||
// TODO: delete this block after migration
|
||||
@@ -178,7 +179,7 @@ func restore(file string) (*FileStore, error) {
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
log.Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err)
|
||||
log.WithContext(ctx).Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err)
|
||||
// if the All group didn't exist we probably don't have routes to update
|
||||
continue
|
||||
}
|
||||
@@ -236,7 +237,7 @@ func restore(file string) (*FileStore, error) {
|
||||
}
|
||||
|
||||
// we need this persist to apply changes we made to account.Peers (we set them to Disconnected)
|
||||
err = store.persist(store.storeFile)
|
||||
err = store.persist(ctx, store.storeFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -246,7 +247,7 @@ func restore(file string) (*FileStore, error) {
|
||||
|
||||
// persist account data to a file
|
||||
// It is recommended to call it with locking FileStore.mux
|
||||
func (s *FileStore) persist(file string) error {
|
||||
func (s *FileStore) persist(ctx context.Context, file string) error {
|
||||
start := time.Now()
|
||||
err := util.WriteJson(file, s)
|
||||
if err != nil {
|
||||
@@ -256,23 +257,23 @@ func (s *FileStore) persist(file string) error {
|
||||
if s.metrics != nil {
|
||||
s.metrics.StoreMetrics().CountPersistenceDuration(took)
|
||||
}
|
||||
log.Debugf("took %d ms to persist the FileStore", took.Milliseconds())
|
||||
log.WithContext(ctx).Debugf("took %d ms to persist the FileStore", took.Milliseconds())
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcquireGlobalLock acquires global lock across all the accounts and returns a function that releases the lock
|
||||
func (s *FileStore) AcquireGlobalLock() (unlock func()) {
|
||||
log.Debugf("acquiring global lock")
|
||||
func (s *FileStore) AcquireGlobalLock(ctx context.Context) (unlock func()) {
|
||||
log.WithContext(ctx).Debugf("acquiring global lock")
|
||||
start := time.Now()
|
||||
s.globalAccountLock.Lock()
|
||||
|
||||
unlock = func() {
|
||||
s.globalAccountLock.Unlock()
|
||||
log.Debugf("released global lock in %v", time.Since(start))
|
||||
log.WithContext(ctx).Debugf("released global lock in %v", time.Since(start))
|
||||
}
|
||||
|
||||
took := time.Since(start)
|
||||
log.Debugf("took %v to acquire global lock", took)
|
||||
log.WithContext(ctx).Debugf("took %v to acquire global lock", took)
|
||||
if s.metrics != nil {
|
||||
s.metrics.StoreMetrics().CountGlobalLockAcquisitionDuration(took)
|
||||
}
|
||||
@@ -281,8 +282,8 @@ func (s *FileStore) AcquireGlobalLock() (unlock func()) {
|
||||
}
|
||||
|
||||
// AcquireAccountWriteLock acquires account lock for writing to a resource and returns a function that releases the lock
|
||||
func (s *FileStore) AcquireAccountWriteLock(accountID string) (unlock func()) {
|
||||
log.Debugf("acquiring lock for account %s", accountID)
|
||||
func (s *FileStore) AcquireAccountWriteLock(ctx context.Context, accountID string) (unlock func()) {
|
||||
log.WithContext(ctx).Debugf("acquiring lock for account %s", accountID)
|
||||
start := time.Now()
|
||||
value, _ := s.accountLocks.LoadOrStore(accountID, &sync.Mutex{})
|
||||
mtx := value.(*sync.Mutex)
|
||||
@@ -290,7 +291,7 @@ func (s *FileStore) AcquireAccountWriteLock(accountID string) (unlock func()) {
|
||||
|
||||
unlock = func() {
|
||||
mtx.Unlock()
|
||||
log.Debugf("released lock for account %s in %v", accountID, time.Since(start))
|
||||
log.WithContext(ctx).Debugf("released lock for account %s in %v", accountID, time.Since(start))
|
||||
}
|
||||
|
||||
return unlock
|
||||
@@ -298,11 +299,11 @@ func (s *FileStore) AcquireAccountWriteLock(accountID string) (unlock func()) {
|
||||
|
||||
// AcquireAccountReadLock AcquireAccountWriteLock acquires account lock for reading a resource and returns a function that releases the lock
|
||||
// This method is still returns a write lock as file store can't handle read locks
|
||||
func (s *FileStore) AcquireAccountReadLock(accountID string) (unlock func()) {
|
||||
return s.AcquireAccountWriteLock(accountID)
|
||||
func (s *FileStore) AcquireAccountReadLock(ctx context.Context, accountID string) (unlock func()) {
|
||||
return s.AcquireAccountWriteLock(ctx, accountID)
|
||||
}
|
||||
|
||||
func (s *FileStore) SaveAccount(account *Account) error {
|
||||
func (s *FileStore) SaveAccount(ctx context.Context, account *Account) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -338,10 +339,10 @@ func (s *FileStore) SaveAccount(account *Account) error {
|
||||
s.PrivateDomain2AccountID[accountCopy.Domain] = accountCopy.Id
|
||||
}
|
||||
|
||||
return s.persist(s.storeFile)
|
||||
return s.persist(ctx, s.storeFile)
|
||||
}
|
||||
|
||||
func (s *FileStore) DeleteAccount(account *Account) error {
|
||||
func (s *FileStore) DeleteAccount(ctx context.Context, account *Account) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -373,7 +374,7 @@ func (s *FileStore) DeleteAccount(account *Account) error {
|
||||
|
||||
delete(s.Accounts, account.Id)
|
||||
|
||||
return s.persist(s.storeFile)
|
||||
return s.persist(ctx, s.storeFile)
|
||||
}
|
||||
|
||||
// DeleteHashedPAT2TokenIDIndex removes an entry from the indexing map HashedPAT2TokenID
|
||||
@@ -397,7 +398,7 @@ func (s *FileStore) DeleteTokenID2UserIDIndex(tokenID string) error {
|
||||
}
|
||||
|
||||
// GetAccountByPrivateDomain returns account by private domain
|
||||
func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
||||
func (s *FileStore) GetAccountByPrivateDomain(_ context.Context, domain string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -415,7 +416,7 @@ func (s *FileStore) GetAccountByPrivateDomain(domain string) (*Account, error) {
|
||||
}
|
||||
|
||||
// GetAccountBySetupKey returns account by setup key id
|
||||
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
||||
func (s *FileStore) GetAccountBySetupKey(_ context.Context, setupKey string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -433,7 +434,7 @@ func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
||||
}
|
||||
|
||||
// GetTokenIDByHashedToken returns the id of a personal access token by its hashed secret
|
||||
func (s *FileStore) GetTokenIDByHashedToken(token string) (string, error) {
|
||||
func (s *FileStore) GetTokenIDByHashedToken(_ context.Context, token string) (string, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -446,7 +447,7 @@ func (s *FileStore) GetTokenIDByHashedToken(token string) (string, error) {
|
||||
}
|
||||
|
||||
// GetUserByTokenID returns a User object a tokenID belongs to
|
||||
func (s *FileStore) GetUserByTokenID(tokenID string) (*User, error) {
|
||||
func (s *FileStore) GetUserByTokenID(_ context.Context, tokenID string) (*User, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -469,7 +470,7 @@ func (s *FileStore) GetUserByTokenID(tokenID string) (*User, error) {
|
||||
}
|
||||
|
||||
// GetAllAccounts returns all accounts
|
||||
func (s *FileStore) GetAllAccounts() (all []*Account) {
|
||||
func (s *FileStore) GetAllAccounts(_ context.Context) (all []*Account) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
for _, a := range s.Accounts {
|
||||
@@ -490,7 +491,7 @@ func (s *FileStore) getAccount(accountID string) (*Account, error) {
|
||||
}
|
||||
|
||||
// GetAccount returns an account for ID
|
||||
func (s *FileStore) GetAccount(accountID string) (*Account, error) {
|
||||
func (s *FileStore) GetAccount(_ context.Context, accountID string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -503,7 +504,7 @@ func (s *FileStore) GetAccount(accountID string) (*Account, error) {
|
||||
}
|
||||
|
||||
// GetAccountByUser returns a user account
|
||||
func (s *FileStore) GetAccountByUser(userID string) (*Account, error) {
|
||||
func (s *FileStore) GetAccountByUser(_ context.Context, userID string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -521,7 +522,7 @@ func (s *FileStore) GetAccountByUser(userID string) (*Account, error) {
|
||||
}
|
||||
|
||||
// GetAccountByPeerID returns an account for a given peer ID
|
||||
func (s *FileStore) GetAccountByPeerID(peerID string) (*Account, error) {
|
||||
func (s *FileStore) GetAccountByPeerID(ctx context.Context, peerID string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -539,7 +540,7 @@ func (s *FileStore) GetAccountByPeerID(peerID string) (*Account, error) {
|
||||
// check Account.Peers for a match
|
||||
if _, ok := account.Peers[peerID]; !ok {
|
||||
delete(s.PeerID2AccountID, peerID)
|
||||
log.Warnf("removed stale peerID %s to accountID %s index", peerID, accountID)
|
||||
log.WithContext(ctx).Warnf("removed stale peerID %s to accountID %s index", peerID, accountID)
|
||||
return nil, status.NewPeerNotFoundError(peerID)
|
||||
}
|
||||
|
||||
@@ -547,7 +548,7 @@ func (s *FileStore) GetAccountByPeerID(peerID string) (*Account, error) {
|
||||
}
|
||||
|
||||
// GetAccountByPeerPubKey returns an account for a given peer WireGuard public key
|
||||
func (s *FileStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
|
||||
func (s *FileStore) GetAccountByPeerPubKey(ctx context.Context, peerKey string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -572,14 +573,14 @@ func (s *FileStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
|
||||
}
|
||||
if stale {
|
||||
delete(s.PeerKeyID2AccountID, peerKey)
|
||||
log.Warnf("removed stale peerKey %s to accountID %s index", peerKey, accountID)
|
||||
log.WithContext(ctx).Warnf("removed stale peerKey %s to accountID %s index", peerKey, accountID)
|
||||
return nil, status.NewPeerNotFoundError(peerKey)
|
||||
}
|
||||
|
||||
return account.Copy(), nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccountIDByPeerPubKey(peerKey string) (string, error) {
|
||||
func (s *FileStore) GetAccountIDByPeerPubKey(_ context.Context, peerKey string) (string, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -603,7 +604,7 @@ func (s *FileStore) GetAccountIDByUserID(userID string) (string, error) {
|
||||
return accountID, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccountIDBySetupKey(setupKey string) (string, error) {
|
||||
func (s *FileStore) GetAccountIDBySetupKey(_ context.Context, setupKey string) (string, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -615,7 +616,7 @@ func (s *FileStore) GetAccountIDBySetupKey(setupKey string) (string, error) {
|
||||
return accountID, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetPeerByPeerPubKey(peerKey string) (*nbpeer.Peer, error) {
|
||||
func (s *FileStore) GetPeerByPeerPubKey(_ context.Context, peerKey string) (*nbpeer.Peer, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -638,7 +639,7 @@ func (s *FileStore) GetPeerByPeerPubKey(peerKey string) (*nbpeer.Peer, error) {
|
||||
return nil, status.NewPeerNotFoundError(peerKey)
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccountSettings(accountID string) (*Settings, error) {
|
||||
func (s *FileStore) GetAccountSettings(_ context.Context, accountID string) (*Settings, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -656,13 +657,13 @@ func (s *FileStore) GetInstallationID() string {
|
||||
}
|
||||
|
||||
// SaveInstallationID saves the installation ID
|
||||
func (s *FileStore) SaveInstallationID(ID string) error {
|
||||
func (s *FileStore) SaveInstallationID(ctx context.Context, ID string) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
s.InstallationID = ID
|
||||
|
||||
return s.persist(s.storeFile)
|
||||
return s.persist(ctx, s.storeFile)
|
||||
}
|
||||
|
||||
// SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things.
|
||||
@@ -732,13 +733,13 @@ func (s *FileStore) GetPostureCheckByChecksDefinition(accountID string, checks *
|
||||
}
|
||||
|
||||
// Close the FileStore persisting data to disk
|
||||
func (s *FileStore) Close() error {
|
||||
func (s *FileStore) Close(ctx context.Context) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
log.Infof("closing FileStore")
|
||||
log.WithContext(ctx).Infof("closing FileStore")
|
||||
|
||||
return s.persist(s.storeFile)
|
||||
return s.persist(ctx, s.storeFile)
|
||||
}
|
||||
|
||||
// GetStoreEngine returns FileStoreEngine
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"net"
|
||||
"path/filepath"
|
||||
@@ -27,12 +28,12 @@ func TestStalePeerIndices(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
account, err := store.GetAccount("bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||
account, err := store.GetAccount(context.Background(), "bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||
require.NoError(t, err)
|
||||
|
||||
peerID := "some_peer"
|
||||
@@ -42,24 +43,24 @@ func TestStalePeerIndices(t *testing.T) {
|
||||
Key: peerKey,
|
||||
}
|
||||
|
||||
err = store.SaveAccount(account)
|
||||
err = store.SaveAccount(context.Background(), account)
|
||||
require.NoError(t, err)
|
||||
|
||||
account.DeletePeer(peerID)
|
||||
|
||||
err = store.SaveAccount(account)
|
||||
err = store.SaveAccount(context.Background(), account)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.GetAccountByPeerID(peerID)
|
||||
_, err = store.GetAccountByPeerID(context.Background(), peerID)
|
||||
require.Error(t, err, "expecting to get an error when found stale index")
|
||||
|
||||
_, err = store.GetAccountByPeerPubKey(peerKey)
|
||||
_, err = store.GetAccountByPeerPubKey(context.Background(), peerKey)
|
||||
require.Error(t, err, "expecting to get an error when found stale index")
|
||||
}
|
||||
|
||||
func TestNewStore(t *testing.T) {
|
||||
store := newStore(t)
|
||||
defer store.Close()
|
||||
defer store.Close(context.Background())
|
||||
|
||||
if store.Accounts == nil || len(store.Accounts) != 0 {
|
||||
t.Errorf("expected to create a new empty Accounts map when creating a new FileStore")
|
||||
@@ -88,9 +89,9 @@ func TestNewStore(t *testing.T) {
|
||||
|
||||
func TestSaveAccount(t *testing.T) {
|
||||
store := newStore(t)
|
||||
defer store.Close()
|
||||
defer store.Close(context.Background())
|
||||
|
||||
account := newAccountWithId("account_id", "testuser", "")
|
||||
account := newAccountWithId(context.Background(), "account_id", "testuser", "")
|
||||
setupKey := GenerateDefaultSetupKey()
|
||||
account.SetupKeys[setupKey.Key] = setupKey
|
||||
account.Peers["testpeer"] = &nbpeer.Peer{
|
||||
@@ -103,7 +104,7 @@ func TestSaveAccount(t *testing.T) {
|
||||
}
|
||||
|
||||
// SaveAccount should trigger persist
|
||||
err := store.SaveAccount(account)
|
||||
err := store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -133,11 +134,11 @@ func TestDeleteAccount(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer store.Close()
|
||||
defer store.Close(context.Background())
|
||||
|
||||
var account *Account
|
||||
for _, a := range store.Accounts {
|
||||
@@ -147,7 +148,7 @@ func TestDeleteAccount(t *testing.T) {
|
||||
|
||||
require.NotNil(t, account, "failed to restore a FileStore file and get at least one account")
|
||||
|
||||
err = store.DeleteAccount(account)
|
||||
err = store.DeleteAccount(context.Background(), account)
|
||||
require.NoError(t, err, "failed to delete account, error: %v", err)
|
||||
|
||||
_, ok := store.Accounts[account.Id]
|
||||
@@ -183,9 +184,9 @@ func TestDeleteAccount(t *testing.T) {
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
store := newStore(t)
|
||||
defer store.Close()
|
||||
defer store.Close(context.Background())
|
||||
|
||||
account := newAccountWithId("account_id", "testuser", "")
|
||||
account := newAccountWithId(context.Background(), "account_id", "testuser", "")
|
||||
account.Peers["testpeer"] = &nbpeer.Peer{
|
||||
Key: "peerkey",
|
||||
SetupKey: "peerkeysetupkey",
|
||||
@@ -228,12 +229,12 @@ func TestStore(t *testing.T) {
|
||||
})
|
||||
|
||||
// SaveAccount should trigger persist
|
||||
err := store.SaveAccount(account)
|
||||
err := store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
restored, err := NewFileStore(store.storeFile, nil)
|
||||
restored, err := NewFileStore(context.Background(), store.storeFile, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -281,7 +282,7 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -319,7 +320,7 @@ func TestRestoreGroups_Migration(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -332,11 +333,11 @@ func TestRestoreGroups_Migration(t *testing.T) {
|
||||
Name: "All",
|
||||
},
|
||||
}
|
||||
err = store.SaveAccount(account)
|
||||
err = store.SaveAccount(context.Background(), account)
|
||||
require.NoError(t, err, "failed to save account")
|
||||
|
||||
// restore account with default group with empty Issue field
|
||||
if store, err = NewFileStore(storeDir, nil); err != nil {
|
||||
if store, err = NewFileStore(context.Background(), storeDir, nil); err != nil {
|
||||
return
|
||||
}
|
||||
account = store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||
@@ -353,18 +354,18 @@ func TestGetAccountByPrivateDomain(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
existingDomain := "test.com"
|
||||
|
||||
account, err := store.GetAccountByPrivateDomain(existingDomain)
|
||||
account, err := store.GetAccountByPrivateDomain(context.Background(), existingDomain)
|
||||
require.NoError(t, err, "should found account")
|
||||
require.Equal(t, existingDomain, account.Domain, "domains should match")
|
||||
|
||||
_, err = store.GetAccountByPrivateDomain("missing-domain.com")
|
||||
_, err = store.GetAccountByPrivateDomain(context.Background(), "missing-domain.com")
|
||||
require.Error(t, err, "should return error on domain lookup")
|
||||
}
|
||||
|
||||
@@ -382,7 +383,7 @@ func TestFileStore_GetAccount(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -393,7 +394,7 @@ func TestFileStore_GetAccount(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err := store.GetAccount(expected.Id)
|
||||
account, err := store.GetAccount(context.Background(), expected.Id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -424,13 +425,13 @@ func TestFileStore_GetTokenIDByHashedToken(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hashedToken := accounts.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"].Users["f4f6d672-63fb-11ec-90d6-0242ac120003"].PATs["9dj38s35-63fb-11ec-90d6-0242ac120003"].HashedToken
|
||||
tokenID, err := store.GetTokenIDByHashedToken(hashedToken)
|
||||
tokenID, err := store.GetTokenIDByHashedToken(context.Background(), hashedToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -441,7 +442,7 @@ func TestFileStore_GetTokenIDByHashedToken(t *testing.T) {
|
||||
|
||||
func TestFileStore_DeleteHashedPAT2TokenIDIndex(t *testing.T) {
|
||||
store := newStore(t)
|
||||
defer store.Close()
|
||||
defer store.Close(context.Background())
|
||||
store.HashedPAT2TokenID["someHashedToken"] = "someTokenId"
|
||||
|
||||
err := store.DeleteHashedPAT2TokenIDIndex("someHashedToken")
|
||||
@@ -478,13 +479,13 @@ func TestFileStore_GetTokenIDByHashedToken_Failure(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wrongToken := sha256.Sum256([]byte("someNotValidTokenThatFails1234"))
|
||||
_, err = store.GetTokenIDByHashedToken(string(wrongToken[:]))
|
||||
_, err = store.GetTokenIDByHashedToken(context.Background(), string(wrongToken[:]))
|
||||
|
||||
assert.Error(t, err, "GetTokenIDByHashedToken should throw error if token invalid")
|
||||
}
|
||||
@@ -503,13 +504,13 @@ func TestFileStore_GetUserByTokenID(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tokenID := accounts.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"].Users["f4f6d672-63fb-11ec-90d6-0242ac120003"].PATs["9dj38s35-63fb-11ec-90d6-0242ac120003"].ID
|
||||
user, err := store.GetUserByTokenID(tokenID)
|
||||
user, err := store.GetUserByTokenID(context.Background(), tokenID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -531,13 +532,13 @@ func TestFileStore_GetUserByTokenID_Failure(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wrongTokenID := "someNonExistingTokenID"
|
||||
_, err = store.GetUserByTokenID(wrongTokenID)
|
||||
_, err = store.GetUserByTokenID(context.Background(), wrongTokenID)
|
||||
|
||||
assert.Error(t, err, "GetUserByTokenID should throw error if tokenID invalid")
|
||||
}
|
||||
@@ -550,7 +551,7 @@ func TestFileStore_SavePeerStatus(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -576,7 +577,7 @@ func TestFileStore_SavePeerStatus(t *testing.T) {
|
||||
Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()},
|
||||
}
|
||||
|
||||
err = store.SaveAccount(account)
|
||||
err = store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -602,11 +603,11 @@ func TestFileStore_SavePeerLocation(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store, err := NewFileStore(storeDir, nil)
|
||||
store, err := NewFileStore(context.Background(), storeDir, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
account, err := store.GetAccount("bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||
account, err := store.GetAccount(context.Background(), "bf1c8084-ba50-4ce7-9439-34653001fc3b")
|
||||
require.NoError(t, err)
|
||||
|
||||
peer := &nbpeer.Peer{
|
||||
@@ -625,7 +626,7 @@ func TestFileStore_SavePeerLocation(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
|
||||
account.Peers[peer.ID] = peer
|
||||
err = store.SaveAccount(account)
|
||||
err = store.SaveAccount(context.Background(), account)
|
||||
require.NoError(t, err)
|
||||
|
||||
peer.Location.ConnectionIP = net.ParseIP("35.1.1.1")
|
||||
@@ -636,7 +637,7 @@ func TestFileStore_SavePeerLocation(t *testing.T) {
|
||||
err = store.SavePeerLocation(account.Id, account.Peers[peer.ID])
|
||||
assert.NoError(t, err)
|
||||
|
||||
account, err = store.GetAccount(account.Id)
|
||||
account, err = store.GetAccount(context.Background(), account.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := account.Peers[peer.ID].Location
|
||||
@@ -645,7 +646,7 @@ func TestFileStore_SavePeerLocation(t *testing.T) {
|
||||
|
||||
func newStore(t *testing.T) *FileStore {
|
||||
t.Helper()
|
||||
store, err := NewFileStore(t.TempDir(), nil)
|
||||
store, err := NewFileStore(context.Background(), t.TempDir(), nil)
|
||||
if err != nil {
|
||||
t.Errorf("failed creating a new store")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package geolocation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -52,7 +53,7 @@ type Country struct {
|
||||
CountryName string
|
||||
}
|
||||
|
||||
func NewGeolocation(dataDir string) (*Geolocation, error) {
|
||||
func NewGeolocation(ctx context.Context, dataDir string) (*Geolocation, error) {
|
||||
if err := loadGeolocationDatabases(dataDir); err != nil {
|
||||
return nil, fmt.Errorf("failed to load MaxMind databases: %v", err)
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func NewGeolocation(dataDir string) (*Geolocation, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
locationDB, err := NewSqliteStore(dataDir)
|
||||
locationDB, err := NewSqliteStore(ctx, dataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,7 +84,7 @@ func NewGeolocation(dataDir string) (*Geolocation, error) {
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
go geo.reloader()
|
||||
go geo.reloader(ctx)
|
||||
|
||||
return geo, nil
|
||||
}
|
||||
@@ -165,19 +166,19 @@ func (gl *Geolocation) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gl *Geolocation) reloader() {
|
||||
func (gl *Geolocation) reloader(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-gl.stopCh:
|
||||
return
|
||||
case <-time.After(gl.reloadCheckInterval):
|
||||
if err := gl.locationDB.reload(); err != nil {
|
||||
log.Errorf("geonames db reload failed: %s", err)
|
||||
if err := gl.locationDB.reload(ctx); err != nil {
|
||||
log.WithContext(ctx).Errorf("geonames db reload failed: %s", err)
|
||||
}
|
||||
|
||||
newSha256sum1, err := calculateFileSHA256(gl.mmdbPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err)
|
||||
log.WithContext(ctx).Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(gl.sha256sum, newSha256sum1) {
|
||||
@@ -186,30 +187,30 @@ func (gl *Geolocation) reloader() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
newSha256sum2, err := calculateFileSHA256(gl.mmdbPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err)
|
||||
log.WithContext(ctx).Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(newSha256sum1, newSha256sum2) {
|
||||
log.Errorf("sha256 sum changed during reloading of '%s'", gl.mmdbPath)
|
||||
log.WithContext(ctx).Errorf("sha256 sum changed during reloading of '%s'", gl.mmdbPath)
|
||||
continue
|
||||
}
|
||||
err = gl.reload(newSha256sum2)
|
||||
err = gl.reload(ctx, newSha256sum2)
|
||||
if err != nil {
|
||||
log.Errorf("mmdb reload failed: %s", err)
|
||||
log.WithContext(ctx).Errorf("mmdb reload failed: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Tracef("No changes in '%s', no need to reload. Next check is in %.0f seconds.",
|
||||
log.WithContext(ctx).Tracef("No changes in '%s', no need to reload. Next check is in %.0f seconds.",
|
||||
gl.mmdbPath, gl.reloadCheckInterval.Seconds())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *Geolocation) reload(newSha256sum []byte) error {
|
||||
func (gl *Geolocation) reload(ctx context.Context, newSha256sum []byte) error {
|
||||
gl.mux.Lock()
|
||||
defer gl.mux.Unlock()
|
||||
|
||||
log.Infof("Reloading '%s'", gl.mmdbPath)
|
||||
log.WithContext(ctx).Infof("Reloading '%s'", gl.mmdbPath)
|
||||
|
||||
err := gl.db.Close()
|
||||
if err != nil {
|
||||
@@ -224,7 +225,7 @@ func (gl *Geolocation) reload(newSha256sum []byte) error {
|
||||
gl.db = db
|
||||
gl.sha256sum = newSha256sum
|
||||
|
||||
log.Infof("Successfully reloaded '%s'", gl.mmdbPath)
|
||||
log.WithContext(ctx).Infof("Successfully reloaded '%s'", gl.mmdbPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package geolocation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -50,10 +51,10 @@ type SqliteStore struct {
|
||||
sha256sum []byte
|
||||
}
|
||||
|
||||
func NewSqliteStore(dataDir string) (*SqliteStore, error) {
|
||||
func NewSqliteStore(ctx context.Context, dataDir string) (*SqliteStore, error) {
|
||||
file := filepath.Join(dataDir, GeoSqliteDBFile)
|
||||
|
||||
db, err := connectDB(file)
|
||||
db, err := connectDB(ctx, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,13 +116,13 @@ func (s *SqliteStore) GetCitiesByCountry(countryISOCode string) ([]City, error)
|
||||
}
|
||||
|
||||
// reload attempts to reload the SqliteStore's database if the database file has changed.
|
||||
func (s *SqliteStore) reload() error {
|
||||
func (s *SqliteStore) reload(ctx context.Context) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
newSha256sum1, err := calculateFileSHA256(s.filePath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err)
|
||||
log.WithContext(ctx).Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(s.sha256sum, newSha256sum1) {
|
||||
@@ -136,11 +137,11 @@ func (s *SqliteStore) reload() error {
|
||||
return fmt.Errorf("sha256 sum changed during reloading of '%s'", s.filePath)
|
||||
}
|
||||
|
||||
log.Infof("Reloading '%s'", s.filePath)
|
||||
log.WithContext(ctx).Infof("Reloading '%s'", s.filePath)
|
||||
_ = s.close()
|
||||
s.closed = true
|
||||
|
||||
newDb, err := connectDB(s.filePath)
|
||||
newDb, err := connectDB(ctx, s.filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,9 +149,9 @@ func (s *SqliteStore) reload() error {
|
||||
s.closed = false
|
||||
s.db = newDb
|
||||
|
||||
log.Infof("Successfully reloaded '%s'", s.filePath)
|
||||
log.WithContext(ctx).Infof("Successfully reloaded '%s'", s.filePath)
|
||||
} else {
|
||||
log.Tracef("No changes in '%s', no need to reload", s.filePath)
|
||||
log.WithContext(ctx).Tracef("No changes in '%s', no need to reload", s.filePath)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -168,10 +169,10 @@ func (s *SqliteStore) close() error {
|
||||
}
|
||||
|
||||
// connectDB connects to an SQLite database and prepares it by setting up an in-memory database.
|
||||
func connectDB(filePath string) (*gorm.DB, error) {
|
||||
func connectDB(ctx context.Context, filePath string) (*gorm.DB, error) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
log.Debugf("took %v to setup geoname db", time.Since(start))
|
||||
log.WithContext(ctx).Debugf("took %v to setup geoname db", time.Since(start))
|
||||
}()
|
||||
|
||||
_, err := fileExists(filePath)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/xid"
|
||||
@@ -21,11 +22,11 @@ func (e *GroupLinkError) Error() string {
|
||||
}
|
||||
|
||||
// GetGroup object of the peers
|
||||
func (am *DefaultAccountManager) GetGroup(accountID, groupID, userID string) (*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) GetGroup(ctx context.Context, accountID, groupID, userID string) (*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,11 +49,11 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID, userID string) (*n
|
||||
}
|
||||
|
||||
// GetAllGroups returns all groups in an account
|
||||
func (am *DefaultAccountManager) GetAllGroups(accountID string, userID string) ([]*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) GetAllGroups(ctx context.Context, accountID string, userID string) ([]*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,11 +76,11 @@ func (am *DefaultAccountManager) GetAllGroups(accountID string, userID string) (
|
||||
}
|
||||
|
||||
// GetGroupByName filters all groups in an account by name and returns the one with the most peers
|
||||
func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) GetGroupByName(ctx context.Context, groupName, accountID string) (*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -108,11 +109,11 @@ func (am *DefaultAccountManager) GetGroupByName(groupName, accountID string) (*n
|
||||
}
|
||||
|
||||
// SaveGroup object of the peers
|
||||
func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *nbgroup.Group) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) SaveGroup(ctx context.Context, accountID, userID string, newGroup *nbgroup.Group) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,11 +151,11 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *n
|
||||
account.Groups[newGroup.ID] = newGroup
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
if err = am.Store.SaveAccount(ctx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
am.updateAccountPeers(account)
|
||||
am.updateAccountPeers(ctx, account)
|
||||
|
||||
// the following snippet tracks the activity and stores the group events in the event store.
|
||||
// It has to happen after all the operations have been successfully performed.
|
||||
@@ -165,16 +166,16 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *n
|
||||
removedPeers = difference(oldGroup.Peers, newGroup.Peers)
|
||||
} else {
|
||||
addedPeers = append(addedPeers, newGroup.Peers...)
|
||||
am.StoreEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
|
||||
am.StoreEvent(ctx, userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta())
|
||||
}
|
||||
|
||||
for _, p := range addedPeers {
|
||||
peer := account.Peers[p]
|
||||
if peer == nil {
|
||||
log.Errorf("peer %s not found under account %s while saving group", p, accountID)
|
||||
log.WithContext(ctx).Errorf("peer %s not found under account %s while saving group", p, accountID)
|
||||
continue
|
||||
}
|
||||
am.StoreEvent(userID, peer.ID, accountID, activity.GroupAddedToPeer,
|
||||
am.StoreEvent(ctx, userID, peer.ID, accountID, activity.GroupAddedToPeer,
|
||||
map[string]any{
|
||||
"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
|
||||
"peer_fqdn": peer.FQDN(am.GetDNSDomain()),
|
||||
@@ -184,10 +185,10 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *n
|
||||
for _, p := range removedPeers {
|
||||
peer := account.Peers[p]
|
||||
if peer == nil {
|
||||
log.Errorf("peer %s not found under account %s while saving group", p, accountID)
|
||||
log.WithContext(ctx).Errorf("peer %s not found under account %s while saving group", p, accountID)
|
||||
continue
|
||||
}
|
||||
am.StoreEvent(userID, peer.ID, accountID, activity.GroupRemovedFromPeer,
|
||||
am.StoreEvent(ctx, userID, peer.ID, accountID, activity.GroupRemovedFromPeer,
|
||||
map[string]any{
|
||||
"group": newGroup.Name, "group_id": newGroup.ID, "peer_ip": peer.IP.String(),
|
||||
"peer_fqdn": peer.FQDN(am.GetDNSDomain()),
|
||||
@@ -213,11 +214,11 @@ func difference(a, b []string) []string {
|
||||
}
|
||||
|
||||
// DeleteGroup object of the peers
|
||||
func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountId)
|
||||
func (am *DefaultAccountManager) DeleteGroup(ctx context.Context, accountId, userId, groupID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountId)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountId)
|
||||
account, err := am.Store.GetAccount(ctx, accountId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -315,23 +316,23 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
||||
delete(account.Groups, groupID)
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
if err = am.Store.SaveAccount(ctx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
am.StoreEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
|
||||
am.StoreEvent(ctx, userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
|
||||
|
||||
am.updateAccountPeers(account)
|
||||
am.updateAccountPeers(ctx, account)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListGroups objects of the peers
|
||||
func (am *DefaultAccountManager) ListGroups(accountID string) ([]*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) ListGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -345,11 +346,11 @@ func (am *DefaultAccountManager) ListGroups(accountID string) ([]*nbgroup.Group,
|
||||
}
|
||||
|
||||
// GroupAddPeer appends peer to the group
|
||||
func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) GroupAddPeer(ctx context.Context, accountID, groupID, peerID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -371,21 +372,21 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
||||
}
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
if err = am.Store.SaveAccount(ctx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
am.updateAccountPeers(account)
|
||||
am.updateAccountPeers(ctx, account)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GroupDeletePeer removes peer from the group
|
||||
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(accountID)
|
||||
func (am *DefaultAccountManager) GroupDeletePeer(ctx context.Context, accountID, groupID, peerID string) error {
|
||||
unlock := am.Store.AcquireAccountWriteLock(ctx, accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
account, err := am.Store.GetAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -399,13 +400,13 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID stri
|
||||
for i, itemID := range group.Peers {
|
||||
if itemID == peerID {
|
||||
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
||||
if err := am.Store.SaveAccount(account); err != nil {
|
||||
if err := am.Store.SaveAccount(ctx, account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
am.updateAccountPeers(account)
|
||||
am.updateAccountPeers(ctx, account)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
@@ -26,7 +27,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
||||
}
|
||||
for _, group := range account.Groups {
|
||||
group.Issued = nbgroup.GroupIssuedIntegration
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group)
|
||||
if err != nil {
|
||||
t.Errorf("should allow to create %s groups", nbgroup.GroupIssuedIntegration)
|
||||
}
|
||||
@@ -34,7 +35,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
||||
|
||||
for _, group := range account.Groups {
|
||||
group.Issued = nbgroup.GroupIssuedJWT
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group)
|
||||
if err != nil {
|
||||
t.Errorf("should allow to create %s groups", nbgroup.GroupIssuedJWT)
|
||||
}
|
||||
@@ -42,7 +43,7 @@ func TestDefaultAccountManager_CreateGroup(t *testing.T) {
|
||||
for _, group := range account.Groups {
|
||||
group.Issued = nbgroup.GroupIssuedAPI
|
||||
group.ID = ""
|
||||
err = am.SaveGroup(account.Id, groupAdminUserID, group)
|
||||
err = am.SaveGroup(context.Background(), account.Id, groupAdminUserID, group)
|
||||
if err == nil {
|
||||
t.Errorf("should not create api group with the same name, %s", group.Name)
|
||||
}
|
||||
@@ -104,7 +105,7 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
err = am.DeleteGroup(account.Id, groupAdminUserID, testCase.groupID)
|
||||
err = am.DeleteGroup(context.Background(), account.Id, groupAdminUserID, testCase.groupID)
|
||||
if err == nil {
|
||||
t.Errorf("delete %s group successfully", testCase.groupID)
|
||||
return
|
||||
@@ -225,7 +226,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
||||
Id: "example user",
|
||||
AutoGroups: []string{groupForUsers.ID},
|
||||
}
|
||||
account := newAccountWithId(accountID, groupAdminUserID, domain)
|
||||
account := newAccountWithId(context.Background(), accountID, groupAdminUserID, domain)
|
||||
account.Routes[routeResource.ID] = routeResource
|
||||
account.Routes[routePeerGroupResource.ID] = routePeerGroupResource
|
||||
account.NameServerGroups[nameServerGroup.ID] = nameServerGroup
|
||||
@@ -233,18 +234,18 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
||||
account.SetupKeys[setupKey.Id] = setupKey
|
||||
account.Users[user.Id] = user
|
||||
|
||||
err := am.Store.SaveAccount(account)
|
||||
err := am.Store.SaveAccount(context.Background(), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute2)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForNameServerGroups)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForPolicies)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForSetupKeys)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForUsers)
|
||||
_ = am.SaveGroup(accountID, groupAdminUserID, groupForIntegration)
|
||||
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForRoute)
|
||||
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForRoute2)
|
||||
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForNameServerGroups)
|
||||
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForPolicies)
|
||||
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForSetupKeys)
|
||||
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForUsers)
|
||||
_ = am.SaveGroup(context.Background(), accountID, groupAdminUserID, groupForIntegration)
|
||||
|
||||
return am.Store.GetAccount(account.Id)
|
||||
return am.Store.GetAccount(context.Background(), account.Id)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
pb "github.com/golang/protobuf/proto" // nolint
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -19,8 +18,10 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
"github.com/netbirdio/netbird/management/proto"
|
||||
nbContext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
internalStatus "github.com/netbirdio/netbird/management/server/status"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
)
|
||||
@@ -30,17 +31,17 @@ type GRPCServer struct {
|
||||
accountManager AccountManager
|
||||
wgKey wgtypes.Key
|
||||
proto.UnimplementedManagementServiceServer
|
||||
peersUpdateManager *PeersUpdateManager
|
||||
config *Config
|
||||
turnCredentialsManager TURNCredentialsManager
|
||||
jwtValidator *jwtclaims.JWTValidator
|
||||
jwtClaimsExtractor *jwtclaims.ClaimsExtractor
|
||||
appMetrics telemetry.AppMetrics
|
||||
ephemeralManager *EphemeralManager
|
||||
peersUpdateManager *PeersUpdateManager
|
||||
config *Config
|
||||
turnRelayTokenManager TURNRelayTokenManager
|
||||
jwtValidator *jwtclaims.JWTValidator
|
||||
jwtClaimsExtractor *jwtclaims.ClaimsExtractor
|
||||
appMetrics telemetry.AppMetrics
|
||||
ephemeralManager *EphemeralManager
|
||||
}
|
||||
|
||||
// NewServer creates a new Management server
|
||||
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager, appMetrics telemetry.AppMetrics, ephemeralManager *EphemeralManager) (*GRPCServer, error) {
|
||||
func NewServer(ctx context.Context, config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnRelayTokenManager TURNRelayTokenManager, appMetrics telemetry.AppMetrics, ephemeralManager *EphemeralManager) (*GRPCServer, error) {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -50,6 +51,7 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
|
||||
|
||||
if config.HttpConfig != nil && config.HttpConfig.AuthIssuer != "" && config.HttpConfig.AuthAudience != "" && validateURL(config.HttpConfig.AuthKeysLocation) {
|
||||
jwtValidator, err = jwtclaims.NewJWTValidator(
|
||||
ctx,
|
||||
config.HttpConfig.AuthIssuer,
|
||||
config.GetAuthAudiences(),
|
||||
config.HttpConfig.AuthKeysLocation,
|
||||
@@ -59,7 +61,7 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
|
||||
return nil, status.Errorf(codes.Internal, "unable to create new jwt middleware, err: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Debug("unable to use http config to create new jwt middleware")
|
||||
log.WithContext(ctx).Debug("unable to use http config to create new jwt middleware")
|
||||
}
|
||||
|
||||
if appMetrics != nil {
|
||||
@@ -85,14 +87,14 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
|
||||
return &GRPCServer{
|
||||
wgKey: key,
|
||||
// peerKey -> event channel
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
accountManager: accountManager,
|
||||
config: config,
|
||||
turnCredentialsManager: turnCredentialsManager,
|
||||
jwtValidator: jwtValidator,
|
||||
jwtClaimsExtractor: jwtClaimsExtractor,
|
||||
appMetrics: appMetrics,
|
||||
ephemeralManager: ephemeralManager,
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
accountManager: accountManager,
|
||||
config: config,
|
||||
turnRelayTokenManager: turnRelayTokenManager,
|
||||
jwtValidator: jwtValidator,
|
||||
jwtClaimsExtractor: jwtClaimsExtractor,
|
||||
appMetrics: appMetrics,
|
||||
ephemeralManager: ephemeralManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -126,47 +128,61 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
|
||||
if s.appMetrics != nil {
|
||||
s.appMetrics.GRPCMetrics().CountSyncRequest()
|
||||
}
|
||||
realIP := getRealIP(srv.Context())
|
||||
log.Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||
|
||||
ctx := srv.Context()
|
||||
|
||||
realIP := getRealIP(ctx)
|
||||
|
||||
syncReq := &proto.SyncRequest{}
|
||||
peerKey, err := s.parseRequest(req, syncReq)
|
||||
peerKey, err := s.parseRequest(ctx, req, syncReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, nbContext.PeerIDKey, peerKey.String())
|
||||
accountID, err := s.accountManager.GetAccountIDForPeerKey(ctx, peerKey.String())
|
||||
if err != nil {
|
||||
// this case should not happen and already indicates an issue but we don't want the system to fail due to being unable to log in detail
|
||||
accountID = "UNKNOWN"
|
||||
}
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, nbContext.AccountIDKey, accountID)
|
||||
|
||||
log.WithContext(ctx).Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||
|
||||
if syncReq.GetMeta() == nil {
|
||||
log.Tracef("peer system meta has to be provided on sync. Peer %s, remote addr %s", peerKey.String(), realIP)
|
||||
log.WithContext(ctx).Tracef("peer system meta has to be provided on sync. Peer %s, remote addr %s", peerKey.String(), realIP)
|
||||
}
|
||||
|
||||
peer, netMap, err := s.accountManager.SyncAndMarkPeer(peerKey.String(), extractPeerMeta(syncReq.GetMeta()), realIP)
|
||||
peer, netMap, postureChecks, err := s.accountManager.SyncAndMarkPeer(ctx, peerKey.String(), extractPeerMeta(ctx, syncReq.GetMeta()), realIP)
|
||||
if err != nil {
|
||||
return mapError(err)
|
||||
return mapError(ctx, err)
|
||||
}
|
||||
|
||||
err = s.sendInitialSync(peerKey, peer, netMap, srv)
|
||||
err = s.sendInitialSync(ctx, peerKey, peer, netMap, postureChecks, srv)
|
||||
if err != nil {
|
||||
log.Debugf("error while sending initial sync for %s: %v", peerKey.String(), err)
|
||||
log.WithContext(ctx).Debugf("error while sending initial sync for %s: %v", peerKey.String(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
updates := s.peersUpdateManager.CreateChannel(peer.ID)
|
||||
updates := s.peersUpdateManager.CreateChannel(ctx, peer.ID)
|
||||
|
||||
s.ephemeralManager.OnPeerConnected(peer)
|
||||
s.ephemeralManager.OnPeerConnected(ctx, peer)
|
||||
|
||||
if s.config.TURNConfig.TimeBasedCredentials {
|
||||
s.turnCredentialsManager.SetupRefresh(peer.ID)
|
||||
s.turnRelayTokenManager.SetupRefresh(ctx, peer.ID)
|
||||
}
|
||||
|
||||
if s.appMetrics != nil {
|
||||
s.appMetrics.GRPCMetrics().CountSyncRequestDuration(time.Since(reqStart))
|
||||
}
|
||||
|
||||
return s.handleUpdates(peerKey, peer, updates, srv)
|
||||
return s.handleUpdates(ctx, peerKey, peer, updates, srv)
|
||||
}
|
||||
|
||||
// handleUpdates sends updates to the connected peer until the updates channel is closed.
|
||||
func (s *GRPCServer) handleUpdates(peerKey wgtypes.Key, peer *nbpeer.Peer, updates chan *UpdateMessage, srv proto.ManagementService_SyncServer) error {
|
||||
func (s *GRPCServer) handleUpdates(ctx context.Context, peerKey wgtypes.Key, peer *nbpeer.Peer, updates chan *UpdateMessage, srv proto.ManagementService_SyncServer) error {
|
||||
for {
|
||||
select {
|
||||
// condition when there are some updates
|
||||
@@ -176,21 +192,21 @@ func (s *GRPCServer) handleUpdates(peerKey wgtypes.Key, peer *nbpeer.Peer, updat
|
||||
}
|
||||
|
||||
if !open {
|
||||
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
||||
s.cancelPeerRoutines(peer)
|
||||
log.WithContext(ctx).Debugf("updates channel for peer %s was closed", peerKey.String())
|
||||
s.cancelPeerRoutines(ctx, peer)
|
||||
return nil
|
||||
}
|
||||
log.Debugf("received an update for peer %s", peerKey.String())
|
||||
log.WithContext(ctx).Debugf("received an update for peer %s", peerKey.String())
|
||||
|
||||
if err := s.sendUpdate(peerKey, peer, update, srv); err != nil {
|
||||
if err := s.sendUpdate(ctx, peerKey, peer, update, srv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// condition when client <-> server connection has been terminated
|
||||
case <-srv.Context().Done():
|
||||
// happens when connection drops, e.g. client disconnects
|
||||
log.Debugf("stream of peer %s has been closed", peerKey.String())
|
||||
s.cancelPeerRoutines(peer)
|
||||
log.WithContext(ctx).Debugf("stream of peer %s has been closed", peerKey.String())
|
||||
s.cancelPeerRoutines(ctx, peer)
|
||||
return srv.Context().Err()
|
||||
}
|
||||
}
|
||||
@@ -198,10 +214,10 @@ func (s *GRPCServer) handleUpdates(peerKey wgtypes.Key, peer *nbpeer.Peer, updat
|
||||
|
||||
// sendUpdate encrypts the update message using the peer key and the server's wireguard key,
|
||||
// then sends the encrypted message to the connected peer via the sync server.
|
||||
func (s *GRPCServer) sendUpdate(peerKey wgtypes.Key, peer *nbpeer.Peer, update *UpdateMessage, srv proto.ManagementService_SyncServer) error {
|
||||
func (s *GRPCServer) sendUpdate(ctx context.Context, peerKey wgtypes.Key, peer *nbpeer.Peer, update *UpdateMessage, srv proto.ManagementService_SyncServer) error {
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, update.Update)
|
||||
if err != nil {
|
||||
s.cancelPeerRoutines(peer)
|
||||
s.cancelPeerRoutines(ctx, peer)
|
||||
return status.Errorf(codes.Internal, "failed processing update message")
|
||||
}
|
||||
err = srv.SendMsg(&proto.EncryptedMessage{
|
||||
@@ -209,37 +225,37 @@ func (s *GRPCServer) sendUpdate(peerKey wgtypes.Key, peer *nbpeer.Peer, update *
|
||||
Body: encryptedResp,
|
||||
})
|
||||
if err != nil {
|
||||
s.cancelPeerRoutines(peer)
|
||||
s.cancelPeerRoutines(ctx, peer)
|
||||
return status.Errorf(codes.Internal, "failed sending update message")
|
||||
}
|
||||
log.Debugf("sent an update to peer %s", peerKey.String())
|
||||
log.WithContext(ctx).Debugf("sent an update to peer %s", peerKey.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GRPCServer) cancelPeerRoutines(peer *nbpeer.Peer) {
|
||||
s.peersUpdateManager.CloseChannel(peer.ID)
|
||||
s.turnCredentialsManager.CancelRefresh(peer.ID)
|
||||
_ = s.accountManager.CancelPeerRoutines(peer)
|
||||
s.ephemeralManager.OnPeerDisconnected(peer)
|
||||
func (s *GRPCServer) cancelPeerRoutines(ctx context.Context, peer *nbpeer.Peer) {
|
||||
s.peersUpdateManager.CloseChannel(ctx, peer.ID)
|
||||
s.turnRelayTokenManager.CancelRefresh(peer.ID)
|
||||
_ = s.accountManager.CancelPeerRoutines(ctx, peer)
|
||||
s.ephemeralManager.OnPeerDisconnected(ctx, peer)
|
||||
}
|
||||
|
||||
func (s *GRPCServer) validateToken(jwtToken string) (string, error) {
|
||||
func (s *GRPCServer) validateToken(ctx context.Context, jwtToken string) (string, error) {
|
||||
if s.jwtValidator == nil {
|
||||
return "", status.Error(codes.Internal, "no jwt validator set")
|
||||
}
|
||||
|
||||
token, err := s.jwtValidator.ValidateAndParse(jwtToken)
|
||||
token, err := s.jwtValidator.ValidateAndParse(ctx, jwtToken)
|
||||
if err != nil {
|
||||
return "", status.Errorf(codes.InvalidArgument, "invalid jwt token, err: %v", err)
|
||||
}
|
||||
claims := s.jwtClaimsExtractor.FromToken(token)
|
||||
// we need to call this method because if user is new, we will automatically add it to existing or create a new account
|
||||
_, _, err = s.accountManager.GetAccountFromToken(claims)
|
||||
_, _, err = s.accountManager.GetAccountFromToken(ctx, claims)
|
||||
if err != nil {
|
||||
return "", status.Errorf(codes.Internal, "unable to fetch account with claims, err: %v", err)
|
||||
}
|
||||
|
||||
if err := s.accountManager.CheckUserAccessByJWTGroups(claims); err != nil {
|
||||
if err := s.accountManager.CheckUserAccessByJWTGroups(ctx, claims); err != nil {
|
||||
return "", status.Errorf(codes.PermissionDenied, err.Error())
|
||||
}
|
||||
|
||||
@@ -247,7 +263,7 @@ func (s *GRPCServer) validateToken(jwtToken string) (string, error) {
|
||||
}
|
||||
|
||||
// maps internal internalStatus.Error to gRPC status.Error
|
||||
func mapError(err error) error {
|
||||
func mapError(ctx context.Context, err error) error {
|
||||
if e, ok := internalStatus.FromError(err); ok {
|
||||
switch e.Type() {
|
||||
case internalStatus.PermissionDenied:
|
||||
@@ -263,11 +279,11 @@ func mapError(err error) error {
|
||||
default:
|
||||
}
|
||||
}
|
||||
log.Errorf("got an unhandled error: %s", err)
|
||||
log.WithContext(ctx).Errorf("got an unhandled error: %s", err)
|
||||
return status.Errorf(codes.Internal, "failed handling request")
|
||||
}
|
||||
|
||||
func extractPeerMeta(meta *proto.PeerSystemMeta) nbpeer.PeerSystemMeta {
|
||||
func extractPeerMeta(ctx context.Context, meta *proto.PeerSystemMeta) nbpeer.PeerSystemMeta {
|
||||
if meta == nil {
|
||||
return nbpeer.PeerSystemMeta{}
|
||||
}
|
||||
@@ -281,7 +297,7 @@ func extractPeerMeta(meta *proto.PeerSystemMeta) nbpeer.PeerSystemMeta {
|
||||
for _, addr := range meta.GetNetworkAddresses() {
|
||||
netAddr, err := netip.ParsePrefix(addr.GetNetIP())
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse netip address, %s: %v", addr.GetNetIP(), err)
|
||||
log.WithContext(ctx).Warnf("failed to parse netip address, %s: %v", addr.GetNetIP(), err)
|
||||
continue
|
||||
}
|
||||
networkAddresses = append(networkAddresses, nbpeer.NetworkAddress{
|
||||
@@ -321,10 +337,10 @@ func extractPeerMeta(meta *proto.PeerSystemMeta) nbpeer.PeerSystemMeta {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GRPCServer) parseRequest(req *proto.EncryptedMessage, parsed pb.Message) (wgtypes.Key, error) {
|
||||
func (s *GRPCServer) parseRequest(ctx context.Context, req *proto.EncryptedMessage, parsed pb.Message) (wgtypes.Key, error) {
|
||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||
if err != nil {
|
||||
log.Warnf("error while parsing peer's WireGuard public key %s.", req.WgPubKey)
|
||||
log.WithContext(ctx).Warnf("error while parsing peer's WireGuard public key %s.", req.WgPubKey)
|
||||
return wgtypes.Key{}, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", req.WgPubKey)
|
||||
}
|
||||
|
||||
@@ -351,22 +367,32 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
||||
s.appMetrics.GRPCMetrics().CountLoginRequest()
|
||||
}
|
||||
realIP := getRealIP(ctx)
|
||||
log.Debugf("Login request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||
log.WithContext(ctx).Debugf("Login request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||
|
||||
loginReq := &proto.LoginRequest{}
|
||||
peerKey, err := s.parseRequest(req, loginReq)
|
||||
peerKey, err := s.parseRequest(ctx, req, loginReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, nbContext.PeerIDKey, peerKey.String())
|
||||
accountID, err := s.accountManager.GetAccountIDForPeerKey(ctx, peerKey.String())
|
||||
if err != nil {
|
||||
// this case should not happen and already indicates an issue but we don't want the system to fail due to being unable to log in detail
|
||||
accountID = "UNKNOWN"
|
||||
}
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, nbContext.AccountIDKey, accountID)
|
||||
|
||||
if loginReq.GetMeta() == nil {
|
||||
msg := status.Errorf(codes.FailedPrecondition,
|
||||
"peer system meta has to be provided to log in. Peer %s, remote addr %s", peerKey.String(), realIP)
|
||||
log.Warn(msg)
|
||||
log.WithContext(ctx).Warn(msg)
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
userID, err := s.processJwtToken(loginReq, peerKey)
|
||||
userID, err := s.processJwtToken(ctx, loginReq, peerKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -376,33 +402,38 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
||||
sshKey = loginReq.GetPeerKeys().GetSshPubKey()
|
||||
}
|
||||
|
||||
peer, netMap, err := s.accountManager.LoginPeer(PeerLogin{
|
||||
peer, netMap, postureChecks, err := s.accountManager.LoginPeer(ctx, PeerLogin{
|
||||
WireGuardPubKey: peerKey.String(),
|
||||
SSHKey: string(sshKey),
|
||||
Meta: extractPeerMeta(loginReq.GetMeta()),
|
||||
Meta: extractPeerMeta(ctx, loginReq.GetMeta()),
|
||||
UserID: userID,
|
||||
SetupKey: loginReq.GetSetupKey(),
|
||||
ConnectionIP: realIP,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnf("failed logging in peer %s: %s", peerKey, err)
|
||||
return nil, mapError(err)
|
||||
log.WithContext(ctx).Warnf("failed logging in peer %s: %s", peerKey, err)
|
||||
return nil, mapError(ctx, err)
|
||||
}
|
||||
|
||||
// if the login request contains setup key then it is a registration request
|
||||
if loginReq.GetSetupKey() != "" {
|
||||
s.ephemeralManager.OnPeerDisconnected(peer)
|
||||
s.ephemeralManager.OnPeerDisconnected(ctx, peer)
|
||||
}
|
||||
|
||||
trt, err := s.turnRelayTokenManager.Generate()
|
||||
if err != nil {
|
||||
log.Errorf("failed generating TURN and Relay token: %v", err)
|
||||
}
|
||||
|
||||
// if peer has reached this point then it has logged in
|
||||
loginResp := &proto.LoginResponse{
|
||||
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
|
||||
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil, trt),
|
||||
PeerConfig: toPeerConfig(peer, netMap.Network, s.accountManager.GetDNSDomain()),
|
||||
Checks: toProtocolChecks(s.accountManager, peerKey.String()),
|
||||
Checks: toProtocolChecks(ctx, postureChecks),
|
||||
}
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp)
|
||||
if err != nil {
|
||||
log.Warnf("failed encrypting peer %s message", peer.ID)
|
||||
log.WithContext(ctx).Warnf("failed encrypting peer %s message", peer.ID)
|
||||
return nil, status.Errorf(codes.Internal, "failed logging in peer")
|
||||
}
|
||||
|
||||
@@ -417,16 +448,16 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
|
||||
//
|
||||
// The user ID can be empty if the token is not provided, which is acceptable if the peer is already
|
||||
// registered or if it uses a setup key to register.
|
||||
func (s *GRPCServer) processJwtToken(loginReq *proto.LoginRequest, peerKey wgtypes.Key) (string, error) {
|
||||
func (s *GRPCServer) processJwtToken(ctx context.Context, loginReq *proto.LoginRequest, peerKey wgtypes.Key) (string, error) {
|
||||
userID := ""
|
||||
if loginReq.GetJwtToken() != "" {
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
userID, err = s.validateToken(loginReq.GetJwtToken())
|
||||
userID, err = s.validateToken(ctx, loginReq.GetJwtToken())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
log.Warnf("failed validating JWT token sent from peer %s with error %v. "+
|
||||
log.WithContext(ctx).Warnf("failed validating JWT token sent from peer %s with error %v. "+
|
||||
"Trying again as it may be due to the IdP cache issue", peerKey.String(), err)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
@@ -454,7 +485,7 @@ func ToResponseProto(configProto Protocol) proto.HostConfig_Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *proto.WiretrusteeConfig {
|
||||
func toWiretrusteeConfig(config *Config, turnCredentials *TURNRelayToken, relayToken *TURNRelayToken) *proto.WiretrusteeConfig {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -470,8 +501,8 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
||||
var username string
|
||||
var password string
|
||||
if turnCredentials != nil {
|
||||
username = turnCredentials.Username
|
||||
password = turnCredentials.Password
|
||||
username = turnCredentials.Payload
|
||||
password = turnCredentials.Signature
|
||||
} else {
|
||||
username = turn.Username
|
||||
password = turn.Password
|
||||
@@ -486,6 +517,18 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
||||
})
|
||||
}
|
||||
|
||||
var relayCfg *proto.RelayConfig
|
||||
if config.RelayConfig != nil && config.RelayConfig.Address != "" {
|
||||
relayCfg = &proto.RelayConfig{
|
||||
Urls: []string{config.RelayConfig.Address},
|
||||
}
|
||||
|
||||
if relayToken != nil {
|
||||
relayCfg.TokenPayload = relayToken.Payload
|
||||
relayCfg.TokenSignature = relayToken.Signature
|
||||
}
|
||||
}
|
||||
|
||||
return &proto.WiretrusteeConfig{
|
||||
Stuns: stuns,
|
||||
Turns: turns,
|
||||
@@ -493,6 +536,7 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
||||
Uri: config.Signal.URI,
|
||||
Protocol: ToResponseProto(config.Signal.Proto),
|
||||
},
|
||||
Relay: relayCfg,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,8 +564,8 @@ func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePee
|
||||
return remotePeers
|
||||
}
|
||||
|
||||
func toSyncResponse(accountManager AccountManager, config *Config, peer *nbpeer.Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse {
|
||||
wtConfig := toWiretrusteeConfig(config, turnCredentials)
|
||||
func toSyncResponse(ctx context.Context, config *Config, peer *nbpeer.Peer, turnCredentials *TURNRelayToken, relayCredentials *TURNRelayToken, networkMap *NetworkMap, dnsName string, checks []*posture.Checks) *proto.SyncResponse {
|
||||
wtConfig := toWiretrusteeConfig(config, turnCredentials, relayCredentials)
|
||||
|
||||
pConfig := toPeerConfig(peer, networkMap.Network, dnsName)
|
||||
|
||||
@@ -551,7 +595,7 @@ func toSyncResponse(accountManager AccountManager, config *Config, peer *nbpeer.
|
||||
FirewallRules: firewallRules,
|
||||
FirewallRulesIsEmpty: len(firewallRules) == 0,
|
||||
},
|
||||
Checks: toProtocolChecks(accountManager, peer.Key),
|
||||
Checks: toProtocolChecks(ctx, checks),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,16 +605,17 @@ func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Em
|
||||
}
|
||||
|
||||
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
|
||||
func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *nbpeer.Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error {
|
||||
func (s *GRPCServer) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, peer *nbpeer.Peer, networkMap *NetworkMap, postureChecks []*posture.Checks, srv proto.ManagementService_SyncServer) error {
|
||||
// make secret time based TURN credentials optional
|
||||
var turnCredentials *TURNCredentials
|
||||
if s.config.TURNConfig.TimeBasedCredentials {
|
||||
creds := s.turnCredentialsManager.GenerateCredentials()
|
||||
turnCredentials = &creds
|
||||
} else {
|
||||
turnCredentials = nil
|
||||
var turnCredentials *TURNRelayToken
|
||||
trt, err := s.turnRelayTokenManager.Generate()
|
||||
if err != nil {
|
||||
log.Errorf("failed generating TURN and Relay token: %v", err)
|
||||
}
|
||||
plainResp := toSyncResponse(s.accountManager, s.config, peer, turnCredentials, networkMap, s.accountManager.GetDNSDomain())
|
||||
if s.config.TURNConfig.TimeBasedCredentials {
|
||||
turnCredentials = trt
|
||||
}
|
||||
plainResp := toSyncResponse(ctx, s.config, peer, turnCredentials, trt, networkMap, s.accountManager.GetDNSDomain(), postureChecks)
|
||||
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||
if err != nil {
|
||||
@@ -583,7 +628,7 @@ func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *nbpeer.Peer, net
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed sending SyncResponse %v", err)
|
||||
log.WithContext(ctx).Errorf("failed sending SyncResponse %v", err)
|
||||
return status.Errorf(codes.Internal, "error handling request")
|
||||
}
|
||||
|
||||
@@ -597,14 +642,14 @@ func (s *GRPCServer) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.
|
||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||
if err != nil {
|
||||
errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetDeviceAuthorizationFlow request.", req.WgPubKey)
|
||||
log.Warn(errMSG)
|
||||
log.WithContext(ctx).Warn(errMSG)
|
||||
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||
}
|
||||
|
||||
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, &proto.DeviceAuthorizationFlowRequest{})
|
||||
if err != nil {
|
||||
errMSG := fmt.Sprintf("error while decrypting peer's message with Wireguard public key %s.", req.WgPubKey)
|
||||
log.Warn(errMSG)
|
||||
log.WithContext(ctx).Warn(errMSG)
|
||||
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||
}
|
||||
|
||||
@@ -645,18 +690,18 @@ func (s *GRPCServer) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.
|
||||
// GetPKCEAuthorizationFlow returns a pkce authorization flow information
|
||||
// This is used for initiating an Oauth 2 pkce authorization grant flow
|
||||
// which will be used by our clients to Login
|
||||
func (s *GRPCServer) GetPKCEAuthorizationFlow(_ context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||
func (s *GRPCServer) GetPKCEAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
||||
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
|
||||
if err != nil {
|
||||
errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetPKCEAuthorizationFlow request.", req.WgPubKey)
|
||||
log.Warn(errMSG)
|
||||
log.WithContext(ctx).Warn(errMSG)
|
||||
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||
}
|
||||
|
||||
err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, &proto.PKCEAuthorizationFlowRequest{})
|
||||
if err != nil {
|
||||
errMSG := fmt.Sprintf("error while decrypting peer's message with Wireguard public key %s.", req.WgPubKey)
|
||||
log.Warn(errMSG)
|
||||
log.WithContext(ctx).Warn(errMSG)
|
||||
return nil, status.Error(codes.InvalidArgument, errMSG)
|
||||
}
|
||||
|
||||
@@ -692,10 +737,10 @@ func (s *GRPCServer) GetPKCEAuthorizationFlow(_ context.Context, req *proto.Encr
|
||||
// peer's under the same account of any updates.
|
||||
func (s *GRPCServer) SyncMeta(ctx context.Context, req *proto.EncryptedMessage) (*proto.Empty, error) {
|
||||
realIP := getRealIP(ctx)
|
||||
log.Debugf("Sync meta request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||
log.WithContext(ctx).Debugf("Sync meta request from peer [%s] [%s]", req.WgPubKey, realIP.String())
|
||||
|
||||
syncMetaReq := &proto.SyncMetaRequest{}
|
||||
peerKey, err := s.parseRequest(req, syncMetaReq)
|
||||
peerKey, err := s.parseRequest(ctx, req, syncMetaReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -703,27 +748,21 @@ func (s *GRPCServer) SyncMeta(ctx context.Context, req *proto.EncryptedMessage)
|
||||
if syncMetaReq.GetMeta() == nil {
|
||||
msg := status.Errorf(codes.FailedPrecondition,
|
||||
"peer system meta has to be provided on sync. Peer %s, remote addr %s", peerKey.String(), realIP)
|
||||
log.Warn(msg)
|
||||
log.WithContext(ctx).Warn(msg)
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
err = s.accountManager.SyncPeerMeta(peerKey.String(), extractPeerMeta(syncMetaReq.GetMeta()))
|
||||
err = s.accountManager.SyncPeerMeta(ctx, peerKey.String(), extractPeerMeta(ctx, syncMetaReq.GetMeta()))
|
||||
if err != nil {
|
||||
return nil, mapError(err)
|
||||
return nil, mapError(ctx, err)
|
||||
}
|
||||
|
||||
return &proto.Empty{}, nil
|
||||
}
|
||||
|
||||
// toProtocolChecks returns posture checks for the peer that needs to be evaluated on the client side.
|
||||
func toProtocolChecks(accountManager AccountManager, peerKey string) []*proto.Checks {
|
||||
postureChecks, err := accountManager.GetPeerAppliedPostureChecks(peerKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed getting peer's: %s posture checks: %v", peerKey, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
protoChecks := make([]*proto.Checks, 0)
|
||||
// toProtocolChecks converts posture checks to protocol checks.
|
||||
func toProtocolChecks(ctx context.Context, postureChecks []*posture.Checks) []*proto.Checks {
|
||||
protoChecks := make([]*proto.Checks, 0, len(postureChecks))
|
||||
for _, postureCheck := range postureChecks {
|
||||
protoChecks = append(protoChecks, toProtocolCheck(postureCheck))
|
||||
}
|
||||
@@ -732,7 +771,7 @@ func toProtocolChecks(accountManager AccountManager, peerKey string) []*proto.Ch
|
||||
}
|
||||
|
||||
// toProtocolCheck converts a posture.Checks to a proto.Checks.
|
||||
func toProtocolCheck(postureCheck posture.Checks) *proto.Checks {
|
||||
func toProtocolCheck(postureCheck *posture.Checks) *proto.Checks {
|
||||
protoCheck := &proto.Checks{}
|
||||
|
||||
if check := postureCheck.Checks.ProcessCheck; check != nil {
|
||||
|
||||
@@ -35,34 +35,34 @@ func NewAccountsHandler(accountManager server.AccountManager, authCfg AuthCfg) *
|
||||
// GetAllAccounts is HTTP GET handler that returns a list of accounts. Effectively returns just a single account.
|
||||
func (h *AccountsHandler) GetAllAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if !(user.HasAdminPower() || user.IsServiceUser) {
|
||||
util.WriteError(status.Errorf(status.PermissionDenied, "the user has no permission to access account data"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.PermissionDenied, "the user has no permission to access account data"), w)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toAccountResponse(account)
|
||||
util.WriteJSONObject(w, []*api.Account{resp})
|
||||
util.WriteJSONObject(r.Context(), w, []*api.Account{resp})
|
||||
}
|
||||
|
||||
// UpdateAccount is HTTP PUT handler that updates the provided account. Updates only account settings (server.Settings)
|
||||
func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
_, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
_, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
accountID := vars["accountId"]
|
||||
if len(accountID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid accountID ID"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid accountID ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,15 +96,15 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
|
||||
settings.JWTAllowGroups = *req.Settings.JwtAllowGroups
|
||||
}
|
||||
|
||||
updatedAccount, err := h.accountManager.UpdateAccountSettings(accountID, user.Id, settings)
|
||||
updatedAccount, err := h.accountManager.UpdateAccountSettings(r.Context(), accountID, user.Id, settings)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toAccountResponse(updatedAccount)
|
||||
|
||||
util.WriteJSONObject(w, &resp)
|
||||
util.WriteJSONObject(r.Context(), w, &resp)
|
||||
}
|
||||
|
||||
// DeleteAccount is a HTTP DELETE handler to delete an account
|
||||
@@ -118,17 +118,17 @@ func (h *AccountsHandler) DeleteAccount(w http.ResponseWriter, r *http.Request)
|
||||
vars := mux.Vars(r)
|
||||
targetAccountID := vars["accountId"]
|
||||
if len(targetAccountID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid account ID"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid account ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.accountManager.DeleteAccount(targetAccountID, claims.UserId)
|
||||
err := h.accountManager.DeleteAccount(r.Context(), targetAccountID, claims.UserId)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, emptyObject{})
|
||||
util.WriteJSONObject(r.Context(), w, emptyObject{})
|
||||
}
|
||||
|
||||
func toAccountResponse(account *server.Account) *api.Account {
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -22,10 +23,10 @@ import (
|
||||
func initAccountsTestData(account *server.Account, admin *server.User) *AccountsHandler {
|
||||
return &AccountsHandler{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
GetAccountFromTokenFunc: func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return account, admin, nil
|
||||
},
|
||||
UpdateAccountSettingsFunc: func(accountID, userID string, newSettings *server.Settings) (*server.Account, error) {
|
||||
UpdateAccountSettingsFunc: func(ctx context.Context, accountID, userID string, newSettings *server.Settings) (*server.Account, error) {
|
||||
halfYearLimit := 180 * 24 * time.Hour
|
||||
if newSettings.PeerLoginExpiration > halfYearLimit {
|
||||
return nil, status.Errorf(status.InvalidArgument, "peer login expiration can't be larger than 180 days")
|
||||
|
||||
@@ -32,16 +32,16 @@ func NewDNSSettingsHandler(accountManager server.AccountManager, authCfg AuthCfg
|
||||
// GetDNSSettings returns the DNS settings for the account
|
||||
func (h *DNSSettingsHandler) GetDNSSettings(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.WithContext(r.Context()).Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
dnsSettings, err := h.accountManager.GetDNSSettings(account.Id, user.Id)
|
||||
dnsSettings, err := h.accountManager.GetDNSSettings(r.Context(), account.Id, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,15 +49,15 @@ func (h *DNSSettingsHandler) GetDNSSettings(w http.ResponseWriter, r *http.Reque
|
||||
DisabledManagementGroups: dnsSettings.DisabledManagementGroups,
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, apiDNSSettings)
|
||||
util.WriteJSONObject(r.Context(), w, apiDNSSettings)
|
||||
}
|
||||
|
||||
// UpdateDNSSettings handles update to DNS settings of an account
|
||||
func (h *DNSSettingsHandler) UpdateDNSSettings(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ func (h *DNSSettingsHandler) UpdateDNSSettings(w http.ResponseWriter, r *http.Re
|
||||
DisabledManagementGroups: req.DisabledManagementGroups,
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveDNSSettings(account.Id, user.Id, updateDNSSettings)
|
||||
err = h.accountManager.SaveDNSSettings(r.Context(), account.Id, user.Id, updateDNSSettings)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,5 +82,5 @@ func (h *DNSSettingsHandler) UpdateDNSSettings(w http.ResponseWriter, r *http.Re
|
||||
DisabledManagementGroups: updateDNSSettings.DisabledManagementGroups,
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, &resp)
|
||||
util.WriteJSONObject(r.Context(), w, &resp)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -42,16 +43,16 @@ var testingDNSSettingsAccount = &server.Account{
|
||||
func initDNSSettingsTestData() *DNSSettingsHandler {
|
||||
return &DNSSettingsHandler{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
GetDNSSettingsFunc: func(accountID string, userID string) (*server.DNSSettings, error) {
|
||||
GetDNSSettingsFunc: func(ctx context.Context, accountID string, userID string) (*server.DNSSettings, error) {
|
||||
return &testingDNSSettingsAccount.DNSSettings, nil
|
||||
},
|
||||
SaveDNSSettingsFunc: func(accountID string, userID string, dnsSettingsToSave *server.DNSSettings) error {
|
||||
SaveDNSSettingsFunc: func(ctx context.Context, accountID string, userID string, dnsSettingsToSave *server.DNSSettings) error {
|
||||
if dnsSettingsToSave != nil {
|
||||
return nil
|
||||
}
|
||||
return status.Errorf(status.InvalidArgument, "the dns settings provided are nil")
|
||||
},
|
||||
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
GetAccountFromTokenFunc: func(ctx context.Context, _ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return testingDNSSettingsAccount, testingDNSSettingsAccount.Users[testDNSSettingsUserID], nil
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -33,16 +34,16 @@ func NewEventsHandler(accountManager server.AccountManager, authCfg AuthCfg) *Ev
|
||||
// GetAllEvents list of the given account
|
||||
func (h *EventsHandler) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.WithContext(r.Context()).Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
accountEvents, err := h.accountManager.GetEvents(account.Id, user.Id)
|
||||
accountEvents, err := h.accountManager.GetEvents(r.Context(), account.Id, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
events := make([]*api.Event, len(accountEvents))
|
||||
@@ -50,20 +51,20 @@ func (h *EventsHandler) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
||||
events[i] = toEventResponse(e)
|
||||
}
|
||||
|
||||
err = h.fillEventsWithUserInfo(events, account.Id, user.Id)
|
||||
err = h.fillEventsWithUserInfo(r.Context(), events, account.Id, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, events)
|
||||
util.WriteJSONObject(r.Context(), w, events)
|
||||
}
|
||||
|
||||
func (h *EventsHandler) fillEventsWithUserInfo(events []*api.Event, accountId, userId string) error {
|
||||
func (h *EventsHandler) fillEventsWithUserInfo(ctx context.Context, events []*api.Event, accountId, userId string) error {
|
||||
// build email, name maps based on users
|
||||
userInfos, err := h.accountManager.GetUsersFromAccount(accountId, userId)
|
||||
userInfos, err := h.accountManager.GetUsersFromAccount(ctx, accountId, userId)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get users from account: %s", err)
|
||||
log.WithContext(ctx).Errorf("failed to get users from account: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ func (h *EventsHandler) fillEventsWithUserInfo(events []*api.Event, accountId, u
|
||||
if event.InitiatorEmail == "" {
|
||||
event.InitiatorEmail, ok = emails[event.InitiatorId]
|
||||
if !ok {
|
||||
log.Warnf("failed to resolve email for initiator: %s", event.InitiatorId)
|
||||
log.WithContext(ctx).Warnf("failed to resolve email for initiator: %s", event.InitiatorId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -22,13 +23,13 @@ import (
|
||||
func initEventsTestData(account string, user *server.User, events ...*activity.Event) *EventsHandler {
|
||||
return &EventsHandler{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
GetEventsFunc: func(accountID, userID string) ([]*activity.Event, error) {
|
||||
GetEventsFunc: func(_ context.Context, accountID, userID string) ([]*activity.Event, error) {
|
||||
if accountID == account {
|
||||
return events, nil
|
||||
}
|
||||
return []*activity.Event{}, nil
|
||||
},
|
||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
GetAccountFromTokenFunc: func(_ context.Context, claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return &server.Account{
|
||||
Id: claims.AccountId,
|
||||
Domain: "hotmail.com",
|
||||
@@ -37,7 +38,7 @@ func initEventsTestData(account string, user *server.User, events ...*activity.E
|
||||
},
|
||||
}, user, nil
|
||||
},
|
||||
GetUsersFromAccountFunc: func(accountID, userID string) ([]*server.UserInfo, error) {
|
||||
GetUsersFromAccountFunc: func(_ context.Context, accountID, userID string) ([]*server.UserInfo, error) {
|
||||
return make([]*server.UserInfo, 0), nil
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -35,13 +36,13 @@ func initGeolocationTestData(t *testing.T) *GeolocationsHandler {
|
||||
err = util.CopyFileContents(geonamesDBPath, path.Join(tempDir, geolocation.GeoSqliteDBFile))
|
||||
assert.NoError(t, err)
|
||||
|
||||
geo, err := geolocation.NewGeolocation(tempDir)
|
||||
geo, err := geolocation.NewGeolocation(context.Background(), tempDir)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() { _ = geo.Stop() })
|
||||
|
||||
return &GeolocationsHandler{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
GetAccountFromTokenFunc: func(_ context.Context, claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
user := server.NewAdminUser("test_user")
|
||||
return &server.Account{
|
||||
Id: claims.AccountId,
|
||||
|
||||
@@ -40,19 +40,19 @@ func NewGeolocationsHandlerHandler(accountManager server.AccountManager, geoloca
|
||||
// GetAllCountries retrieves a list of all countries
|
||||
func (l *GeolocationsHandler) GetAllCountries(w http.ResponseWriter, r *http.Request) {
|
||||
if err := l.authenticateUser(r); err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if l.geolocationManager == nil {
|
||||
// TODO: update error message to include geo db self hosted doc link when ready
|
||||
util.WriteError(status.Errorf(status.PreconditionFailed, "Geo location database is not initialized"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.PreconditionFailed, "Geo location database is not initialized"), w)
|
||||
return
|
||||
}
|
||||
|
||||
allCountries, err := l.geolocationManager.GetAllCountries()
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -60,32 +60,32 @@ func (l *GeolocationsHandler) GetAllCountries(w http.ResponseWriter, r *http.Req
|
||||
for _, country := range allCountries {
|
||||
countries = append(countries, toCountryResponse(country))
|
||||
}
|
||||
util.WriteJSONObject(w, countries)
|
||||
util.WriteJSONObject(r.Context(), w, countries)
|
||||
}
|
||||
|
||||
// GetCitiesByCountry retrieves a list of cities based on the given country code
|
||||
func (l *GeolocationsHandler) GetCitiesByCountry(w http.ResponseWriter, r *http.Request) {
|
||||
if err := l.authenticateUser(r); err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
countryCode := vars["country"]
|
||||
if !countryCodeRegex.MatchString(countryCode) {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid country code"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid country code"), w)
|
||||
return
|
||||
}
|
||||
|
||||
if l.geolocationManager == nil {
|
||||
util.WriteError(status.Errorf(status.PreconditionFailed, "Geo location database is not initialized. "+
|
||||
util.WriteError(r.Context(), status.Errorf(status.PreconditionFailed, "Geo location database is not initialized. "+
|
||||
"Check the self-hosted Geo database documentation at https://docs.netbird.io/selfhosted/geo-support"), w)
|
||||
return
|
||||
}
|
||||
|
||||
allCities, err := l.geolocationManager.GetCitiesByCountry(countryCode)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -93,12 +93,12 @@ func (l *GeolocationsHandler) GetCitiesByCountry(w http.ResponseWriter, r *http.
|
||||
for _, city := range allCities {
|
||||
cities = append(cities, toCityResponse(city))
|
||||
}
|
||||
util.WriteJSONObject(w, cities)
|
||||
util.WriteJSONObject(r.Context(), w, cities)
|
||||
}
|
||||
|
||||
func (l *GeolocationsHandler) authenticateUser(r *http.Request) error {
|
||||
claims := l.claimsExtractor.FromRequestContext(r)
|
||||
_, user, err := l.accountManager.GetAccountFromToken(claims)
|
||||
_, user, err := l.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -35,16 +35,16 @@ func NewGroupsHandler(accountManager server.AccountManager, authCfg AuthCfg) *Gr
|
||||
// GetAllGroups list for the account
|
||||
func (h *GroupsHandler) GetAllGroups(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.WithContext(r.Context()).Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
groups, err := h.accountManager.GetAllGroups(account.Id, user.Id)
|
||||
groups, err := h.accountManager.GetAllGroups(r.Context(), account.Id, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -53,42 +53,42 @@ func (h *GroupsHandler) GetAllGroups(w http.ResponseWriter, r *http.Request) {
|
||||
groupsResponse = append(groupsResponse, toGroupResponse(account, group))
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, groupsResponse)
|
||||
util.WriteJSONObject(r.Context(), w, groupsResponse)
|
||||
}
|
||||
|
||||
// UpdateGroup handles update to a group identified by a given ID
|
||||
func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
groupID, ok := vars["groupId"]
|
||||
if !ok {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "group ID field is missing"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "group ID field is missing"), w)
|
||||
return
|
||||
}
|
||||
if len(groupID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "group ID can't be empty"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "group ID can't be empty"), w)
|
||||
return
|
||||
}
|
||||
|
||||
eg, ok := account.Groups[groupID]
|
||||
if !ok {
|
||||
util.WriteError(status.Errorf(status.NotFound, "couldn't find group with ID %s", groupID), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.NotFound, "couldn't find group with ID %s", groupID), w)
|
||||
return
|
||||
}
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
if allGroup.ID == groupID {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -118,21 +118,21 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
IntegrationReference: eg.IntegrationReference,
|
||||
}
|
||||
|
||||
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
|
||||
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
||||
util.WriteError(err, w)
|
||||
if err := h.accountManager.SaveGroup(r.Context(), account.Id, user.Id, &group); err != nil {
|
||||
log.WithContext(r.Context()).Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, toGroupResponse(account, &group))
|
||||
util.WriteJSONObject(r.Context(), w, toGroupResponse(account, &group))
|
||||
}
|
||||
|
||||
// CreateGroup handles group creation request
|
||||
func (h *GroupsHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func (h *GroupsHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if req.Name == "" {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -160,62 +160,62 @@ func (h *GroupsHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
||||
Issued: nbgroup.GroupIssuedAPI,
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
|
||||
err = h.accountManager.SaveGroup(r.Context(), account.Id, user.Id, &group)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, toGroupResponse(account, &group))
|
||||
util.WriteJSONObject(r.Context(), w, toGroupResponse(account, &group))
|
||||
}
|
||||
|
||||
// DeleteGroup handles group deletion request
|
||||
func (h *GroupsHandler) DeleteGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
aID := account.Id
|
||||
|
||||
groupID := mux.Vars(r)["groupId"]
|
||||
if len(groupID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid group ID"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid group ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if allGroup.ID == groupID {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "deleting group ALL is not allowed"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "deleting group ALL is not allowed"), w)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountManager.DeleteGroup(aID, user.Id, groupID)
|
||||
err = h.accountManager.DeleteGroup(r.Context(), aID, user.Id, groupID)
|
||||
if err != nil {
|
||||
_, ok := err.(*server.GroupLinkError)
|
||||
if ok {
|
||||
util.WriteErrorResponse(err.Error(), http.StatusBadRequest, w)
|
||||
return
|
||||
}
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, emptyObject{})
|
||||
util.WriteJSONObject(r.Context(), w, emptyObject{})
|
||||
}
|
||||
|
||||
// GetGroup returns a group
|
||||
func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -223,19 +223,19 @@ func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||
case http.MethodGet:
|
||||
groupID := mux.Vars(r)["groupId"]
|
||||
if len(groupID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid group ID"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid group ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
group, err := h.accountManager.GetGroup(account.Id, groupID, user.Id)
|
||||
group, err := h.accountManager.GetGroup(r.Context(), account.Id, groupID, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, toGroupResponse(account, group))
|
||||
util.WriteJSONObject(r.Context(), w, toGroupResponse(account, group))
|
||||
default:
|
||||
util.WriteError(status.Errorf(status.NotFound, "HTTP method not found"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.NotFound, "HTTP method not found"), w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -32,13 +33,13 @@ var TestPeers = map[string]*nbpeer.Peer{
|
||||
func initGroupTestData(user *server.User, _ ...*nbgroup.Group) *GroupsHandler {
|
||||
return &GroupsHandler{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
SaveGroupFunc: func(accountID, userID string, group *nbgroup.Group) error {
|
||||
SaveGroupFunc: func(_ context.Context, accountID, userID string, group *nbgroup.Group) error {
|
||||
if !strings.HasPrefix(group.ID, "id-") {
|
||||
group.ID = "id-was-set"
|
||||
}
|
||||
return nil
|
||||
},
|
||||
GetGroupFunc: func(_, groupID, _ string) (*nbgroup.Group, error) {
|
||||
GetGroupFunc: func(_ context.Context, _, groupID, _ string) (*nbgroup.Group, error) {
|
||||
if groupID != "idofthegroup" {
|
||||
return nil, status.Errorf(status.NotFound, "not found")
|
||||
}
|
||||
@@ -55,7 +56,7 @@ func initGroupTestData(user *server.User, _ ...*nbgroup.Group) *GroupsHandler {
|
||||
Issued: nbgroup.GroupIssuedAPI,
|
||||
}, nil
|
||||
},
|
||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
GetAccountFromTokenFunc: func(_ context.Context, claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return &server.Account{
|
||||
Id: claims.AccountId,
|
||||
Domain: "hotmail.com",
|
||||
@@ -70,7 +71,7 @@ func initGroupTestData(user *server.User, _ ...*nbgroup.Group) *GroupsHandler {
|
||||
},
|
||||
}, user, nil
|
||||
},
|
||||
DeleteGroupFunc: func(accountID, userId, groupID string) error {
|
||||
DeleteGroupFunc: func(_ context.Context, accountID, userId, groupID string) error {
|
||||
if groupID == "linked-grp" {
|
||||
return &server.GroupLinkError{
|
||||
Resource: "something",
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/rs/cors"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
s "github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||
@@ -57,6 +58,11 @@ func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationMa
|
||||
|
||||
corsMiddleware := cors.AllowAll()
|
||||
|
||||
claimsExtractor = jwtclaims.NewClaimsExtractor(
|
||||
jwtclaims.WithAudience(authCfg.Audience),
|
||||
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
|
||||
)
|
||||
|
||||
acMiddleware := middleware.NewAccessControl(
|
||||
authCfg.Audience,
|
||||
authCfg.UserIDClaim,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// GetUser function defines a function to fetch user from Account by jwtclaims.AuthorizationClaims
|
||||
type GetUser func(claims jwtclaims.AuthorizationClaims) (*server.User, error)
|
||||
type GetUser func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (*server.User, error)
|
||||
|
||||
// AccessControl middleware to restrict to make POST/PUT/DELETE requests by admin only
|
||||
type AccessControl struct {
|
||||
@@ -46,15 +47,15 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler {
|
||||
|
||||
claims := a.claimsExtract.FromRequestContext(r)
|
||||
|
||||
user, err := a.getUser(claims)
|
||||
user, err := a.getUser(r.Context(), claims)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get user from claims: %s", err)
|
||||
util.WriteError(status.Errorf(status.Unauthorized, "invalid JWT"), w)
|
||||
log.WithContext(r.Context()).Errorf("failed to get user from claims: %s", err)
|
||||
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "invalid JWT"), w)
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsBlocked() {
|
||||
util.WriteError(status.Errorf(status.PermissionDenied, "the user has no access to the API or is blocked"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.PermissionDenied, "the user has no access to the API or is blocked"), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -63,12 +64,12 @@ func (a *AccessControl) Handler(h http.Handler) http.Handler {
|
||||
case http.MethodDelete, http.MethodPost, http.MethodPatch, http.MethodPut:
|
||||
|
||||
if tokenPathRegexp.MatchString(r.URL.Path) {
|
||||
log.Debugf("valid Path")
|
||||
log.WithContext(r.Context()).Debugf("valid Path")
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteError(status.Errorf(status.PermissionDenied, "only users with admin power can perform this operation"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.PermissionDenied, "only users with admin power can perform this operation"), w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
nbContext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
|
||||
"github.com/netbirdio/netbird/management/server/http/util"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
@@ -19,16 +20,16 @@ import (
|
||||
)
|
||||
|
||||
// GetAccountFromPATFunc function
|
||||
type GetAccountFromPATFunc func(token string) (*server.Account, *server.User, *server.PersonalAccessToken, error)
|
||||
type GetAccountFromPATFunc func(ctx context.Context, token string) (*server.Account, *server.User, *server.PersonalAccessToken, error)
|
||||
|
||||
// ValidateAndParseTokenFunc function
|
||||
type ValidateAndParseTokenFunc func(token string) (*jwt.Token, error)
|
||||
type ValidateAndParseTokenFunc func(ctx context.Context, token string) (*jwt.Token, error)
|
||||
|
||||
// MarkPATUsedFunc function
|
||||
type MarkPATUsedFunc func(token string) error
|
||||
type MarkPATUsedFunc func(ctx context.Context, token string) error
|
||||
|
||||
// CheckUserAccessByJWTGroupsFunc function
|
||||
type CheckUserAccessByJWTGroupsFunc func(claims jwtclaims.AuthorizationClaims) error
|
||||
type CheckUserAccessByJWTGroupsFunc func(ctx context.Context, claims jwtclaims.AuthorizationClaims) error
|
||||
|
||||
// AuthMiddleware middleware to verify personal access tokens (PAT) and JWT tokens
|
||||
type AuthMiddleware struct {
|
||||
@@ -85,23 +86,27 @@ func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
|
||||
case "bearer":
|
||||
err := m.checkJWTFromRequest(w, r, auth)
|
||||
if err != nil {
|
||||
log.Errorf("Error when validating JWT claims: %s", err.Error())
|
||||
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
|
||||
log.WithContext(r.Context()).Errorf("Error when validating JWT claims: %s", err.Error())
|
||||
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "token invalid"), w)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
case "token":
|
||||
err := m.checkPATFromRequest(w, r, auth)
|
||||
if err != nil {
|
||||
log.Debugf("Error when validating PAT claims: %s", err.Error())
|
||||
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
|
||||
log.WithContext(r.Context()).Debugf("Error when validating PAT claims: %s", err.Error())
|
||||
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "token invalid"), w)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
default:
|
||||
util.WriteError(status.Errorf(status.Unauthorized, "no valid authentication provided"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "no valid authentication provided"), w)
|
||||
return
|
||||
}
|
||||
claims := m.claimsExtractor.FromRequestContext(r)
|
||||
//nolint
|
||||
ctx := context.WithValue(r.Context(), nbContext.UserIDKey, claims.UserId)
|
||||
//nolint
|
||||
ctx = context.WithValue(ctx, nbContext.AccountIDKey, claims.AccountId)
|
||||
h.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -114,7 +119,7 @@ func (m *AuthMiddleware) checkJWTFromRequest(w http.ResponseWriter, r *http.Requ
|
||||
return fmt.Errorf("Error extracting token: %w", err)
|
||||
}
|
||||
|
||||
validatedToken, err := m.validateAndParseToken(token)
|
||||
validatedToken, err := m.validateAndParseToken(r.Context(), token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -123,7 +128,7 @@ func (m *AuthMiddleware) checkJWTFromRequest(w http.ResponseWriter, r *http.Requ
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := m.verifyUserAccess(validatedToken); err != nil {
|
||||
if err := m.verifyUserAccess(r.Context(), validatedToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -138,9 +143,9 @@ func (m *AuthMiddleware) checkJWTFromRequest(w http.ResponseWriter, r *http.Requ
|
||||
// verifyUserAccess checks if a user, based on a validated JWT token,
|
||||
// is allowed access, particularly in cases where the admin enabled JWT
|
||||
// group propagation and designated certain groups with access permissions.
|
||||
func (m *AuthMiddleware) verifyUserAccess(validatedToken *jwt.Token) error {
|
||||
func (m *AuthMiddleware) verifyUserAccess(ctx context.Context, validatedToken *jwt.Token) error {
|
||||
authClaims := m.claimsExtractor.FromToken(validatedToken)
|
||||
return m.checkUserAccessByJWTGroups(authClaims)
|
||||
return m.checkUserAccessByJWTGroups(ctx, authClaims)
|
||||
}
|
||||
|
||||
// CheckPATFromRequest checks if the PAT is valid
|
||||
@@ -152,7 +157,7 @@ func (m *AuthMiddleware) checkPATFromRequest(w http.ResponseWriter, r *http.Requ
|
||||
return fmt.Errorf("Error extracting token: %w", err)
|
||||
}
|
||||
|
||||
account, user, pat, err := m.getAccountFromPAT(token)
|
||||
account, user, pat, err := m.getAccountFromPAT(r.Context(), token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid Token: %w", err)
|
||||
}
|
||||
@@ -160,7 +165,7 @@ func (m *AuthMiddleware) checkPATFromRequest(w http.ResponseWriter, r *http.Requ
|
||||
return fmt.Errorf("token expired")
|
||||
}
|
||||
|
||||
err = m.markPATUsed(pat.ID)
|
||||
err = m.markPATUsed(r.Context(), pat.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -15,15 +16,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
audience = "audience"
|
||||
userIDClaim = "userIDClaim"
|
||||
accountID = "accountID"
|
||||
domain = "domain"
|
||||
userID = "userID"
|
||||
tokenID = "tokenID"
|
||||
PAT = "nbp_PAT"
|
||||
JWT = "JWT"
|
||||
wrongToken = "wrongToken"
|
||||
audience = "audience"
|
||||
userIDClaim = "userIDClaim"
|
||||
accountID = "accountID"
|
||||
domain = "domain"
|
||||
domainCategory = "domainCategory"
|
||||
userID = "userID"
|
||||
tokenID = "tokenID"
|
||||
PAT = "nbp_PAT"
|
||||
JWT = "JWT"
|
||||
wrongToken = "wrongToken"
|
||||
)
|
||||
|
||||
var testAccount = &server.Account{
|
||||
@@ -47,14 +49,14 @@ var testAccount = &server.Account{
|
||||
},
|
||||
}
|
||||
|
||||
func mockGetAccountFromPAT(token string) (*server.Account, *server.User, *server.PersonalAccessToken, error) {
|
||||
func mockGetAccountFromPAT(_ context.Context, token string) (*server.Account, *server.User, *server.PersonalAccessToken, error) {
|
||||
if token == PAT {
|
||||
return testAccount, testAccount.Users[userID], testAccount.Users[userID].PATs[tokenID], nil
|
||||
}
|
||||
return nil, nil, nil, fmt.Errorf("PAT invalid")
|
||||
}
|
||||
|
||||
func mockValidateAndParseToken(token string) (*jwt.Token, error) {
|
||||
func mockValidateAndParseToken(_ context.Context, token string) (*jwt.Token, error) {
|
||||
if token == JWT {
|
||||
return &jwt.Token{
|
||||
Claims: jwt.MapClaims{
|
||||
@@ -67,14 +69,14 @@ func mockValidateAndParseToken(token string) (*jwt.Token, error) {
|
||||
return nil, fmt.Errorf("JWT invalid")
|
||||
}
|
||||
|
||||
func mockMarkPATUsed(token string) error {
|
||||
func mockMarkPATUsed(_ context.Context, token string) error {
|
||||
if token == tokenID {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Should never get reached")
|
||||
}
|
||||
|
||||
func mockCheckUserAccessByJWTGroups(claims jwtclaims.AuthorizationClaims) error {
|
||||
func mockCheckUserAccessByJWTGroups(_ context.Context, claims jwtclaims.AuthorizationClaims) error {
|
||||
if testAccount.Id != claims.AccountId {
|
||||
return fmt.Errorf("account with id %s does not exist", claims.AccountId)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func ShouldBypass(requestPath string, h http.Handler, w http.ResponseWriter, r *
|
||||
for bypassPath := range bypassPaths {
|
||||
matched, err := path.Match(bypassPath, requestPath)
|
||||
if err != nil {
|
||||
log.Errorf("Error matching path %s with %s from %s: %v", bypassPath, requestPath, GetList(), err)
|
||||
log.WithContext(r.Context()).Errorf("Error matching path %s with %s from %s: %v", bypassPath, requestPath, GetList(), err)
|
||||
continue
|
||||
}
|
||||
if matched {
|
||||
|
||||
@@ -36,16 +36,16 @@ func NewNameserversHandler(accountManager server.AccountManager, authCfg AuthCfg
|
||||
// GetAllNameservers returns the list of nameserver groups for the account
|
||||
func (h *NameserversHandler) GetAllNameservers(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.WithContext(r.Context()).Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
nsGroups, err := h.accountManager.ListNameServerGroups(account.Id, user.Id)
|
||||
nsGroups, err := h.accountManager.ListNameServerGroups(r.Context(), account.Id, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -54,15 +54,15 @@ func (h *NameserversHandler) GetAllNameservers(w http.ResponseWriter, r *http.Re
|
||||
apiNameservers = append(apiNameservers, toNameserverGroupResponse(r))
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, apiNameservers)
|
||||
util.WriteJSONObject(r.Context(), w, apiNameservers)
|
||||
}
|
||||
|
||||
// CreateNameserverGroup handles nameserver group creation request
|
||||
func (h *NameserversHandler) CreateNameserverGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,33 +75,33 @@ func (h *NameserversHandler) CreateNameserverGroup(w http.ResponseWriter, r *htt
|
||||
|
||||
nsList, err := toServerNSList(req.Nameservers)
|
||||
if err != nil {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid NS servers format"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid NS servers format"), w)
|
||||
return
|
||||
}
|
||||
|
||||
nsGroup, err := h.accountManager.CreateNameServerGroup(account.Id, req.Name, req.Description, nsList, req.Groups, req.Primary, req.Domains, req.Enabled, user.Id, req.SearchDomainsEnabled)
|
||||
nsGroup, err := h.accountManager.CreateNameServerGroup(r.Context(), account.Id, req.Name, req.Description, nsList, req.Groups, req.Primary, req.Domains, req.Enabled, user.Id, req.SearchDomainsEnabled)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toNameserverGroupResponse(nsGroup)
|
||||
|
||||
util.WriteJSONObject(w, &resp)
|
||||
util.WriteJSONObject(r.Context(), w, &resp)
|
||||
}
|
||||
|
||||
// UpdateNameserverGroup handles update to a nameserver group identified by a given ID
|
||||
func (h *NameserversHandler) UpdateNameserverGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
nsGroupID := mux.Vars(r)["nsgroupId"]
|
||||
if len(nsGroupID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func (h *NameserversHandler) UpdateNameserverGroup(w http.ResponseWriter, r *htt
|
||||
|
||||
nsList, err := toServerNSList(req.Nameservers)
|
||||
if err != nil {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid NS servers format"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid NS servers format"), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -130,66 +130,66 @@ func (h *NameserversHandler) UpdateNameserverGroup(w http.ResponseWriter, r *htt
|
||||
SearchDomainsEnabled: req.SearchDomainsEnabled,
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveNameServerGroup(account.Id, user.Id, updatedNSGroup)
|
||||
err = h.accountManager.SaveNameServerGroup(r.Context(), account.Id, user.Id, updatedNSGroup)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toNameserverGroupResponse(updatedNSGroup)
|
||||
|
||||
util.WriteJSONObject(w, &resp)
|
||||
util.WriteJSONObject(r.Context(), w, &resp)
|
||||
}
|
||||
|
||||
// DeleteNameserverGroup handles nameserver group deletion request
|
||||
func (h *NameserversHandler) DeleteNameserverGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
nsGroupID := mux.Vars(r)["nsgroupId"]
|
||||
if len(nsGroupID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountManager.DeleteNameServerGroup(account.Id, nsGroupID, user.Id)
|
||||
err = h.accountManager.DeleteNameServerGroup(r.Context(), account.Id, nsGroupID, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, emptyObject{})
|
||||
util.WriteJSONObject(r.Context(), w, emptyObject{})
|
||||
}
|
||||
|
||||
// GetNameserverGroup handles a nameserver group Get request identified by ID
|
||||
func (h *NameserversHandler) GetNameserverGroup(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.claimsExtractor.FromRequestContext(r)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(r.Context(), claims)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.WithContext(r.Context()).Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
nsGroupID := mux.Vars(r)["nsgroupId"]
|
||||
if len(nsGroupID) == 0 {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||
util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "invalid nameserver group ID"), w)
|
||||
return
|
||||
}
|
||||
|
||||
nsGroup, err := h.accountManager.GetNameServerGroup(account.Id, user.Id, nsGroupID)
|
||||
nsGroup, err := h.accountManager.GetNameServerGroup(r.Context(), account.Id, user.Id, nsGroupID)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
resp := toNameserverGroupResponse(nsGroup)
|
||||
|
||||
util.WriteJSONObject(w, &resp)
|
||||
util.WriteJSONObject(r.Context(), w, &resp)
|
||||
}
|
||||
|
||||
func toServerNSList(apiNSList []api.Nameserver) ([]nbdns.NameServer, error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -61,13 +62,13 @@ var baseExistingNSGroup = &nbdns.NameServerGroup{
|
||||
func initNameserversTestData() *NameserversHandler {
|
||||
return &NameserversHandler{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
GetNameServerGroupFunc: func(accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||
GetNameServerGroupFunc: func(_ context.Context, accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||
if nsGroupID == existingNSGroupID {
|
||||
return baseExistingNSGroup.Copy(), nil
|
||||
}
|
||||
return nil, status.Errorf(status.NotFound, "nameserver group with ID %s not found", nsGroupID)
|
||||
},
|
||||
CreateNameServerGroupFunc: func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, _ string, searchDomains bool) (*nbdns.NameServerGroup, error) {
|
||||
CreateNameServerGroupFunc: func(_ context.Context, accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, _ string, searchDomains bool) (*nbdns.NameServerGroup, error) {
|
||||
return &nbdns.NameServerGroup{
|
||||
ID: existingNSGroupID,
|
||||
Name: name,
|
||||
@@ -80,16 +81,16 @@ func initNameserversTestData() *NameserversHandler {
|
||||
SearchDomainsEnabled: searchDomains,
|
||||
}, nil
|
||||
},
|
||||
DeleteNameServerGroupFunc: func(accountID, nsGroupID, _ string) error {
|
||||
DeleteNameServerGroupFunc: func(_ context.Context, accountID, nsGroupID, _ string) error {
|
||||
return nil
|
||||
},
|
||||
SaveNameServerGroupFunc: func(accountID, _ string, nsGroupToSave *nbdns.NameServerGroup) error {
|
||||
SaveNameServerGroupFunc: func(_ context.Context, accountID, _ string, nsGroupToSave *nbdns.NameServerGroup) error {
|
||||
if nsGroupToSave.ID == existingNSGroupID {
|
||||
return nil
|
||||
}
|
||||
return status.Errorf(status.NotFound, "nameserver group with ID %s was not found", nsGroupToSave.ID)
|
||||
},
|
||||
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
GetAccountFromTokenFunc: func(_ context.Context, _ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return testingNSAccount, testingAccount.Users["test_user"], nil
|
||||
},
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user