Skip to content

Commit

Permalink
feat: Creating key sets for WireGuard tunnels
Browse files Browse the repository at this point in the history
  • Loading branch information
vaerh committed Sep 24, 2023
1 parent 4eda474 commit e2d28a3
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 20 deletions.
27 changes: 27 additions & 0 deletions docs/data-sources/wireguard_keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# routeros_wireguard_keys (Data Source)
Creating keys for a WireGuard tunnels.



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

### Optional

- `number` (Number) The number of key sets.

### Read-Only

- `id` (String) The ID of this resource.
- `keys` (List of Object) (see [below for nested schema](#nestedatt--keys))

<a id="nestedatt--keys"></a>
### Nested Schema for `keys`

Read-Only:

- `preshared` (String)
- `private` (String)
- `public` (String)


11 changes: 11 additions & 0 deletions examples/data-sources/wireguard_keys.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
data "routeros_wireguard_keys" "wgk" {
number = 3
}

output "wg_keys" {
value = data.routeros_wireguard_keys.wgk.keys[*]
}

output "wg_key" {
value = data.routeros_wireguard_keys.wgk.keys[2].private
}
153 changes: 153 additions & 0 deletions routeros/datasource_wireguard_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package routeros

import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"golang.org/x/crypto/curve25519"
)

func DatasourceWireguardKeys() *schema.Resource {
return &schema.Resource{
Description: "Creating key sets for WireGuard tunnels.",
ReadContext: datasourceMakeWGKeys,
Schema: map[string]*schema.Schema{
"number": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "The number of key sets.",
},
"keys": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"preshared": {
Type: schema.TypeString,
Computed: true,
Description: "Pre-shared secret key.",
},
"private": {
Type: schema.TypeString,
Computed: true,
Description: "Private WG key.",
},
"public": {
Type: schema.TypeString,
Computed: true,
Description: "Public WG Key.",
},
},
},
},
},
}
}

// https://github.com/WireGuard/wgctrl-go/blob/master/wgtypes/types.go
// KeyLen is the expected key length for a WireGuard key.
const KeyLen = 32 // wgh.KeyLen

// A Key is a public, private, or pre-shared secret key. The Key constructor
// functions in this package can be used to create Keys suitable for each of
// these applications.
type Key [KeyLen]byte

// GenerateKey generates a Key suitable for use as a pre-shared secret key from
// a cryptographically safe source.
//
// The output Key should not be used as a private key; use GeneratePrivateKey
// instead.
func GenerateKey() (Key, error) {
b := make([]byte, KeyLen)
if _, err := rand.Read(b); err != nil {
return Key{}, fmt.Errorf("failed to read random bytes: %v", err)
}

return NewKey(b)
}

// GeneratePrivateKey generates a Key suitable for use as a private key from a
// cryptographically safe source.
func GeneratePrivateKey() (Key, error) {
key, err := GenerateKey()
if err != nil {
return Key{}, err
}

// Modify random bytes using algorithm described at:
// https://cr.yp.to/ecdh.html.
key[0] &= 248
key[31] &= 127
key[31] |= 64

return key, nil
}

// NewKey creates a Key from an existing byte slice. The byte slice must be
// exactly 32 bytes in length.
func NewKey(b []byte) (Key, error) {
if len(b) != KeyLen {
return Key{}, fmt.Errorf("incorrect key size: %d", len(b))
}

var k Key
copy(k[:], b)

return k, nil
}

// PublicKey computes a public key from the private key k.
//
// PublicKey should only be called when k is a private key.
func (k Key) PublicKey() Key {
var (
pub [KeyLen]byte
priv = [KeyLen]byte(k)
)

// ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html,
// so no need to specify it.
curve25519.ScalarBaseMult(&pub, &priv)

return Key(pub)
}

// String returns the base64-encoded string representation of a Key.
//
// ParseKey can be used to produce a new Key from this string.
func (k Key) String() string {
return base64.StdEncoding.EncodeToString(k[:])
}

func datasourceMakeWGKeys(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var res []map[string]any

for i := 0; i < d.Get("number").(int); i++ {
keys := make(map[string]any)

key, err := GeneratePrivateKey()
if err != nil {
return diag.FromErr(err)
}
keys["private"] = key.String()
keys["public"] = key.PublicKey().String()

key, err = GenerateKey()
if err != nil {
return diag.FromErr(err)
}
keys["preshared"] = key.String()

res = append(res, keys)
}

d.SetId("wg_keys")

return diag.FromErr(d.Set("keys", res))
}
39 changes: 39 additions & 0 deletions routeros/datasource_wireguard_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package routeros

