Compare commits

..

3 Commits

Author SHA1 Message Date
braginini
5d197cd5f9 Return user info in the peer API response 2022-09-23 18:32:25 +02:00
braginini
6bee984b46 Return user info in the peer APi response 2022-09-23 18:24:31 +02:00
braginini
2ee7d69f80 Add UserInfo to PeerInfo 2022-09-23 18:16:24 +02:00
17 changed files with 291 additions and 435 deletions

View File

@@ -292,6 +292,9 @@ func isProviderConfigValid(config ProviderConfig) error {
if config.ClientID == "" {
return fmt.Errorf(errorMSGFormat, "Client ID")
}
if config.ClientSecret == "" {
return fmt.Errorf(errorMSGFormat, "Client Secret")
}
if config.TokenEndpoint == "" {
return fmt.Errorf(errorMSGFormat, "Token Endpoint")
}

View File

@@ -9,16 +9,14 @@ import (
import "github.com/google/nftables"
const (
ipv6Forwarding = "netbird-rt-ipv6-forwarding"
ipv4Forwarding = "netbird-rt-ipv4-forwarding"
ipv6Nat = "netbird-rt-ipv6-nat"
ipv4Nat = "netbird-rt-ipv4-nat"
natFormat = "netbird-nat-%s"
forwardingFormat = "netbird-fwd-%s"
inNatFormat = "netbird-nat-in-%s"
inForwardingFormat = "netbird-fwd-in-%s"
ipv6 = "ipv6"
ipv4 = "ipv4"
ipv6Forwarding = "netbird-rt-ipv6-forwarding"
ipv4Forwarding = "netbird-rt-ipv4-forwarding"
ipv6Nat = "netbird-rt-ipv6-nat"
ipv4Nat = "netbird-rt-ipv4-nat"
natFormat = "netbird-nat-%s"
forwardingFormat = "netbird-fwd-%s"
ipv6 = "ipv6"
ipv4 = "ipv4"
)
func genKey(format string, input string) string {
@@ -55,13 +53,3 @@ func NewFirewall(parentCTX context.Context) firewallManager {
return manager
}
func getInPair(pair routerPair) routerPair {
return routerPair{
ID: pair.ID,
// invert source/destination
source: pair.destination,
destination: pair.source,
masquerade: pair.masquerade,
}
}

View File

@@ -311,37 +311,7 @@ func (i *iptablesManager) InsertRoutingRules(pair routerPair) error {
i.mux.Lock()
defer i.mux.Unlock()
err := i.insertRoutingRule(forwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, routingFinalForwardJump, pair)
if err != nil {
return err
}
err = i.insertRoutingRule(inForwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, routingFinalForwardJump, getInPair(pair))
if err != nil {
return err
}
if !pair.masquerade {
return nil
}
err = i.insertRoutingRule(natFormat, iptablesNatTable, iptablesRoutingNatChain, routingFinalNatJump, pair)
if err != nil {
return err
}
err = i.insertRoutingRule(inNatFormat, iptablesNatTable, iptablesRoutingNatChain, routingFinalNatJump, getInPair(pair))
if err != nil {
return err
}
return nil
}
// insertRoutingRule inserts an iptable rule
func (i *iptablesManager) insertRoutingRule(keyFormat, table, chain, jump string, pair routerPair) error {
var err error
prefix := netip.MustParsePrefix(pair.source)
ipVersion := ipv4
iptablesClient := i.ipv4Client
@@ -350,22 +320,43 @@ func (i *iptablesManager) insertRoutingRule(keyFormat, table, chain, jump string
ipVersion = ipv6
}
ruleKey := genKey(keyFormat, pair.ID)
rule := genRuleSpec(jump, ruleKey, pair.source, pair.destination)
existingRule, found := i.rules[ipVersion][ruleKey]
forwardRuleKey := genKey(forwardingFormat, pair.ID)
forwardRule := genRuleSpec(routingFinalForwardJump, forwardRuleKey, pair.source, pair.destination)
existingRule, found := i.rules[ipVersion][forwardRuleKey]
if found {
err = iptablesClient.DeleteIfExists(table, chain, existingRule...)
err = iptablesClient.DeleteIfExists(iptablesFilterTable, iptablesRoutingForwardingChain, existingRule...)
if err != nil {
return fmt.Errorf("iptables: error while removing existing %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
return fmt.Errorf("iptables: error while removing existing forwarding rule for %s: %v", pair.destination, err)
}
delete(i.rules[ipVersion], ruleKey)
delete(i.rules[ipVersion], forwardRuleKey)
}
err = iptablesClient.Insert(table, chain, 1, rule...)
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forwardRule...)
if err != nil {
return fmt.Errorf("iptables: error while adding new %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
return fmt.Errorf("iptables: error while adding new forwarding rule for %s: %v", pair.destination, err)
}
i.rules[ipVersion][ruleKey] = rule
i.rules[ipVersion][forwardRuleKey] = forwardRule
if !pair.masquerade {
return nil
}
natRuleKey := genKey(natFormat, pair.ID)
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, pair.source, pair.destination)
existingRule, found = i.rules[ipVersion][natRuleKey]
if found {
err = iptablesClient.DeleteIfExists(iptablesNatTable, iptablesRoutingNatChain, existingRule...)
if err != nil {
return fmt.Errorf("iptables: error while removing existing nat rulefor %s: %v", pair.destination, err)
}
delete(i.rules[ipVersion], natRuleKey)
}
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, natRule...)
if err != nil {
return fmt.Errorf("iptables: error while adding new nat rulefor %s: %v", pair.destination, err)
}
i.rules[ipVersion][natRuleKey] = natRule
return nil
}
@@ -375,37 +366,7 @@ func (i *iptablesManager) RemoveRoutingRules(pair routerPair) error {
i.mux.Lock()
defer i.mux.Unlock()
err := i.removeRoutingRule(forwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, pair)
if err != nil {
return err
}
err = i.removeRoutingRule(inForwardingFormat, iptablesFilterTable, iptablesRoutingForwardingChain, getInPair(pair))
if err != nil {
return err
}
if !pair.masquerade {
return nil
}
err = i.removeRoutingRule(natFormat, iptablesNatTable, iptablesRoutingNatChain, pair)
if err != nil {
return err
}
err = i.removeRoutingRule(inNatFormat, iptablesNatTable, iptablesRoutingNatChain, getInPair(pair))
if err != nil {
return err
}
return nil
}
// removeRoutingRule removes an iptables rule
func (i *iptablesManager) removeRoutingRule(keyFormat, table, chain string, pair routerPair) error {
var err error
prefix := netip.MustParsePrefix(pair.source)
ipVersion := ipv4
iptablesClient := i.ipv4Client
@@ -414,23 +375,29 @@ func (i *iptablesManager) removeRoutingRule(keyFormat, table, chain string, pair
ipVersion = ipv6
}
ruleKey := genKey(keyFormat, pair.ID)
existingRule, found := i.rules[ipVersion][ruleKey]
forwardRuleKey := genKey(forwardingFormat, pair.ID)
existingRule, found := i.rules[ipVersion][forwardRuleKey]
if found {
err = iptablesClient.DeleteIfExists(table, chain, existingRule...)
err = iptablesClient.DeleteIfExists(iptablesFilterTable, iptablesRoutingForwardingChain, existingRule...)
if err != nil {
return fmt.Errorf("iptables: error while removing existing %s rule for %s: %v", getIptablesRuleType(table), pair.destination, err)
return fmt.Errorf("iptables: error while removing existing forwarding rule for %s: %v", pair.destination, err)
}
}
delete(i.rules[ipVersion], ruleKey)
delete(i.rules[ipVersion], forwardRuleKey)
if !pair.masquerade {
return nil
}
natRuleKey := genKey(natFormat, pair.ID)
existingRule, found = i.rules[ipVersion][natRuleKey]
if found {
err = iptablesClient.DeleteIfExists(iptablesNatTable, iptablesRoutingNatChain, existingRule...)
if err != nil {
return fmt.Errorf("iptables: error while removing existing nat rule for %s: %v", pair.destination, err)
}
}
delete(i.rules[ipVersion], natRuleKey)
return nil
}
func getIptablesRuleType(table string) string {
ruleType := "forwarding"
if table == iptablesNatTable {
ruleType = "nat"
}
return ruleType
}

View File

@@ -159,17 +159,6 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
require.True(t, found, "forwarding rule should exist in the manager map")
require.Equal(t, forwardRule[:4], foundRule[:4], "stored forwarding rule should match")
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
exists, err = iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, inForwardRule...)
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
require.True(t, exists, "income forwarding rule should exist")
foundRule, found = manager.rules[testCase.ipVersion][inForwardRuleKey]
require.True(t, found, "income forwarding rule should exist in the manager map")
require.Equal(t, inForwardRule[:4], foundRule[:4], "stored income forwarding rule should match")
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
@@ -183,23 +172,7 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) {
} else {
require.False(t, exists, "nat rule should not be created")
_, foundNat := manager.rules[testCase.ipVersion][natRuleKey]
require.False(t, foundNat, "nat rule should not exist in the map")
}
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, inNatRule...)
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
if testCase.inputPair.masquerade {
require.True(t, exists, "income nat rule should be created")
foundNatRule, foundNat := manager.rules[testCase.ipVersion][inNatRuleKey]
require.True(t, foundNat, "income nat rule should exist in the map")
require.Equal(t, inNatRule[:4], foundNatRule[:4], "stored income nat rule should match")
} else {
require.False(t, exists, "nat rule should not be created")
_, foundNat := manager.rules[testCase.ipVersion][inNatRuleKey]
require.False(t, foundNat, "income nat rule should not exist in the map")
require.False(t, foundNat, "nat rule should exist in the map")
}
})
}
@@ -240,24 +213,12 @@ func TestIptablesManager_RemoveRoutingRules(t *testing.T) {
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, forwardRule...)
require.NoError(t, err, "inserting rule should not return error")
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
inForwardRule := genRuleSpec(routingFinalForwardJump, inForwardRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
err = iptablesClient.Insert(iptablesFilterTable, iptablesRoutingForwardingChain, 1, inForwardRule...)
require.NoError(t, err, "inserting rule should not return error")
natRuleKey := genKey(natFormat, testCase.inputPair.ID)
natRule := genRuleSpec(routingFinalNatJump, natRuleKey, testCase.inputPair.source, testCase.inputPair.destination)
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, natRule...)
require.NoError(t, err, "inserting rule should not return error")
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
inNatRule := genRuleSpec(routingFinalNatJump, inNatRuleKey, getInPair(testCase.inputPair).source, getInPair(testCase.inputPair).destination)
err = iptablesClient.Insert(iptablesNatTable, iptablesRoutingNatChain, 1, inNatRule...)
require.NoError(t, err, "inserting rule should not return error")
delete(manager.rules, ipv4)
delete(manager.rules, ipv6)
@@ -274,26 +235,12 @@ func TestIptablesManager_RemoveRoutingRules(t *testing.T) {
_, found := manager.rules[testCase.ipVersion][forwardRuleKey]
require.False(t, found, "forwarding rule should exist in the manager map")
exists, err = iptablesClient.Exists(iptablesFilterTable, iptablesRoutingForwardingChain, inForwardRule...)
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesFilterTable, iptablesRoutingForwardingChain)
require.False(t, exists, "income forwarding rule should not exist")
_, found = manager.rules[testCase.ipVersion][inForwardRuleKey]
require.False(t, found, "income forwarding rule should exist in the manager map")
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, natRule...)
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
require.False(t, exists, "nat rule should not exist")
_, found = manager.rules[testCase.ipVersion][natRuleKey]
require.False(t, found, "nat rule should exist in the manager map")
exists, err = iptablesClient.Exists(iptablesNatTable, iptablesRoutingNatChain, inNatRule...)
require.NoError(t, err, "should be able to query the iptables %s %s table and %s chain", testCase.ipVersion, iptablesNatTable, iptablesRoutingNatChain)
require.False(t, exists, "income nat rule should not exist")
_, found = manager.rules[testCase.ipVersion][inNatRuleKey]
require.False(t, found, "income nat rule should exist in the manager map")
require.False(t, found, "forwarding rule should exist in the manager map")
})
}

