mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-20 17:58:02 +02:00
Add login expiration fields to peer HTTP API (#687)
Return login expiration related fields in the Peer HTTP GET endpoint. Support enable/disable peer's login expiration via HTTP PUT.
This commit is contained in:
parent
b64f5ffcb4
commit
756ce96da9
@ -61,6 +61,10 @@ const (
|
||||
PeerSSHDisabled
|
||||
// PeerRenamed indicates that a user renamed a peer
|
||||
PeerRenamed
|
||||
// PeerLoginExpirationEnabled indicates that a user enabled login expiration of a peer
|
||||
PeerLoginExpirationEnabled
|
||||
// PeerLoginExpirationDisabled indicates that a user disabled login expiration of a peer
|
||||
PeerLoginExpirationDisabled
|
||||
// NameserverGroupCreated indicates that a user created a nameservers group
|
||||
NameserverGroupCreated
|
||||
// NameserverGroupDeleted indicates that a user deleted a nameservers group
|
||||
@ -130,6 +134,10 @@ const (
|
||||
PeerSSHDisabledMessage string = "Peer SSH server disabled"
|
||||
// PeerRenamedMessage is a human-readable text message of the PeerRenamed activity
|
||||
PeerRenamedMessage string = "Peer renamed"
|
||||
// PeerLoginExpirationDisabledMessage is a human-readable text message of the PeerLoginExpirationDisabled activity
|
||||
PeerLoginExpirationDisabledMessage string = "Peer login expiration disabled"
|
||||
// PeerLoginExpirationEnabledMessage is a human-readable text message of the PeerLoginExpirationEnabled activity
|
||||
PeerLoginExpirationEnabledMessage string = "Peer login expiration enabled"
|
||||
// NameserverGroupCreatedMessage is a human-readable text message of the NameserverGroupCreated activity
|
||||
NameserverGroupCreatedMessage string = "Nameserver group created"
|
||||
// NameserverGroupDeletedMessage is a human-readable text message of the NameserverGroupDeleted activity
|
||||
@ -202,6 +210,10 @@ func (a Activity) Message() string {
|
||||
return PeerSSHEnabledMessage
|
||||
case PeerSSHDisabled:
|
||||
return PeerSSHDisabledMessage
|
||||
case PeerLoginExpirationEnabled:
|
||||
return PeerLoginExpirationEnabledMessage
|
||||
case PeerLoginExpirationDisabled:
|
||||
return PeerLoginExpirationDisabledMessage
|
||||
case PeerRenamed:
|
||||
return PeerRenamedMessage
|
||||
case NameserverGroupCreated:
|
||||
@ -278,6 +290,10 @@ func (a Activity) StringCode() string {
|
||||
return "peer.ssh.enable"
|
||||
case PeerSSHDisabled:
|
||||
return "peer.ssh.disable"
|
||||
case PeerLoginExpirationDisabled:
|
||||
return "peer.login.expiration.disable"
|
||||
case PeerLoginExpirationEnabled:
|
||||
return "peer.login.expiration.enable"
|
||||
case NameserverGroupCreated:
|
||||
return "nameserver.group.add"
|
||||
case NameserverGroupDeleted:
|
||||
|
@ -145,6 +145,16 @@ components:
|
||||
dns_label:
|
||||
description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||
type: string
|
||||
login_expiration_enabled:
|
||||
description: Indicates whether peer login expiration has been enabled or not
|
||||
type: boolean
|
||||
login_expired:
|
||||
description: Indicates whether peer's login expired or not
|
||||
type: boolean
|
||||
last_login:
|
||||
description: Last time this peer performed log in (authentication). E.g., user authenticated.
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- ip
|
||||
- connected
|
||||
@ -155,6 +165,9 @@ components:
|
||||
- ssh_enabled
|
||||
- hostname
|
||||
- dns_label
|
||||
- login_expiration_enabled
|
||||
- login_expired
|
||||
- last_login
|
||||
SetupKey:
|
||||
type: object
|
||||
properties:
|
||||
@ -542,7 +555,7 @@ components:
|
||||
"account.create", "dns.setting.disabled.management.group.delete",
|
||||
"route.add", "route.delete", "route.update",
|
||||
"nameserver.group.add", "nameserver.group.delete", "nameserver.group.update",
|
||||
"peer.ssh.disable", "peer.ssh.enable", "peer.rename"
|
||||
"peer.ssh.disable", "peer.ssh.enable", "peer.rename", "peer.login.expiration.disable", "peer.login.expiration.enable"
|
||||
]
|
||||
initiator_id:
|
||||
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||
@ -741,7 +754,7 @@ paths:
|
||||
type: string
|
||||
description: The Peer ID
|
||||
requestBody:
|
||||
description: update to peers
|
||||
description: update a peer
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
@ -751,9 +764,12 @@ paths:
|
||||
type: string
|
||||
ssh_enabled:
|
||||
type: boolean
|
||||
login_expiration_enabled:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
- ssh_enabled
|
||||
- login_expiration_enabled
|
||||
responses:
|
||||
'200':
|
||||
description: A Peer object
|
||||
|
@ -21,6 +21,8 @@ const (
|
||||
EventActivityCodeNameserverGroupAdd EventActivityCode = "nameserver.group.add"
|
||||
EventActivityCodeNameserverGroupDelete EventActivityCode = "nameserver.group.delete"
|
||||
EventActivityCodeNameserverGroupUpdate EventActivityCode = "nameserver.group.update"
|
||||
EventActivityCodePeerLoginExpirationDisable EventActivityCode = "peer.login.expiration.disable"
|
||||
EventActivityCodePeerLoginExpirationEnable EventActivityCode = "peer.login.expiration.enable"
|
||||
EventActivityCodePeerRename EventActivityCode = "peer.rename"
|
||||
EventActivityCodePeerSshDisable EventActivityCode = "peer.ssh.disable"
|
||||
EventActivityCodePeerSshEnable EventActivityCode = "peer.ssh.enable"
|
||||
@ -326,9 +328,18 @@ type Peer struct {
|
||||
// Ip Peer's IP address
|
||||
Ip string `json:"ip"`
|
||||
|
||||
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
|
||||
// LastSeen Last time peer connected to Netbird's management service
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
|
||||
// LoginExpirationEnabled Indicates whether peer login expiration has been enabled or not
|
||||
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
||||
|
||||
// LoginExpired Indicates whether peer's login expired or not
|
||||
LoginExpired bool `json:"login_expired"`
|
||||
|
||||
// Name Peer's hostname
|
||||
Name string `json:"name"`
|
||||
|
||||
@ -626,8 +637,9 @@ type PutApiGroupsIdJSONBody struct {
|
||||
|
||||
// PutApiPeersIdJSONBody defines parameters for PutApiPeersId.
|
||||
type PutApiPeersIdJSONBody struct {
|
||||
Name string `json:"name"`
|
||||
SshEnabled bool `json:"ssh_enabled"`
|
||||
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
||||
Name string `json:"name"`
|
||||
SshEnabled bool `json:"ssh_enabled"`
|
||||
}
|
||||
|
||||
// PatchApiRoutesIdJSONBody defines parameters for PatchApiRoutesId.
|
||||
|
@ -47,7 +47,8 @@ func (h *Peers) updatePeer(account *server.Account, user *server.User, peerID st
|
||||
return
|
||||
}
|
||||
|
||||
update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name}
|
||||
update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name,
|
||||
LoginExpirationEnabled: req.LoginExpirationEnabled}
|
||||
peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
@ -150,19 +151,25 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string
|
||||
if fqdn == "" {
|
||||
fqdn = peer.DNSLabel
|
||||
}
|
||||
|
||||
expired, _ := peer.LoginExpired(account.Settings)
|
||||
|
||||
return &api.Peer{
|
||||
Id: peer.ID,
|
||||
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,
|
||||
Hostname: peer.Meta.Hostname,
|
||||
UserId: &peer.UserID,
|
||||
UiVersion: &peer.Meta.UIVersion,
|
||||
DnsLabel: fqdn,
|
||||
Id: peer.ID,
|
||||
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,
|
||||
Hostname: peer.Meta.Hostname,
|
||||
UserId: &peer.UserID,
|
||||
UiVersion: &peer.Meta.UIVersion,
|
||||
DnsLabel: fqdn,
|
||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||
LastLogin: peer.LastLogin,
|
||||
LoginExpired: expired,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
@ -8,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
|
||||
@ -23,6 +25,13 @@ const testPeerID = "test_peer"
|
||||
func initTestMetaData(peers ...*server.Peer) *Peers {
|
||||
return &Peers{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) {
|
||||
p := peers[0].Copy()
|
||||
p.SSHEnabled = update.SSHEnabled
|
||||
p.LoginExpirationEnabled = update.LoginExpirationEnabled
|
||||
p.Name = update.Name
|
||||
return p, nil
|
||||
},
|
||||
GetPeerFunc: func(accountID, peerID, userID string) (*server.Peer, error) {
|
||||
return peers[0], nil
|
||||
},
|
||||
@ -40,6 +49,10 @@ func initTestMetaData(peers ...*server.Peer) *Peers {
|
||||
Users: map[string]*server.User{
|
||||
"test_user": user,
|
||||
},
|
||||
Settings: &server.Settings{
|
||||
PeerLoginExpirationEnabled: true,
|
||||
PeerLoginExpiration: time.Hour,
|
||||
},
|
||||
}, user, nil
|
||||
},
|
||||
},
|
||||
@ -58,38 +71,15 @@ func initTestMetaData(peers ...*server.Peer) *Peers {
|
||||
// Tests the GetPeers endpoint reachable in the route /api/peers
|
||||
// Use the metadata generated by initTestMetaData() to check for values
|
||||
func TestGetPeers(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
expectedArray bool
|
||||
}{
|
||||
{
|
||||
name: "GetPeersMetaData",
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/peers/",
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArray: true,
|
||||
},
|
||||
{
|
||||
name: "GetPeer",
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/peers/" + testPeerID,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArray: false,
|
||||
},
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
peer := &server.Peer{
|
||||
ID: testPeerID,
|
||||
Key: "key",
|
||||
SetupKey: "setupkey",
|
||||
IP: net.ParseIP("100.64.0.1"),
|
||||
Status: &server.PeerStatus{},
|
||||
Name: "PeerName",
|
||||
ID: testPeerID,
|
||||
Key: "key",
|
||||
SetupKey: "setupkey",
|
||||
IP: net.ParseIP("100.64.0.1"),
|
||||
Status: &server.PeerStatus{},
|
||||
Name: "PeerName",
|
||||
LoginExpirationEnabled: false,
|
||||
Meta: server.PeerSystemMeta{
|
||||
Hostname: "hostname",
|
||||
GoOS: "GoOS",
|
||||
@ -101,6 +91,49 @@ func TestGetPeers(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedUpdatedPeer := peer.Copy()
|
||||
expectedUpdatedPeer.LoginExpirationEnabled = true
|
||||
expectedUpdatedPeer.SSHEnabled = true
|
||||
expectedUpdatedPeer.Name = "New Name"
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
requestType string
|
||||
requestPath string
|
||||
requestBody io.Reader
|
||||
expectedArray bool
|
||||
expectedPeer *server.Peer
|
||||
}{
|
||||
{
|
||||
name: "GetPeersMetaData",
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/peers/",
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArray: true,
|
||||
expectedPeer: peer,
|
||||
},
|
||||
{
|
||||
name: "GetPeer",
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/peers/" + testPeerID,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArray: false,
|
||||
expectedPeer: peer,
|
||||
},
|
||||
{
|
||||
name: "PutPeer",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/peers/" + testPeerID,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArray: false,
|
||||
requestBody: bytes.NewBufferString("{\"login_expiration_enabled\":true,\"name\":\"New Name\",\"ssh_enabled\":true}"),
|
||||
expectedPeer: expectedUpdatedPeer,
|
||||
},
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
p := initTestMetaData(peer)
|
||||
|
||||
for _, tc := range tt {
|
||||
@ -112,6 +145,7 @@ func TestGetPeers(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/peers/", p.GetPeers).Methods("GET")
|
||||
router.HandleFunc("/api/peers/{id}", p.HandlePeer).Methods("GET")
|
||||
router.HandleFunc("/api/peers/{id}", p.HandlePeer).Methods("PUT")
|
||||
router.ServeHTTP(recorder, req)
|
||||
|
||||
res := recorder.Result()
|
||||
@ -144,10 +178,12 @@ func TestGetPeers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, got.Name, peer.Name)
|
||||
assert.Equal(t, got.Version, peer.Meta.WtVersion)
|
||||
assert.Equal(t, got.Ip, peer.IP.String())
|
||||
assert.Equal(t, got.Name, tc.expectedPeer.Name)
|
||||
assert.Equal(t, got.Version, tc.expectedPeer.Meta.WtVersion)
|
||||
assert.Equal(t, got.Ip, tc.expectedPeer.IP.String())
|
||||
assert.Equal(t, got.Os, "OS core")
|
||||
assert.Equal(t, got.LoginExpirationEnabled, tc.expectedPeer.LoginExpirationEnabled)
|
||||
assert.Equal(t, got.SshEnabled, tc.expectedPeer.SSHEnabled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -86,15 +86,17 @@ func (p *Peer) Copy() *Peer {
|
||||
}
|
||||
}
|
||||
|
||||
// LoginExpired indicates whether peer's login has expired or not.
|
||||
// If Peer.LastLogin plus the expiresIn duration has happened already then login has expired.
|
||||
// Return true if login has expired, false otherwise and time left to expiration (negative when expired).
|
||||
// Expiration can be disabled/enabled on the Account or Peer level.
|
||||
// LoginExpired indicates whether the peer's login has expired or not.
|
||||
// If Peer.LastLogin plus the expiresIn duration has happened already; then login has expired.
|
||||
// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired).
|
||||
// Login expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property.
|
||||
// Login expiration can also be disabled/enabled globally on the Account level via Settings.PeerLoginExpirationEnabled
|
||||
// and if disabled on the Account level, then Peer.LoginExpirationEnabled is ineffective.
|
||||
func (p *Peer) LoginExpired(accountSettings *Settings) (bool, time.Duration) {
|
||||
expiresAt := p.LastLogin.Add(accountSettings.PeerLoginExpiration)
|
||||
now := time.Now()
|
||||
left := expiresAt.Sub(now)
|
||||
return accountSettings.PeerLoginExpirationEnabled && p.LoginExpirationEnabled && (left <= 0), left
|
||||
timeLeft := expiresAt.Sub(now)
|
||||
return accountSettings.PeerLoginExpirationEnabled && p.LoginExpirationEnabled && (timeLeft <= 0), timeLeft
|
||||
}
|
||||
|
||||
// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain
|
||||
@ -206,7 +208,7 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePeer updates peer. Only Peer.Name and Peer.SSHEnabled can be updated.
|
||||
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated.
|
||||
func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Peer) (*Peer, error) {
|
||||
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
@ -246,6 +248,16 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe
|
||||
am.storeEvent(userID, peer.ID, accountID, activity.PeerRenamed, peer.EventMeta(am.GetDNSDomain()))
|
||||
}
|
||||
|
||||
if peer.LoginExpirationEnabled != update.LoginExpirationEnabled {
|
||||
peer.LoginExpirationEnabled = update.LoginExpirationEnabled
|
||||
|
||||
event := activity.PeerLoginExpirationEnabled
|
||||
if !update.LoginExpirationEnabled {
|
||||
event = activity.PeerLoginExpirationDisabled
|
||||
}
|
||||
am.storeEvent(userID, peer.IP.String(), accountID, event, peer.EventMeta(am.GetDNSDomain()))
|
||||
}
|
||||
|
||||
account.UpdatePeer(peer)
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
|
Loading…
x
Reference in New Issue
Block a user