From 624411dbedb2896400e1008cef8e14c3d23fdfe7 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Tue, 3 May 2022 16:08:22 -0400 Subject: [PATCH 1/8] Force new root CA creation on out of band changes --- testutil/testutl.go | 1 + util/util.go | 6 +- .../resource_pki_secret_backend_root_cert.go | 82 ++++++++++-- ...ource_pki_secret_backend_root_cert_test.go | 121 +++++++++++++----- 4 files changed, 166 insertions(+), 44 deletions(-) create mode 100644 testutil/testutl.go diff --git a/testutil/testutl.go b/testutil/testutl.go new file mode 100644 index 000000000..110b2e6a7 --- /dev/null +++ b/testutil/testutl.go @@ -0,0 +1 @@ +package testutil diff --git a/util/util.go b/util/util.go index eb85f63e5..9b309d69d 100644 --- a/util/util.go +++ b/util/util.go @@ -42,7 +42,11 @@ func ToStringArray(input []interface{}) []string { } func Is404(err error) bool { - return strings.Contains(err.Error(), "Code: 404") + return IsHTTPErrorCode(err, http.StatusNotFound) +} + +func IsHTTPErrorCode(err error, code int) bool { + return strings.Contains(err.Error(), fmt.Sprintf("Code: %d", code)) } func CalculateConflictsWith(self string, group []string) []string { diff --git a/vault/resource_pki_secret_backend_root_cert.go b/vault/resource_pki_secret_backend_root_cert.go index 49b281147..f317166d3 100644 --- a/vault/resource_pki_secret_backend_root_cert.go +++ b/vault/resource_pki_secret_backend_root_cert.go @@ -1,21 +1,59 @@ package vault import ( + "context" + "crypto/x509" + "encoding/pem" "fmt" + "io" "log" + "net/http" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/sdk/helper/certutil" + + "github.com/hashicorp/terraform-provider-vault/util" ) func pkiSecretBackendRootCertResource() *schema.Resource { return &schema.Resource{ Create: pkiSecretBackendRootCertCreate, - Read: pkiSecretBackendRootCertRead, - Update: pkiSecretBackendRootCertUpdate, Delete: pkiSecretBackendRootCertDelete, + Update: func(data *schema.ResourceData, i interface{}) error { + return nil + }, + Read: func(data *schema.ResourceData, i interface{}) error { + return nil + }, + CustomizeDiff: func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { + client := meta.(*api.Client) + cert, err := getCACertificate(client, d.Get("backend").(string)) + if err != nil { + return err + } + + if cert != nil { + key := "serial" + cur := d.Get(key).(string) + n := certutil.GetHexFormatted(cert.SerialNumber.Bytes(), ":") + if err := d.SetNew(key, n); err != nil { + return err + } + + o, _ := d.GetChange(key) + // don't force new on new resources + if o.(string) != "" && cur != n { + if err := d.ForceNew(key); err != nil { + return err + } + } + + } + return nil + }, Schema: map[string]*schema.Schema{ "backend": { @@ -177,7 +215,7 @@ func pkiSecretBackendRootCertResource() *schema.Resource { "certificate": { Type: schema.TypeString, Computed: true, - Description: "The certicate.", + Description: "The certificate.", }, "issuing_ca": { Type: schema.TypeString, @@ -281,15 +319,43 @@ func pkiSecretBackendRootCertCreate(d *schema.ResourceData, meta interface{}) er d.Set("serial", resp.Data["serial_number"]) d.SetId(path) - return pkiSecretBackendRootCertRead(d, meta) -} -func pkiSecretBackendRootCertRead(d *schema.ResourceData, meta interface{}) error { return nil } -func pkiSecretBackendRootCertUpdate(d *schema.ResourceData, m interface{}) error { - return nil +func getCACertificate(client *api.Client, mount string) (*x509.Certificate, error) { + path := fmt.Sprintf("/v1/%s/ca/pem", mount) + req := client.NewRequest(http.MethodGet, path) + req.ClientToken = "" + resp, err := client.RawRequest(req) + if err != nil { + if util.IsHTTPErrorCode(err, http.StatusNotFound) || util.IsHTTPErrorCode(err, http.StatusForbidden) { + return nil, nil + } + return nil, err + } + + if resp == nil { + return nil, fmt.Errorf("expected a response body, got nil response") + } + + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + log.Printf("[INFO] Reading current CA") + b, _ := pem.Decode(data) + if b != nil { + cert, err := x509.ParseCertificate(b.Bytes) + if err != nil { + return nil, err + } + return cert, nil + } + + return nil, nil } func pkiSecretBackendRootCertDelete(d *schema.ResourceData, meta interface{}) error { diff --git a/vault/resource_pki_secret_backend_root_cert_test.go b/vault/resource_pki_secret_backend_root_cert_test.go index b8810f0dd..00d7d7fda 100644 --- a/vault/resource_pki_secret_backend_root_cert_test.go +++ b/vault/resource_pki_secret_backend_root_cert_test.go @@ -17,6 +17,25 @@ import ( func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { path := "pki-" + strconv.Itoa(acctest.RandInt()) + resourceName := "vault_pki_secret_backend_root_cert.test" + + checks := []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "backend", path), + resource.TestCheckResourceAttr(resourceName, "type", "internal"), + resource.TestCheckResourceAttr(resourceName, "common_name", "test Root CA"), + resource.TestCheckResourceAttr(resourceName, "ttl", "86400"), + resource.TestCheckResourceAttr(resourceName, "format", "pem"), + resource.TestCheckResourceAttr(resourceName, "private_key_format", "der"), + resource.TestCheckResourceAttr(resourceName, "key_type", "rsa"), + resource.TestCheckResourceAttr(resourceName, "key_bits", "4096"), + resource.TestCheckResourceAttr(resourceName, "ou", "test"), + resource.TestCheckResourceAttr(resourceName, "organization", "test"), + resource.TestCheckResourceAttr(resourceName, "country", "test"), + resource.TestCheckResourceAttr(resourceName, "locality", "test"), + resource.TestCheckResourceAttr(resourceName, "province", "test"), + resource.TestCheckResourceAttrSet(resourceName, "serial"), + } + resource.Test(t, resource.TestCase{ Providers: testProviders, PreCheck: func() { testutil.TestAccPreCheck(t) }, @@ -24,22 +43,52 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testPkiSecretBackendRootCertificateConfig_basic(path), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "backend", path), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "type", "internal"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "common_name", "test Root CA"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "ttl", "86400"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "format", "pem"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "private_key_format", "der"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "key_type", "rsa"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "key_bits", "4096"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "ou", "test"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "organization", "test"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "country", "test"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "locality", "test"), - resource.TestCheckResourceAttr("vault_pki_secret_backend_root_cert.test", "province", "test"), - resource.TestCheckResourceAttrSet("vault_pki_secret_backend_root_cert.test", "serial"), - ), + Check: resource.ComposeTestCheckFunc(checks...), + }, + { + PreConfig: func() { + client := testProvider.Meta().(*api.Client) + _, err := client.Logical().Delete(fmt.Sprintf("%s/root", path)) + if err != nil { + t.Fatal(err) + } + }, + Config: testPkiSecretBackendRootCertificateConfig_basic(path), + }, + { + // test unmounted backend + PreConfig: func() { + client := testProvider.Meta().(*api.Client) + if err := client.Sys().Unmount(path); err != nil { + t.Fatal(err) + } + }, + Config: testPkiSecretBackendRootCertificateConfig_basic(path), + }, + { + // test out of band update to the root CA + PreConfig: func() { + client := testProvider.Meta().(*api.Client) + _, err := client.Logical().Delete(fmt.Sprintf("%s/root", path)) + if err != nil { + t.Fatal(err) + } + genPath := pkiSecretBackendIntermediateSetSignedReadPath(path, "internal") + resp, err := client.Logical().Write(genPath, + map[string]interface{}{ + "common_name": "out-of-band", + }, + ) + if err != nil { + t.Fatal(err) + } + + if resp == nil { + t.Fatalf("empty response for write on path %s", genPath) + } + }, + Config: testPkiSecretBackendRootCertificateConfig_basic(path), + Check: resource.ComposeTestCheckFunc(checks...), }, }, }) @@ -69,30 +118,32 @@ func testPkiSecretBackendRootCertificateDestroy(s *terraform.State) error { } func testPkiSecretBackendRootCertificateConfig_basic(path string) string { - return fmt.Sprintf(` + config := fmt.Sprintf(` resource "vault_mount" "test" { - path = "%s" - type = "pki" - description = "test" + path = "%s" + type = "pki" + description = "test" default_lease_ttl_seconds = "86400" max_lease_ttl_seconds = "86400" } resource "vault_pki_secret_backend_root_cert" "test" { - depends_on = [ "vault_mount.test" ] - backend = vault_mount.test.path - type = "internal" - common_name = "test Root CA" - ttl = "86400" - format = "pem" - private_key_format = "der" - key_type = "rsa" - key_bits = 4096 + backend = vault_mount.test.path + type = "internal" + common_name = "test Root CA" + ttl = "86400" + format = "pem" + private_key_format = "der" + key_type = "rsa" + key_bits = 4096 exclude_cn_from_sans = true - ou = "test" - organization = "test" - country = "test" - locality = "test" - province = "test" -}`, path) + ou = "test" + organization = "test" + country = "test" + locality = "test" + province = "test" +} +`, path) + + return config } From 7abee158fe54ce81a2eae7a7cd2298018d148945 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Tue, 3 May 2022 18:07:06 -0400 Subject: [PATCH 2/8] Add read function for detecting an orphaned mount --- util/util.go | 30 +++++++++-- .../resource_pki_secret_backend_root_cert.go | 53 ++++++++++++++----- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/util/util.go b/util/util.go index 9b309d69d..de94dc967 100644 --- a/util/util.go +++ b/util/util.go @@ -42,11 +42,16 @@ func ToStringArray(input []interface{}) []string { } func Is404(err error) bool { - return IsHTTPErrorCode(err, http.StatusNotFound) + return ErrorContainsHTTPCode(err, http.StatusNotFound) } -func IsHTTPErrorCode(err error, code int) bool { - return strings.Contains(err.Error(), fmt.Sprintf("Code: %d", code)) +func ErrorContainsHTTPCode(err error, codes ...int) bool { + for _, code := range codes { + if strings.Contains(err.Error(), fmt.Sprintf("Code: %d", code)) { + return true + } + } + return false } func CalculateConflictsWith(self string, group []string) []string { @@ -300,3 +305,22 @@ func SetResourceData(d *schema.ResourceData, data map[string]interface{}) error return nil } + +// NormalizeMountPath to be in a form valid for accessing values from api.MountOutput +func NormalizeMountPath(path string) string { + return strings.Trim(path, "/") + "/" +} + +// CheckMountEnabled in Vault, path must contain a trailing '/', +func CheckMountEnabled(client *api.Client, path string) (bool, error) { + mounts, err := client.Sys().ListMounts() + if err != nil { + return false, err + } + + if _, ok := mounts[NormalizeMountPath(path)]; !ok { + return true, nil + } + + return false, nil +} diff --git a/vault/resource_pki_secret_backend_root_cert.go b/vault/resource_pki_secret_backend_root_cert.go index f317166d3..ff6f3a2ee 100644 --- a/vault/resource_pki_secret_backend_root_cert.go +++ b/vault/resource_pki_secret_backend_root_cert.go @@ -25,10 +25,18 @@ func pkiSecretBackendRootCertResource() *schema.Resource { Update: func(data *schema.ResourceData, i interface{}) error { return nil }, - Read: func(data *schema.ResourceData, i interface{}) error { - return nil - }, + //Read: func(data *schema.ResourceData, i interface{}) error { + // return nil + //}, + Read: pkiSecretBackendRootCertRead, CustomizeDiff: func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { + key := "serial" + o, _ := d.GetChange(key) + // skip on new resource + if o.(string) == "" { + return nil + } + client := meta.(*api.Client) cert, err := getCACertificate(client, d.Get("backend").(string)) if err != nil { @@ -36,22 +44,18 @@ func pkiSecretBackendRootCertResource() *schema.Resource { } if cert != nil { - key := "serial" - cur := d.Get(key).(string) n := certutil.GetHexFormatted(cert.SerialNumber.Bytes(), ":") - if err := d.SetNew(key, n); err != nil { - return err - } - - o, _ := d.GetChange(key) - // don't force new on new resources - if o.(string) != "" && cur != n { + if d.Get(key).(string) != n { + if err := d.SetNewComputed(key); err != nil { + return err + } if err := d.ForceNew(key); err != nil { return err } } } + return nil }, @@ -323,13 +327,35 @@ func pkiSecretBackendRootCertCreate(d *schema.ResourceData, meta interface{}) er return nil } +func pkiSecretBackendRootCertRead(d *schema.ResourceData, meta interface{}) error { + if d.IsNewResource() { + return nil + } + + client := meta.(*api.Client) + path := d.Get("backend").(string) + enabled, err := util.CheckMountEnabled(client, path) + if err != nil { + log.Printf("[WARN] Failed to check if mount %q exist, preempting the read operation", path) + return nil + } + + // trigger a resource re-creation whenever the engine's mount has disappeared + if enabled { + log.Printf("[WARN] Mount %q does not exist, setting resource for re-creation", path) + d.SetId("") + } + + return nil +} + func getCACertificate(client *api.Client, mount string) (*x509.Certificate, error) { path := fmt.Sprintf("/v1/%s/ca/pem", mount) req := client.NewRequest(http.MethodGet, path) req.ClientToken = "" resp, err := client.RawRequest(req) if err != nil { - if util.IsHTTPErrorCode(err, http.StatusNotFound) || util.IsHTTPErrorCode(err, http.StatusForbidden) { + if util.ErrorContainsHTTPCode(err, http.StatusNotFound, http.StatusForbidden) { return nil, nil } return nil, err @@ -345,7 +371,6 @@ func getCACertificate(client *api.Client, mount string) (*x509.Certificate, erro return nil, err } - log.Printf("[INFO] Reading current CA") b, _ := pem.Decode(data) if b != nil { cert, err := x509.ParseCertificate(b.Bytes) From bf19e0cc1a19932331387058f01a39a1ac5fed85 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Tue, 3 May 2022 18:42:16 -0400 Subject: [PATCH 3/8] Drop vestigial Read func --- vault/resource_pki_secret_backend_root_cert.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/vault/resource_pki_secret_backend_root_cert.go b/vault/resource_pki_secret_backend_root_cert.go index ff6f3a2ee..62c130f55 100644 --- a/vault/resource_pki_secret_backend_root_cert.go +++ b/vault/resource_pki_secret_backend_root_cert.go @@ -25,9 +25,6 @@ func pkiSecretBackendRootCertResource() *schema.Resource { Update: func(data *schema.ResourceData, i interface{}) error { return nil }, - //Read: func(data *schema.ResourceData, i interface{}) error { - // return nil - //}, Read: pkiSecretBackendRootCertRead, CustomizeDiff: func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { key := "serial" From 1ac9b551d4acd17fbf732d26ccd2e5854bd98cce Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Wed, 4 May 2022 08:26:15 -0400 Subject: [PATCH 4/8] Remove extraneous file --- testutil/testutl.go | 1 - 1 file changed, 1 deletion(-) delete mode 100644 testutil/testutl.go diff --git a/testutil/testutl.go b/testutil/testutl.go deleted file mode 100644 index 110b2e6a7..000000000 --- a/testutil/testutl.go +++ /dev/null @@ -1 +0,0 @@ -package testutil From fd6cb1af982200f591e635a747ff32b7bcbf5ac6 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Thu, 5 May 2022 09:47:18 -0400 Subject: [PATCH 5/8] Correct logic in CheckMountEnabled() --- util/util.go | 6 ++---- vault/resource_pki_secret_backend_root_cert.go | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/util/util.go b/util/util.go index de94dc967..1450b767b 100644 --- a/util/util.go +++ b/util/util.go @@ -318,9 +318,7 @@ func CheckMountEnabled(client *api.Client, path string) (bool, error) { return false, err } - if _, ok := mounts[NormalizeMountPath(path)]; !ok { - return true, nil - } + _, ok := mounts[NormalizeMountPath(path)] - return false, nil + return ok, nil } diff --git a/vault/resource_pki_secret_backend_root_cert.go b/vault/resource_pki_secret_backend_root_cert.go index 62c130f55..bd4ba7a5d 100644 --- a/vault/resource_pki_secret_backend_root_cert.go +++ b/vault/resource_pki_secret_backend_root_cert.go @@ -338,7 +338,7 @@ func pkiSecretBackendRootCertRead(d *schema.ResourceData, meta interface{}) erro } // trigger a resource re-creation whenever the engine's mount has disappeared - if enabled { + if !enabled { log.Printf("[WARN] Mount %q does not exist, setting resource for re-creation", path) d.SetId("") } From 14011340bfbe323ad0729ede0a5b3e9551bfdaf3 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Thu, 5 May 2022 14:46:27 -0400 Subject: [PATCH 6/8] Add support for testing certificate renewal - add new serial_number field, deprecation serial --- .../resource_pki_secret_backend_cert_test.go | 57 +++++++++++++--- .../resource_pki_secret_backend_root_cert.go | 35 ++++++++++ ...ource_pki_secret_backend_root_cert_test.go | 67 +++++++++++++++---- .../r/pki_secret_backend_root_cert.html.md | 8 ++- 4 files changed, 143 insertions(+), 24 deletions(-) diff --git a/vault/resource_pki_secret_backend_cert_test.go b/vault/resource_pki_secret_backend_cert_test.go index fd0471745..61b1a9827 100644 --- a/vault/resource_pki_secret_backend_cert_test.go +++ b/vault/resource_pki_secret_backend_cert_test.go @@ -22,6 +22,7 @@ import ( type testPKICertStore struct { cert string + serialNumber string expectRevoked bool } @@ -341,22 +342,34 @@ func testPkiSecretBackendCertWaitUntilRenewal(n string) resource.TestCheckFunc { } } -func testCapturePKICert(resourcePath string, store *testPKICertStore) resource.TestCheckFunc { +func testCapturePKICert(resourceName string, store *testPKICertStore) resource.TestCheckFunc { return func(s *terraform.State) error { - for _, rs := range s.RootModule().Resources { - if rs.Type != "vault_pki_secret_backend_cert" { - continue - } + rs, err := testGetResourceFromRootModule(s, resourceName) + if err != nil { + return err + } + + cert, ok := rs.Primary.Attributes["certificate"] + if !ok { + return fmt.Errorf("certificate not found in state") + } + store.cert = cert - store.cert = rs.Primary.Attributes["certificate"] - v, err := strconv.ParseBool(rs.Primary.Attributes["revoke"]) + sn, ok := rs.Primary.Attributes["serial_number"] + if !ok { + return fmt.Errorf("serial_number not found in state") + } + store.serialNumber = sn + + if val, ok := rs.Primary.Attributes["revoke"]; ok { + v, err := strconv.ParseBool(val) if err != nil { return err } store.expectRevoked = v - return nil } - return fmt.Errorf("certificate not found in state") + + return nil } } @@ -414,3 +427,29 @@ func testPKICertRevocation(path string, store *testPKICertStore) resource.TestCh return nil } } + +func testPKICertReIssued(resourceName string, store *testPKICertStore) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, err := testGetResourceFromRootModule(s, resourceName) + if err != nil { + return err + } + if store.serialNumber == "" { + return fmt.Errorf("serial_number must be set on test store %#v", store) + } + + if store.serialNumber == rs.Primary.Attributes["serial_number"] { + return fmt.Errorf("expected certificate not re-issued, serial_number was not changed") + } + + return nil + } +} + +func testGetResourceFromRootModule(s *terraform.State, resourceName string) (*terraform.ResourceState, error) { + if rs, ok := s.RootModule().Resources[resourceName]; ok { + return rs, nil + } + + return nil, fmt.Errorf("expected resource %q, not found in state", resourceName) +} diff --git a/vault/resource_pki_secret_backend_root_cert.go b/vault/resource_pki_secret_backend_root_cert.go index bd4ba7a5d..bdcecdafd 100644 --- a/vault/resource_pki_secret_backend_root_cert.go +++ b/vault/resource_pki_secret_backend_root_cert.go @@ -26,6 +26,14 @@ func pkiSecretBackendRootCertResource() *schema.Resource { return nil }, Read: pkiSecretBackendRootCertRead, + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + Type: pkiSecretBackendRootCertV0().CoreConfigSchema().ImpliedType(), + Upgrade: pkiSecretBackendRootCertUpgradeV0, + }, + }, + SchemaVersion: 1, CustomizeDiff: func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { key := "serial" o, _ := d.GetChange(key) @@ -226,8 +234,14 @@ func pkiSecretBackendRootCertResource() *schema.Resource { "serial": { Type: schema.TypeString, Computed: true, + Deprecated: "Use serial_number instead", Description: "The serial number.", }, + "serial_number": { + Type: schema.TypeString, + Computed: true, + Description: "The certificate's serial number, hex formatted.", + }, }, } } @@ -318,6 +332,7 @@ func pkiSecretBackendRootCertCreate(d *schema.ResourceData, meta interface{}) er d.Set("certificate", resp.Data["certificate"]) d.Set("issuing_ca", resp.Data["issuing_ca"]) d.Set("serial", resp.Data["serial_number"]) + d.Set("serial_number", resp.Data["serial_number"]) d.SetId(path) @@ -402,3 +417,23 @@ func pkiSecretBackendIntermediateSetSignedReadPath(backend string, rootType stri func pkiSecretBackendIntermediateSetSignedDeletePath(backend string) string { return strings.Trim(backend, "/") + "/root" } + +func pkiSecretBackendRootCertV0() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "serial_number": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func pkiSecretBackendRootCertUpgradeV0( + _ context.Context, rawState map[string]interface{}, _ interface{}, +) (map[string]interface{}, error) { + rawState["serial_number"] = rawState["serial"] + + return rawState, nil +} diff --git a/vault/resource_pki_secret_backend_root_cert_test.go b/vault/resource_pki_secret_backend_root_cert_test.go index 00d7d7fda..cf379a57d 100644 --- a/vault/resource_pki_secret_backend_root_cert_test.go +++ b/vault/resource_pki_secret_backend_root_cert_test.go @@ -2,6 +2,7 @@ package vault import ( "fmt" + "reflect" "strconv" "strings" "testing" @@ -19,6 +20,8 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { resourceName := "vault_pki_secret_backend_root_cert.test" + var store testPKICertStore + checks := []resource.TestCheckFunc{ resource.TestCheckResourceAttr(resourceName, "backend", path), resource.TestCheckResourceAttr(resourceName, "type", "internal"), @@ -34,6 +37,7 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "locality", "test"), resource.TestCheckResourceAttr(resourceName, "province", "test"), resource.TestCheckResourceAttrSet(resourceName, "serial"), + resource.TestCheckResourceAttrSet(resourceName, "serial_number"), } resource.Test(t, resource.TestCase{ @@ -43,17 +47,11 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testPkiSecretBackendRootCertificateConfig_basic(path), - Check: resource.ComposeTestCheckFunc(checks...), - }, - { - PreConfig: func() { - client := testProvider.Meta().(*api.Client) - _, err := client.Logical().Delete(fmt.Sprintf("%s/root", path)) - if err != nil { - t.Fatal(err) - } - }, - Config: testPkiSecretBackendRootCertificateConfig_basic(path), + Check: resource.ComposeTestCheckFunc( + append(checks, + testCapturePKICert(resourceName, &store), + )..., + ), }, { // test unmounted backend @@ -64,6 +62,12 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { } }, Config: testPkiSecretBackendRootCertificateConfig_basic(path), + Check: resource.ComposeTestCheckFunc( + append(checks, + testPKICertReIssued(resourceName, &store), + testCapturePKICert(resourceName, &store), + )..., + ), }, { // test out of band update to the root CA @@ -88,7 +92,11 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { } }, Config: testPkiSecretBackendRootCertificateConfig_basic(path), - Check: resource.ComposeTestCheckFunc(checks...), + Check: resource.ComposeTestCheckFunc( + append(checks, + testPKICertReIssued(resourceName, &store), + )..., + ), }, }, }) @@ -147,3 +155,38 @@ resource "vault_pki_secret_backend_root_cert" "test" { return config } + +func Test_pkiSecretBackendRootCertUpgradeV0(t *testing.T) { + tests := []struct { + name string + rawState map[string]interface{} + want map[string]interface{} + wantErr bool + }{ + { + name: "basic", + rawState: map[string]interface{}{ + "serial": "aa:bb:cc:dd:ee", + }, + want: map[string]interface{}{ + "serial": "aa:bb:cc:dd:ee", + "serial_number": "aa:bb:cc:dd:ee", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := pkiSecretBackendRootCertUpgradeV0(nil, tt.rawState, nil) + + if tt.wantErr { + if err == nil { + t.Fatalf("pkiSecretBackendRootCertUpgradeV0() error = %#v, wantErr %#v", err, tt.wantErr) + } + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("pkiSecretBackendRootCertUpgradeV0() got = %#v, want %#v", got, tt.want) + } + }) + } +} diff --git a/website/docs/r/pki_secret_backend_root_cert.html.md b/website/docs/r/pki_secret_backend_root_cert.html.md index ddde253cd..a56ea0b2c 100644 --- a/website/docs/r/pki_secret_backend_root_cert.html.md +++ b/website/docs/r/pki_secret_backend_root_cert.html.md @@ -88,8 +88,10 @@ The following arguments are supported: In addition to the fields above, the following attributes are exported: -* `certificate` - The certificate +* `certificate` - The certificate. -* `issuing_ca` - The issuing CA +* `issuing_ca` - The issuing CA certificate. -* `serial` - The serial +* `serial` - Deprecated, use `serial_number` instead. + +* `serial_number` - The certificate's serial number, hex formatted. From 2cc24828e34d5a9cffdaee6ce4c5b1b6cf45ec12 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Thu, 5 May 2022 15:38:46 -0400 Subject: [PATCH 7/8] Docs: move serial to the deprecations section --- website/docs/r/pki_secret_backend_sign.html.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/docs/r/pki_secret_backend_sign.html.md b/website/docs/r/pki_secret_backend_sign.html.md index 5945a5253..269f6ac96 100644 --- a/website/docs/r/pki_secret_backend_sign.html.md +++ b/website/docs/r/pki_secret_backend_sign.html.md @@ -97,6 +97,10 @@ In addition to the fields above, the following attributes are exported: * `ca_chain` - The CA chain -* `serial` - The serial +* `serial_number` - The certificate's serial number, hex formatted. * `expiration` - The expiration date of the certificate in unix epoch format + +## Deprecations + +* `serial` - Use `serial_number` instead. From 35570aa63a5a45172dffb3ef09236c152ae244a3 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Fri, 6 May 2022 16:43:50 -0400 Subject: [PATCH 8/8] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 655c0cfdb..9cb3c36af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ ## 3.6.0 (Unreleased) +IMPROVEMENTS: +* `resource/pki_secret_backend_root_cert`: Force new root CA resource creation on out-of-band changes. + ([#1428](https://github.com/hashicorp/terraform-provider-vault/pull/1428)) + BUGS: * `resource/pki_secret_backend_root_sign_intermediate`: Ensure that the `certificate_bundle`, and `ca_chain` do not contain duplicate certificates.