Skip to content

Commit

Permalink
tailscale: use V2 client for device authorization
Browse files Browse the repository at this point in the history
Updates tailscale/corp#21867

Signed-off-by: Percy Wegmann <percy@tailscale.com>
  • Loading branch information
oxtoacart committed Aug 2, 2024
1 parent 95ac057 commit 4823b76
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 64 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/stretchr/testify v1.9.0
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/tailscale/tailscale-client-go v1.17.1-0.20240729175651-90a1e935cc19
github.com/tailscale/tailscale-client-go/v2 v2.0.0-20240801195603-6096900af9df
github.com/tailscale/tailscale-client-go/v2 v2.0.0-20240802162120-6b9dd0c993f7
golang.org/x/tools v0.23.0
tailscale.com v1.70.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29X
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/tailscale-client-go v1.17.1-0.20240729175651-90a1e935cc19 h1:fRLv1yZH1ueL1cnpLhOnOymoBfMCIviCn0e0VkAjkK4=
github.com/tailscale/tailscale-client-go v1.17.1-0.20240729175651-90a1e935cc19/go.mod h1:jbwJyHniK3nyLttwcDTXnfdDQEnADvc4VMOP8hZWnR0=
github.com/tailscale/tailscale-client-go/v2 v2.0.0-20240801195603-6096900af9df h1:XjHI0pFxhttM1nEn/WNGOO3wrJYEJSZarJm0i5WFXgY=
github.com/tailscale/tailscale-client-go/v2 v2.0.0-20240801195603-6096900af9df/go.mod h1:i/MSgQ71kdyh1Wdp50XxrIgtsyO4uZ2SZSPd83lGKHM=
github.com/tailscale/tailscale-client-go/v2 v2.0.0-20240802162120-6b9dd0c993f7 h1:+TDfp+C1iJfFA835NnP2yzrlwK1NneYFVoEG07bocmM=
github.com/tailscale/tailscale-client-go/v2 v2.0.0-20240802162120-6b9dd0c993f7/go.mod h1:i/MSgQ71kdyh1Wdp50XxrIgtsyO4uZ2SZSPd83lGKHM=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
Expand Down
50 changes: 10 additions & 40 deletions tailscale/resource_device_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/tailscale/tailscale-client-go/tailscale"
)

func resourceDeviceAuthorization() *schema.Resource {
Expand All @@ -32,30 +30,16 @@ func resourceDeviceAuthorization() *schema.Resource {
}

func resourceDeviceAuthorizationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*Clients).V1
client := m.(*Clients).V2
deviceID := d.Get("device_id").(string)

devices, err := client.Devices(ctx)
device, err := client.Devices().Get(ctx, deviceID)
if err != nil {
return diagnosticsError(err, "Failed to fetch devices")
}

var selected *tailscale.Device
for _, device := range devices {
if device.ID != deviceID {
continue
}

selected = &device
break
}

if selected == nil {
return diag.Errorf("Could not find device with id %s", deviceID)
return diagnosticsError(err, "Failed to fetch device")
}

d.SetId(selected.ID)
d.Set("authorized", selected.Authorized)
d.SetId(device.ID)
d.Set("authorized", device.Authorized)
return nil
}

Expand All @@ -75,36 +59,22 @@ func resourceDeviceAuthorizationCreate(ctx context.Context, d *schema.ResourceDa
}

func resourceDeviceAuthorizationUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*Clients).V1
client := m.(*Clients).V2
deviceID := d.Get("device_id").(string)

devices, err := client.Devices(ctx)
device, err := client.Devices().Get(ctx, deviceID)
if err != nil {
return diagnosticsError(err, "Failed to fetch devices")
}

var selected *tailscale.Device
for _, device := range devices {
if device.ID != deviceID {
continue
}

selected = &device
break
}

if selected == nil {
return diag.Errorf("Could not find device with id %s", deviceID)
return diagnosticsError(err, "Failed to fetch device")
}

