From 8cb204b20b8357e3ebe233b9ec31dda9f67f0356 Mon Sep 17 00:00:00 2001 From: Mario Minardi Date: Wed, 24 Apr 2024 12:14:51 -0600 Subject: [PATCH] tailscale: add `dns_split_nameservers` resource Add `resource_dns_split_nameservers` to allow for controlling split DNS settings for a given tailnet. Updates https://github.com/tailscale/corp/issues/19483 Signed-off-by: Mario Minardi --- docs/resources/dns_split_nameservers.md | 33 ++++++ .../resource.tf | 5 + go.mod | 2 +- go.sum | 2 + tailscale/provider.go | 19 +-- tailscale/resource_dns_split_nameservers.go | 112 ++++++++++++++++++ .../resource_dns_split_nameservers_test.go | 29 +++++ 7 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 docs/resources/dns_split_nameservers.md create mode 100644 examples/resources/tailscale_dns_split_nameservers/resource.tf create mode 100644 tailscale/resource_dns_split_nameservers.go create mode 100644 tailscale/resource_dns_split_nameservers_test.go diff --git a/docs/resources/dns_split_nameservers.md b/docs/resources/dns_split_nameservers.md new file mode 100644 index 00000000..b7581fd3 --- /dev/null +++ b/docs/resources/dns_split_nameservers.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "tailscale_dns_split_nameservers Resource - terraform-provider-tailscale" +subcategory: "" +description: |- + The dnssplitnameservers resource allows you to configure split DNS nameservers for your Tailscale network. See https://tailscale.com/kb/1054/dns for more information. +--- + +# tailscale_dns_split_nameservers (Resource) + +The dns_split_nameservers resource allows you to configure split DNS nameservers for your Tailscale network. See https://tailscale.com/kb/1054/dns for more information. + +## Example Usage + +```terraform +resource "tailscale_dns_split_nameservers" "sample_split_nameservers" { + domain = "foo.example.com" + + nameservers = ["1.1.1.1"] +} +``` + + +## Schema + +### Required + +- `domain` (String) Domain to configure split DNS for. Requests for this domain will be resolved using the provided nameservers. +- `nameservers` (Set of String) Devices on your network will use these nameservers to resolve DNS names. IPv4 or IPv6 addresses are accepted. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/examples/resources/tailscale_dns_split_nameservers/resource.tf b/examples/resources/tailscale_dns_split_nameservers/resource.tf new file mode 100644 index 00000000..f2d36a26 --- /dev/null +++ b/examples/resources/tailscale_dns_split_nameservers/resource.tf @@ -0,0 +1,5 @@ +resource "tailscale_dns_split_nameservers" "sample_split_nameservers" { + domain = "foo.example.com" + + nameservers = ["1.1.1.1"] +} diff --git a/go.mod b/go.mod index 29cdb7bc..87a74416 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 github.com/stretchr/testify v1.9.0 github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a - github.com/tailscale/tailscale-client-go v1.16.0 + github.com/tailscale/tailscale-client-go v1.16.1-0.20240424180809-5a248d9547d4 golang.org/x/tools v0.18.0 tailscale.com v1.60.1 ) diff --git a/go.sum b/go.sum index eec2c632..5c38aa68 100644 --- a/go.sum +++ b/go.sum @@ -188,6 +188,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.16.0 h1:mipLVbha5SAVGZT/h3I5qZMrrGXz0iAw4QWXKDfe0l0= github.com/tailscale/tailscale-client-go v1.16.0/go.mod h1:v7ssNztaIngJixKoWAgnfhEbzOyRVPI5qT/niBcoI4k= +github.com/tailscale/tailscale-client-go v1.16.1-0.20240424180809-5a248d9547d4 h1:25DO9pOi+kHDkQzuUEbPuQY9fSClaMZBG04f/pKuhhg= +github.com/tailscale/tailscale-client-go v1.16.1-0.20240424180809-5a248d9547d4/go.mod h1:v7ssNztaIngJixKoWAgnfhEbzOyRVPI5qT/niBcoI4k= 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= diff --git a/tailscale/provider.go b/tailscale/provider.go index 2754b72b..453e7672 100644 --- a/tailscale/provider.go +++ b/tailscale/provider.go @@ -66,15 +66,16 @@ func Provider(options ...ProviderOption) *schema.Provider { }, }, ResourcesMap: map[string]*schema.Resource{ - "tailscale_acl": resourceACL(), - "tailscale_dns_nameservers": resourceDNSNameservers(), - "tailscale_dns_preferences": resourceDNSPreferences(), - "tailscale_dns_search_paths": resourceDNSSearchPaths(), - "tailscale_device_subnet_routes": resourceDeviceSubnetRoutes(), - "tailscale_device_authorization": resourceDeviceAuthorization(), - "tailscale_tailnet_key": resourceTailnetKey(), - "tailscale_device_tags": resourceDeviceTags(), - "tailscale_device_key": resourceDeviceKey(), + "tailscale_acl": resourceACL(), + "tailscale_dns_nameservers": resourceDNSNameservers(), + "tailscale_dns_preferences": resourceDNSPreferences(), + "tailscale_dns_search_paths": resourceDNSSearchPaths(), + "tailscale_dns_split_nameservers": resourceDNSSplitNameservers(), + "tailscale_device_subnet_routes": resourceDeviceSubnetRoutes(), + "tailscale_device_authorization": resourceDeviceAuthorization(), + "tailscale_tailnet_key": resourceTailnetKey(), + "tailscale_device_tags": resourceDeviceTags(), + "tailscale_device_key": resourceDeviceKey(), }, DataSourcesMap: map[string]*schema.Resource{ "tailscale_device": dataSourceDevice(), diff --git a/tailscale/resource_dns_split_nameservers.go b/tailscale/resource_dns_split_nameservers.go new file mode 100644 index 00000000..1de3a093 --- /dev/null +++ b/tailscale/resource_dns_split_nameservers.go @@ -0,0 +1,112 @@ +package tailscale + +import ( + "context" + + "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 resourceDNSSplitNameservers() *schema.Resource { + return &schema.Resource{ + Description: "The dns_split_nameservers resource allows you to configure split DNS nameservers for your Tailscale network. See https://tailscale.com/kb/1054/dns for more information.", + ReadContext: resourceSplitDNSNameserversRead, + CreateContext: resourceSplitDNSNameserversCreate, + UpdateContext: resourceSplitDNSNameserversUpdate, + DeleteContext: resourceSplitDNSNameserversDelete, + Schema: map[string]*schema.Schema{ + "domain": { + Type: schema.TypeString, + Description: "Domain to configure split DNS for. Requests for this domain will be resolved using the provided nameservers.", + Required: true, + }, + "nameservers": { + Type: schema.TypeSet, + Description: "Devices on your network will use these nameservers to resolve DNS names. IPv4 or IPv6 addresses are accepted.", + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceSplitDNSNameserversRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*tailscale.Client) + splitDNS, err := client.DNSSplitDNS(ctx) + if err != nil { + return diagnosticsError(err, "Failed to fetch split DNS configs") + } + + nameservers := splitDNS[d.Get("domain").(string)] + + if err = d.Set("nameservers", nameservers); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSplitDNSNameserversCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*tailscale.Client) + nameserversSet := d.Get("nameservers").(*schema.Set) + domain := d.Get("domain").(string) + + nameserversList := nameserversSet.List() + + req := make(tailscale.SplitDnsRequest) + var nameservers []string + for _, nameserver := range nameserversList { + nameservers = append(nameservers, nameserver.(string)) + } + req[domain] = &nameservers + + if err := client.UpdateDNSSplitDNS(ctx, req); err != nil { + return diagnosticsError(err, "Failed to set dns split nameservers") + } + + d.SetId(domain) + return nil +} + +func resourceSplitDNSNameserversUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + if !d.HasChange("nameservers") { + return resourceSplitDNSNameserversRead(ctx, d, m) + } + + client := m.(*tailscale.Client) + domain := d.Get("domain").(string) + nameserversSet := d.Get("nameservers").(*schema.Set) + + nameserversList := nameserversSet.List() + + req := make(tailscale.SplitDnsRequest) + var nameservers []string + for _, nameserver := range nameserversList { + nameservers = append(nameservers, nameserver.(string)) + } + req[domain] = &nameservers + + if err := client.UpdateDNSSplitDNS(ctx, req); err != nil { + return diagnosticsError(err, "Failed to set dns split nameservers") + } + + return nil +} + +func resourceSplitDNSNameserversDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(*tailscale.Client) + domain := d.Get("domain").(string) + + req := make(tailscale.SplitDnsRequest) + req[domain] = &[]string{} + + if err := client.UpdateDNSSplitDNS(ctx, req); err != nil { + return diagnosticsError(err, "Failed to set dns split nameservers") + } + + return nil +} diff --git a/tailscale/resource_dns_split_nameservers_test.go b/tailscale/resource_dns_split_nameservers_test.go new file mode 100644 index 00000000..1b44645b --- /dev/null +++ b/tailscale/resource_dns_split_nameservers_test.go @@ -0,0 +1,29 @@ +package tailscale_test + +import ( + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const testSplitNameservers = ` + resource "tailscale_dns_split_nameservers" "test_nameservers" { + domain = "example.com" + nameservers = ["1.2.3.4", "4.5.6.7"] + }` + +func TestProvider_TailscaleSplitDNSNameservers(t *testing.T) { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + PreCheck: func() { + testServer.ResponseCode = http.StatusOK + testServer.ResponseBody = nil + }, + ProviderFactories: testProviderFactories(t), + Steps: []resource.TestStep{ + testResourceCreated("tailscale_dns_split_nameservers.test_nameservers", testSplitNameservers), + testResourceDestroyed("tailscale_dns_split_nameservers.test_nameservers", testSplitNameservers), + }, + }) +}