View File

@@ -12,6 +12,7 @@ import (
)
import "github.com/google/nftables"
//
const (
nftablesTable = "netbird-rt"
nftablesRoutingForwardingChain = "netbird-rt-fwd"
@@ -247,77 +248,53 @@ func (n *nftablesManager) InsertRoutingRules(pair routerPair) error {
n.mux.Lock()
defer n.mux.Unlock()
err := n.refreshRulesMap()
if err != nil {
return err
}
err = n.insertRoutingRule(forwardingFormat, nftablesRoutingForwardingChain, pair, false)
if err != nil {
return err
}
err = n.insertRoutingRule(inForwardingFormat, nftablesRoutingForwardingChain, getInPair(pair), false)
if err != nil {
return err
}
if pair.masquerade {
err = n.insertRoutingRule(natFormat, nftablesRoutingNatChain, pair, true)
if err != nil {
return err
}
err = n.insertRoutingRule(inNatFormat, nftablesRoutingNatChain, getInPair(pair), true)
if err != nil {
return err
}
}
err = n.conn.Flush()
if err != nil {
return fmt.Errorf("nftables: unable to insert rules for %s: %v", pair.destination, err)
}
return nil
}
// insertRoutingRule inserts a nftable rule to the conn client flush queue
func (n *nftablesManager) insertRoutingRule(format, chain string, pair routerPair, isNat bool) error {
prefix := netip.MustParsePrefix(pair.source)
sourceExp := generateCIDRMatcherExpressions("source", pair.source)
destExp := generateCIDRMatcherExpressions("destination", pair.destination)
var expression []expr.Any
if isNat {
expression = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...)
fwdKey := genKey(forwardingFormat, pair.ID)
if prefix.Addr().Unmap().Is4() {
n.rules[fwdKey] = n.conn.InsertRule(&nftables.Rule{
Table: n.tableIPv4,
Chain: n.chains[ipv4][nftablesRoutingForwardingChain],
Exprs: forwardExp,
UserData: []byte(fwdKey),
})
} else {
expression = append(sourceExp, append(destExp, exprCounterAccept...)...)
n.rules[fwdKey] = n.conn.InsertRule(&nftables.Rule{
Table: n.tableIPv6,
Chain: n.chains[ipv6][nftablesRoutingForwardingChain],
Exprs: forwardExp,
UserData: []byte(fwdKey),
})
}
ruleKey := genKey(format, pair.ID)
if pair.masquerade {
natExp := append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
natKey := genKey(natFormat, pair.ID)
_, exists := n.rules[ruleKey]
if exists {
err := n.removeRoutingRule(format, pair)
if err != nil {
return err
if prefix.Addr().Unmap().Is4() {
n.rules[natKey] = n.conn.InsertRule(&nftables.Rule{
Table: n.tableIPv4,
Chain: n.chains[ipv4][nftablesRoutingNatChain],
Exprs: natExp,
UserData: []byte(natKey),
})
} else {
n.rules[natKey] = n.conn.InsertRule(&nftables.Rule{
Table: n.tableIPv6,
Chain: n.chains[ipv6][nftablesRoutingNatChain],
Exprs: natExp,
UserData: []byte(natKey),
})
}
}
if prefix.Addr().Unmap().Is4() {
n.rules[ruleKey] = n.conn.InsertRule(&nftables.Rule{
Table: n.tableIPv4,
Chain: n.chains[ipv4][chain],
Exprs: expression,
UserData: []byte(ruleKey),
})
} else {
n.rules[ruleKey] = n.conn.InsertRule(&nftables.Rule{
Table: n.tableIPv6,
Chain: n.chains[ipv6][chain],
Exprs: expression,
UserData: []byte(ruleKey),
})
err := n.conn.Flush()
if err != nil {
return fmt.Errorf("nftables: unable to insert rules for %s: %v", pair.destination, err)
}
return nil
}
@@ -332,26 +309,26 @@ func (n *nftablesManager) RemoveRoutingRules(pair routerPair) error {
return err
}
err = n.removeRoutingRule(forwardingFormat, pair)
if err != nil {
return err
fwdKey := genKey(forwardingFormat, pair.ID)
natKey := genKey(natFormat, pair.ID)
fwdRule, found := n.rules[fwdKey]
if found {
err = n.conn.DelRule(fwdRule)
if err != nil {
return fmt.Errorf("nftables: unable to remove forwarding rule for %s: %v", pair.destination, err)
}
log.Debugf("nftables: removing forwarding rule for %s", pair.destination)
delete(n.rules, fwdKey)
}
err = n.removeRoutingRule(inForwardingFormat, getInPair(pair))
if err != nil {
return err
natRule, found := n.rules[natKey]
if found {
err = n.conn.DelRule(natRule)
if err != nil {
return fmt.Errorf("nftables: unable to remove nat rule for %s: %v", pair.destination, err)
}
log.Debugf("nftables: removing nat rule for %s", pair.destination)
delete(n.rules, natKey)
}
err = n.removeRoutingRule(natFormat, pair)
if err != nil {
return err
}
err = n.removeRoutingRule(inNatFormat, getInPair(pair))
if err != nil {
return err
}
err = n.conn.Flush()
if err != nil {
return fmt.Errorf("nftables: received error while applying rule removal for %s: %v", pair.destination, err)
@@ -360,29 +337,6 @@ func (n *nftablesManager) RemoveRoutingRules(pair routerPair) error {
return nil
}
// removeRoutingRule add a nftable rule to the removal queue and delete from rules map
func (n *nftablesManager) removeRoutingRule(format string, pair routerPair) error {
ruleKey := genKey(format, pair.ID)
rule, found := n.rules[ruleKey]
if found {
ruleType := "forwarding"
if rule.Chain.Type == nftables.ChainTypeNAT {
ruleType = "nat"
}
err := n.conn.DelRule(rule)
if err != nil {
return fmt.Errorf("nftables: unable to remove %s rule for %s: %v", ruleType, pair.destination, err)
}
log.Debugf("nftables: removing %s rule for %s", ruleType, pair.destination)
delete(n.rules, ruleKey)
}
return nil
}
// getPayloadDirectives get expression directives based on ip version and direction
func getPayloadDirectives(direction string, isIPv4 bool, isIPv6 bool) (uint32, uint32, []byte) {
switch {

View File

@@ -189,45 +189,6 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) {
}
require.Equal(t, 1, found, "should find at least 1 rule to test")
}
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
testingExpression = append(sourceExp, destExp...)
inFwdRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
found = 0
for _, registeredChains := range manager.chains {
for _, chain := range registeredChains {
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
for _, rule := range rules {
if len(rule.UserData) > 0 && string(rule.UserData) == inFwdRuleKey {
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "income forwarding rule elements should match")
found = 1
}
}
}
}
require.Equal(t, 1, found, "should find at least 1 rule to test")
if testCase.inputPair.masquerade {
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
found := 0
for _, registeredChains := range manager.chains {
for _, chain := range registeredChains {
rules, err := nftablesTestingClient.GetRules(chain.Table, chain)
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
for _, rule := range rules {
if len(rule.UserData) > 0 && string(rule.UserData) == inNatRuleKey {
require.ElementsMatchf(t, rule.Exprs[:len(testingExpression)], testingExpression, "income nat rule elements should match")
found = 1
}
}
}
}
require.Equal(t, 1, found, "should find at least 1 rule to test")
}
})
}
}
@@ -280,28 +241,6 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
UserData: []byte(natRuleKey),
})
sourceExp = generateCIDRMatcherExpressions("source", getInPair(testCase.inputPair).source)
destExp = generateCIDRMatcherExpressions("destination", getInPair(testCase.inputPair).destination)
forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...)
inForwardRuleKey := genKey(inForwardingFormat, testCase.inputPair.ID)
insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{
Table: table,
Chain: manager.chains[testCase.ipVersion][nftablesRoutingForwardingChain],
Exprs: forwardExp,
UserData: []byte(inForwardRuleKey),
})
natExp = append(sourceExp, append(destExp, &expr.Counter{}, &expr.Masq{})...)
inNatRuleKey := genKey(inNatFormat, testCase.inputPair.ID)
insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{
Table: table,
Chain: manager.chains[testCase.ipVersion][nftablesRoutingNatChain],
Exprs: natExp,
UserData: []byte(inNatRuleKey),
})
err = nftablesTestingClient.Flush()
require.NoError(t, err, "shouldn't return error")
@@ -320,10 +259,8 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) {
require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name)
for _, rule := range rules {
if len(rule.UserData) > 0 {
require.NotEqual(t, insertedForwarding.UserData, rule.UserData, "forwarding rule should not exist")
require.NotEqual(t, insertedNat.UserData, rule.UserData, "nat rule should not exist")
require.NotEqual(t, insertedInForwarding.UserData, rule.UserData, "income forwarding rule should not exist")
require.NotEqual(t, insertedInNat.UserData, rule.UserData, "income nat rule should not exist")
require.NotEqual(t, insertedForwarding.UserData, rule.UserData, "forwarding rule should exist")
require.NotEqual(t, insertedNat.UserData, rule.UserData, "nat rule should exist")
}
}
}

