Skip to content

Commit

Permalink
tailscale: support ACL contents as HuJSON
Browse files Browse the repository at this point in the history
The `acl` argument of the `tailscale_acl` resource can now be a HuJSON
string. Instead of unmarshalling `acl` into an `ACL` struct of the [API
client](https://github.com/tailscale/tailscale-client-go) just to have
the client serialize it into JSON again, policy content gets passed
to the Tailscale API verbatim.

This allows users to define their policy as HuJSON strings, with
comments being preserved. Since JSON is a subset of HuJSON, this is
backwards compatible, so I am not adding a separate field for this as
has been previously suggested in #227.

Validation is now performed by calling the [Validate and test policy
file](https://github.com/tailscale/tailscale/blob/main/api.md#validate-and-test-policy-file)
API, which will help catch any semantic errors in the policy at
`terraform plan` stage (for example, when a syntactically correct policy
contains configuration that is not supported by the Tailnet's current
[pricing plan](https://tailscale.com/pricing)).

Finally, this will also allow users to use new fields in the policy
without requiring a new release of the Terraform provider.

I've also added a new `hujson` field to the `tailscale_acl` data
resource that shows current policy as a HuJSON string.

Fixes #331
Fixes #227

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
  • Loading branch information
knyar committed Feb 15, 2024
1 parent 42d5483 commit 838159a
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 127 deletions.
3 changes: 2 additions & 1 deletion docs/data-sources/acl.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ The acl data source gets the Tailscale ACL for a tailnet

### Read-Only

- `hujson` (String) The contents of Tailscale ACL as a HuJSON string
- `id` (String) The ID of this resource.
- `json` (String) The contents of Tailscale ACL as JSON
- `json` (String) The contents of Tailscale ACL as a JSON string
23 changes: 20 additions & 3 deletions docs/resources/acl.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,42 @@ If tests are defined in the ACL (the top-level "tests" section), ACL validation
## Example Usage

```terraform
resource "tailscale_acl" "sample_acl" {
resource "tailscale_acl" "as_json" {
acl = jsonencode({
acls : [
{
// Allow all users access to all ports.
action = "accept",
users = ["*"],
ports = ["*:*"],
}],
},
],
})
}
resource "tailscale_acl" "as_hujson" {
acl = <<EOF
{
// Comments in HuJSON policy are preserved when the policy is applied.
"acls": [
{
// Allow all users access to all ports.
action = "accept",
users = ["*"],
ports = ["*:*"],
},
],
}
EOF
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `acl` (String) The JSON-based policy that defines which devices and users are allowed to connect in your network
- `acl` (String) The policy that defines which devices and users are allowed to connect in your network. Can be either a JSON or a HuJSON string.

### Optional

Expand Down
21 changes: 19 additions & 2 deletions examples/resources/tailscale_acl/resource.tf
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
resource "tailscale_acl" "sample_acl" {
resource "tailscale_acl" "as_json" {
acl = jsonencode({
acls : [
{
// Allow all users access to all ports.
action = "accept",
users = ["*"],
ports = ["*:*"],
}],
},
],
})
}

resource "tailscale_acl" "as_hujson" {
acl = <<EOF
{
// Comments in HuJSON policy are preserved when the policy is applied.
"acls": [
{
// Allow all users access to all ports.
action = "accept",
users = ["*"],
ports = ["*:*"],
},
],
}
EOF
}
16 changes: 7 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
module github.com/tailscale/terraform-provider-tailscale

go 1.21

toolchain go1.21.0
go 1.22.0

require (
github.com/google/go-cmp v0.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/terraform-plugin-docs v0.18.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.32.0
github.com/stretchr/testify v1.8.4
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/tailscale/tailscale-client-go v1.15.0
github.com/tailscale/tailscale-client-go v1.16.0
golang.org/x/tools v0.17.0
tailscale.com v1.58.2
)
Expand All @@ -31,6 +28,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/hashicorp/cli v1.1.6 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
Expand Down Expand Up @@ -75,12 +73,12 @@ require (
github.com/zclconf/go-cty v1.14.2 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
Expand Down
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/tailscale-client-go v1.15.0 h1:5M1f7k3nOcRbCC6bs3TLDhWyP/Q6OH455ER88QMN7SI=
github.com/tailscale/tailscale-client-go v1.15.0/go.mod h1:RMwBhvUj9/L1gGcX4PTEFPDoHIRtsNkGWEwBUrDHoIg=
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/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 All @@ -215,8 +215,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
Expand All @@ -230,10 +230,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -253,8 +253,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
Expand Down
25 changes: 17 additions & 8 deletions tailscale/data_source_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package tailscale

import (
"context"
"encoding/json"

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

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

Expand All @@ -18,7 +18,12 @@ func dataSourceACL() *schema.Resource {
"json": {
Computed: true,
Type: schema.TypeString,
Description: "The contents of Tailscale ACL as JSON",
Description: "The contents of Tailscale ACL as a JSON string",
},
"hujson": {
Computed: true,
Type: schema.TypeString,
Description: "The contents of Tailscale ACL as a HuJSON string",
},
},
}
Expand All @@ -27,17 +32,21 @@ func dataSourceACL() *schema.Resource {
func dataSourceACLRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*tailscale.Client)

acl, err := client.ACL(ctx)
acl, err := client.RawACL(ctx)
if err != nil {
return diagnosticsError(err, "Failed to fetch ACL")
}

aclJson, err := json.Marshal(acl)
huj, err := hujson.Parse([]byte(acl))
if err != nil {
return diag.FromErr(err)
return diagnosticsError(err, "Failed to parse ACL as HuJSON")
}
if err := d.Set("hujson", huj.String()); err != nil {
return diagnosticsError(err, "Failed to set 'hujson'")
}
if err := d.Set("json", string(aclJson)); err != nil {
return diag.Errorf("setting json: %s", err)

huj.Minimize()
if err := d.Set("json", huj.String()); err != nil {
return diagnosticsError(err, "Failed to set 'json'")
}

d.SetId(createUUID())
Expand Down
Loading

0 comments on commit 838159a

Please sign in to comment.