mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-09 15:25:20 +02:00
[management] REST client impersonation (#3879)
This commit is contained in:
@ -86,6 +86,7 @@ func NewWithBearerToken(managementURL, token string) *Client {
|
||||
)
|
||||
}
|
||||
|
||||
// NewWithOptions initialize new Client instance with options
|
||||
func NewWithOptions(opts ...option) *Client {
|
||||
client := &Client{
|
||||
httpClient: http.DefaultClient,
|
||||
@ -115,6 +116,7 @@ func (c *Client) initialize() {
|
||||
c.Events = &EventsAPI{c}
|
||||
}
|
||||
|
||||
// NewRequest creates and executes new management API request
|
||||
func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, c.managementURL+path, body)
|
||||
if err != nil {
|
||||
|
48
management/client/rest/impersonation.go
Normal file
48
management/client/rest/impersonation.go
Normal file
@ -0,0 +1,48 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Impersonate returns a Client impersonated for a specific account
|
||||
func (c *Client) Impersonate(account string) *Client {
|
||||
client := NewWithOptions(
|
||||
WithManagementURL(c.managementURL),
|
||||
WithAuthHeader(c.authHeader),
|
||||
WithHttpClient(newImpersonatedHttpClient(c, account)),
|
||||
)
|
||||
return client
|
||||
}
|
||||
|
||||
type impersonatedHttpClient struct {
|
||||
baseClient HttpClient
|
||||
account string
|
||||
}
|
||||
|
||||
func newImpersonatedHttpClient(c *Client, account string) *impersonatedHttpClient {
|
||||
if hc, ok := c.httpClient.(*impersonatedHttpClient); ok {
|
||||
hc.account = account
|
||||
return hc
|
||||
}
|
||||
|
||||
return &impersonatedHttpClient{
|
||||
baseClient: c.httpClient,
|
||||
account: account,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *impersonatedHttpClient) Do(req *http.Request) (*http.Response, error) {
|
||||
parsedURL, err := url.Parse(req.URL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := parsedURL.Query()
|
||||
query.Set("account", c.account)
|
||||
parsedURL.RawQuery = query.Encode()
|
||||
|
||||
req.URL = parsedURL
|
||||
|
||||
return c.baseClient.Do(req)
|
||||
}
|
77
management/client/rest/impersonation_test.go
Normal file
77
management/client/rest/impersonation_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package rest_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/management/client/rest"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
)
|
||||
|
||||
var (
|
||||
testImpersonatedAccount = api.Account{
|
||||
Id: "ImpersonatedTest",
|
||||
Settings: api.AccountSettings{
|
||||
Extra: &api.AccountExtraSettings{
|
||||
PeerApprovalEnabled: false,
|
||||
},
|
||||
GroupsPropagationEnabled: ptr(true),
|
||||
JwtGroupsEnabled: ptr(false),
|
||||
PeerInactivityExpiration: 7,
|
||||
PeerInactivityExpirationEnabled: true,
|
||||
PeerLoginExpiration: 24,
|
||||
PeerLoginExpirationEnabled: true,
|
||||
RegularUsersViewBlocked: false,
|
||||
RoutingPeerDnsResolutionEnabled: ptr(false),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestImpersonation_Peers_List_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
impersonatedClient := c.Impersonate(testImpersonatedAccount.Id)
|
||||
mux.HandleFunc("/api/peers", func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, r.URL.Query().Get("account"), testImpersonatedAccount.Id)
|
||||
retBytes, _ := json.Marshal([]api.Peer{testPeer})
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := impersonatedClient.Peers.List(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, ret, 1)
|
||||
assert.Equal(t, testPeer, ret[0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestImpersonation_Change_Account(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
impersonatedClient := c.Impersonate(testImpersonatedAccount.Id)
|
||||
mux.HandleFunc("/api/peers", func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, r.URL.Query().Get("account"), testImpersonatedAccount.Id)
|
||||
retBytes, _ := json.Marshal([]api.Peer{testPeer})
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
_, err := impersonatedClient.Peers.List(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
impersonatedClient = impersonatedClient.Impersonate("another-test-account")
|
||||
mux.HandleFunc("/api/peers/Test", func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, r.URL.Query().Get("account"), "another-test-account")
|
||||
retBytes, _ := json.Marshal(testPeer)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
_, err = impersonatedClient.Peers.Get(context.Background(), "Test")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
@ -2,32 +2,41 @@ package rest
|
||||
|
||||
import "net/http"
|
||||
|
||||
// option modifier for creation of Client
|
||||
type option func(*Client)
|
||||
|
||||
// HTTPClient interface for HTTP client
|
||||
type HttpClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// WithHTTPClient overrides HTTPClient used
|
||||
func WithHttpClient(client HttpClient) option {
|
||||
return func(c *Client) {
|
||||
c.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
// WithBearerToken uses provided bearer token acquired from SSO for authentication
|
||||
func WithBearerToken(token string) option {
|
||||
return WithAuthHeader("Bearer " + token)
|
||||
}
|
||||
|
||||
// WithPAT uses provided Personal Access Token
|
||||
// (created from NetBird Management Dashboard) for authentication
|
||||
func WithPAT(token string) option {
|
||||
return WithAuthHeader("Token " + token)
|
||||
}
|
||||
|
||||
// WithManagementURL overrides target NetBird Management server
|
||||
func WithManagementURL(url string) option {
|
||||
return func(c *Client) {
|
||||
c.managementURL = url
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthHeader overrides auth header completely, this should generally not be used
|
||||
// and WithBearerToken or WithPAT should be used instead
|
||||
func WithAuthHeader(value string) option {
|
||||
return func(c *Client) {
|
||||
c.authHeader = value
|
||||
|
@ -45,7 +45,7 @@ type Settings struct {
|
||||
// Extra is a dictionary of Account settings
|
||||
Extra *ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"`
|
||||
|
||||
// LazyConnectionEnabled indicates wether the experimental feature is enabled or disabled
|
||||
// LazyConnectionEnabled indicates if the experimental feature is enabled or disabled
|
||||
LazyConnectionEnabled bool `gorm:"default:false"`
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user