import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

const testDatasourceWireGuardKeys = "data.routeros_wireguard_keys.keys"

func TestAccDatasourceWireGuardKeys_basic(t *testing.T) {
t.Run("WG keys", func(t *testing.T) {
resource.Test(t, resource.TestCase{
ProviderFactories: testAccProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccDatasourceWireGuardKeysConfig(),
Check: resource.ComposeTestCheckFunc(
testResourcePrimaryInstanceId(testDatasourceWireGuardKeys),
),
},
},
})

})
}

func testAccDatasourceWireGuardKeysConfig() string {
return `
provider "routeros" {
insecure = true
}
data "routeros_wireguard_keys" "keys" {
number = 3
}
`
}
41 changes: 21 additions & 20 deletions routeros/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,27 @@ func Provider() *schema.Provider {
ResourcesMap: map[string]*schema.Resource{

// IP objects
"routeros_ip_dhcp_client": ResourceDhcpClient(),
"routeros_ip_dhcp_server": ResourceDhcpServer(),
"routeros_ip_dhcp_server_network": ResourceDhcpServerNetwork(),
"routeros_ip_dhcp_server_lease": ResourceDhcpServerLease(),
"routeros_ip_dhcp_server_option": ResourceDhcpServerOption(),
"routeros_ip_dhcp_server_option_set": ResourceDhcpServerOptionSet(),
"routeros_ip_firewall_addr_list": ResourceIPFirewallAddrList(),
"routeros_ip_dhcp_client": ResourceDhcpClient(),
"routeros_ip_dhcp_server": ResourceDhcpServer(),
"routeros_ip_dhcp_server_network": ResourceDhcpServerNetwork(),
"routeros_ip_dhcp_server_lease": ResourceDhcpServerLease(),
"routeros_ip_dhcp_server_option": ResourceDhcpServerOption(),
"routeros_ip_dhcp_server_option_set": ResourceDhcpServerOptionSet(),
"routeros_ip_firewall_addr_list": ResourceIPFirewallAddrList(),
"routeros_ip_firewall_connection_tracking": ResourceIPConnectionTracking(),
"routeros_ip_firewall_filter": ResourceIPFirewallFilter(),
"routeros_ip_firewall_mangle": ResourceIPFirewallMangle(),
"routeros_ip_firewall_nat": ResourceIPFirewallNat(),
"routeros_ip_address": ResourceIPAddress(),
"routeros_ip_pool": ResourceIPPool(),
"routeros_ip_route": ResourceIPRoute(),
"routeros_ip_dns": ResourceDns(),
"routeros_ip_dns_record": ResourceDnsRecord(),
"routeros_ip_service": ResourceIpService(),
"routeros_ipv6_address": ResourceIPv6Address(),
"routeros_ipv6_firewall_addr_list": ResourceIPv6FirewallAddrList(),
"routeros_ipv6_firewall_filter": ResourceIPv6FirewallFilter(),
"routeros_ipv6_route": ResourceIPv6Route(),
"routeros_ip_firewall_filter": ResourceIPFirewallFilter(),
"routeros_ip_firewall_mangle": ResourceIPFirewallMangle(),
"routeros_ip_firewall_nat": ResourceIPFirewallNat(),
"routeros_ip_address": ResourceIPAddress(),
"routeros_ip_pool": ResourceIPPool(),
"routeros_ip_route": ResourceIPRoute(),
"routeros_ip_dns": ResourceDns(),
"routeros_ip_dns_record": ResourceDnsRecord(),
"routeros_ip_service": ResourceIpService(),
"routeros_ipv6_address": ResourceIPv6Address(),
"routeros_ipv6_firewall_addr_list": ResourceIPv6FirewallAddrList(),
"routeros_ipv6_firewall_filter": ResourceIPv6FirewallFilter(),
"routeros_ipv6_route": ResourceIPv6Route(),

// Aliases for IP objects to retain compatibility between original and fork
"routeros_dhcp_client": ResourceDhcpClient(),
Expand Down Expand Up @@ -181,6 +181,7 @@ func Provider() *schema.Provider {
"routeros_ip_routes": DatasourceIPRoutes(),
"routeros_firewall": DatasourceFirewall(),
"routeros_ipv6_addresses": DatasourceIPv6Addresses(),
"routeros_wireguard_keys": DatasourceWireguardKeys(),
},
ConfigureContextFunc: NewClient,
}
Expand Down

0 comments on commit e2d28a3

Please sign in to comment.