Skip to content

Commit

Permalink
v2: add TailnetSettingsResource
Browse files Browse the repository at this point in the history
Supports getting and updating Tailnet settings.

Updates tailscale/corp#21628

Signed-off-by: Percy Wegmann <[email protected]>
  • Loading branch information
oxtoacart committed Aug 19, 2024
1 parent 2a9680f commit 3a9fb56
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 9 deletions.
25 changes: 16 additions & 9 deletions v2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ type (
initOnce sync.Once

// Specific resources
contacts *ContactsResource
devicePosture *DevicePostureResource
devices *DevicesResource
dns *DNSResource
keys *KeysResource
logging *LoggingResource
policyFile *PolicyFileResource
users *UsersResource
webhooks *WebhooksResource
contacts *ContactsResource
devicePosture *DevicePostureResource
devices *DevicesResource
dns *DNSResource
keys *KeysResource
logging *LoggingResource
policyFile *PolicyFileResource
tailnetSettings *TailnetSettingsResource
users *UsersResource
webhooks *WebhooksResource
}

// APIError type describes an error as returned by the Tailscale API.
Expand Down Expand Up @@ -105,6 +106,7 @@ func (c *Client) init() {
c.keys = &KeysResource{c}
c.logging = &LoggingResource{c}
c.policyFile = &PolicyFileResource{c}
c.tailnetSettings = &TailnetSettingsResource{c}
c.users = &UsersResource{c}
c.webhooks = &WebhooksResource{c}
})
Expand Down Expand Up @@ -159,6 +161,11 @@ func (c *Client) PolicyFile() *PolicyFileResource {
return c.policyFile
}

func (c *Client) TailnetSettings() *TailnetSettingsResource {
c.init()
return c.tailnetSettings
}

