diff --git a/README.md b/README.md index 3a0e63f..0cc1d5d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,53 @@ resource "zerotier_network" "your_network" { If you don't specify either an assignment pool or a managed route, while it's perfectly valid, your network won't be very useful, so try to do both. +Full list of properties: + +```hcl +resource "zerotier_network" "your_network" { + name = "your_network_name" + + # Optional values + # description = "Managed by Terraform" + # rules_source = "Default rule pulled from ZeroTier" + + # private = true + + # Assign IPv4 addresses from the assignment_pool + # auto_assign_v4 = true + + # Effectively assign IPv6 using RFC4193 (/128 for each device) + # auto_assign_rfc4193 = true + + # Effectively assign IPv6 using ZeroTier's 6PLANE scheme (/80 routable for each device) + # auto_assign_6plane = false + + # Assign IPv6 addresses from the assignment_pool + # auto_assign_v6 = false + + # Multiple assignment pools allowed + # assignment_pool { + # cidr = "IPv4 or IPv6 CIDR notation" + # } + # assignment_pool { + # first = "IPv4 or IPv6 address" # eg 10.96.0.2 + # last = "IPv4 or IPv6 address" # eg 10.96.0.254 + # } + + # Multiple routes configuration allowed + # route { + # target = "${var.zt_cidr}" + # } + # route { + # target = "${var.other_network}" + # via = "${local.gateway_ip}" + # } + + # Computed + # id: Network ID +} +``` + #### Multiple routes You can have more than one assignment pool, and more than one route. Multiple @@ -251,6 +298,16 @@ resource "zerotier_member" "hector" { # see ZeroTier Manual section on L2/ethernet bridging allow_ethernet_bridging = true + # Computed properties available to interpolate + + # rfc4193_address + # Computed RFC4193 (IPv6 /128) address based on the network and node id + # Always calculated, and determined if they are used by the network resource + + # 6plane_address + # Computed 6PLANE (IPv6 /80) address based on the network and node id + # Always calculated, and determined if they are used by the network resource + } ``` diff --git a/zerotier/client.go b/zerotier/client.go index a74fc4e..0aeb2b4 100644 --- a/zerotier/client.go +++ b/zerotier/client.go @@ -31,12 +31,19 @@ type V4AssignModeConfig struct { ZT bool `json:"zt"` } +type V6AssignModeConfig struct { + ZT bool `json:"zt"` + SixPLANE bool `json:"6plane"` + RFC4193 bool `json:"rfc4193"` +} + type Config struct { Name string `json:"name"` Private bool `json:"private"` Routes []Route `json:"routes"` IpAssignmentPools []IpRange `json:"ipAssignmentPools"` V4AssignMode V4AssignModeConfig `json:"v4AssignMode"` + V6AssignMode V6AssignModeConfig `json:"v6AssignMode"` } type ConfigReadOnly struct { diff --git a/zerotier/resource_zerotier_member.go b/zerotier/resource_zerotier_member.go index d72c8c8..227c92d 100644 --- a/zerotier/resource_zerotier_member.go +++ b/zerotier/resource_zerotier_member.go @@ -65,12 +65,23 @@ func resourceZeroTierMember() *schema.Resource { Default: false, }, "ip_assignments": { - Type: schema.TypeList, - Optional: true, + Type: schema.TypeSet, + Description: "List of IP routed and assigned byt ZeroTier controller assignment pool. Does not include RFC4193 nor 6PLANE addresses, only those from assignment pool or manually provided.", + Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, }, }, + "rfc4193_address": { + Type: schema.TypeString, + Description: "Computed RFC4193 (IPv6 /128) address. Always calculated and only actually assigned on the member if RFC4193 is configured on the network.", + Computed: true, + }, + "6plane_address": { + Type: schema.TypeString, + Description: "Computed 6PLANE (IPv6 /80) address. Always calculated and only actually assigned on the member if 6PLANE is configured on the network.", + Computed: true, + }, "capabilities": { Type: schema.TypeList, Optional: true, @@ -152,7 +163,7 @@ func memberFromResourceData(d *schema.ResourceData) (*Member, error) { for i := range capsRaw { caps[i] = capsRaw[i].(int) } - ipsRaw := d.Get("ip_assignments").([]interface{}) + ipsRaw := d.Get("ip_assignments").(*schema.Set).List() ips := make([]string, len(ipsRaw)) for i := range ipsRaw { ips[i] = ipsRaw[i].(string) @@ -193,6 +204,33 @@ func resourceNetworkAndNodeIdentifiers(d *schema.ResourceData) (string, string) return nwid, nodeID } +// Receive a string and format every 4th element with a ":" +func buildIPV6(data string) (result string) { + s := strings.SplitAfter(data, "") + end := len(s) - 1 + result = "" + for i, s := range s { + result += s + if (i+1)%4 == 0 && i != end { + result += ":" + } + } + return +} + +func sixPlaneAddress(d *schema.ResourceData) string { + nwid, nodeID := resourceNetworkAndNodeIdentifiers(d) + return buildIPV6("fd" + nwid + "9993" + nodeID) +} + +func rfc4193Address(d *schema.ResourceData) string { + nwid, nodeID := resourceNetworkAndNodeIdentifiers(d) + nwidInt, _ := strconv.ParseUint(nwid, 16, 64) + networkMask := uint32((nwidInt >> 32) ^ nwidInt) + networkPrefix := strconv.FormatUint(uint64(networkMask), 16) + return buildIPV6("fc" + networkPrefix + nodeID + "000000000001") +} + func resourceMemberRead(d *schema.ResourceData, m interface{}) error { client := m.(*ZeroTierClient) @@ -221,6 +259,8 @@ func resourceMemberRead(d *schema.ResourceData, m interface{}) error { d.Set("allow_ethernet_bridging", member.Config.ActiveBridge) d.Set("no_auto_assign_ips", member.Config.NoAutoAssignIps) d.Set("ip_assignments", member.Config.IpAssignments) + d.Set("rfc4193_address", rfc4193Address(d)) + d.Set("6plane_address", sixPlaneAddress(d)) d.Set("capabilities", member.Config.Capabilities) setTags(d, member) diff --git a/zerotier/resource_zerotier_network.go b/zerotier/resource_zerotier_network.go index 4bfbac8..e98b64e 100644 --- a/zerotier/resource_zerotier_network.go +++ b/zerotier/resource_zerotier_network.go @@ -65,6 +65,24 @@ func resourceZeroTierNetwork() *schema.Resource { Optional: true, Default: true, }, + "auto_assign_v6": &schema.Schema{ + Type: schema.TypeBool, + Description: "Auto assign IPv6 to members from ZeroTier assignment pool", + Optional: true, + Default: false, + }, + "auto_assign_6plane": &schema.Schema{ + Type: schema.TypeBool, + Description: "Auto assign IPv6 /60 to members using 6PLANE adressing", + Optional: true, + Default: false, + }, + "auto_assign_rfc4193": &schema.Schema{ + Type: schema.TypeBool, + Description: "Auto assign IPv6 /128 to members using RFC4193 adressing", + Optional: true, + Default: true, + }, "route": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -145,9 +163,16 @@ func fromResourceData(d *schema.ResourceData) (*Network, error) { RulesSource: d.Get("rules_source").(string), Description: d.Get("description").(string), Config: &Config{ - Name: d.Get("name").(string), - Private: d.Get("private").(bool), - V4AssignMode: V4AssignModeConfig{ZT: true}, + Name: d.Get("name").(string), + Private: d.Get("private").(bool), + V4AssignMode: V4AssignModeConfig{ + ZT: d.Get("auto_assign_v4").(bool), + }, + V6AssignMode: V6AssignModeConfig{ + ZT: d.Get("auto_assign_v6").(bool), + SixPLANE: d.Get("auto_assign_6plane").(bool), + RFC4193: d.Get("auto_assign_rfc4193").(bool), + }, Routes: routes, IpAssignmentPools: pools, }, @@ -190,6 +215,9 @@ func resourceNetworkRead(d *schema.ResourceData, m interface{}) error { d.Set("description", net.Description) d.Set("private", net.Config.Private) d.Set("auto_assign_v4", net.Config.V4AssignMode.ZT) + d.Set("auto_assign_v6", net.Config.V6AssignMode.ZT) + d.Set("auto_assign_6plane", net.Config.V6AssignMode.SixPLANE) + d.Set("auto_assign_rfc4193", net.Config.V6AssignMode.RFC4193) d.Set("rules_source", net.RulesSource) setRoutes(d, net)