mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-29 13:46:41 +00:00
Compare commits
10 Commits
feature/ke
...
v0.21.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a366d9e208 | ||
|
|
c000c05435 | ||
|
|
d409219b51 | ||
|
|
8b619a8224 | ||
|
|
ed075bc9b9 | ||
|
|
8eb098d6fd | ||
|
|
68a8687c80 | ||
|
|
f7d97b02fd | ||
|
|
2691e729cd | ||
|
|
b524a9d49d |
@@ -53,6 +53,7 @@ jobs:
|
|||||||
CI_NETBIRD_MGMT_IDP: "none"
|
CI_NETBIRD_MGMT_IDP: "none"
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
||||||
|
CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified"
|
||||||
|
|
||||||
- name: check values
|
- name: check values
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ var sshCmd = &cobra.Command{
|
|||||||
go func() {
|
go func() {
|
||||||
// blocking
|
// blocking
|
||||||
if err := runSSH(sshctx, host, []byte(config.SSHKey), cmd); err != nil {
|
if err := runSSH(sshctx, host, []byte(config.SSHKey), cmd); err != nil {
|
||||||
log.Print(err)
|
log.Debug(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
@@ -92,12 +93,10 @@ func runSSH(ctx context.Context, addr string, pemKey []byte, cmd *cobra.Command)
|
|||||||
c, err := nbssh.DialWithKey(fmt.Sprintf("%s:%d", addr, port), user, pemKey)
|
c, err := nbssh.DialWithKey(fmt.Sprintf("%s:%d", addr, port), user, pemKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.Printf("Error: %v\n", err)
|
cmd.Printf("Error: %v\n", err)
|
||||||
cmd.Printf("Couldn't connect. " +
|
cmd.Printf("Couldn't connect. Please check the connection status or if the ssh server is enabled on the other peer" +
|
||||||
"You might be disconnected from the NetBird network, or the NetBird agent isn't running.\n" +
|
"You can verify the connection by running:\n\n" +
|
||||||
"Run the status command: \n\n" +
|
" netbird status\n\n")
|
||||||
" netbird status\n\n" +
|
return err
|
||||||
"It might also be that the SSH server is disabled on the agent you are trying to connect to.\n")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package ssh
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/creack/pty"
|
|
||||||
"github.com/gliderlabs/ssh"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,11 +10,22 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/creack/pty"
|
||||||
|
"github.com/gliderlabs/ssh"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultSSHPort is the default SSH port of the NetBird's embedded SSH server
|
// DefaultSSHPort is the default SSH port of the NetBird's embedded SSH server
|
||||||
const DefaultSSHPort = 44338
|
const DefaultSSHPort = 44338
|
||||||
|
|
||||||
|
// TerminalTimeout is the timeout for terminal session to be ready
|
||||||
|
const TerminalTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
// TerminalBackoffDelay is the delay between terminal session readiness checks
|
||||||
|
const TerminalBackoffDelay = 500 * time.Millisecond
|
||||||
|
|
||||||
// DefaultSSHServer is a function that creates DefaultServer
|
// DefaultSSHServer is a function that creates DefaultServer
|
||||||
func DefaultSSHServer(hostKeyPEM []byte, addr string) (Server, error) {
|
func DefaultSSHServer(hostKeyPEM []byte, addr string) (Server, error) {
|
||||||
return newDefaultServer(hostKeyPEM, addr)
|
return newDefaultServer(hostKeyPEM, addr)
|
||||||
@@ -137,6 +145,8 @@ func (srv *DefaultServer) sessionHandler(session ssh.Session) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
log.Infof("Establishing SSH session for %s from host %s", session.User(), session.RemoteAddr().String())
|
||||||
|
|
||||||
localUser, err := userNameLookup(session.User())
|
localUser, err := userNameLookup(session.User())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, err = fmt.Fprintf(session, "remote SSH server couldn't find local user %s\n", session.User()) //nolint
|
_, err = fmt.Fprintf(session, "remote SSH server couldn't find local user %s\n", session.User()) //nolint
|
||||||
@@ -172,6 +182,7 @@ func (srv *DefaultServer) sessionHandler(session ssh.Session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("Login command: %s", cmd.String())
|
||||||
file, err := pty.Start(cmd)
|
file, err := pty.Start(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed starting SSH server %v", err)
|
log.Errorf("failed starting SSH server %v", err)
|
||||||
@@ -199,6 +210,7 @@ func (srv *DefaultServer) sessionHandler(session ssh.Session) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Debugf("SSH session ended")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *DefaultServer) stdInOut(file *os.File, session ssh.Session) {
|
func (srv *DefaultServer) stdInOut(file *os.File, session ssh.Session) {
|
||||||
@@ -206,17 +218,29 @@ func (srv *DefaultServer) stdInOut(file *os.File, session ssh.Session) {
|
|||||||
// stdin
|
// stdin
|
||||||
_, err := io.Copy(file, session)
|
_, err := io.Copy(file, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = session.Exit(1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
// AWS Linux 2 machines need some time to open the terminal so we need to wait for it
|
||||||
// stdout
|
timer := time.NewTimer(TerminalTimeout)
|
||||||
_, err := io.Copy(session, file)
|
for {
|
||||||
if err != nil {
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
_, _ = session.Write([]byte("Reached timeout while opening connection\n"))
|
||||||
|
_ = session.Exit(1)
|
||||||
return
|
return
|
||||||
|
default:
|
||||||
|
// stdout
|
||||||
|
writtenBytes, err := io.Copy(session, file)
|
||||||
|
if err != nil && writtenBytes != 0 {
|
||||||
|
_ = session.Exit(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(TerminalBackoffDelay)
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts SSH server. Blocking
|
// Start starts SSH server. Blocking
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (c *tunDevice) createWithUserspace() (NetInterface, error) {
|
|||||||
c.wrapper = newDeviceWrapper(tunIface)
|
c.wrapper = newDeviceWrapper(tunIface)
|
||||||
|
|
||||||
// We need to create a wireguard-go device and listen to configuration requests
|
// We need to create a wireguard-go device and listen to configuration requests
|
||||||
tunDev := device.NewDevice(tunIface, c.iceBind, device.NewLogger(device.LogLevelSilent, "[netbird] "))
|
tunDev := device.NewDevice(c.wrapper, c.iceBind, device.NewLogger(device.LogLevelSilent, "[netbird] "))
|
||||||
err = tunDev.Up()
|
err = tunDev.Up()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = tunIface.Close()
|
_ = tunIface.Close()
|
||||||
|
|||||||
@@ -97,16 +97,9 @@ curl "${NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT}" -q -o openid-configuration.js
|
|||||||
|
|
||||||
export NETBIRD_AUTH_AUTHORITY=$(jq -r '.issuer' openid-configuration.json)
|
export NETBIRD_AUTH_AUTHORITY=$(jq -r '.issuer' openid-configuration.json)
|
||||||
export NETBIRD_AUTH_JWT_CERTS=$(jq -r '.jwks_uri' openid-configuration.json)
|
export NETBIRD_AUTH_JWT_CERTS=$(jq -r '.jwks_uri' openid-configuration.json)
|
||||||
export NETBIRD_AUTH_SUPPORTED_SCOPES=$(jq -r '.scopes_supported | join(" ")' openid-configuration.json)
|
|
||||||
export NETBIRD_AUTH_TOKEN_ENDPOINT=$(jq -r '.token_endpoint' openid-configuration.json)
|
export NETBIRD_AUTH_TOKEN_ENDPOINT=$(jq -r '.token_endpoint' openid-configuration.json)
|
||||||
export NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT=$(jq -r '.device_authorization_endpoint' openid-configuration.json)
|
export NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT=$(jq -r '.device_authorization_endpoint' openid-configuration.json)
|
||||||
|
|
||||||
if [ "$NETBIRD_USE_AUTH0" == "true" ]; then
|
|
||||||
export NETBIRD_AUTH_SUPPORTED_SCOPES="openid profile email offline_access api email_verified"
|
|
||||||
else
|
|
||||||
export NETBIRD_AUTH_SUPPORTED_SCOPES="openid profile email offline_access api"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -z "${NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID}" ]]; then
|
if [[ ! -z "${NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID}" ]]; then
|
||||||
# user enabled Device Authorization Grant feature
|
# user enabled Device Authorization Grant feature
|
||||||
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="hosted"
|
export NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="hosted"
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
|||||||
NETBIRD_AUTH_AUDIENCE=""
|
NETBIRD_AUTH_AUDIENCE=""
|
||||||
# e.g. netbird-client
|
# e.g. netbird-client
|
||||||
NETBIRD_AUTH_CLIENT_ID=""
|
NETBIRD_AUTH_CLIENT_ID=""
|
||||||
NETBIRD_AUTH_CLIENT_SECRET=""
|
# indicates the scopes that will be requested to the IDP
|
||||||
|
NETBIRD_AUTH_SUPPORTED_SCOPES=""
|
||||||
|
# NETBIRD_AUTH_CLIENT_SECRET is required only by Google workspace.
|
||||||
|
# NETBIRD_AUTH_CLIENT_SECRET=""
|
||||||
# if you want to use a custom claim for the user ID instead of 'sub', set it here
|
# if you want to use a custom claim for the user ID instead of 'sub', set it here
|
||||||
# NETBIRD_AUTH_USER_ID_CLAIM=""
|
# NETBIRD_AUTH_USER_ID_CLAIM=""
|
||||||
# indicates whether to use Auth0 or not: true or false
|
# indicates whether to use Auth0 or not: true or false
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ NETBIRD_DOMAIN=$CI_NETBIRD_DOMAIN
|
|||||||
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://example.eu.auth0.com/.well-known/openid-configuration"
|
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://example.eu.auth0.com/.well-known/openid-configuration"
|
||||||
# e.g. netbird-client
|
# e.g. netbird-client
|
||||||
NETBIRD_AUTH_CLIENT_ID=$CI_NETBIRD_AUTH_CLIENT_ID
|
NETBIRD_AUTH_CLIENT_ID=$CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
|
NETBIRD_AUTH_SUPPORTED_SCOPES=$CI_NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||||
NETBIRD_AUTH_CLIENT_SECRET=$CI_NETBIRD_AUTH_CLIENT_SECRET
|
NETBIRD_AUTH_CLIENT_SECRET=$CI_NETBIRD_AUTH_CLIENT_SECRET
|
||||||
# indicates whether to use Auth0 or not: true or false
|
# indicates whether to use Auth0 or not: true or false
|
||||||
NETBIRD_USE_AUTH0=$CI_NETBIRD_USE_AUTH0
|
NETBIRD_USE_AUTH0=$CI_NETBIRD_USE_AUTH0
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const (
|
|||||||
PublicCategory = "public"
|
PublicCategory = "public"
|
||||||
PrivateCategory = "private"
|
PrivateCategory = "private"
|
||||||
UnknownCategory = "unknown"
|
UnknownCategory = "unknown"
|
||||||
|
GroupIssuedAPI = "api"
|
||||||
|
GroupIssuedJWT = "jwt"
|
||||||
CacheExpirationMax = 7 * 24 * 3600 * time.Second // 7 days
|
CacheExpirationMax = 7 * 24 * 3600 * time.Second // 7 days
|
||||||
CacheExpirationMin = 3 * 24 * 3600 * time.Second // 3 days
|
CacheExpirationMin = 3 * 24 * 3600 * time.Second // 3 days
|
||||||
DefaultPeerLoginExpiration = 24 * time.Hour
|
DefaultPeerLoginExpiration = 24 * time.Hour
|
||||||
@@ -139,6 +141,13 @@ type Settings struct {
|
|||||||
// PeerLoginExpiration is a setting that indicates when peer login expires.
|
// PeerLoginExpiration is a setting that indicates when peer login expires.
|
||||||
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
|
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
|
||||||
PeerLoginExpiration time.Duration
|
PeerLoginExpiration time.Duration
|
||||||
|
|
||||||
|
// JWTGroupsEnabled allows extract groups from JWT claim, which name defined in the JWTGroupsClaimName
|
||||||
|
// and add it to account groups.
|
||||||
|
JWTGroupsEnabled bool
|
||||||
|
|
||||||
|
// JWTGroupsClaimName from which we extract groups name to add it to account groups
|
||||||
|
JWTGroupsClaimName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy copies the Settings struct
|
// Copy copies the Settings struct
|
||||||
@@ -146,6 +155,8 @@ func (s *Settings) Copy() *Settings {
|
|||||||
return &Settings{
|
return &Settings{
|
||||||
PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled,
|
PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled,
|
||||||
PeerLoginExpiration: s.PeerLoginExpiration,
|
PeerLoginExpiration: s.PeerLoginExpiration,
|
||||||
|
JWTGroupsEnabled: s.JWTGroupsEnabled,
|
||||||
|
JWTGroupsClaimName: s.JWTGroupsClaimName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,6 +623,28 @@ func (a *Account) GetPeer(peerID string) *Peer {
|
|||||||
return a.Peers[peerID]
|
return a.Peers[peerID]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddJWTGroups to existed groups if they does not exists
|
||||||
|
func (a *Account) AddJWTGroups(groups []string) (int, error) {
|
||||||
|
existedGroups := make(map[string]*Group)
|
||||||
|
for _, g := range a.Groups {
|
||||||
|
existedGroups[g.Name] = g
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int
|
||||||
|
for _, name := range groups {
|
||||||
|
if _, ok := existedGroups[name]; !ok {
|
||||||
|
id := xid.New().String()
|
||||||
|
a.Groups[id] = &Group{
|
||||||
|
ID: id,
|
||||||
|
Name: name,
|
||||||
|
Issued: GroupIssuedJWT,
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
// BuildManager creates a new DefaultAccountManager with a provided Store
|
// BuildManager creates a new DefaultAccountManager with a provided Store
|
||||||
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
||||||
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store,
|
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store,
|
||||||
@@ -1241,6 +1274,38 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if account.Settings.JWTGroupsEnabled {
|
||||||
|
if account.Settings.JWTGroupsClaimName == "" {
|
||||||
|
log.Errorf("JWT groups are enabled but no claim name is set")
|
||||||
|
return account, user, nil
|
||||||
|
}
|
||||||
|
if claim, ok := claims.Raw[account.Settings.JWTGroupsClaimName]; ok {
|
||||||
|
if slice, ok := claim.([]interface{}); ok {
|
||||||
|
var groups []string
|
||||||
|
for _, item := range slice {
|
||||||
|
if g, ok := item.(string); ok {
|
||||||
|
groups = append(groups, g)
|
||||||
|
} else {
|
||||||
|
log.Errorf("JWT claim %q is not a string: %v", account.Settings.JWTGroupsClaimName, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err := account.AddJWTGroups(groups)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to add JWT groups: %v", err)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
|
log.Errorf("failed to save account: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debugf("JWT claim %q is not a string array", account.Settings.JWTGroupsClaimName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debugf("JWT claim %q not found", account.Settings.JWTGroupsClaimName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return account, user, nil
|
return account, user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1344,8 +1409,9 @@ func (am *DefaultAccountManager) GetDNSDomain() string {
|
|||||||
func addAllGroup(account *Account) error {
|
func addAllGroup(account *Account) error {
|
||||||
if len(account.Groups) == 0 {
|
if len(account.Groups) == 0 {
|
||||||
allGroup := &Group{
|
allGroup := &Group{
|
||||||
ID: xid.New().String(),
|
ID: xid.New().String(),
|
||||||
Name: "All",
|
Name: "All",
|
||||||
|
Issued: GroupIssuedAPI,
|
||||||
}
|
}
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
allGroup.Peers = append(allGroup.Peers, peer.ID)
|
allGroup.Peers = append(allGroup.Peers, peer.ID)
|
||||||
@@ -1373,33 +1439,28 @@ func addAllGroup(account *Account) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
|
// newAccountWithId creates a new Account with a default SetupKey (doesn't store in a Store) and provided id
|
||||||
func newAccountWithId(accountId, userId, domain string) *Account {
|
func newAccountWithId(accountID, userID, domain string) *Account {
|
||||||
log.Debugf("creating new account")
|
log.Debugf("creating new account")
|
||||||
|
|
||||||
setupKeys := make(map[string]*SetupKey)
|
|
||||||
defaultKey := GenerateDefaultSetupKey()
|
|
||||||
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration, []string{},
|
|
||||||
SetupKeyUnlimitedUsage)
|
|
||||||
setupKeys[defaultKey.Key] = defaultKey
|
|
||||||
setupKeys[oneOffKey.Key] = oneOffKey
|
|
||||||
network := NewNetwork()
|
network := NewNetwork()
|
||||||
peers := make(map[string]*Peer)
|
peers := make(map[string]*Peer)
|
||||||
users := make(map[string]*User)
|
users := make(map[string]*User)
|
||||||
routes := make(map[string]*route.Route)
|
routes := make(map[string]*route.Route)
|
||||||
|
setupKeys := map[string]*SetupKey{}
|
||||||
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
||||||
users[userId] = NewAdminUser(userId)
|
users[userID] = NewAdminUser(userID)
|
||||||
dnsSettings := &DNSSettings{
|
dnsSettings := &DNSSettings{
|
||||||
DisabledManagementGroups: make([]string, 0),
|
DisabledManagementGroups: make([]string, 0),
|
||||||
}
|
}
|
||||||
log.Debugf("created new account %s with setup key %s", accountId, defaultKey.Key)
|
log.Debugf("created new account %s", accountID)
|
||||||
|
|
||||||
acc := &Account{
|
acc := &Account{
|
||||||
Id: accountId,
|
Id: accountID,
|
||||||
SetupKeys: setupKeys,
|
SetupKeys: setupKeys,
|
||||||
Network: network,
|
Network: network,
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
Users: users,
|
Users: users,
|
||||||
CreatedBy: userId,
|
CreatedBy: userID,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
Routes: routes,
|
Routes: routes,
|
||||||
NameServerGroups: nameServersGroups,
|
NameServerGroups: nameServersGroups,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
@@ -53,7 +54,7 @@ func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy
|
|||||||
t.Errorf("expected account to have len(Peers) = %v, got %v", 0, len(account.Peers))
|
t.Errorf("expected account to have len(Peers) = %v, got %v", 0, len(account.Peers))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(account.SetupKeys) != 2 {
|
if len(account.SetupKeys) != 0 {
|
||||||
t.Errorf("expected account to have len(SetupKeys) = %v, got %v", 2, len(account.SetupKeys))
|
t.Errorf("expected account to have len(SetupKeys) = %v, got %v", 2, len(account.SetupKeys))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,6 +461,69 @@ func TestDefaultAccountManager_GetAccountFromToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
||||||
|
userId := "user-id"
|
||||||
|
domain := "test.domain"
|
||||||
|
|
||||||
|
initAccount := newAccountWithId("", userId, domain)
|
||||||
|
manager, err := createManager(t)
|
||||||
|
require.NoError(t, err, "unable to create account manager")
|
||||||
|
|
||||||
|
accountID := initAccount.Id
|
||||||
|
_, err = manager.GetAccountByUserOrAccountID(userId, accountID, domain)
|
||||||
|
require.NoError(t, err, "create init user failed")
|
||||||
|
|
||||||
|
claims := jwtclaims.AuthorizationClaims{
|
||||||
|
AccountId: accountID,
|
||||||
|
Domain: domain,
|
||||||
|
UserId: userId,
|
||||||
|
DomainCategory: "test-category",
|
||||||
|
Raw: jwt.MapClaims{"idp-groups": []interface{}{"group1", "group2"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("JWT groups disabled", func(t *testing.T) {
|
||||||
|
account, _, err := manager.GetAccountFromToken(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)
|
||||||
|
require.NoError(t, err, "save account failed")
|
||||||
|
|
||||||
|
account, _, err := manager.GetAccountFromToken(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")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("JWT groups enabled", func(t *testing.T) {
|
||||||
|
initAccount.Settings.JWTGroupsEnabled = true
|
||||||
|
initAccount.Settings.JWTGroupsClaimName = "idp-groups"
|
||||||
|
err := manager.Store.SaveAccount(initAccount)
|
||||||
|
require.NoError(t, err, "save account failed")
|
||||||
|
|
||||||
|
account, _, err := manager.GetAccountFromToken(claims)
|
||||||
|
require.NoError(t, err, "get account by token failed")
|
||||||
|
require.Len(t, account.Groups, 3, "groups should be added to the account")
|
||||||
|
|
||||||
|
groupsByNames := map[string]*Group{}
|
||||||
|
for _, g := range account.Groups {
|
||||||
|
groupsByNames[g.Name] = g
|
||||||
|
}
|
||||||
|
|
||||||
|
g1, ok := groupsByNames["group1"]
|
||||||
|
require.True(t, ok, "group1 should be added to the account")
|
||||||
|
require.Equal(t, g1.Name, "group1", "group1 name should match")
|
||||||
|
require.Equal(t, g1.Issued, GroupIssuedJWT, "group1 issued should match")
|
||||||
|
|
||||||
|
g2, ok := groupsByNames["group2"]
|
||||||
|
require.True(t, ok, "group2 should be added to the account")
|
||||||
|
require.Equal(t, g2.Name, "group2", "group2 name should match")
|
||||||
|
require.Equal(t, g2.Issued, GroupIssuedJWT, "group2 issued should match")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccountManager_GetAccountFromPAT(t *testing.T) {
|
func TestAccountManager_GetAccountFromPAT(t *testing.T) {
|
||||||
store := newStore(t)
|
store := newStore(t)
|
||||||
account := newAccountWithId("account_id", "testuser", "")
|
account := newAccountWithId("account_id", "testuser", "")
|
||||||
@@ -704,20 +768,21 @@ func TestAccountManager_AddPeer(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := createAccount(manager, "test_account", "account_creator", "netbird.cloud")
|
userID := "account_creator"
|
||||||
|
account, err := createAccount(manager, "test_account", userID, "netbird.cloud")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serial := account.Network.CurrentSerial() // should be 0
|
serial := account.Network.CurrentSerial() // should be 0
|
||||||
|
|
||||||
var setupKey *SetupKey
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID)
|
||||||
for _, key := range account.SetupKeys {
|
if err != nil {
|
||||||
setupKey = key
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if setupKey == nil {
|
if err != nil {
|
||||||
t.Errorf("expecting account to have a default setup key")
|
t.Fatal("error creating setup key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,16 +923,13 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var setupKey *SetupKey
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID)
|
||||||
for _, key := range account.SetupKeys {
|
if err != nil {
|
||||||
setupKey = key
|
return
|
||||||
if setupKey.Type == SetupKeyReusable {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if setupKey == nil {
|
if err != nil {
|
||||||
t.Errorf("expecting account to have a default setup key")
|
t.Fatal("error creating setup key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1042,9 +1104,14 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var setupKey *SetupKey
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID)
|
||||||
for _, key := range account.SetupKeys {
|
if err != nil {
|
||||||
setupKey = key
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating setup key")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := wgtypes.GenerateKey()
|
key, err := wgtypes.GenerateKey()
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/proto"
|
"github.com/netbirdio/netbird/management/proto"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultTTL = 300
|
const defaultTTL = 300
|
||||||
@@ -199,8 +201,10 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
|||||||
for _, gID := range nsGroup.Groups {
|
for _, gID := range nsGroup.Groups {
|
||||||
_, found := groupList[gID]
|
_, found := groupList[gID]
|
||||||
if found {
|
if found {
|
||||||
peerNSGroups = append(peerNSGroups, nsGroup.Copy())
|
if !peerIsNameserver(account.GetPeer(peerID), nsGroup) {
|
||||||
break
|
peerNSGroups = append(peerNSGroups, nsGroup.Copy())
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,6 +212,16 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
|||||||
return peerNSGroups
|
return peerNSGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// peerIsNameserver returns true if the peer is a nameserver for a nsGroup
|
||||||
|
func peerIsNameserver(peer *Peer, nsGroup *nbdns.NameServerGroup) bool {
|
||||||
|
for _, ns := range nsGroup.NameServers {
|
||||||
|
if peer.IP.Equal(ns.IP.AsSlice()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func addPeerLabelsToAccount(account *Account, peerLabels lookupMap) {
|
func addPeerLabelsToAccount(account *Account, peerLabels lookupMap) {
|
||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
label, err := getPeerHostLabel(peer.Name, peerLabels)
|
label, err := getPeerHostLabel(peer.Name, peerLabels)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
)
|
)
|
||||||
@@ -17,6 +19,7 @@ const (
|
|||||||
dnsAccountID = "testingAcc"
|
dnsAccountID = "testingAcc"
|
||||||
dnsAdminUserID = "testingAdminUser"
|
dnsAdminUserID = "testingAdminUser"
|
||||||
dnsRegularUserID = "testingRegularUser"
|
dnsRegularUserID = "testingRegularUser"
|
||||||
|
dnsNSGroup1 = "ns1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetDNSSettings(t *testing.T) {
|
func TestGetDNSSettings(t *testing.T) {
|
||||||
@@ -163,6 +166,7 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, newAccountDNSConfig.DNSConfig.CustomZones, 1, "default DNS config should have one custom zone for peers")
|
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")
|
require.True(t, newAccountDNSConfig.DNSConfig.ServiceEnable, "default DNS config should have local DNS service enabled")
|
||||||
|
require.Len(t, newAccountDNSConfig.DNSConfig.NameServerGroups, 0, "updated DNS config should have no nameserver groups since peer 1 is NS for the only existing NS group")
|
||||||
|
|
||||||
dnsSettings := account.DNSSettings.Copy()
|
dnsSettings := account.DNSSettings.Copy()
|
||||||
dnsSettings.DisabledManagementGroups = append(dnsSettings.DisabledManagementGroups, dnsGroup1ID)
|
dnsSettings.DisabledManagementGroups = append(dnsSettings.DisabledManagementGroups, dnsGroup1ID)
|
||||||
@@ -174,11 +178,11 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
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.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")
|
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(peer2.ID)
|
||||||
require.NoError(t, err)
|
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.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")
|
require.True(t, peer2AccountDNSConfig.DNSConfig.ServiceEnable, "DNS config should have DNS service enabled for peers not in the disabled group")
|
||||||
|
require.Len(t, peer2AccountDNSConfig.DNSConfig.NameServerGroups, 1, "updated DNS config should have 1 nameserver groups since peer 2 is part of the group All")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||||
@@ -246,7 +250,7 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = am.AddPeer("", dnsAdminUserID, peer1)
|
savedPeer1, _, err := am.AddPeer("", dnsAdminUserID, peer1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -284,6 +288,24 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
|
|||||||
account.Groups[newGroup1.ID] = newGroup1
|
account.Groups[newGroup1.ID] = newGroup1
|
||||||
account.Groups[newGroup2.ID] = newGroup2
|
account.Groups[newGroup2.ID] = newGroup2
|
||||||
|
|
||||||
|
allGroup, err := account.GetGroupAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
account.NameServerGroups[dnsNSGroup1] = &dns.NameServerGroup{
|
||||||
|
ID: dnsNSGroup1,
|
||||||
|
Name: "ns-group-1",
|
||||||
|
NameServers: []dns.NameServer{{
|
||||||
|
IP: netip.MustParseAddr(savedPeer1.IP.String()),
|
||||||
|
NSType: dns.UDPNameServerType,
|
||||||
|
Port: dns.DefaultDNSPort,
|
||||||
|
}},
|
||||||
|
Primary: true,
|
||||||
|
Enabled: true,
|
||||||
|
Groups: []string{allGroup.ID},
|
||||||
|
}
|
||||||
|
|
||||||
err = am.Store.SaveAccount(account)
|
err = am.Store.SaveAccount(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -157,6 +157,14 @@ func restore(file string) (*FileStore, error) {
|
|||||||
addPeerLabelsToAccount(account, existingLabels)
|
addPeerLabelsToAccount(account, existingLabels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: delete this block after migration
|
||||||
|
// Set API as issuer for groups which has not this field
|
||||||
|
for _, group := range account.Groups {
|
||||||
|
if group.Issued == "" {
|
||||||
|
group.Issued = GroupIssuedAPI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
allGroup, err := account.GetGroupAll()
|
allGroup, err := account.GetGroupAll()
|
||||||
if err != nil {
|
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.Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err)
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ func TestRestore(t *testing.T) {
|
|||||||
require.Len(t, store.TokenID2UserID, 1, "failed to restore a FileStore wrong TokenID2UserID mapping length")
|
require.Len(t, store.TokenID2UserID, 1, "failed to restore a FileStore wrong TokenID2UserID mapping length")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: outdated, delete this
|
||||||
func TestRestorePolicies_Migration(t *testing.T) {
|
func TestRestorePolicies_Migration(t *testing.T) {
|
||||||
storeDir := t.TempDir()
|
storeDir := t.TempDir()
|
||||||
|
|
||||||
@@ -296,6 +297,40 @@ func TestRestorePolicies_Migration(t *testing.T) {
|
|||||||
"failed to restore a FileStore file - missing Account Policies Sources")
|
"failed to restore a FileStore file - missing Account Policies Sources")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRestoreGroups_Migration(t *testing.T) {
|
||||||
|
storeDir := t.TempDir()
|
||||||
|
|
||||||
|
err := util.CopyFileContents("testdata/store.json", filepath.Join(storeDir, "store.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := NewFileStore(storeDir, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create default group
|
||||||
|
account := store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||||
|
account.Groups = map[string]*Group{
|
||||||
|
"cfefqs706sqkneg59g3g": {
|
||||||
|
ID: "cfefqs706sqkneg59g3g",
|
||||||
|
Name: "All",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = store.SaveAccount(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 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account = store.Accounts["bf1c8084-ba50-4ce7-9439-34653001fc3b"]
|
||||||
|
|
||||||
|
require.Contains(t, account.Groups, "cfefqs706sqkneg59g3g", "failed to restore a FileStore file - missing Account Groups")
|
||||||
|
require.Equal(t, GroupIssuedAPI, account.Groups["cfefqs706sqkneg59g3g"].Issued, "default group should has API issued mark")
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetAccountByPrivateDomain(t *testing.T) {
|
func TestGetAccountByPrivateDomain(t *testing.T) {
|
||||||
storeDir := t.TempDir()
|
storeDir := t.TempDir()
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ type Group struct {
|
|||||||
// Name visible in the UI
|
// Name visible in the UI
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
// Issued of the group
|
||||||
|
Issued string
|
||||||
|
|
||||||
// Peers list of the group
|
// Peers list of the group
|
||||||
Peers []string
|
Peers []string
|
||||||
}
|
}
|
||||||
@@ -45,9 +48,10 @@ func (g *Group) EventMeta() map[string]any {
|
|||||||
|
|
||||||
func (g *Group) Copy() *Group {
|
func (g *Group) Copy() *Group {
|
||||||
return &Group{
|
return &Group{
|
||||||
ID: g.ID,
|
ID: g.ID,
|
||||||
Name: g.Name,
|
Name: g.Name,
|
||||||
Peers: g.Peers[:],
|
Issued: g.Issued,
|
||||||
|
Peers: g.Peers[:],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,10 +72,19 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAccount, err := h.accountManager.UpdateAccountSettings(accountID, user.Id, &server.Settings{
|
settings := &server.Settings{
|
||||||
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
|
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
|
||||||
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
|
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if req.Settings.JwtGroupsEnabled != nil {
|
||||||
|
settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled
|
||||||
|
}
|
||||||
|
if req.Settings.JwtGroupsClaimName != nil {
|
||||||
|
settings.JWTGroupsClaimName = *req.Settings.JwtGroupsClaimName
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAccount, err := h.accountManager.UpdateAccountSettings(accountID, user.Id, settings)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
@@ -93,6 +102,8 @@ func toAccountResponse(account *server.Account) *api.Account {
|
|||||||
Settings: api.AccountSettings{
|
Settings: api.AccountSettings{
|
||||||
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
|
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
|
||||||
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
|
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
|
||||||
|
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
|
||||||
|
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
|||||||
accountID := "test_account"
|
accountID := "test_account"
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user")
|
||||||
|
|
||||||
|
sr := func(v string) *string { return &v }
|
||||||
|
br := func(v bool) *bool { return &v }
|
||||||
|
|
||||||
handler := initAccountsTestData(&server.Account{
|
handler := initAccountsTestData(&server.Account{
|
||||||
Id: accountID,
|
Id: accountID,
|
||||||
Domain: "hotmail.com",
|
Domain: "hotmail.com",
|
||||||
@@ -91,6 +94,8 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
|||||||
expectedSettings: api.AccountSettings{
|
expectedSettings: api.AccountSettings{
|
||||||
PeerLoginExpiration: int(time.Hour.Seconds()),
|
PeerLoginExpiration: int(time.Hour.Seconds()),
|
||||||
PeerLoginExpirationEnabled: false,
|
PeerLoginExpirationEnabled: false,
|
||||||
|
JwtGroupsClaimName: sr(""),
|
||||||
|
JwtGroupsEnabled: br(false),
|
||||||
},
|
},
|
||||||
expectedArray: true,
|
expectedArray: true,
|
||||||
expectedID: accountID,
|
expectedID: accountID,
|
||||||
@@ -105,6 +110,24 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
|||||||
expectedSettings: api.AccountSettings{
|
expectedSettings: api.AccountSettings{
|
||||||
PeerLoginExpiration: 15552000,
|
PeerLoginExpiration: 15552000,
|
||||||
PeerLoginExpirationEnabled: true,
|
PeerLoginExpirationEnabled: true,
|
||||||
|
JwtGroupsClaimName: sr(""),
|
||||||
|
JwtGroupsEnabled: br(false),
|
||||||
|
},
|
||||||
|
expectedArray: false,
|
||||||
|
expectedID: accountID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PutAccount OK wiht JWT",
|
||||||
|
expectedBody: true,
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/accounts/" + accountID,
|
||||||
|
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": false,\"jwt_groups_enabled\":true,\"jwt_groups_claim_name\":\"roles\"}}"),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedSettings: api.AccountSettings{
|
||||||
|
PeerLoginExpiration: 15552000,
|
||||||
|
PeerLoginExpirationEnabled: false,
|
||||||
|
JwtGroupsClaimName: sr("roles"),
|
||||||
|
JwtGroupsEnabled: br(true),
|
||||||
},
|
},
|
||||||
expectedArray: false,
|
expectedArray: false,
|
||||||
expectedID: accountID,
|
expectedID: accountID,
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ components:
|
|||||||
description: Period of time after which peer login expires (seconds).
|
description: Period of time after which peer login expires (seconds).
|
||||||
type: integer
|
type: integer
|
||||||
example: 43200
|
example: 43200
|
||||||
|
jwt_groups_enabled:
|
||||||
|
description: Allows extract groups from JWT claim and add it to account groups.
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
jwt_groups_claim_name:
|
||||||
|
description: Name of the claim from which we extract groups names to add it to account groups.
|
||||||
|
type: string
|
||||||
|
example: "roles"
|
||||||
required:
|
required:
|
||||||
- peer_login_expiration_enabled
|
- peer_login_expiration_enabled
|
||||||
- peer_login_expiration
|
- peer_login_expiration
|
||||||
@@ -462,6 +470,10 @@ components:
|
|||||||
description: Count of peers associated to the group
|
description: Count of peers associated to the group
|
||||||
type: integer
|
type: integer
|
||||||
example: 2
|
example: 2
|
||||||
|
issued:
|
||||||
|
description: How group was issued by API or from JWT token
|
||||||
|
type: string
|
||||||
|
example: api
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
|
|||||||
@@ -129,6 +129,12 @@ type AccountRequest struct {
|
|||||||
|
|
||||||
// AccountSettings defines model for AccountSettings.
|
// AccountSettings defines model for AccountSettings.
|
||||||
type AccountSettings struct {
|
type AccountSettings struct {
|
||||||
|
// JwtGroupsClaimName Name of the claim from which we extract groups names to add it to account groups.
|
||||||
|
JwtGroupsClaimName *string `json:"jwt_groups_claim_name,omitempty"`
|
||||||
|
|
||||||
|
// JwtGroupsEnabled Allows extract groups from JWT claim and add it to account groups.
|
||||||
|
JwtGroupsEnabled *bool `json:"jwt_groups_enabled,omitempty"`
|
||||||
|
|
||||||
// PeerLoginExpiration Period of time after which peer login expires (seconds).
|
// PeerLoginExpiration Period of time after which peer login expires (seconds).
|
||||||
PeerLoginExpiration int `json:"peer_login_expiration"`
|
PeerLoginExpiration int `json:"peer_login_expiration"`
|
||||||
|
|
||||||
@@ -174,6 +180,9 @@ type Group struct {
|
|||||||
// Id Group ID
|
// Id Group ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Issued How group was issued by API or from JWT token
|
||||||
|
Issued *string `json:"issued,omitempty"`
|
||||||
|
|
||||||
// Name Group Name identifier
|
// Name Group Name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
@@ -189,6 +198,9 @@ type GroupMinimum struct {
|
|||||||
// Id Group ID
|
// Id Group ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Issued How group was issued by API or from JWT token
|
||||||
|
Issued *string `json:"issued,omitempty"`
|
||||||
|
|
||||||
// Name Group Name identifier
|
// Name Group Name identifier
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok = account.Groups[groupID]
|
eg, ok := account.Groups[groupID]
|
||||||
if !ok {
|
if !ok {
|
||||||
util.WriteError(status.Errorf(status.NotFound, "couldn't find group with ID %s", groupID), w)
|
util.WriteError(status.Errorf(status.NotFound, "couldn't find group with ID %s", groupID), w)
|
||||||
return
|
return
|
||||||
@@ -107,9 +107,10 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
peers = *req.Peers
|
peers = *req.Peers
|
||||||
}
|
}
|
||||||
group := server.Group{
|
group := server.Group{
|
||||||
ID: groupID,
|
ID: groupID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
|
Issued: eg.Issued,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
|
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
|
||||||
@@ -149,9 +150,10 @@ func (h *GroupsHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {
|
|||||||
peers = *req.Peers
|
peers = *req.Peers
|
||||||
}
|
}
|
||||||
group := server.Group{
|
group := server.Group{
|
||||||
ID: xid.New().String(),
|
ID: xid.New().String(),
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
|
Issued: server.GroupIssuedAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
|
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
|
||||||
@@ -237,6 +239,7 @@ func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
|
|||||||
Id: group.ID,
|
Id: group.ID,
|
||||||
Name: group.Name,
|
Name: group.Name,
|
||||||
PeersCount: len(group.Peers),
|
PeersCount: len(group.Peers),
|
||||||
|
Issued: &group.Issued,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pid := range group.Peers {
|
for _, pid := range group.Peers {
|
||||||
|
|||||||
@@ -42,9 +42,17 @@ func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandle
|
|||||||
if groupID != "idofthegroup" {
|
if groupID != "idofthegroup" {
|
||||||
return nil, status.Errorf(status.NotFound, "not found")
|
return nil, status.Errorf(status.NotFound, "not found")
|
||||||
}
|
}
|
||||||
|
if groupID == "id-jwt-group" {
|
||||||
|
return &server.Group{
|
||||||
|
ID: "id-jwt-group",
|
||||||
|
Name: "Default Group",
|
||||||
|
Issued: server.GroupIssuedJWT,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
return &server.Group{
|
return &server.Group{
|
||||||
ID: "idofthegroup",
|
ID: "idofthegroup",
|
||||||
Name: "Group",
|
Name: "Group",
|
||||||
|
Issued: server.GroupIssuedAPI,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
UpdateGroupFunc: func(_ string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error) {
|
UpdateGroupFunc: func(_ string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error) {
|
||||||
@@ -80,8 +88,9 @@ func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandle
|
|||||||
user.Id: user,
|
user.Id: user,
|
||||||
},
|
},
|
||||||
Groups: map[string]*server.Group{
|
Groups: map[string]*server.Group{
|
||||||
"id-existed": {ID: "id-existed", Peers: []string{"A", "B"}},
|
"id-jwt-group": {ID: "id-jwt-group", Name: "From JWT", Issued: server.GroupIssuedJWT},
|
||||||
"id-all": {ID: "id-all", Name: "All"},
|
"id-existed": {ID: "id-existed", Peers: []string{"A", "B"}, Issued: server.GroupIssuedAPI},
|
||||||
|
"id-all": {ID: "id-all", Name: "All", Issued: server.GroupIssuedAPI},
|
||||||
},
|
},
|
||||||
}, user, nil
|
}, user, nil
|
||||||
},
|
},
|
||||||
@@ -169,6 +178,8 @@ func TestGetGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteGroup(t *testing.T) {
|
func TestWriteGroup(t *testing.T) {
|
||||||
|
groupIssuedAPI := "api"
|
||||||
|
groupIssuedJWT := "jwt"
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
name string
|
name string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
@@ -187,8 +198,9 @@ func TestWriteGroup(t *testing.T) {
|
|||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedBody: true,
|
expectedBody: true,
|
||||||
expectedGroup: &api.Group{
|
expectedGroup: &api.Group{
|
||||||
Id: "id-was-set",
|
Id: "id-was-set",
|
||||||
Name: "Default POSTed Group",
|
Name: "Default POSTed Group",
|
||||||
|
Issued: &groupIssuedAPI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -208,8 +220,9 @@ func TestWriteGroup(t *testing.T) {
|
|||||||
[]byte(`{"Name":"Default POSTed Group"}`)),
|
[]byte(`{"Name":"Default POSTed Group"}`)),
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedGroup: &api.Group{
|
expectedGroup: &api.Group{
|
||||||
Id: "id-existed",
|
Id: "id-existed",
|
||||||
Name: "Default POSTed Group",
|
Name: "Default POSTed Group",
|
||||||
|
Issued: &groupIssuedAPI,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -230,6 +243,19 @@ func TestWriteGroup(t *testing.T) {
|
|||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Write Group PUT not not change Issue",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/groups/id-jwt-group",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(`{"Name":"changed","Issued":"api"}`)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedGroup: &api.Group{
|
||||||
|
Id: "id-jwt-group",
|
||||||
|
Name: "changed",
|
||||||
|
Issued: &groupIssuedJWT,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUser := server.NewAdminUser("test_user")
|
adminUser := server.NewAdminUser("test_user")
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
package jwtclaims
|
package jwtclaims
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
// AuthorizationClaims stores authorization information from JWTs
|
// AuthorizationClaims stores authorization information from JWTs
|
||||||
type AuthorizationClaims struct {
|
type AuthorizationClaims struct {
|
||||||
UserId string
|
UserId string
|
||||||
AccountId string
|
AccountId string
|
||||||
Domain string
|
Domain string
|
||||||
DomainCategory string
|
DomainCategory string
|
||||||
|
|
||||||
|
Raw jwt.MapClaims
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,9 @@ func NewClaimsExtractor(options ...ClaimsExtractorOption) *ClaimsExtractor {
|
|||||||
// FromToken extracts claims from the token (after auth)
|
// FromToken extracts claims from the token (after auth)
|
||||||
func (c *ClaimsExtractor) FromToken(token *jwt.Token) AuthorizationClaims {
|
func (c *ClaimsExtractor) FromToken(token *jwt.Token) AuthorizationClaims {
|
||||||
claims := token.Claims.(jwt.MapClaims)
|
claims := token.Claims.(jwt.MapClaims)
|
||||||
jwtClaims := AuthorizationClaims{}
|
jwtClaims := AuthorizationClaims{
|
||||||
|
Raw: claims,
|
||||||
|
}
|
||||||
userID, ok := claims[c.userIDClaim].(string)
|
userID, ok := claims[c.userIDClaim].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return jwtClaims
|
return jwtClaims
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
|
|||||||
Domain: "test.com",
|
Domain: "test.com",
|
||||||
AccountId: "testAcc",
|
AccountId: "testAcc",
|
||||||
DomainCategory: "public",
|
DomainCategory: "public",
|
||||||
|
Raw: jwt.MapClaims{
|
||||||
|
"https://login/wt_account_domain": "test.com",
|
||||||
|
"https://login/wt_account_domain_category": "public",
|
||||||
|
"https://login/wt_account_id": "testAcc",
|
||||||
|
"sub": "test",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
expectedMSG: "extracted claims should match input claims",
|
expectedMSG: "extracted claims should match input claims",
|
||||||
@@ -59,6 +65,10 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
|
|||||||
inputAuthorizationClaims: AuthorizationClaims{
|
inputAuthorizationClaims: AuthorizationClaims{
|
||||||
UserId: "test",
|
UserId: "test",
|
||||||
AccountId: "testAcc",
|
AccountId: "testAcc",
|
||||||
|
Raw: jwt.MapClaims{
|
||||||
|
"https://login/wt_account_id": "testAcc",
|
||||||
|
"sub": "test",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
expectedMSG: "extracted claims should match input claims",
|
expectedMSG: "extracted claims should match input claims",
|
||||||
@@ -70,6 +80,10 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
|
|||||||
inputAuthorizationClaims: AuthorizationClaims{
|
inputAuthorizationClaims: AuthorizationClaims{
|
||||||
UserId: "test",
|
UserId: "test",
|
||||||
Domain: "test.com",
|
Domain: "test.com",
|
||||||
|
Raw: jwt.MapClaims{
|
||||||
|
"https://login/wt_account_domain": "test.com",
|
||||||
|
"sub": "test",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
expectedMSG: "extracted claims should match input claims",
|
expectedMSG: "extracted claims should match input claims",
|
||||||
@@ -82,6 +96,11 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
|
|||||||
UserId: "test",
|
UserId: "test",
|
||||||
Domain: "test.com",
|
Domain: "test.com",
|
||||||
AccountId: "testAcc",
|
AccountId: "testAcc",
|
||||||
|
Raw: jwt.MapClaims{
|
||||||
|
"https://login/wt_account_domain": "test.com",
|
||||||
|
"https://login/wt_account_id": "testAcc",
|
||||||
|
"sub": "test",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
expectedMSG: "extracted claims should match input claims",
|
expectedMSG: "extracted claims should match input claims",
|
||||||
@@ -92,6 +111,9 @@ func TestExtractClaimsFromRequestContext(t *testing.T) {
|
|||||||
inputAudiance: "https://login/",
|
inputAudiance: "https://login/",
|
||||||
inputAuthorizationClaims: AuthorizationClaims{
|
inputAuthorizationClaims: AuthorizationClaims{
|
||||||
UserId: "test",
|
UserId: "test",
|
||||||
|
Raw: jwt.MapClaims{
|
||||||
|
"sub": "test",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testingFunc: require.EqualValues,
|
testingFunc: require.EqualValues,
|
||||||
expectedMSG: "extracted claims should match input claims",
|
expectedMSG: "extracted claims should match input claims",
|
||||||
|
|||||||
@@ -78,11 +78,14 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var setupKey *SetupKey
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userId)
|
||||||
for _, key := range account.SetupKeys {
|
if err != nil {
|
||||||
if key.Type == SetupKeyReusable {
|
return
|
||||||
setupKey = key
|
}
|
||||||
}
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating setup key")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
peerKey1, err := wgtypes.GeneratePrivateKey()
|
peerKey1, err := wgtypes.GeneratePrivateKey()
|
||||||
@@ -328,7 +331,15 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupKey := getSetupKey(account, SetupKeyReusable)
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating setup key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
peerKey1, err := wgtypes.GeneratePrivateKey()
|
peerKey1, err := wgtypes.GeneratePrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -394,7 +405,15 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// two peers one added by a regular user and one with a setup key
|
// two peers one added by a regular user and one with a setup key
|
||||||
setupKey := getSetupKey(account, SetupKeyReusable)
|
setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, adminUser)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error creating setup key")
|
||||||
|
return
|
||||||
|
}
|
||||||
peerKey1, err := wgtypes.GeneratePrivateKey()
|
peerKey1, err := wgtypes.GeneratePrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -470,13 +489,3 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NotNil(t, peer)
|
assert.NotNil(t, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSetupKey(account *Account, keyType SetupKeyType) *SetupKey {
|
|
||||||
var setupKey *SetupKey
|
|
||||||
for _, key := range account.SetupKeys {
|
|
||||||
if key.Type == keyType {
|
|
||||||
setupKey = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return setupKey
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user