func (c *Client) Users() *UsersResource {
c.init()
return c.users
Expand Down
76 changes: 76 additions & 0 deletions v2/tailnet_settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package tsclient

import (
"context"
"net/http"
)

// TailnetSettingsResource provides an API to view/control settings for a tailnet.
type TailnetSettingsResource struct {
*Client
}

type (
// TailnetSettings represents the current settings of a tailnet.
// See https://tailscale.com/api#model/tailnetsettings.
TailnetSettings struct {
DevicesApprovalOn bool `json:"devicesApprovalOn"`
DevicesAutoUpdatesOn bool `json:"devicesAutoUpdatesOn"`
DevicesKeyDurationDays int `json:"devicesKeyDurationDays"` // days before device key expiry

UsersApprovalOn bool `json:"usersApprovalOn"`
UsersRoleAllowedToJoinExternalTailnets RoleAllowedToJoinExternalTailnets `json:"usersRoleAllowedToJoinExternalTailnets"`

NetworkFlowLoggingOn bool `json:"networkFlowLoggingOn"`
RegionalRoutingOn bool `json:"regionalRoutingOn"`
PostureIdentityCollectionOn bool `json:"postureIdentityCollectionOn"`
}

// UpdateTailnetSettingsRequest is a request to update the settings of a tailnet.
// Nil values indicate that the existing setting should be left unchanged.
UpdateTailnetSettingsRequest struct {
DevicesApprovalOn *bool `json:"devicesApprovalOn,omitempty"`
DevicesAutoUpdatesOn *bool `json:"devicesAutoUpdatesOn,omitempty"`
DevicesKeyDurationDays *int `json:"devicesKeyDurationDays,omitempty"` // days before device key expiry

UsersApprovalOn *bool `json:"usersApprovalOn,omitempty"`
UsersRoleAllowedToJoinExternalTailnets *RoleAllowedToJoinExternalTailnets `json:"usersRoleAllowedToJoinExternalTailnets,omitempty"`

NetworkFlowLoggingOn *bool `json:"networkFlowLoggingOn,omitempty"`
RegionalRoutingOn *bool `json:"regionalRoutingOn,omitempty"`
PostureIdentityCollectionOn *bool `json:"postureIdentityCollectionOn,omitempty"`
}

// RoleAllowedToJoinExternalTailnets constrains which users are allowed to join external tailnets
// based on their role.
RoleAllowedToJoinExternalTailnets string
)

const (
RoleAllowedToJoinExternalTailnetsNone RoleAllowedToJoinExternalTailnets = "none"
RoleAllowedToJoinExternalTailnetsAdmin RoleAllowedToJoinExternalTailnets = "admin"
RoleAllowedToJoinExternalTailnetsMember RoleAllowedToJoinExternalTailnets = "member"
)

// Get retrieves the current TailnetSettings.
// See https://tailscale.com/api#tag/tailnetsettings/GET/tailnet/{tailnet}/settings.
func (tsr *TailnetSettingsResource) Get(ctx context.Context) (*TailnetSettings, error) {
req, err := tsr.buildRequest(ctx, http.MethodGet, tsr.buildTailnetURL("settings"))
if err != nil {
return nil, err
}

var resp TailnetSettings
return &resp, tsr.do(req, &resp)
}

// Update updates the tailnet settings.
// See https://tailscale.com/api#tag/tailnetsettings/PATCH/tailnet/{tailnet}/settings.
func (tsr *TailnetSettingsResource) Update(ctx context.Context, request UpdateTailnetSettingsRequest) error {
req, err := tsr.buildRequest(ctx, http.MethodPatch, tsr.buildTailnetURL("settings"), requestBody(request))
if err != nil {
return err
}

return tsr.do(req, nil)
}
63 changes: 63 additions & 0 deletions v2/tailnet_settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package tsclient_test

import (
"context"
"encoding/json"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
tsclient "github.com/tailscale/tailscale-client-go/v2"
)

func TestClient_TailnetSettings_Get(t *testing.T) {
t.Parallel()

client, server := NewTestHarness(t)
server.ResponseCode = http.StatusOK

expected := tsclient.TailnetSettings{
DevicesApprovalOn: true,
DevicesAutoUpdatesOn: true,
DevicesKeyDurationDays: 5,
UsersApprovalOn: true,
UsersRoleAllowedToJoinExternalTailnets: tsclient.RoleAllowedToJoinExternalTailnetsMember,
NetworkFlowLoggingOn: true,
RegionalRoutingOn: true,
PostureIdentityCollectionOn: true,
}
server.ResponseBody = expected

actual, err := client.TailnetSettings().Get(context.Background())
assert.NoError(t, err)
assert.Equal(t, http.MethodGet, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/settings", server.Path)
assert.Equal(t, &expected, actual)
}

func TestClient_TailnetSettings_Update(t *testing.T) {
t.Parallel()

client, server := NewTestHarness(t)
server.ResponseCode = http.StatusOK
server.ResponseBody = nil

updateRequest := tsclient.UpdateTailnetSettingsRequest{
DevicesApprovalOn: tsclient.PointerTo(true),
DevicesAutoUpdatesOn: tsclient.PointerTo(true),
DevicesKeyDurationDays: tsclient.PointerTo(5),
UsersApprovalOn: tsclient.PointerTo(true),
UsersRoleAllowedToJoinExternalTailnets: tsclient.PointerTo(tsclient.RoleAllowedToJoinExternalTailnetsMember),
NetworkFlowLoggingOn: tsclient.PointerTo(true),
RegionalRoutingOn: tsclient.PointerTo(true),
PostureIdentityCollectionOn: tsclient.PointerTo(true),
}
err := client.TailnetSettings().Update(context.Background(), updateRequest)
assert.NoError(t, err)
assert.Equal(t, http.MethodPatch, server.Method)
assert.Equal(t, "/api/v2/tailnet/example.com/settings", server.Path)
var receivedRequest tsclient.UpdateTailnetSettingsRequest
err = json.Unmarshal(server.Body.Bytes(), &receivedRequest)
assert.NoError(t, err)
assert.EqualValues(t, updateRequest, receivedRequest)
}

0 comments on commit 3a9fb56

Please sign in to comment.