// Currently, the Tailscale API only supports authorizing a device, but not un-authorizing one. So if the device
// data from the API states it is authorized then we can't do anything else here.
if selected.Authorized {
if device.Authorized {
d.Set("authorized", true)
return nil
}

if err = client.AuthorizeDevice(ctx, deviceID); err != nil {
if err = client.Devices().SetAuthorized(ctx, deviceID, true); err != nil {
return diagnosticsError(err, "Failed to authorize device")
}

Expand Down
129 changes: 108 additions & 21 deletions tailscale/resource_device_authorization_test.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,129 @@
package tailscale_test

import (
"net/http"
"context"
"errors"
"fmt"
"os"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

"github.com/tailscale/tailscale-client-go/tailscale"
tsclient "github.com/tailscale/tailscale-client-go/v2"
"github.com/tailscale/terraform-provider-tailscale/tailscale"
)

const testDeviceAuthorization = `
var testDeviceAuthorization = fmt.Sprintf(`
data "tailscale_device" "test_device" {
name = "device.example.com"
name = "%s"
}
resource "tailscale_device_authorization" "test_authorization" {
device_id = data.tailscale_device.test_device.id
authorized = true
}`
}`, os.Getenv("TAILSCALE_TEST_DEVICE_NAME"))

var expectedDeviceAuthorizationBasic = &tsclient.Device{
Authorized: true,
}

func TestAccTailscaleDeviceAuthorization_Basic(t *testing.T) {
device := &tsclient.Device{}

func TestProvider_TailscaleDeviceAuthorization(t *testing.T) {
resource.Test(t, resource.TestCase{
IsUnitTest: true,
PreCheck: func() {
testServer.ResponseCode = http.StatusOK
testServer.ResponseBody = map[string][]tailscale.Device{
"devices": {
{
Name: "device.example.com",
ID: "123",
},
},
}
},
ProviderFactories: testProviderFactories(t),
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories(t),
CheckDestroy: testAccCheckDeviceAuthorizationDestroyBasic,
Steps: []resource.TestStep{
testResourceCreated("tailscale_device_authorization.test_authorization", testDeviceAuthorization),
testResourceDestroyed("tailscale_device_authorization.test_authorization", testDeviceAuthorization),
{
Config: testDeviceAuthorization,
Check: resource.ComposeTestCheckFunc(
testAccCheckAccountAuthorizationExists("tailscale_device_authorization.test_authorization", device),
testAccCheckDeviceAuthorizationBasic(device),
resource.TestCheckResourceAttr("tailscale_device_authorization.test_authorization", "authorized", "true"),
),
},
},
})
}

func testAccCheckAccountAuthorizationExists(resourceName string, device *tsclient.Device) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}

if rs.Primary.ID == "" {
return fmt.Errorf("resource has no ID set")
}

out, err := getDevice(rs)
if err != nil {
return err
}

*device = *out
return nil
}
}

func testAccCheckDeviceAuthorizationBasic(device *tsclient.Device) resource.TestCheckFunc {
return func(s *terraform.State) error {
if err := checkDeviceAuthorization(device, expectedDeviceAuthorizationBasic); err != nil {
return err
}

return nil
}
}

func testAccCheckDeviceAuthorizationDestroyBasic(s *terraform.State) error {
return testAccCheckDeviceAuthorizationDestroy(s, expectedDeviceAuthorizationBasic)
}

func testAccCheckDeviceAuthorizationDestroy(s *terraform.State, expected *tsclient.Device) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "tailscale_device_authorization" {
continue
}

if rs.Primary.ID == "" {
return fmt.Errorf("resource has no ID set")
}

device, err := getDevice(rs)
if err != nil {
return err
}

return checkDeviceAuthorization(device, expected)
}
return nil
}

func getDevice(rs *terraform.ResourceState) (*tsclient.Device, error) {
client := testAccProvider.Meta().(*tailscale.Clients).V2

devices, err := client.Devices().List(context.Background())
if err != nil {
return nil, err
}

for _, device := range devices {
if device.ID == rs.Primary.ID {
return &device, nil
}
}

return nil, errors.New("device not found")
}

func checkDeviceAuthorization(actual *tsclient.Device, expected *tsclient.Device) error {
if actual.Authorized != expected.Authorized {
return fmt.Errorf("bad authorization status, expected %v, got %v", expected.Authorized, actual.Authorized)
}

return nil
}

0 comments on commit 4823b76

Please sign in to comment.