From dffff063ab513e4adad3c8ca656bf40092344729 Mon Sep 17 00:00:00 2001 From: Dan Petersen Date: Wed, 1 Jan 2020 07:29:21 +0100 Subject: [PATCH] Add certificate resource --- CHANGELOG.md | 1 + README.md | 11 + ...esource_virtual_environment_certificate.tf | 28 +++ proxmox/virtual_environment_certificate.go | 37 ++++ .../virtual_environment_certificate_types.go | 37 ++++ proxmox/virtual_environment_dns_types.go | 6 +- proxmoxtf/provider.go | 15 +- ...esource_virtual_environment_certificate.go | 203 ++++++++++++++++++ ...ce_virtual_environment_certificate_test.go | 47 ++++ proxmoxtf/resource_virtual_environment_vm.go | 1 + 10 files changed, 376 insertions(+), 10 deletions(-) create mode 100644 example/resource_virtual_environment_certificate.tf create mode 100644 proxmox/virtual_environment_certificate.go create mode 100644 proxmox/virtual_environment_certificate_types.go create mode 100644 proxmoxtf/resource_virtual_environment_certificate.go create mode 100644 proxmoxtf/resource_virtual_environment_certificate_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f0ad1b..c6324e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ FEATURES: * **New Data Source:** `proxmox_virtual_environment_dns` +* **New Resource:** `proxmox_virtual_environment_certificate` * **New Resource:** `proxmox_virtual_environment_dns` ENHANCEMENTS: diff --git a/README.md b/README.md index 621a7b0b..10cd66c3 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,17 @@ This data source doesn't accept arguments. #### Virtual Environment +##### Certificate (proxmox_virtual_environment_certificate) + +###### Arguments +* `certificate` - (Required) The PEM encoded certificate +* `certificate_chain` - (Optional) The PEM encoded certificate chain +* `node_name` - (Required) A node name +* `private_key` - (Required) The PEM encoded private key + +###### Attributes +This resource doesn't expose any additional attributes. + ##### DNS (proxmox_virtual_environment_dns) ###### Arguments diff --git a/example/resource_virtual_environment_certificate.tf b/example/resource_virtual_environment_certificate.tf new file mode 100644 index 00000000..6f02ce2c --- /dev/null +++ b/example/resource_virtual_environment_certificate.tf @@ -0,0 +1,28 @@ +resource "proxmox_virtual_environment_certificate" "example" { + certificate = "${tls_self_signed_cert.proxmox_virtual_environment_certificate.cert_pem}" + node_name = "${data.proxmox_virtual_environment_nodes.example.names[0]}" + private_key = "${tls_private_key.proxmox_virtual_environment_certificate.private_key_pem}" +} + +resource "tls_private_key" "proxmox_virtual_environment_certificate" { + algorithm = "RSA" + rsa_bits = 2048 +} + +resource "tls_self_signed_cert" "proxmox_virtual_environment_certificate" { + key_algorithm = "${tls_private_key.proxmox_virtual_environment_certificate.algorithm}" + private_key_pem = "${tls_private_key.proxmox_virtual_environment_certificate.private_key_pem}" + + subject { + common_name = "example.com" + organization = "Terraform Provider for Proxmox" + } + + validity_period_hours = 8760 + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + ] +} diff --git a/proxmox/virtual_environment_certificate.go b/proxmox/virtual_environment_certificate.go new file mode 100644 index 00000000..4e9f6ce2 --- /dev/null +++ b/proxmox/virtual_environment_certificate.go @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmox + +import ( + "errors" + "fmt" + "net/url" +) + +// DeleteCertificate deletes the custom certificate for a node. +func (c *VirtualEnvironmentClient) DeleteCertificate(nodeName string, d *VirtualEnvironmentCertificateDeleteRequestBody) error { + return c.DoRequest(hmDELETE, fmt.Sprintf("nodes/%s/certificates/custom", url.PathEscape(nodeName)), d, nil) +} + +// ListCertificates retrieves the list of certificates for a node. +func (c *VirtualEnvironmentClient) ListCertificates(nodeName string) (*[]VirtualEnvironmentCertificateListResponseData, error) { + resBody := &VirtualEnvironmentCertificateListResponseBody{} + err := c.DoRequest(hmGET, fmt.Sprintf("nodes/%s/certificates/info", url.PathEscape(nodeName)), nil, resBody) + + if err != nil { + return nil, err + } + + if resBody.Data == nil { + return nil, errors.New("The server did not include a data object in the response") + } + + return resBody.Data, nil +} + +// UpdateCertificate updates the custom certificate for a node. +func (c *VirtualEnvironmentClient) UpdateCertificate(nodeName string, d *VirtualEnvironmentCertificateUpdateRequestBody) error { + return c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/certificates/custom", url.PathEscape(nodeName)), d, nil) +} diff --git a/proxmox/virtual_environment_certificate_types.go b/proxmox/virtual_environment_certificate_types.go new file mode 100644 index 00000000..38441d00 --- /dev/null +++ b/proxmox/virtual_environment_certificate_types.go @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmox + +// VirtualEnvironmentCertificateDeleteRequestBody contains the data for a custom certificate delete request. +type VirtualEnvironmentCertificateDeleteRequestBody struct { + Restart *CustomBool `json:"restart,omitempty" url:"restart,omitempty,int"` +} + +// VirtualEnvironmentCertificateListResponseBody contains the body from a certificate list response. +type VirtualEnvironmentCertificateListResponseBody struct { + Data *[]VirtualEnvironmentCertificateListResponseData `json:"data,omitempty"` +} + +// VirtualEnvironmentCertificateListResponseData contains the data from a certificate list response. +type VirtualEnvironmentCertificateListResponseData struct { + Certificates *string `json:"pem,omitempty"` + FileName *string `json:"filename,omitempty"` + Fingerprint *string `json:"fingerprint,omitempty"` + Issuer *string `json:"issuer,omitempty"` + NotAfter *CustomTimestamp `json:"notafter,omitempty"` + NotBefore *CustomTimestamp `json:"notbefore,omitempty"` + PublicKeyBits *int `json:"public-key-bits,omitempty"` + PublicKeyType *string `json:"public-key-type,omitempty"` + Subject *string `json:"subject,omitempty"` + SubjectAlternativeNames *[]string `json:"san,omitempty"` +} + +// VirtualEnvironmentCertificateUpdateRequestBody contains the body for a custom certificate update request. +type VirtualEnvironmentCertificateUpdateRequestBody struct { + Certificates string `json:"certificates" url:"certificates"` + Force *CustomBool `json:"force,omitempty" url:"force,omitempty,int"` + PrivateKey *string `json:"key,omitempty" url:"key,omitempty"` + Restart *CustomBool `json:"restart,omitempty" url:"restart,omitempty,int"` +} diff --git a/proxmox/virtual_environment_dns_types.go b/proxmox/virtual_environment_dns_types.go index 9523f746..231d7ea2 100644 --- a/proxmox/virtual_environment_dns_types.go +++ b/proxmox/virtual_environment_dns_types.go @@ -4,12 +4,12 @@ package proxmox -// VirtualEnvironmentDNSGetResponseBody contains the body from an pool get response. +// VirtualEnvironmentDNSGetResponseBody contains the body from a DNS get response. type VirtualEnvironmentDNSGetResponseBody struct { Data *VirtualEnvironmentDNSGetResponseData `json:"data,omitempty"` } -// VirtualEnvironmentDNSGetResponseData contains the data from an pool get response. +// VirtualEnvironmentDNSGetResponseData contains the data from a DNS get response. type VirtualEnvironmentDNSGetResponseData struct { Server1 *string `json:"dns1,omitempty" url:"dns1,omitempty"` Server2 *string `json:"dns2,omitempty" url:"dns2,omitempty"` @@ -17,7 +17,7 @@ type VirtualEnvironmentDNSGetResponseData struct { SearchDomain *string `json:"search,omitempty" url:"search,omitempty"` } -// VirtualEnvironmentDNSUpdateRequestBody contains the data for an pool create request. +// VirtualEnvironmentDNSUpdateRequestBody contains the body for a DNS update request. type VirtualEnvironmentDNSUpdateRequestBody struct { Server1 *string `json:"dns1,omitempty" url:"dns1,omitempty"` Server2 *string `json:"dns2,omitempty" url:"dns2,omitempty"` diff --git a/proxmoxtf/provider.go b/proxmoxtf/provider.go index 35b397cf..64be255f 100644 --- a/proxmoxtf/provider.go +++ b/proxmoxtf/provider.go @@ -44,13 +44,14 @@ func Provider() *schema.Provider { "proxmox_virtual_environment_version": dataSourceVirtualEnvironmentVersion(), }, ResourcesMap: map[string]*schema.Resource{ - "proxmox_virtual_environment_dns": resourceVirtualEnvironmentDNS(), - "proxmox_virtual_environment_file": resourceVirtualEnvironmentFile(), - "proxmox_virtual_environment_group": resourceVirtualEnvironmentGroup(), - "proxmox_virtual_environment_pool": resourceVirtualEnvironmentPool(), - "proxmox_virtual_environment_role": resourceVirtualEnvironmentRole(), - "proxmox_virtual_environment_user": resourceVirtualEnvironmentUser(), - "proxmox_virtual_environment_vm": resourceVirtualEnvironmentVM(), + "proxmox_virtual_environment_certificate": resourceVirtualEnvironmentCertificate(), + "proxmox_virtual_environment_dns": resourceVirtualEnvironmentDNS(), + "proxmox_virtual_environment_file": resourceVirtualEnvironmentFile(), + "proxmox_virtual_environment_group": resourceVirtualEnvironmentGroup(), + "proxmox_virtual_environment_pool": resourceVirtualEnvironmentPool(), + "proxmox_virtual_environment_role": resourceVirtualEnvironmentRole(), + "proxmox_virtual_environment_user": resourceVirtualEnvironmentUser(), + "proxmox_virtual_environment_vm": resourceVirtualEnvironmentVM(), }, Schema: map[string]*schema.Schema{ mkProviderVirtualEnvironment: &schema.Schema{ diff --git a/proxmoxtf/resource_virtual_environment_certificate.go b/proxmoxtf/resource_virtual_environment_certificate.go new file mode 100644 index 00000000..959d84a4 --- /dev/null +++ b/proxmoxtf/resource_virtual_environment_certificate.go @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmoxtf + +import ( + "fmt" + "strings" + + "github.com/danitso/terraform-provider-proxmox/proxmox" + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + dvResourceVirtualEnvironmentCertificateCertificateChain = "" + dvResourceVirtualEnvironmentCertificateOverwrite = false + + mkResourceVirtualEnvironmentCertificateCertificate = "certificate" + mkResourceVirtualEnvironmentCertificateCertificateChain = "certificate_chain" + mkResourceVirtualEnvironmentCertificateNodeName = "node_name" + mkResourceVirtualEnvironmentCertificateOverwrite = "overwrite" + mkResourceVirtualEnvironmentCertificatePrivateKey = "private_key" +) + +func resourceVirtualEnvironmentCertificate() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentCertificateCertificate: &schema.Schema{ + Type: schema.TypeString, + Description: "The PEM encoded certificate", + Required: true, + }, + mkResourceVirtualEnvironmentCertificateCertificateChain: &schema.Schema{ + Type: schema.TypeString, + Description: "The PEM encoded certificate chain", + Optional: true, + Default: dvResourceVirtualEnvironmentCertificateCertificateChain, + }, + mkResourceVirtualEnvironmentCertificateNodeName: &schema.Schema{ + Type: schema.TypeString, + Description: "The node name", + Required: true, + ForceNew: true, + }, + mkResourceVirtualEnvironmentCertificateOverwrite: &schema.Schema{ + Type: schema.TypeBool, + Description: "Whether to overwrite an existing certificate", + Optional: true, + Default: dvResourceVirtualEnvironmentCertificateOverwrite, + }, + mkResourceVirtualEnvironmentCertificatePrivateKey: &schema.Schema{ + Type: schema.TypeString, + Description: "The PEM encoded private key", + Required: true, + Sensitive: true, + }, + }, + Create: resourceVirtualEnvironmentCertificateCreate, + Read: resourceVirtualEnvironmentCertificateRead, + Update: resourceVirtualEnvironmentCertificateUpdate, + Delete: resourceVirtualEnvironmentCertificateDelete, + } +} + +func resourceVirtualEnvironmentCertificateCreate(d *schema.ResourceData, m interface{}) error { + err := resourceVirtualEnvironmentCertificateUpdate(d, m) + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentCertificateNodeName).(string) + + d.SetId(fmt.Sprintf("%s_certificate", nodeName)) + + return nil +} + +func resourceVirtualEnvironmentCertificateGetUpdateBody(d *schema.ResourceData, m interface{}) (*proxmox.VirtualEnvironmentCertificateUpdateRequestBody, error) { + certificate := d.Get(mkResourceVirtualEnvironmentCertificateCertificate).(string) + certificateChain := d.Get(mkResourceVirtualEnvironmentCertificateCertificateChain).(string) + overwrite := proxmox.CustomBool(d.Get(mkResourceVirtualEnvironmentCertificateOverwrite).(bool)) + privateKey := d.Get(mkResourceVirtualEnvironmentCertificatePrivateKey).(string) + + combinedCertificates := strings.TrimSpace(certificate) + "\n" + + if certificateChain != "" { + combinedCertificates += strings.TrimSpace(certificateChain) + "\n" + } + + force := overwrite + + if d.Id() != "" { + force = proxmox.CustomBool(true) + } + + restart := proxmox.CustomBool(true) + + body := &proxmox.VirtualEnvironmentCertificateUpdateRequestBody{ + Certificates: combinedCertificates, + Force: &force, + PrivateKey: &privateKey, + Restart: &restart, + } + + return body, nil +} + +func resourceVirtualEnvironmentCertificateRead(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentCertificateNodeName).(string) + list, err := veClient.ListCertificates(nodeName) + + if err != nil { + return err + } + + d.Set(mkResourceVirtualEnvironmentCertificateCertificate, "") + d.Set(mkResourceVirtualEnvironmentCertificateCertificateChain, "") + + certificateChain := d.Get(mkResourceVirtualEnvironmentCertificateCertificateChain).(string) + + for _, c := range *list { + if c.FileName != nil && *c.FileName == "pveproxy-ssl.pem" { + if c.Certificates != nil { + newCertificate := "" + newCertificateChain := "" + + if certificateChain != "" { + certificates := strings.Split(strings.TrimSpace(*c.Certificates), "\n") + newCertificate = certificates[0] + "\n" + + if len(certificates) > 1 { + newCertificateChain = strings.Join(certificates[1:], "\n") + "\n" + } + } else { + newCertificate = *c.Certificates + } + + d.Set(mkResourceVirtualEnvironmentCertificateCertificate, newCertificate) + d.Set(mkResourceVirtualEnvironmentCertificateCertificateChain, newCertificateChain) + } + } + } + + return nil +} + +func resourceVirtualEnvironmentCertificateUpdate(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentCertificateNodeName).(string) + + body, err := resourceVirtualEnvironmentCertificateGetUpdateBody(d, m) + + if err != nil { + return err + } + + err = veClient.UpdateCertificate(nodeName, body) + + if err != nil { + return err + } + + return resourceVirtualEnvironmentCertificateRead(d, m) +} + +func resourceVirtualEnvironmentCertificateDelete(d *schema.ResourceData, m interface{}) error { + config := m.(providerConfiguration) + veClient, err := config.GetVEClient() + + if err != nil { + return err + } + + nodeName := d.Get(mkResourceVirtualEnvironmentCertificateNodeName).(string) + restart := proxmox.CustomBool(true) + + err = veClient.DeleteCertificate(nodeName, &proxmox.VirtualEnvironmentCertificateDeleteRequestBody{ + Restart: &restart, + }) + + if err != nil { + return err + } + + d.SetId("") + + return nil +} diff --git a/proxmoxtf/resource_virtual_environment_certificate_test.go b/proxmoxtf/resource_virtual_environment_certificate_test.go new file mode 100644 index 00000000..4dbdecb6 --- /dev/null +++ b/proxmoxtf/resource_virtual_environment_certificate_test.go @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package proxmoxtf + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" +) + +// TestResourceVirtualEnvironmentCertificateInstantiation tests whether the ResourceVirtualEnvironmentCertificate instance can be instantiated. +func TestResourceVirtualEnvironmentCertificateInstantiation(t *testing.T) { + s := resourceVirtualEnvironmentCertificate() + + if s == nil { + t.Fatalf("Cannot instantiate resourceVirtualEnvironmentCertificate") + } +} + +// TestResourceVirtualEnvironmentCertificateSchema tests the resourceVirtualEnvironmentCertificate schema. +func TestResourceVirtualEnvironmentCertificateSchema(t *testing.T) { + s := resourceVirtualEnvironmentCertificate() + + testRequiredArguments(t, s, []string{ + mkResourceVirtualEnvironmentCertificateCertificate, + mkResourceVirtualEnvironmentCertificateNodeName, + mkResourceVirtualEnvironmentCertificatePrivateKey, + }) + + testOptionalArguments(t, s, []string{ + mkResourceVirtualEnvironmentCertificateCertificateChain, + }) + + testSchemaValueTypes(t, s, []string{ + mkResourceVirtualEnvironmentCertificateCertificate, + mkResourceVirtualEnvironmentCertificateCertificateChain, + mkResourceVirtualEnvironmentCertificateNodeName, + mkResourceVirtualEnvironmentCertificatePrivateKey, + }, []schema.ValueType{ + schema.TypeString, + schema.TypeString, + schema.TypeString, + schema.TypeString, + }) +} diff --git a/proxmoxtf/resource_virtual_environment_vm.go b/proxmoxtf/resource_virtual_environment_vm.go index f8546749..98b237f2 100644 --- a/proxmoxtf/resource_virtual_environment_vm.go +++ b/proxmoxtf/resource_virtual_environment_vm.go @@ -349,6 +349,7 @@ func resourceVirtualEnvironmentVM() *schema.Resource { Description: "The SSH password", Optional: true, ForceNew: true, + Sensitive: true, Default: dvResourceVirtualEnvironmentVMCloudInitUserAccountPassword, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { return strings.ReplaceAll(old, "*", "") == ""