View File

@@ -109,9 +109,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
return err
}
ctx, cancelStream := context.WithCancel(c.ctx)
defer cancelStream()
stream, err := c.connectToStream(ctx, *serverPubKey)
stream, err := c.connectToStream(*serverPubKey)
if err != nil {
log.Debugf("failed to open Management Service stream: %s", err)
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.PermissionDenied {
@@ -147,7 +145,7 @@ func (c *GrpcClient) Sync(msgHandler func(msg *proto.SyncResponse) error) error
return nil
}
func (c *GrpcClient) connectToStream(ctx context.Context, serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
func (c *GrpcClient) connectToStream(serverPubKey wgtypes.Key) (proto.ManagementService_SyncClient, error) {
req := &proto.SyncRequest{}
myPrivateKey := c.key
@@ -158,12 +156,9 @@ func (c *GrpcClient) connectToStream(ctx context.Context, serverPubKey wgtypes.K
log.Errorf("failed encrypting message: %s", err)
return nil, err
}
syncReq := &proto.EncryptedMessage{WgPubKey: myPublicKey.String(), Body: encryptedReq}
sync, err := c.realClient.Sync(ctx, syncReq)
if err != nil {
return nil, err
}
return sync, nil
return c.realClient.Sync(c.ctx, syncReq)
}
func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, serverPubKey wgtypes.Key, msgHandler func(msg *proto.SyncResponse) error) error {

View File

@@ -15,7 +15,6 @@ import (
"google.golang.org/grpc/status"
"math/rand"
"reflect"
"regexp"
"strings"
"sync"
"time"
@@ -48,6 +47,7 @@ type AccountManager interface {
IsUserAdmin(claims jwtclaims.AuthorizationClaims) (bool, error)
AccountExists(accountId string) (*bool, error)
GetPeer(peerKey string) (*Peer, error)
GetPeers(accountID string) ([]*PeerInfo, error)
MarkPeerConnected(peerKey string, connected bool) error
RenamePeer(accountId string, peerKey string, newName string) (*Peer, error)
DeletePeer(accountId string, peerKey string) (*Peer, error)
@@ -58,7 +58,7 @@ type AccountManager interface {
AddPeer(setupKey string, userId string, peer *Peer) (*Peer, error)
UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error
UpdatePeerSSHKey(peerKey string, sshKey string) error
GetUsersFromAccount(accountId string) ([]*UserInfo, error)
GetUsers(accountId string) ([]*UserInfo, error)
GetGroup(accountId, groupID string) (*Group, error)
SaveGroup(accountId string, group *Group) error
UpdateGroup(accountID string, groupID string, operations []GroupUpdateOperation) (*Group, error)
@@ -481,7 +481,7 @@ func (am *DefaultAccountManager) GetAccountWithAuthorizationClaims(
) (*Account, error) {
// if Account ID is part of the claims
// it means that we've already classified the domain and user has an account
if claims.DomainCategory != PrivateCategory || !isDomainValid(claims.Domain) {
if claims.DomainCategory != PrivateCategory {
return am.GetAccountByUserOrAccountId(claims.UserId, claims.AccountId, claims.Domain)
} else if claims.AccountId != "" {
accountFromID, err := am.GetAccountById(claims.AccountId)
@@ -521,11 +521,6 @@ func (am *DefaultAccountManager) GetAccountWithAuthorizationClaims(
}
}
func isDomainValid(domain string) bool {
re := regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`)
return re.Match([]byte(domain))
}
// AccountExists checks whether account exists (returns true) or not (returns false)
func (am *DefaultAccountManager) AccountExists(accountId string) (*bool, error) {
am.mux.Lock()

View File

@@ -140,7 +140,6 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
expectedMSG string
expectedUserRole UserRole
expectedDomainCategory string
expectedDomain string
expectedPrimaryDomainStatus bool
expectedCreatedBy string
expectedUsers []string
@@ -169,7 +168,6 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin,
expectedDomainCategory: "",
expectedDomain: publicDomain,
expectedPrimaryDomainStatus: false,
expectedCreatedBy: "pub-domain-user",
expectedUsers: []string{"pub-domain-user"},
@@ -190,7 +188,6 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
testingFunc: require.NotEqual,
expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin,
expectedDomain: unknownDomain,
expectedDomainCategory: "",
expectedPrimaryDomainStatus: false,
expectedCreatedBy: "unknown-domain-user",
@@ -208,7 +205,6 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
testingFunc: require.NotEqual,
expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin,
expectedDomain: privateDomain,
expectedDomainCategory: PrivateCategory,
expectedPrimaryDomainStatus: true,
expectedCreatedBy: "pvt-domain-user",
@@ -231,7 +227,6 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
testingFunc: require.Equal,
expectedMSG: "account IDs should match",
expectedUserRole: UserRoleUser,
expectedDomain: privateDomain,
expectedDomainCategory: PrivateCategory,
expectedPrimaryDomainStatus: true,
expectedCreatedBy: defaultInitAccount.UserId,
@@ -249,7 +244,6 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
testingFunc: require.Equal,
expectedMSG: "account IDs should match",
expectedUserRole: UserRoleAdmin,
expectedDomain: defaultInitAccount.Domain,
expectedDomainCategory: PrivateCategory,
expectedPrimaryDomainStatus: true,
expectedCreatedBy: defaultInitAccount.UserId,
@@ -268,32 +262,12 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
testingFunc: require.Equal,
expectedMSG: "account IDs should match",
expectedUserRole: UserRoleAdmin,
expectedDomain: defaultInitAccount.Domain,
expectedDomainCategory: PrivateCategory,
expectedPrimaryDomainStatus: true,
expectedCreatedBy: defaultInitAccount.UserId,
expectedUsers: []string{defaultInitAccount.UserId},
}
testCase7 := test{
name: "User With Private Category And Empty Domain",
inputClaims: jwtclaims.AuthorizationClaims{
Domain: "",
UserId: "pvt-domain-user",
DomainCategory: PrivateCategory,
},
inputInitUserParams: defaultInitAccount,
testingFunc: require.NotEqual,
expectedMSG: "account IDs shouldn't match",
expectedUserRole: UserRoleAdmin,
expectedDomain: "",
expectedDomainCategory: "",
expectedPrimaryDomainStatus: false,
expectedCreatedBy: "pvt-domain-user",
expectedUsers: []string{"pvt-domain-user"},
}
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5, testCase6, testCase7} {
for _, testCase := range []test{testCase1, testCase2, testCase3, testCase4, testCase5, testCase6} {
t.Run(testCase.name, func(t *testing.T) {
manager, err := createManager(t)
require.NoError(t, err, "unable to create account manager")
@@ -320,7 +294,6 @@ func TestDefaultAccountManager_GetAccountWithAuthorizationClaims(t *testing.T) {
require.EqualValues(t, testCase.expectedUserRole, account.Users[testCase.inputClaims.UserId].Role, "expected user role should match")
require.EqualValues(t, testCase.expectedDomainCategory, account.DomainCategory, "expected account domain category should match")
require.EqualValues(t, testCase.expectedPrimaryDomainStatus, account.IsDomainPrimaryAccount, "expected account primary status should match")
require.EqualValues(t, testCase.expectedDomain, account.Domain, "expected account domain should match")
})
}
}
@@ -874,7 +847,7 @@ func TestGetUsersFromAccount(t *testing.T) {
account.Users[user.Id] = user
}
userInfos, err := manager.GetUsersFromAccount(accountId)
userInfos, err := manager.GetUsers(accountId)
if err != nil {
t.Fatal(err)
}

View File

@@ -44,6 +44,18 @@ components:
- name
- role
- auto_groups
UserMinimum:
type: object
properties:
id:
description: User ID
type: string
email:
description: User's email address
type: string
name:
description: User's name from idp provider
type: string
UserRequest:
type: object
properties:
@@ -96,17 +108,24 @@ components:
type: array
items:
$ref: '#/components/schemas/GroupMinimum'
activated_by:
description: Provides information of who activated the Peer. User or Setup Key
type: object
properties:
type:
type: string
value:
type: string
required:
- type
- value
ssh_enabled:
description: Indicates whether SSH server is enabled on this peer
type: boolean
user_id:
description: User ID of the user that enrolled this peer
type: string
hostname:
description: Hostname of the machine
type: string
ui_version:
description: Peer's desktop UI version
user:
$ref: '#/components/schemas/UserMinimum'
host_name:
description: Peer's hostname
type: string
required:
- ip
@@ -115,8 +134,8 @@ components:
- os
- version
- groups
- activated_by
- ssh_enabled
- hostname
SetupKey:
type: object
properties:

View File

@@ -125,14 +125,20 @@ type PatchMinimumOp string
// Peer defines model for Peer.
type Peer struct {
// Provides information of who activated the Peer. User or Setup Key
ActivatedBy struct {
Type string `json:"type"`
Value string `json:"value"`
} `json:"activated_by"`
// Peer to Management connection status
Connected bool `json:"connected"`
// Groups that the peer belongs to
Groups []GroupMinimum `json:"groups"`
// Hostname of the machine
Hostname string `json:"hostname"`
// Peer's hostname
HostName *string `json:"host_name,omitempty"`
// Peer ID
Id string `json:"id"`
@@ -150,13 +156,8 @@ type Peer struct {
Os string `json:"os"`
// Indicates whether SSH server is enabled on this peer
SshEnabled bool `json:"ssh_enabled"`
// Peer's desktop UI version
UiVersion *string `json:"ui_version,omitempty"`
// User ID of the user that enrolled this peer
UserId *string `json:"user_id,omitempty"`
SshEnabled bool `json:"ssh_enabled"`
User *UserMinimum `json:"user,omitempty"`
// Peer's daemon or cli version
Version string `json:"version"`
@@ -375,6 +376,18 @@ type User struct {
Role string `json:"role"`
}
// UserMinimum defines model for UserMinimum.
type UserMinimum struct {
// User's email address
Email *string `json:"email,omitempty"`
// User ID
Id *string `json:"id,omitempty"`
// User's name from idp provider
Name *string `json:"name,omitempty"`
}
// UserRequest defines model for UserRequest.
type UserRequest struct {
// Groups to auto-assign to peers registered by this user

View File

@@ -42,7 +42,7 @@ func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.Re
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
writeJSONObject(w, toPeerResponse(peer, account))
writeJSONObject(w, toPeerResponse(&server.PeerInfo{Peer: peer}, account))
}
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
@@ -83,7 +83,7 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
h.updatePeer(account, peer, w, r)
return
case http.MethodGet:
writeJSONObject(w, toPeerResponse(peer, account))
writeJSONObject(w, toPeerResponse(&server.PeerInfo{Peer: peer}, account))
return
default:
@@ -93,27 +93,34 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
}
func (h *Peers) GetPeers(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
respBody := []*api.Peer{}
for _, peer := range account.Peers {
respBody = append(respBody, toPeerResponse(peer, account))
}
writeJSONObject(w, respBody)
return
default:
if r.Method != http.MethodGet {
http.Error(w, "", http.StatusNotFound)
}
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
peers, err := h.accountManager.GetPeers(account.Id)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
respBody := []*api.Peer{}
for _, peer := range peers {
respBody = append(respBody, toPeerResponse(peer, account))
}
writeJSONObject(w, respBody)
return
}
func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
func toPeerResponse(peer *server.PeerInfo, account *server.Account) *api.Peer {
var groupsInfo []api.GroupMinimum
groupsChecked := make(map[string]struct{})
for _, group := range account.Groups {
@@ -123,7 +130,7 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
}
groupsChecked[group.ID] = struct{}{}
for _, pk := range group.Peers {
if pk == peer.Key {
if pk == peer.Peer.Key {
info := api.GroupMinimum{
Id: group.ID,
Name: group.Name,
@@ -134,18 +141,26 @@ func toPeerResponse(peer *server.Peer, account *server.Account) *api.Peer {
}
}
}
return &api.Peer{
Id: peer.IP.String(),
Name: peer.Name,
Ip: peer.IP.String(),
Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen,
Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core),
Version: peer.Meta.WtVersion,
resp := &api.Peer{
Id: peer.Peer.IP.String(),
Name: peer.Peer.Name,
Ip: peer.Peer.IP.String(),
Connected: peer.Peer.Status.Connected,
LastSeen: peer.Peer.Status.LastSeen,
Os: fmt.Sprintf("%s %s", peer.Peer.Meta.OS, peer.Peer.Meta.Core),
Version: peer.Peer.Meta.WtVersion,
Groups: groupsInfo,
SshEnabled: peer.SSHEnabled,
Hostname: peer.Meta.Hostname,
UserId: &peer.UserID,
UiVersion: &peer.Meta.UIVersion,
SshEnabled: peer.Peer.SSHEnabled,
HostName: &peer.Peer.Meta.Hostname,
}
if peer.UserInfo != nil {
resp.User = &api.UserMinimum{
Email: &peer.UserInfo.Email,
Id: &peer.UserInfo.ID,
Name: &peer.UserInfo.Name,
}
}
return resp
}

View File

@@ -92,9 +92,11 @@ func (h *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
data, err := h.accountManager.GetUsersFromAccount(account.Id)
data, err := h.accountManager.GetUsers(account.Id)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)

View File

@@ -60,7 +60,7 @@ func (am *MockAccountManager) GetUsersFromAccount(accountID string) ([]*server.U
if am.GetUsersFromAccountFunc != nil {
return am.GetUsersFromAccountFunc(accountID)
}
return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount is not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetUsers is not implemented")
}
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface

View File

@@ -31,6 +31,12 @@ type PeerStatus struct {
Connected bool
}
// PeerInfo is a composition of Peer and additional UserInfo
type PeerInfo struct {
Peer *Peer
UserInfo *UserInfo
}
// Peer represents a machine connected to the network.
// The Peer is a Wireguard peer identified by a public key
type Peer struct {
@@ -68,6 +74,44 @@ func (p *Peer) Copy() *Peer {
}
}
// GetPeers returns a list of Peers belonging to the specified account
func (am *DefaultAccountManager) GetPeers(accountID string) ([]*PeerInfo, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
users, err := am.getUsersInfos(account)
if err != nil {
return nil, err
}
var userMap = make(map[string]*UserInfo)
for _, user := range users {
userMap[user.ID] = user
}
var peerInfos []*PeerInfo
for _, peer := range account.Peers {
if peer.UserID == "" {
peerInfos = append(peerInfos, &PeerInfo{
Peer: peer,
UserInfo: nil,
})
} else {
peerInfos = append(peerInfos, &PeerInfo{
Peer: peer.Copy(),
UserInfo: userMap[peer.UserID],
})
}
}
return peerInfos, nil
}
// GetPeer returns a peer from a Store
func (am *DefaultAccountManager) GetPeer(peerKey string) (*Peer, error) {
am.mux.Lock()

View File

@@ -207,16 +207,11 @@ func (am *DefaultAccountManager) IsUserAdmin(claims jwtclaims.AuthorizationClaim
return user.Role == UserRoleAdmin, nil
}
// GetUsersFromAccount performs a batched request for users from IDP by account ID
func (am *DefaultAccountManager) GetUsersFromAccount(accountID string) ([]*UserInfo, error) {
account, err := am.GetAccountById(accountID)
if err != nil {
return nil, err
}
func (am *DefaultAccountManager) getUsersInfos(account *Account) ([]*UserInfo, error) {
var err error
queriedUsers := make([]*idp.UserData, 0)
if !isNil(am.idpManager) {
queriedUsers, err = am.lookupCache(account.Users, accountID)
queriedUsers, err = am.lookupCache(account.Users, account.Id)
if err != nil {
return nil, err
}
@@ -249,3 +244,12 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID string) ([]*UserI
return userInfos, nil
}
// GetUsers performs a batched request for users from IDP by account ID
func (am *DefaultAccountManager) GetUsers(accountID string) ([]*UserInfo, error) {
account, err := am.GetAccountById(accountID)
if err != nil {
return nil, err
}
return am.getUsersInfos(account)
}

View File

@@ -87,7 +87,7 @@ func NewClient(ctx context.Context, addr string, key wgtypes.Key, tlsEnabled boo
}, nil
}
// defaultBackoff is a basic backoff mechanism for general issues
//defaultBackoff is a basic backoff mechanism for general issues
func defaultBackoff(ctx context.Context) backoff.BackOff {
return backoff.WithContext(&backoff.ExponentialBackOff{
InitialInterval: 800 * time.Millisecond,
@@ -121,11 +121,9 @@ func (c *GrpcClient) Receive(msgHandler func(msg *proto.Message) error) error {
return fmt.Errorf("connection to signal is not ready and in %s state", connState)
}
// connect to Signal stream identifying ourselves with a public WireGuard key
// connect to Signal stream identifying ourselves with a public Wireguard key
// todo once the key rotation logic has been implemented, consider changing to some other identifier (received from management)
ctx, cancelStream := context.WithCancel(c.ctx)
defer cancelStream()
stream, err := c.connect(ctx, c.key.PublicKey().String())
stream, err := c.connect(c.key.PublicKey().String())
if err != nil {
log.Warnf("disconnected from the Signal Exchange due to an error: %v", err)
return err
@@ -182,13 +180,15 @@ func (c *GrpcClient) getStreamStatusChan() <-chan struct{} {
return c.connectedCh
}
func (c *GrpcClient) connect(ctx context.Context, key string) (proto.SignalExchange_ConnectStreamClient, error) {
func (c *GrpcClient) connect(key string) (proto.SignalExchange_ConnectStreamClient, error) {
c.stream = nil
// add key fingerprint to the request header to be identified on the server side
md := metadata.New(map[string]string{proto.HeaderId: key})
metaCtx := metadata.NewOutgoingContext(ctx, md)
stream, err := c.realClient.ConnectStream(metaCtx, grpc.WaitForReady(true))
ctx := metadata.NewOutgoingContext(c.ctx, md)
stream, err := c.realClient.ConnectStream(ctx, grpc.WaitForReady(true))
c.stream = stream
if err != nil {
return nil, err