From 85fd6be76bfb5a474f419ff226a969a40bc90c92 Mon Sep 17 00:00:00 2001 From: Vaerh Date: Fri, 17 May 2024 23:48:08 +0300 Subject: [PATCH] feat(ovpn): Add routeros_interface_ovpn_client Closes #452 --- routeros/provider.go | 1 + routeros/resource_ovpn_client.go | 173 ++++++++++++++++++++++++++ routeros/resource_ovpn_client_test.go | 67 ++++++++++ 3 files changed, 241 insertions(+) create mode 100644 routeros/resource_ovpn_client.go create mode 100644 routeros/resource_ovpn_client_test.go diff --git a/routeros/provider.go b/routeros/provider.go index 45b1b700..3911708a 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -151,6 +151,7 @@ func Provider() *schema.Provider { "routeros_interface_list": ResourceInterfaceList(), "routeros_interface_list_member": ResourceInterfaceListMember(), "routeros_interface_ovpn_server": ResourceInterfaceOpenVPNServer(), + "routeros_interface_ovpn_client": ResourceOpenVPNClient(), "routeros_interface_veth": ResourceInterfaceVeth(), "routeros_interface_bonding": ResourceInterfaceBonding(), "routeros_interface_pppoe_client": ResourceInterfacePPPoEClient(), diff --git a/routeros/resource_ovpn_client.go b/routeros/resource_ovpn_client.go new file mode 100644 index 00000000..feb9622b --- /dev/null +++ b/routeros/resource_ovpn_client.go @@ -0,0 +1,173 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*5C", + "add-default-route": "false", + "auth": "sha1", + "certificate": "none", + "cipher": "blowfish128", + * "connect-to": "192.168.1.1", + "disabled": "false", + "hw-crypto": "false", + "mac-address": "02:E7:60:C6:40:EE", + "max-mtu": "1500", + "mode": "ip", + "name": "ovpn-out1", + "password": "", + "port": "1194", + "profile": "default", + "protocol": "tcp", + "route-nopull": "false", + "running": "false", + "tls-version": "any", + "use-peer-dns": "yes", + * "user": "aaa", + "verify-server-certificate": "false" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/OpenVPN +func ResourceOpenVPNClient() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/interface/ovpn-client"), + MetaId: PropId(Id), + + "add_default_route": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to add OVPN remote address as a default route.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "auth": { + Type: schema.TypeString, + Optional: true, + Description: "Authentication methods that the server will accept.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + ValidateDiagFunc: ValidationMultiValInSlice([]string{"md5", "sha1", "null", "sha256", "sha512"}, false, false), + }, + "certificate": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the client certificate.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "cipher": { + Type: schema.TypeString, + Optional: true, + Description: `Allowed ciphers.`, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + ValidateDiagFunc: ValidationMultiValInSlice([]string{ + "null", "aes128-cbc", "aes128-gcm", "aes192-cbc", "aes192-gcm", "aes256-cbc", "aes256-gcm", "blowfish128", + // Backward compatibility with ROS v7.7 + "aes128", "aes192", "aes256", + }, false, false), + }, + KeyComment: PropCommentRw, + "connect_to": { + Type: schema.TypeString, + Required: true, + Description: "Remote address of the OVPN server.", + }, + KeyDisabled: PropDisabledRw, + "hw_crypto": { + Type: schema.TypeBool, + Computed: true, + Description: "", + }, + KeyMacAddress: PropMacAddressRw(`Mac address of OVPN interface. Will be automatically generated if not specified.`, false), + "max_mtu": { + Type: schema.TypeInt, + Optional: true, + Description: "Maximum Transmission Unit. Max packet size that the OVPN interface will be able to send without packet fragmentation.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + ValidateFunc: validation.IntBetween(64, 65535), + }, + "mode": { + Type: schema.TypeString, + Optional: true, + Description: "Layer3 or layer2 tunnel mode (alternatively tun, tap)", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + ValidateFunc: validation.StringInSlice([]string{"ip", "ethernet"}, false), + }, + KeyName: PropName("Descriptive name of the interface."), + "password": { + Type: schema.TypeString, + Optional: true, + Description: "Password used for authentication.", + Sensitive: true, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + Description: "Port to connect to.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + ValidateFunc: validation.IntBetween(1, 65535), + }, + "profile": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies which PPP profile configuration will be used when establishing the tunnel.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "protocol": { + Type: schema.TypeString, + Optional: true, + Description: "Indicates the protocol to use when connecting with the remote endpoint.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + ValidateFunc: validation.StringInSlice([]string{"tcp", "udp"}, false), + }, + "route_nopull": { + Type: schema.TypeBool, + Optional: true, + Description: "Specifies whether to allow the OVPN server to add routes to the OVPN client instance " + + "routing table.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + KeyRunning: PropRunningRo, + "tls_version": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies which TLS versions to allow.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + ValidateFunc: validation.StringInSlice([]string{"any", "only-1.2"}, false), + }, + "use_peer_dns": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to add DNS servers provided by the OVPN server to IP/DNS configuration.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "user": { + Type: schema.TypeString, + Required: true, + Description: "User name used for authentication.", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "verify_server_certificate": { + Type: schema.TypeBool, + Optional: true, + Description: `Checks the certificates CN or SAN against the "connect-to" parameter. The IP or ` + + `hostname must be present in the server's certificate.`, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_ovpn_client_test.go b/routeros/resource_ovpn_client_test.go new file mode 100644 index 00000000..d7457c68 --- /dev/null +++ b/routeros/resource_ovpn_client_test.go @@ -0,0 +1,67 @@ +package routeros + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const testInterfaceOpenVPNClient = "routeros_interface_ovpn_client.ovpn-in1" + +func TestAccOpenVPNClientTest_basic(t *testing.T) { + for _, name := range testNames { + t.Run(name, func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testSetTransportEnv(t, name) + }, + ProviderFactories: testAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccOpenVPNClientConfig(), + Check: resource.ComposeTestCheckFunc( + testResourcePrimaryInstanceId(testInterfaceOpenVPNClient), + resource.TestCheckResourceAttr(testInterfaceOpenVPNClient, "name", "ovpn-in1"), + resource.TestCheckResourceAttr(testInterfaceOpenVPNClient, "user", "user1"), + resource.TestCheckResourceAttr(testInterfaceOpenVPNClient, "connect_to", "192.168.1.1"), + ), + }, + }, + }) + + }) + } +} + +// Complex test for OpenVPN client resources. +func testAccOpenVPNClientConfig() string { + return providerConfig + ` + resource "routeros_system_certificate" "ovpn_ca" { + name = "OpenVPN-Root-CA" + common_name = "OpenVPN Root CA" + key_size = "prime256v1" + key_usage = ["key-cert-sign", "crl-sign"] + trusted = true + sign { + } + } + + resource "routeros_system_certificate" "ovpn_client_crt" { + name = "OpenVPN-Client-Certificate" + common_name = "Mikrotik OpenVPN Client" + key_size = "prime256v1" + key_usage = ["digital-signature", "key-encipherment", "tls-client"] + sign { + ca = routeros_system_certificate.ovpn_ca.name + } + } + + resource "routeros_interface_ovpn_client" "ovpn-in1" { + name = "ovpn-in1" + user = "user1" + connect_to = "192.168.1.1" + certificate = routeros_system_certificate.ovpn_client_crt.name + } +` +}