From 1bfb3b279e34374afa06871c9d38638f3e79fa8a Mon Sep 17 00:00:00 2001 From: Julien Date: Fri, 20 Dec 2024 16:44:35 +0100 Subject: [PATCH 1/2] added config X509 resource --- .changes/feature_config_x509.md | 3 + bastion/provider.go | 1 + bastion/resource_config_x509.go | 174 +++++++++++++++++++++++++++ bastion/resource_config_x509_test.go | 63 ++++++++++ docs/resources/config_x509.md | 47 ++++++++ 5 files changed, 288 insertions(+) create mode 100644 .changes/feature_config_x509.md create mode 100644 bastion/resource_config_x509.go create mode 100644 bastion/resource_config_x509_test.go create mode 100644 docs/resources/config_x509.md diff --git a/.changes/feature_config_x509.md b/.changes/feature_config_x509.md new file mode 100644 index 0000000..e6ab805 --- /dev/null +++ b/.changes/feature_config_x509.md @@ -0,0 +1,3 @@ +FEATURES: + +* **resource/wallix-bastion_config_x509**: added the possibilty to configure the X509 for the GUI and for users authentication \ No newline at end of file diff --git a/bastion/provider.go b/bastion/provider.go index 3311d58..b0d88df 100644 --- a/bastion/provider.go +++ b/bastion/provider.go @@ -72,6 +72,7 @@ func Provider() *schema.Provider { "wallix-bastion_authorization": resourceAuthorization(), "wallix-bastion_checkout_policy": resourceCheckoutPolicy(), "wallix-bastion_cluster": resourceCluster(), + "wallix-bastion_config_x509": resourceConfigX509(), "wallix-bastion_connection_message": resourceConnectionMessage(), "wallix-bastion_connection_policy": resourceConnectionPolicy(), "wallix-bastion_device": resourceDevice(), diff --git a/bastion/resource_config_x509.go b/bastion/resource_config_x509.go new file mode 100644 index 0000000..c352d15 --- /dev/null +++ b/bastion/resource_config_x509.go @@ -0,0 +1,174 @@ +package bastion + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type jsonConfigX509 struct { + CaCertificate string `json:"ca_certificate,omitempty"` + ServerPublicKey string `json:"server_public_key"` + ServerPrivateKey string `json:"server_private_key"` + Enable bool `json:"enable,omitempty"` +} + +func resourceConfigX509() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceConfigX509Create, + ReadContext: resourceConfigX509Read, + UpdateContext: resourceConfigX509Update, + DeleteContext: resourceConfigX509Delete, + Importer: &schema.ResourceImporter{ + State: resourceConfigX509Import, + }, + Schema: map[string]*schema.Schema{ + "ca_certificate": { + Type: schema.TypeString, + Optional: true, + }, + "server_public_key": { + Type: schema.TypeString, + Required: true, + }, + "server_private_key": { + Type: schema.TypeString, + Required: true, + }, + "enable": { + Type: schema.TypeBool, + Optional: true, + }, + }, + } +} + +func resourceConfigX509Create(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // Add the configuration + if err := addConfigX509(ctx, d, m); err != nil { + return diag.FromErr(err) + } + // Use a static ID since the API does not provide one + d.SetId("x509Config") + + return resourceConfigX509Read(ctx, d, m) +} + +func resourceConfigX509Read(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + cfg, err := readConfigX509Options(ctx, m) + if err != nil { + return diag.FromErr(err) + } + + // If no config exists, mark the resource as deleted + if cfg.ServerPublicKey == "" && cfg.ServerPrivateKey == "" { + d.SetId("") + return nil + } + + fillConfigX509(d, cfg) + return nil +} + +func resourceConfigX509Update(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + if err := updateConfigX509(ctx, d, m); err != nil { + return diag.FromErr(err) + } + + return resourceConfigX509Read(ctx, d, m) +} + +func resourceConfigX509Delete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + if err := deleteConfigX509(ctx, m); err != nil { + return diag.FromErr(err) + } + + // Remove the resource from state + d.SetId("") + + return nil +} + +func resourceConfigX509Import(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + // Since the resource does not have a unique ID, use the static "x509Config" ID + d.SetId("x509Config") + return []*schema.ResourceData{d}, nil +} + +func addConfigX509(ctx context.Context, d *schema.ResourceData, m interface{}) error { + c := m.(*Client) + jsonData := prepareConfigX509JSON(d) + body, code, err := c.newRequest(ctx, "/config/x509", http.MethodPost, jsonData) + if err != nil { + return err + } + if code != http.StatusOK && code != http.StatusNoContent { + return fmt.Errorf("API returned error: %d with body:\n%s", code, body) + } + return nil +} + +func readConfigX509Options(ctx context.Context, m interface{}) (jsonConfigX509, error) { + c := m.(*Client) + var result jsonConfigX509 + body, code, err := c.newRequest(ctx, "/config/x509", http.MethodGet, nil) + if err != nil { + return result, err + } + if code == http.StatusNotFound { + return result, nil + } + if code != http.StatusOK { + return result, fmt.Errorf("API returned error: %d with body:\n%s", code, body) + } + err = json.Unmarshal([]byte(body), &result) + if err != nil { + return result, fmt.Errorf("error unmarshaling JSON: %w", err) + } + return result, nil +} + +func updateConfigX509(ctx context.Context, d *schema.ResourceData, m interface{}) error { + c := m.(*Client) + jsonData := prepareConfigX509JSON(d) + body, code, err := c.newRequest(ctx, "/config/x509", http.MethodPut, jsonData) + if err != nil { + return err + } + if code != http.StatusOK && code != http.StatusNoContent { + return fmt.Errorf("API returned error: %d with body:\n%s", code, body) + } + return nil +} + +func deleteConfigX509(ctx context.Context, m interface{}) error { + c := m.(*Client) + body, code, err := c.newRequest(ctx, "/config/x509", http.MethodDelete, nil) + if err != nil { + return err + } + if code != http.StatusOK && code != http.StatusNoContent { + return fmt.Errorf("API returned error: %d with body:\n%s", code, body) + } + return nil +} + +func prepareConfigX509JSON(d *schema.ResourceData) jsonConfigX509 { + return jsonConfigX509{ + CaCertificate: d.Get("ca_certificate").(string), + ServerPublicKey: d.Get("server_public_key").(string), + ServerPrivateKey: d.Get("server_private_key").(string), + Enable: d.Get("enable").(bool), + } +} + +func fillConfigX509(d *schema.ResourceData, jsonData jsonConfigX509) { + d.Set("ca_certificate", jsonData.CaCertificate) + d.Set("server_public_key", jsonData.ServerPublicKey) + d.Set("server_private_key", jsonData.ServerPrivateKey) + d.Set("enable", jsonData.Enable) +} diff --git a/bastion/resource_config_x509_test.go b/bastion/resource_config_x509_test.go new file mode 100644 index 0000000..2780b24 --- /dev/null +++ b/bastion/resource_config_x509_test.go @@ -0,0 +1,63 @@ +package bastion_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +// TestAccResourceConfigX509_basic tests creating, updating the x509 configuration. +func TestAccResourceConfigX509_basic(t *testing.T) { + resourceName := "bastion_x509_config.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, // Ensures necessary environment variables are set + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccResourceConfigX509Basic(), + Check: resource.ComposeTestCheckFunc( + // Verify that the resource exists + resource.TestCheckResourceAttr(resourceName, "ca_certificate", "test-ca-cert"), + resource.TestCheckResourceAttr(resourceName, "server_public_key", "test-public-key"), + resource.TestCheckResourceAttr(resourceName, "server_private_key", "test-private-key"), + resource.TestCheckResourceAttr(resourceName, "enable", "true"), + ), + }, + // Test updating the resource + { + Config: testAccResourceConfigX509Update(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "ca_certificate", "updated-ca-cert"), + resource.TestCheckResourceAttr(resourceName, "server_public_key", "updated-public-key"), + resource.TestCheckResourceAttr(resourceName, "server_private_key", "updated-private-key"), + resource.TestCheckResourceAttr(resourceName, "enable", "false"), + ), + }, + }, + }) +} + +// Test configuration for creating the resource +func testAccResourceConfigX509Basic() string { + return (` +resource "bastion_x509_config" "test" { + ca_certificate = "test-ca-cert" + server_public_key = "test-public-key" + server_private_key = "test-private-key" + enable = true +} +`) +} + +// Test configuration for updating the resource +func testAccResourceConfigX509Update() string { + return (` +resource "bastion_x509_config" "test" { + ca_certificate = "updated-ca-cert" + server_public_key = "updated-public-key" + server_private_key = "updated-private-key" + enable = false +} +`) +} diff --git a/docs/resources/config_x509.md b/docs/resources/config_x509.md new file mode 100644 index 0000000..d718e69 --- /dev/null +++ b/docs/resources/config_x509.md @@ -0,0 +1,47 @@ +# wallix-bastion_config_x509 Resource + +Provides a X509 resource. + +## Example Usage + +```hcl +# Configure the X509 authentication and/or change GUI and API certificates +resource "wallix-bastion_config_x509" "acme-cert" { + ca_certificate = file("${path.root}/chain1.pem") + server_private_key = file("${path.root}/privkey1.pem") + server_public_key = file("${path.root}/cert1.pem") + enable = true +} +``` + +## Argument Reference + +The following arguments are supported: + +- **ca_certificate** (Optional, String) + The ca for users authentication +- **server_private_key** (Required, String) + The server certificate private key +- **server_public_key** (Required, String) + The server certificate public key +- **enable** (Optional, Bool) + Whether or not enable X509 users authentication + +## Attribute Reference + +- **id** (String) + Internal id of X509 config (only in Tfstate since the API does not provide any) +- **ca_certificate** (String) + The server X509 ca certificate for users authentication +- **server_public_key** (String) + The server x509 public certificate +- **enable** (String) + Whether or not the X509 users authentication is enabled + +## Import + +X509 config can be imported using any id (in Tfstate it will always be x509Config ) e.g. + +```shell +terraform import wallix-bastion_device.acme-cert myx509 +``` From 8476f75577de94a70caaa9fca55ca58cf0dbc7ba Mon Sep 17 00:00:00 2001 From: Julien Date: Fri, 20 Dec 2024 17:06:06 +0100 Subject: [PATCH 2/2] linting --- bastion/resource_config_x509.go | 26 ++++++++++++++++++++------ bastion/resource_config_x509_test.go | 10 +++++----- docs/resources/config_x509.md | 18 +++++++++--------- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/bastion/resource_config_x509.go b/bastion/resource_config_x509.go index c352d15..8983338 100644 --- a/bastion/resource_config_x509.go +++ b/bastion/resource_config_x509.go @@ -61,16 +61,19 @@ func resourceConfigX509Create(ctx context.Context, d *schema.ResourceData, m int func resourceConfigX509Read(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { cfg, err := readConfigX509Options(ctx, m) if err != nil { + return diag.FromErr(err) } // If no config exists, mark the resource as deleted if cfg.ServerPublicKey == "" && cfg.ServerPrivateKey == "" { d.SetId("") + return nil } fillConfigX509(d, cfg) + return nil } @@ -93,9 +96,10 @@ func resourceConfigX509Delete(ctx context.Context, d *schema.ResourceData, m int return nil } -func resourceConfigX509Import(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { +func resourceConfigX509Import(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { // Since the resource does not have a unique ID, use the static "x509Config" ID d.SetId("x509Config") + return []*schema.ResourceData{d}, nil } @@ -166,9 +170,19 @@ func prepareConfigX509JSON(d *schema.ResourceData) jsonConfigX509 { } } -func fillConfigX509(d *schema.ResourceData, jsonData jsonConfigX509) { - d.Set("ca_certificate", jsonData.CaCertificate) - d.Set("server_public_key", jsonData.ServerPublicKey) - d.Set("server_private_key", jsonData.ServerPrivateKey) - d.Set("enable", jsonData.Enable) +func fillConfigX509(d *schema.ResourceData, jsonData jsonConfigX509) error { + if err := d.Set("ca_certificate", jsonData.CaCertificate); err != nil { + return err + } + if err := d.Set("server_public_key", jsonData.ServerPublicKey); err != nil { + return err + } + if err := d.Set("server_private_key", jsonData.ServerPrivateKey); err != nil { + return err + } + if err := d.Set("enable", jsonData.Enable); err != nil { + return err + } + + return nil } diff --git a/bastion/resource_config_x509_test.go b/bastion/resource_config_x509_test.go index 2780b24..d819208 100644 --- a/bastion/resource_config_x509_test.go +++ b/bastion/resource_config_x509_test.go @@ -11,20 +11,20 @@ func TestAccResourceConfigX509_basic(t *testing.T) { resourceName := "bastion_x509_config.test" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, // Ensures necessary environment variables are set + PreCheck: func() { testAccPreCheck(t) }, // Ensures necessary environment variables are set. Providers: testAccProviders, Steps: []resource.TestStep{ { Config: testAccResourceConfigX509Basic(), Check: resource.ComposeTestCheckFunc( - // Verify that the resource exists + // Verify that the resource exists. resource.TestCheckResourceAttr(resourceName, "ca_certificate", "test-ca-cert"), resource.TestCheckResourceAttr(resourceName, "server_public_key", "test-public-key"), resource.TestCheckResourceAttr(resourceName, "server_private_key", "test-private-key"), resource.TestCheckResourceAttr(resourceName, "enable", "true"), ), }, - // Test updating the resource + // Test updating the resource. { Config: testAccResourceConfigX509Update(), Check: resource.ComposeTestCheckFunc( @@ -38,7 +38,7 @@ func TestAccResourceConfigX509_basic(t *testing.T) { }) } -// Test configuration for creating the resource +// Test configuration for creating the resource. func testAccResourceConfigX509Basic() string { return (` resource "bastion_x509_config" "test" { @@ -50,7 +50,7 @@ resource "bastion_x509_config" "test" { `) } -// Test configuration for updating the resource +// Test configuration for updating the resource. func testAccResourceConfigX509Update() string { return (` resource "bastion_x509_config" "test" { diff --git a/docs/resources/config_x509.md b/docs/resources/config_x509.md index d718e69..3a5c43d 100644 --- a/docs/resources/config_x509.md +++ b/docs/resources/config_x509.md @@ -7,10 +7,10 @@ Provides a X509 resource. ```hcl # Configure the X509 authentication and/or change GUI and API certificates resource "wallix-bastion_config_x509" "acme-cert" { - ca_certificate = file("${path.root}/chain1.pem") + ca_certificate = file("${path.root}/chain1.pem") server_private_key = file("${path.root}/privkey1.pem") - server_public_key = file("${path.root}/cert1.pem") - enable = true + server_public_key = file("${path.root}/cert1.pem") + enable = true } ``` @@ -29,14 +29,14 @@ The following arguments are supported: ## Attribute Reference -- **id** (String) +- **id** (String) Internal id of X509 config (only in Tfstate since the API does not provide any) -- **ca_certificate** (String) +- **ca_certificate** (String) The server X509 ca certificate for users authentication -- **server_public_key** (String) - The server x509 public certificate -- **enable** (String) - Whether or not the X509 users authentication is enabled +- **server_public_key** (String) + The server x509 public certificate +- **enable** (String) + Whether or not the X509 users authentication is enabled ## Import