mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 16:26:38 +00:00
NetBird SSH (#361)
This PR adds support for SSH access through the NetBird network without managing SSH skeys. NetBird client app has an embedded SSH server (Linux/Mac only) and a netbird ssh command.
This commit is contained in:
@@ -12,7 +12,7 @@ type Client interface {
|
||||
io.Closer
|
||||
Sync(msgHandler func(msg *proto.SyncResponse) error) error
|
||||
GetServerPublicKey() (*wgtypes.Key, error)
|
||||
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info) (*proto.LoginResponse, error)
|
||||
Login(serverKey wgtypes.Key, sysInfo *system.Info) (*proto.LoginResponse, error)
|
||||
Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||
Login(serverKey wgtypes.Key, sysInfo *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sysInfo := system.GetInfo(context.TODO())
|
||||
_, err = client.Login(*key, sysInfo)
|
||||
_, err = client.Login(*key, sysInfo, nil)
|
||||
if err == nil {
|
||||
t.Error("expecting err on unregistered login, got nil")
|
||||
}
|
||||
@@ -186,7 +186,7 @@ func TestClient_LoginRegistered(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
info := system.GetInfo(context.TODO())
|
||||
resp, err := client.Register(*key, ValidKey, "", info)
|
||||
resp, err := client.Register(*key, ValidKey, "", info, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func TestClient_Sync(t *testing.T) {
|
||||
}
|
||||
|
||||
info := system.GetInfo(context.TODO())
|
||||
_, err = client.Register(*serverKey, ValidKey, "", info)
|
||||
_, err = client.Register(*serverKey, ValidKey, "", info, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -232,7 +232,7 @@ func TestClient_Sync(t *testing.T) {
|
||||
}
|
||||
|
||||
info = system.GetInfo(context.TODO())
|
||||
_, err = remoteClient.Register(*serverKey, ValidKey, "", info)
|
||||
_, err = remoteClient.Register(*serverKey, ValidKey, "", info, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -330,7 +330,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
||||
}
|
||||
|
||||
info := system.GetInfo(context.TODO())
|
||||
_, err = testClient.Register(*key, ValidKey, "", info)
|
||||
_, err = testClient.Register(*key, ValidKey, "", info, nil)
|
||||
if err != nil {
|
||||
t.Errorf("error while trying to register client: %v", err)
|
||||
}
|
||||
|
||||
@@ -233,13 +233,21 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
|
||||
// Register registers peer on Management Server. It actually calls a Login endpoint with a provided setup key
|
||||
// Takes care of encrypting and decrypting messages.
|
||||
// This method will also collect system info and send it with the request (e.g. hostname, os, etc)
|
||||
func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info) (*proto.LoginResponse, error) {
|
||||
return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: infoToMetaData(sysInfo), JwtToken: jwtToken})
|
||||
func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, sysInfo *system.Info, pubSSHKey []byte) (*proto.LoginResponse, error) {
|
||||
keys := &proto.PeerKeys{
|
||||
SshPubKey: pubSSHKey,
|
||||
WgPubKey: []byte(c.key.PublicKey().String()),
|
||||
}
|
||||
return c.login(serverKey, &proto.LoginRequest{SetupKey: setupKey, Meta: infoToMetaData(sysInfo), JwtToken: jwtToken, PeerKeys: keys})
|
||||
}
|
||||
|
||||
// Login attempts login to Management Server. Takes care of encrypting and decrypting messages.
|
||||
func (c *GrpcClient) Login(serverKey wgtypes.Key, sysInfo *system.Info) (*proto.LoginResponse, error) {
|
||||
return c.login(serverKey, &proto.LoginRequest{Meta: infoToMetaData(sysInfo)})
|
||||
func (c *GrpcClient) Login(serverKey wgtypes.Key, sysInfo *system.Info, pubSSHKey []byte) (*proto.LoginResponse, error) {
|
||||
keys := &proto.PeerKeys{
|
||||
SshPubKey: pubSSHKey,
|
||||
WgPubKey: []byte(c.key.PublicKey().String()),
|
||||
}
|
||||
return c.login(serverKey, &proto.LoginRequest{Meta: infoToMetaData(sysInfo), PeerKeys: keys})
|
||||
}
|
||||
|
||||
// GetDeviceAuthorizationFlow returns a device authorization flow information.
|
||||
|
||||
@@ -10,8 +10,8 @@ type MockClient struct {
|
||||
CloseFunc func() error
|
||||
SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error
|
||||
GetServerPublicKeyFunc func() (*wgtypes.Key, error)
|
||||
RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error)
|
||||
LoginFunc func(serverKey wgtypes.Key, info *system.Info) (*proto.LoginResponse, error)
|
||||
RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||
LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error)
|
||||
GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
|
||||
}
|
||||
|
||||
@@ -36,18 +36,18 @@ func (m *MockClient) GetServerPublicKey() (*wgtypes.Key, error) {
|
||||
return m.GetServerPublicKeyFunc()
|
||||
}
|
||||
|
||||
func (m *MockClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error) {
|
||||
func (m *MockClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) {
|
||||
if m.RegisterFunc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return m.RegisterFunc(serverKey, setupKey, jwtToken, info)
|
||||
return m.RegisterFunc(serverKey, setupKey, jwtToken, info, sshKey)
|
||||
}
|
||||
|
||||
func (m *MockClient) Login(serverKey wgtypes.Key, info *system.Info) (*proto.LoginResponse, error) {
|
||||
func (m *MockClient) Login(serverKey wgtypes.Key, info *system.Info, sshKey []byte) (*proto.LoginResponse, error) {
|
||||
if m.LoginFunc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return m.LoginFunc(serverKey, info)
|
||||
return m.LoginFunc(serverKey, info, sshKey)
|
||||
}
|
||||
|
||||
func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,9 +71,21 @@ message LoginRequest {
|
||||
PeerSystemMeta meta = 2;
|
||||
// SSO token (can be empty)
|
||||
string jwtToken = 3;
|
||||
// Can be absent for now.
|
||||
PeerKeys peerKeys = 4;
|
||||
|
||||
}
|
||||
// PeerKeys is additional peer info like SSH pub key and WireGuard public key.
|
||||
// This message is sent on Login or register requests, or when a key rotation has to happen.
|
||||
message PeerKeys {
|
||||
|
||||
// sshPubKey represents a public SSH key of the peer. Can be absent.
|
||||
bytes sshPubKey = 1;
|
||||
// wgPubKey represents a public WireGuard key of the peer. Can be absent.
|
||||
bytes wgPubKey = 2;
|
||||
}
|
||||
|
||||
// Peer machine meta data
|
||||
// PeerSystemMeta is machine meta data like OS and version.
|
||||
message PeerSystemMeta {
|
||||
string hostname = 1;
|
||||
string goOS = 2;
|
||||
@@ -143,6 +155,9 @@ message PeerConfig {
|
||||
string address = 1;
|
||||
// Wiretrustee DNS server (a Wireguard DNS config)
|
||||
string dns = 2;
|
||||
|
||||
// SSHConfig of the peer.
|
||||
SSHConfig sshConfig = 3;
|
||||
}
|
||||
|
||||
// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
|
||||
@@ -172,7 +187,22 @@ message RemotePeerConfig {
|
||||
|
||||
// Wireguard allowed IPs of a remote peer e.g. [10.30.30.1/32]
|
||||
repeated string allowedIps = 2;
|
||||
|
||||
// SSHConfig is a SSH config of the remote peer. SSHConfig.sshPubKey should be ignored because peer knows it's SSH key.
|
||||
SSHConfig sshConfig = 3;
|
||||
|
||||
}
|
||||
|
||||
// SSHConfig represents SSH configurations of a peer.
|
||||
message SSHConfig {
|
||||
// sshEnabled indicates whether a SSH server is enabled on this peer
|
||||
bool sshEnabled = 1;
|
||||
|
||||
// sshPubKey is a SSH public key of a peer to be added to authorized_hosts.
|
||||
// This property should be ignore if SSHConfig comes from PeerConfig.
|
||||
bytes sshPubKey = 2;
|
||||
}
|
||||
|
||||
// DeviceAuthorizationFlowRequest empty struct for future expansion
|
||||
message DeviceAuthorizationFlowRequest {}
|
||||
// DeviceAuthorizationFlow represents Device Authorization Flow information
|
||||
|
||||
@@ -51,6 +51,7 @@ type AccountManager interface {
|
||||
GetNetworkMap(peerKey string) (*NetworkMap, error)
|
||||
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)
|
||||
GetGroup(accountId, groupID string) (*Group, error)
|
||||
SaveGroup(accountId string, group *Group) error
|
||||
@@ -65,6 +66,7 @@ type AccountManager interface {
|
||||
UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error)
|
||||
DeleteRule(accountId, ruleID string) error
|
||||
ListRules(accountId string) ([]*Rule, error)
|
||||
UpdatePeer(accountID string, peer *Peer) (*Peer, error)
|
||||
}
|
||||
|
||||
type DefaultAccountManager struct {
|
||||
|
||||
@@ -185,9 +185,15 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
|
||||
return nil, status.Errorf(codes.InvalidArgument, "peer meta data was not provided")
|
||||
}
|
||||
|
||||
var sshKey []byte
|
||||
if req.GetPeerKeys() != nil {
|
||||
sshKey = req.GetPeerKeys().GetSshPubKey()
|
||||
}
|
||||
|
||||
peer, err := s.accountManager.AddPeer(reqSetupKey, userId, &Peer{
|
||||
Key: peerKey.String(),
|
||||
Name: meta.GetHostname(),
|
||||
Key: peerKey.String(),
|
||||
Name: meta.GetHostname(),
|
||||
SSHKey: string(sshKey),
|
||||
Meta: PeerSystemMeta{
|
||||
Hostname: meta.GetHostname(),
|
||||
GoOS: meta.GetGoOS(),
|
||||
@@ -290,6 +296,19 @@ func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto
|
||||
return nil, status.Error(codes.Internal, "internal server error")
|
||||
}
|
||||
}
|
||||
|
||||
var sshKey []byte
|
||||
if loginReq.GetPeerKeys() != nil {
|
||||
sshKey = loginReq.GetPeerKeys().GetSshPubKey()
|
||||
}
|
||||
|
||||
if len(sshKey) > 0 {
|
||||
err = s.accountManager.UpdatePeerSSHKey(peerKey.String(), string(sshKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// if peer has reached this point then it has logged in
|
||||
loginResp := &proto.LoginResponse{
|
||||
WiretrusteeConfig: toWiretrusteeConfig(s.config, nil),
|
||||
@@ -365,7 +384,8 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
||||
|
||||
func toPeerConfig(peer *Peer) *proto.PeerConfig {
|
||||
return &proto.PeerConfig{
|
||||
Address: fmt.Sprintf("%s/%d", peer.IP.String(), SubnetSize), // take it from the network
|
||||
Address: fmt.Sprintf("%s/%d", peer.IP.String(), SubnetSize), // take it from the network
|
||||
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,9 +395,9 @@ func toRemotePeerConfig(peers []*Peer) []*proto.RemotePeerConfig {
|
||||
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||
WgPubKey: rPeer.Key,
|
||||
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
|
||||
SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)},
|
||||
})
|
||||
}
|
||||
|
||||
return remotePeers
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,9 @@ components:
|
||||
required:
|
||||
- type
|
||||
- value
|
||||
ssh_enabled:
|
||||
description: Indicates whether SSH server is enabled on this peer
|
||||
type: boolean
|
||||
required:
|
||||
- ip
|
||||
- connected
|
||||
@@ -93,6 +96,7 @@ components:
|
||||
- version
|
||||
- groups
|
||||
- activated_by
|
||||
- ssh_enabled
|
||||
SetupKey:
|
||||
type: object
|
||||
properties:
|
||||
@@ -397,8 +401,11 @@ paths:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
ssh_enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
- ssh_enabled
|
||||
responses:
|
||||
'200':
|
||||
description: A Peer object
|
||||
|
||||
@@ -115,6 +115,9 @@ type Peer struct {
|
||||
// Peer's operating system and version
|
||||
Os string `json:"os"`
|
||||
|
||||
// Indicates whether SSH server is enabled on this peer
|
||||
SshEnabled bool `json:"ssh_enabled"`
|
||||
|
||||
// Peer's daemon or cli version
|
||||
Version string `json:"version"`
|
||||
}
|
||||
@@ -265,7 +268,8 @@ type PutApiGroupsIdJSONBody struct {
|
||||
|
||||
// PutApiPeersIdJSONBody defines parameters for PutApiPeersId.
|
||||
type PutApiPeersIdJSONBody struct {
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name"`
|
||||
SshEnabled bool `json:"ssh_enabled"`
|
||||
}
|
||||
|
||||
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
||||
|
||||
@@ -34,7 +34,9 @@ func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.Re
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
peer, err = h.accountManager.RenamePeer(account.Id, peer.Key, req.Name)
|
||||
|
||||
update := &server.Peer{Key: peer.Key, SSHEnabled: req.SshEnabled, Name: req.Name}
|
||||
peer, err = h.accountManager.UpdatePeer(account.Id, update)
|
||||
if err != nil {
|
||||
log.Errorf("failed updating peer %s under account %s %v", peerIp, account.Id, err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
@@ -133,13 +135,14 @@ 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,
|
||||
Groups: groupsInfo,
|
||||
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,
|
||||
Groups: groupsInfo,
|
||||
SshEnabled: peer.SSHEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ type MockAccountManager struct {
|
||||
ListRulesFunc func(accountID string) ([]*server.Rule, error)
|
||||
GetUsersFromAccountFunc func(accountID string) ([]*server.UserInfo, error)
|
||||
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
|
||||
UpdatePeerSSHKeyFunc func(peerKey string, sshKey string) error
|
||||
UpdatePeerFunc func(accountID string, peer *server.Peer) (*server.Peer, error)
|
||||
}
|
||||
|
||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||
@@ -48,7 +50,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 not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetUsersFromAccount is not implemented")
|
||||
}
|
||||
|
||||
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
||||
@@ -60,7 +62,7 @@ func (am *MockAccountManager) GetOrCreateAccountByUser(
|
||||
}
|
||||
return nil, status.Errorf(
|
||||
codes.Unimplemented,
|
||||
"method GetOrCreateAccountByUser not implemented",
|
||||
"method GetOrCreateAccountByUser is not implemented",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account,
|
||||
if am.GetAccountByUserFunc != nil {
|
||||
return am.GetAccountByUserFunc(userId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountByUser is not implemented")
|
||||
}
|
||||
|
||||
// AddSetupKey mock implementation of AddSetupKey from server.AccountManager interface
|
||||
@@ -82,7 +84,7 @@ func (am *MockAccountManager) AddSetupKey(
|
||||
if am.AddSetupKeyFunc != nil {
|
||||
return am.AddSetupKeyFunc(accountId, keyName, keyType, expiresIn)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddSetupKey is not implemented")
|
||||
}
|
||||
|
||||
// RevokeSetupKey mock implementation of RevokeSetupKey from server.AccountManager interface
|
||||
@@ -93,7 +95,7 @@ func (am *MockAccountManager) RevokeSetupKey(
|
||||
if am.RevokeSetupKeyFunc != nil {
|
||||
return am.RevokeSetupKeyFunc(accountId, keyId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RevokeSetupKey is not implemented")
|
||||
}
|
||||
|
||||
// RenameSetupKey mock implementation of RenameSetupKey from server.AccountManager interface
|
||||
@@ -105,7 +107,7 @@ func (am *MockAccountManager) RenameSetupKey(
|
||||
if am.RenameSetupKeyFunc != nil {
|
||||
return am.RenameSetupKeyFunc(accountId, keyId, newName)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSetupKey is not implemented")
|
||||
}
|
||||
|
||||
// GetAccountById mock implementation of GetAccountById from server.AccountManager interface
|
||||
@@ -113,7 +115,7 @@ func (am *MockAccountManager) GetAccountById(accountId string) (*server.Account,
|
||||
if am.GetAccountByIdFunc != nil {
|
||||
return am.GetAccountByIdFunc(accountId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountById not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccountById is not implemented")
|
||||
}
|
||||
|
||||
// GetAccountByUserOrAccountId mock implementation of GetAccountByUserOrAccountId from server.AccountManager interface
|
||||
@@ -125,7 +127,7 @@ func (am *MockAccountManager) GetAccountByUserOrAccountId(
|
||||
}
|
||||
return nil, status.Errorf(
|
||||
codes.Unimplemented,
|
||||
"method GetAccountByUserOrAccountId not implemented",
|
||||
"method GetAccountByUserOrAccountId is not implemented",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -138,7 +140,7 @@ func (am *MockAccountManager) GetAccountWithAuthorizationClaims(
|
||||
}
|
||||
return nil, status.Errorf(
|
||||
codes.Unimplemented,
|
||||
"method GetAccountWithAuthorizationClaims not implemented",
|
||||
"method GetAccountWithAuthorizationClaims is not implemented",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -147,7 +149,7 @@ func (am *MockAccountManager) AccountExists(accountId string) (*bool, error) {
|
||||
if am.AccountExistsFunc != nil {
|
||||
return am.AccountExistsFunc(accountId)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AccountExists not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AccountExists is not implemented")
|
||||
}
|
||||
|
||||
// GetPeer mock implementation of GetPeer from server.AccountManager interface
|
||||
@@ -155,7 +157,7 @@ func (am *MockAccountManager) GetPeer(peerKey string) (*server.Peer, error) {
|
||||
if am.GetPeerFunc != nil {
|
||||
return am.GetPeerFunc(peerKey)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeer not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeer is not implemented")
|
||||
}
|
||||
|
||||
// MarkPeerConnected mock implementation of MarkPeerConnected from server.AccountManager interface
|
||||
@@ -163,7 +165,7 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool)
|
||||
if am.MarkPeerConnectedFunc != nil {
|
||||
return am.MarkPeerConnectedFunc(peerKey, connected)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
|
||||
}
|
||||
|
||||
// RenamePeer mock implementation of RenamePeer from server.AccountManager interface
|
||||
@@ -175,7 +177,7 @@ func (am *MockAccountManager) RenamePeer(
|
||||
if am.RenamePeerFunc != nil {
|
||||
return am.RenamePeerFunc(accountId, peerKey, newName)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenamePeer not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenamePeer is not implemented")
|
||||
}
|
||||
|
||||
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
||||
@@ -183,7 +185,7 @@ func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*ser
|
||||
if am.DeletePeerFunc != nil {
|
||||
return am.DeletePeerFunc(accountId, peerKey)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
|
||||
}
|
||||
|
||||
// GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface
|
||||
@@ -191,7 +193,7 @@ func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*ser
|
||||
if am.GetPeerByIPFunc != nil {
|
||||
return am.GetPeerByIPFunc(accountId, peerIP)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP is not implemented")
|
||||
}
|
||||
|
||||
// GetNetworkMap mock implementation of GetNetworkMap from server.AccountManager interface
|
||||
@@ -199,7 +201,7 @@ func (am *MockAccountManager) GetNetworkMap(peerKey string) (*server.NetworkMap,
|
||||
if am.GetNetworkMapFunc != nil {
|
||||
return am.GetNetworkMapFunc(peerKey)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetNetworkMap is not implemented")
|
||||
}
|
||||
|
||||
// AddPeer mock implementation of AddPeer from server.AccountManager interface
|
||||
@@ -211,7 +213,7 @@ func (am *MockAccountManager) AddPeer(
|
||||
if am.AddPeerFunc != nil {
|
||||
return am.AddPeerFunc(setupKey, userId, peer)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddPeer is not implemented")
|
||||
}
|
||||
|
||||
// GetGroup mock implementation of GetGroup from server.AccountManager interface
|
||||
@@ -219,7 +221,7 @@ func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group
|
||||
if am.GetGroupFunc != nil {
|
||||
return am.GetGroupFunc(accountID, groupID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetGroup not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetGroup is not implemented")
|
||||
}
|
||||
|
||||
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
|
||||
@@ -227,7 +229,7 @@ func (am *MockAccountManager) SaveGroup(accountID string, group *server.Group) e
|
||||
if am.SaveGroupFunc != nil {
|
||||
return am.SaveGroupFunc(accountID, group)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method SaveGroup not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented")
|
||||
}
|
||||
|
||||
// UpdateGroup mock implementation of UpdateGroup from server.AccountManager interface
|
||||
@@ -243,7 +245,7 @@ func (am *MockAccountManager) DeleteGroup(accountID, groupID string) error {
|
||||
if am.DeleteGroupFunc != nil {
|
||||
return am.DeleteGroupFunc(accountID, groupID)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteGroup not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteGroup is not implemented")
|
||||
}
|
||||
|
||||
// ListGroups mock implementation of ListGroups from server.AccountManager interface
|
||||
@@ -251,7 +253,7 @@ func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, err
|
||||
if am.ListGroupsFunc != nil {
|
||||
return am.ListGroupsFunc(accountID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListGroups not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListGroups is not implemented")
|
||||
}
|
||||
|
||||
// GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface
|
||||
@@ -259,7 +261,7 @@ func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerKey string) e
|
||||
if am.GroupAddPeerFunc != nil {
|
||||
return am.GroupAddPeerFunc(accountID, groupID, peerKey)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method GroupAddPeer not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method GroupAddPeer is not implemented")
|
||||
}
|
||||
|
||||
// GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface
|
||||
@@ -267,7 +269,7 @@ func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerKey string
|
||||
if am.GroupDeletePeerFunc != nil {
|
||||
return am.GroupDeletePeerFunc(accountID, groupID, peerKey)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer is not implemented")
|
||||
}
|
||||
|
||||
// GroupListPeers mock implementation of GroupListPeers from server.AccountManager interface
|
||||
@@ -275,7 +277,7 @@ func (am *MockAccountManager) GroupListPeers(accountID, groupID string) ([]*serv
|
||||
if am.GroupListPeersFunc != nil {
|
||||
return am.GroupListPeersFunc(accountID, groupID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers is not implemented")
|
||||
}
|
||||
|
||||
// GetRule mock implementation of GetRule from server.AccountManager interface
|
||||
@@ -283,7 +285,7 @@ func (am *MockAccountManager) GetRule(accountID, ruleID string) (*server.Rule, e
|
||||
if am.GetRuleFunc != nil {
|
||||
return am.GetRuleFunc(accountID, ruleID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetRule not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetRule is not implemented")
|
||||
}
|
||||
|
||||
// SaveRule mock implementation of SaveRule from server.AccountManager interface
|
||||
@@ -291,7 +293,7 @@ func (am *MockAccountManager) SaveRule(accountID string, rule *server.Rule) erro
|
||||
if am.SaveRuleFunc != nil {
|
||||
return am.SaveRuleFunc(accountID, rule)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method SaveRule not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method SaveRule is not implemented")
|
||||
}
|
||||
|
||||
// UpdateRule mock implementation of UpdateRule from server.AccountManager interface
|
||||
@@ -307,7 +309,7 @@ func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
if am.DeleteRuleFunc != nil {
|
||||
return am.DeleteRuleFunc(accountID, ruleID)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteRule not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteRule is not implemented")
|
||||
}
|
||||
|
||||
// ListRules mock implementation of ListRules from server.AccountManager interface
|
||||
@@ -315,7 +317,7 @@ func (am *MockAccountManager) ListRules(accountID string) ([]*server.Rule, error
|
||||
if am.ListRulesFunc != nil {
|
||||
return am.ListRulesFunc(accountID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListRules not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListRules is not implemented")
|
||||
}
|
||||
|
||||
// UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface
|
||||
@@ -323,7 +325,7 @@ func (am *MockAccountManager) UpdatePeerMeta(peerKey string, meta server.PeerSys
|
||||
if am.UpdatePeerMetaFunc != nil {
|
||||
return am.UpdatePeerMetaFunc(peerKey, meta)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc not implemented")
|
||||
return status.Errorf(codes.Unimplemented, "method UpdatePeerMetaFunc is not implemented")
|
||||
}
|
||||
|
||||
// IsUserAdmin mock implementation of IsUserAdmin from server.AccountManager interface
|
||||
@@ -331,5 +333,21 @@ func (am *MockAccountManager) IsUserAdmin(claims jwtclaims.AuthorizationClaims)
|
||||
if am.IsUserAdminFunc != nil {
|
||||
return am.IsUserAdminFunc(claims)
|
||||
}
|
||||
return false, status.Errorf(codes.Unimplemented, "method IsUserAdmin not implemented")
|
||||
return false, status.Errorf(codes.Unimplemented, "method IsUserAdmin is not implemented")
|
||||
}
|
||||
|
||||
// UpdatePeerSSHKey mocks UpdatePeerSSHKey function of the account manager
|
||||
func (am *MockAccountManager) UpdatePeerSSHKey(peerKey string, sshKey string) error {
|
||||
if am.UpdatePeerSSHKeyFunc != nil {
|
||||
return am.UpdatePeerSSHKeyFunc(peerKey, sshKey)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method UpdatePeerSSHKey is is not implemented")
|
||||
}
|
||||
|
||||
// UpdatePeer mocks UpdatePeerFunc function of the account manager
|
||||
func (am *MockAccountManager) UpdatePeer(accountID string, peer *server.Peer) (*server.Peer, error) {
|
||||
if am.UpdatePeerFunc != nil {
|
||||
return am.UpdatePeerFunc(accountID, peer)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdatePeerFunc is is not implemented")
|
||||
}
|
||||
|
||||
@@ -47,18 +47,24 @@ type Peer struct {
|
||||
Status *PeerStatus
|
||||
// The user ID that registered the peer
|
||||
UserID string
|
||||
// SSHKey is a public SSH key of the peer
|
||||
SSHKey string
|
||||
// SSHEnabled indicated whether SSH server is enabled on the peer
|
||||
SSHEnabled bool
|
||||
}
|
||||
|
||||
// Copy copies Peer object
|
||||
func (p *Peer) Copy() *Peer {
|
||||
return &Peer{
|
||||
Key: p.Key,
|
||||
SetupKey: p.SetupKey,
|
||||
IP: p.IP,
|
||||
Meta: p.Meta,
|
||||
Name: p.Name,
|
||||
Status: p.Status,
|
||||
UserID: p.UserID,
|
||||
Key: p.Key,
|
||||
SetupKey: p.SetupKey,
|
||||
IP: p.IP,
|
||||
Meta: p.Meta,
|
||||
Name: p.Name,
|
||||
Status: p.Status,
|
||||
UserID: p.UserID,
|
||||
SSHKey: p.SSHKey,
|
||||
SSHEnabled: p.SSHEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +106,41 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerKey string, connected boo
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePeer updates peer. Only Peer.Name and Peer.SSHEnabled can be updated.
|
||||
func (am *DefaultAccountManager) UpdatePeer(accountID string, update *Peer) (*Peer, 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")
|
||||
}
|
||||
|
||||
peer, err := am.Store.GetPeer(update.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerCopy := peer.Copy()
|
||||
if peer.Name != "" {
|
||||
peerCopy.Name = update.Name
|
||||
}
|
||||
peerCopy.SSHEnabled = update.SSHEnabled
|
||||
|
||||
err = am.Store.SavePeer(accountID, peerCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return peerCopy, nil
|
||||
|
||||
}
|
||||
|
||||
// RenamePeer changes peer's name
|
||||
func (am *DefaultAccountManager) RenamePeer(
|
||||
accountId string,
|
||||
@@ -285,13 +326,15 @@ func (am *DefaultAccountManager) AddPeer(
|
||||
}
|
||||
|
||||
newPeer := &Peer{
|
||||
Key: peer.Key,
|
||||
SetupKey: upperKey,
|
||||
IP: nextIp,
|
||||
Meta: peer.Meta,
|
||||
Name: peer.Name,
|
||||
UserID: userID,
|
||||
Status: &PeerStatus{Connected: false, LastSeen: time.Now()},
|
||||
Key: peer.Key,
|
||||
SetupKey: upperKey,
|
||||
IP: nextIp,
|
||||
Meta: peer.Meta,
|
||||
Name: peer.Name,
|
||||
UserID: userID,
|
||||
Status: &PeerStatus{Connected: false, LastSeen: time.Now()},
|
||||
SSHEnabled: false,
|
||||
SSHKey: peer.SSHKey,
|
||||
}
|
||||
|
||||
// add peer to 'All' group
|
||||
@@ -315,6 +358,38 @@ func (am *DefaultAccountManager) AddPeer(
|
||||
return newPeer, nil
|
||||
}
|
||||
|
||||
// UpdatePeerSSHKey updates peer's public SSH key
|
||||
func (am *DefaultAccountManager) UpdatePeerSSHKey(peerKey string, sshKey string) error {
|
||||
am.mux.Lock()
|
||||
defer am.mux.Unlock()
|
||||
|
||||
if sshKey == "" {
|
||||
log.Debugf("empty SSH key provided for peer %s, skipping update", peerKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
peer, err := am.Store.GetPeer(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account, err := am.Store.GetPeerAccount(peerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerCopy := peer.Copy()
|
||||
peerCopy.SSHKey = sshKey
|
||||
|
||||
err = am.Store.SavePeer(account.Id, peerCopy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// trigger network map update
|
||||
return am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
// UpdatePeerMeta updates peer's system metadata
|
||||
func (am *DefaultAccountManager) UpdatePeerMeta(peerKey string, meta PeerSystemMeta) error {
|
||||
am.mux.Lock()
|
||||
@@ -345,7 +420,7 @@ func (am *DefaultAccountManager) UpdatePeerMeta(peerKey string, meta PeerSystemM
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPeersByACL allowed for given peer by ACL
|
||||
// getPeersByACL returns all peers that given peer has access to.
|
||||
func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string) []*Peer {
|
||||
var peers []*Peer
|
||||
srcRules, err := am.Store.GetPeerSrcRules(account.Id, peerKey)
|
||||
@@ -409,7 +484,8 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerKey string)
|
||||
return peers
|
||||
}
|
||||
|
||||
// updateAccountPeers network map constructed by ACL
|
||||
// updateAccountPeers updates all peers that belong to an account.
|
||||
// Should be called when changes have to be synced to peers.
|
||||
func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
||||
// notify other peers of the change
|
||||
peers, err := am.Store.GetAccountPeers(account.Id)
|
||||
@@ -422,7 +498,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
||||
err = am.peersUpdateManager.SendUpdate(p.Key,
|
||||
&UpdateMessage{
|
||||
Update: &proto.SyncResponse{
|
||||
// fill those field for backward compatibility
|
||||
// fill deprecated fields for backward compatibility
|
||||
RemotePeers: update,
|
||||
RemotePeersIsEmpty: len(update) == 0,
|
||||
// new field
|
||||
@@ -430,6 +506,7 @@ func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
||||
Serial: account.Network.CurrentSerial(),
|
||||
RemotePeers: update,
|
||||
RemotePeersIsEmpty: len(update) == 0,
|
||||
PeerConfig: toPeerConfig(p),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
type UpdateMessage struct {
|
||||
Update *proto.SyncResponse
|
||||
}
|
||||
|
||||
type PeersUpdateManager struct {
|
||||
peerChannels map[string]chan *UpdateMessage
|
||||
channelsMux *sync.Mutex
|
||||
|
||||
Reference in New Issue
Block a user