diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b7492ee..6b92642d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ ## Unreleased +FEATURES: + +* Update `vault_pki_secret_backend_root_cert` and `vault_pki_secret_backend_root_sign_intermediate` to support the new fields for the name constraints extension. Requires Vault 1.19+ ([#2396](https://github.com/hashicorp/terraform-provider-vault/pull/2396)). + BUGS: * Do not panic on Vault PKI roles without the cn_validations field: ([#2398](https://github.com/hashicorp/terraform-provider-vault/pull/2398)) - ## 4.6.0 (Jan 15, 2025) FEATURES: @@ -15,7 +18,6 @@ FEATURES: * Update `vault_pki_secret_backend_role` to support the `cn_validations` role field ([#1820](https://github.com/hashicorp/terraform-provider-vault/pull/1820)). * Add new resource `vault_pki_secret_backend_acme_eab` to manage PKI ACME external account binding tokens. Requires Vault 1.14+. ([#2367](https://github.com/hashicorp/terraform-provider-vault/pull/2367)) * Add new data source and resource `vault_pki_secret_backend_config_cmpv2`. Requires Vault 1.18+. *Available only for Vault Enterprise* ([#2330](https://github.com/hashicorp/terraform-provider-vault/pull/2330)) - IMPROVEMENTS: * Support the event `subscribe` policy capability for `vault_policy_document` data source ([#2293](https://github.com/hashicorp/terraform-provider-vault/pull/2293)) diff --git a/go.mod b/go.mod index 7eccf4800..44f80a43a 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/coreos/pkg v0.0.0-20230601102743-20bbbf26f4d8 github.com/denisenkom/go-mssqldb v0.12.3 github.com/go-sql-driver/mysql v1.7.1 + github.com/go-test/deep v1.1.0 github.com/google/uuid v1.6.0 github.com/gosimple/slug v1.13.1 github.com/hashicorp/consul/api v1.27.0 diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 8f33f0a5e..4b7face98 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -315,6 +315,13 @@ const ( FieldMaxPathLength = "max_path_length" FieldExcludeCNFromSans = "exclude_cn_from_sans" FieldPermittedDNSDomains = "permitted_dns_domains" + FieldExcludedDNSDomains = "excluded_dns_domains" + FieldPermittedIPRanges = "permitted_ip_ranges" + FieldExcludedIPRanges = "excluded_ip_ranges" + FieldPermittedEmailAddresses = "permitted_email_addresses" + FieldExcludedEmailAddresses = "excluded_email_addresses" + FieldPermittedURIDomains = "permitted_uri_domains" + FieldExcludedURIDomains = "excluded_uri_domains" FieldIssuerName = "issuer_name" FieldUserIds = "user_ids" FieldIssuerID = "issuer_id" diff --git a/vault/resource_pki_secret_backend_root_cert.go b/vault/resource_pki_secret_backend_root_cert.go index 87bec540d..104943d2c 100644 --- a/vault/resource_pki_secret_backend_root_cert.go +++ b/vault/resource_pki_secret_backend_root_cert.go @@ -222,6 +222,69 @@ func pkiSecretBackendRootCertResource() *schema.Resource { Type: schema.TypeString, }, }, + consts.FieldExcludedDNSDomains: { + Type: schema.TypeList, + Optional: true, + Description: "List of domains for which certificates are not allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldPermittedIPRanges: { + Type: schema.TypeList, + Optional: true, + Description: "List of IP ranges for which certificates are allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldExcludedIPRanges: { + Type: schema.TypeList, + Optional: true, + Description: "List of IP ranges for which certificates are not allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldPermittedEmailAddresses: { + Type: schema.TypeList, + Optional: true, + Description: "List of email addresses for which certificates are allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldExcludedEmailAddresses: { + Type: schema.TypeList, + Optional: true, + Description: "List of email addresses for which certificates are not allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldPermittedURIDomains: { + Type: schema.TypeList, + Optional: true, + Description: "List of URI domains for which certificates are allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldExcludedURIDomains: { + Type: schema.TypeList, + Optional: true, + Description: "List of URI domains for which certificates are not allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, consts.FieldOu: { Type: schema.TypeString, Optional: true, @@ -393,6 +456,20 @@ func pkiSecretBackendRootCertCreate(_ context.Context, d *schema.ResourceData, m } } + // Whether name constraints fields (other than permitted_dns_domains), are supproted, + // See VAULT-32141. + isNameConstraintsExtensionSupported := provider.IsAPISupported(meta, provider.VaultVersion119) + if isNameConstraintsExtensionSupported { + rootCertStringArrayFields = append(rootCertStringArrayFields, + consts.FieldExcludedDNSDomains, + consts.FieldPermittedIPRanges, + consts.FieldExcludedIPRanges, + consts.FieldPermittedEmailAddresses, + consts.FieldExcludedEmailAddresses, + consts.FieldPermittedURIDomains, + consts.FieldExcludedURIDomains, + ) + } data := map[string]interface{}{} rawConfig := d.GetRawConfig() for _, k := range rootCertAPIFields { diff --git a/vault/resource_pki_secret_backend_root_cert_test.go b/vault/resource_pki_secret_backend_root_cert_test.go index 2c1c7be0f..1b2776284 100644 --- a/vault/resource_pki_secret_backend_root_cert_test.go +++ b/vault/resource_pki_secret_backend_root_cert_test.go @@ -4,7 +4,12 @@ package vault import ( + "crypto/x509" + "encoding/pem" + "errors" "fmt" + "github.com/go-test/deep" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "reflect" "strconv" "testing" @@ -19,11 +24,35 @@ import ( func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { path := "pki-" + strconv.Itoa(acctest.RandInt()) - + config := testPkiSecretBackendRootCertificateConfig_basic(path) resourceName := "vault_pki_secret_backend_root_cert.test" + checks := []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, path), + resource.TestCheckResourceAttr(resourceName, consts.FieldType, "internal"), + resource.TestCheckResourceAttr(resourceName, consts.FieldCommonName, "test Root CA"), + resource.TestCheckResourceAttr(resourceName, consts.FieldTTL, "86400"), + resource.TestCheckResourceAttr(resourceName, consts.FieldFormat, "pem"), + resource.TestCheckResourceAttr(resourceName, consts.FieldPrivateKeyFormat, "der"), + resource.TestCheckResourceAttr(resourceName, consts.FieldKeyType, "rsa"), + resource.TestCheckResourceAttr(resourceName, consts.FieldKeyBits, "4096"), + resource.TestCheckResourceAttr(resourceName, consts.FieldOu, "test"), + resource.TestCheckResourceAttr(resourceName, consts.FieldOrganization, "test"), + resource.TestCheckResourceAttr(resourceName, consts.FieldCountry, "test"), + resource.TestCheckResourceAttr(resourceName, consts.FieldLocality, "test"), + resource.TestCheckResourceAttr(resourceName, consts.FieldProvince, "test"), + resource.TestCheckResourceAttrSet(resourceName, consts.FieldSerialNumber), + assertCertificateAttributes(resourceName), + } - store := &testPKICertStore{} + testPkiSecretBackendRootCertificate(t, path, config, resourceName, checks, nil) +} +// TestPkiSecretBackendRootCertificate_name_constraints is just like TestPkiSecretBackendRootCertificate_basic, +// but it uses the permitted_/excluded_ parameters for the name constraints extension. +func TestPkiSecretBackendRootCertificate_name_constraints(t *testing.T) { + path := "pki-" + strconv.Itoa(acctest.RandInt()) + config := testPkiSecretBackendRootCertificateConfig_name_constraints(path) + resourceName := "vault_pki_secret_backend_root_cert.test" checks := []resource.TestCheckFunc{ resource.TestCheckResourceAttr(resourceName, consts.FieldBackend, path), resource.TestCheckResourceAttr(resourceName, consts.FieldType, "internal"), @@ -39,16 +68,107 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, consts.FieldLocality, "test"), resource.TestCheckResourceAttr(resourceName, consts.FieldProvince, "test"), resource.TestCheckResourceAttrSet(resourceName, consts.FieldSerialNumber), - assertCertificateAttributes(resourceName), + + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedDNSDomains+".0", "example.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedDNSDomains+".1", ".example.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldExcludedDNSDomains+".0", "bad.example.com"), + + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedIPRanges+".0", "192.0.2.0/24"), + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedIPRanges+".1", "2001:db8::/32"), + resource.TestCheckResourceAttr(resourceName, consts.FieldExcludedIPRanges+".0", "192.0.3.0/24"), + resource.TestCheckResourceAttr(resourceName, consts.FieldExcludedIPRanges+".1", "2002::/16"), + + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedEmailAddresses+".0", "admin@example.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedEmailAddresses+".1", "info@example.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldExcludedEmailAddresses+".0", "root@example.com"), + + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedURIDomains+".0", "https://example.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldPermittedURIDomains+".1", "https://www.example.com"), + resource.TestCheckResourceAttr(resourceName, consts.FieldExcludedURIDomains+".0", "ftp://example.com"), + func(s *terraform.State) error { + return checkCertificateNameConstraints(resourceName, s) + }, + } + + testPkiSecretBackendRootCertificate(t, path, config, resourceName, checks, func(t *testing.T) { + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion119) + }) +} + +func checkCertificateNameConstraints(resourceName string, s *terraform.State) error { + var cert *x509.Certificate + { + rs, err := testutil.GetResourceFromRootModule(s, resourceName) + if err != nil { + return err + } + + attrs := rs.Primary.Attributes + + // "pem" for resource_pki_secret_backend_root_cert root certs, + // "pem_bundle" for resource_pki_secret_backend_root_sign_intermediate. + if !(attrs["format"] == "pem" || attrs["format"] == "pem_bundle") { + return errors.New("test only valid for resources configured with the 'pem' or 'pem_bundle' format") + } + + certPEM := attrs["certificate"] + if certPEM == "" { + return fmt.Errorf("certificate from state cannot be empty") + } + + b, _ := pem.Decode([]byte(certPEM)) + if err != nil { + return err + } + + cert, err = x509.ParseCertificate(b.Bytes) + if err != nil { + return err + } + } + var failedChecks []error + check := func(fieldName string, actual []string, expected ...string) { + diff := deep.Equal(expected, actual) + if len(diff) > 0 { + failedChecks = append(failedChecks, fmt.Errorf("error in field %q: %v", fieldName, diff)) + } + } + + check(consts.FieldPermittedDNSDomains, cert.PermittedDNSDomains, "example.com", ".example.com") + check(consts.FieldExcludedDNSDomains, cert.ExcludedDNSDomains, "bad.example.com") + var ips []string + for _, ip := range cert.PermittedIPRanges { + ips = append(ips, ip.String()) } + check(consts.FieldPermittedIPRanges, ips, "192.0.2.0/24", "2001:db8::/32") + ips = nil + for _, ip := range cert.ExcludedIPRanges { + ips = append(ips, ip.String()) + } + check(consts.FieldExcludedIPRanges, ips, "192.0.3.0/24", "2002::/16") + check(consts.FieldPermittedEmailAddresses, cert.PermittedEmailAddresses, "admin@example.com", "info@example.com") + check(consts.FieldExcludedEmailAddresses, cert.ExcludedEmailAddresses, "root@example.com") + check(consts.FieldPermittedURIDomains, cert.PermittedURIDomains, "https://example.com", "https://www.example.com") + check(consts.FieldExcludedURIDomains, cert.ExcludedURIDomains, "ftp://example.com") + + return errors.Join(failedChecks...) +} + +func testPkiSecretBackendRootCertificate(t *testing.T, path string, config string, resourceName string, checks []resource.TestCheckFunc, preCheck func(t *testing.T)) { + store := &testPKICertStore{} resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, - PreCheck: func() { testutil.TestAccPreCheck(t) }, - CheckDestroy: testCheckMountDestroyed("vault_mount", consts.MountTypePKI, consts.FieldPath), + PreCheck: func() { + testutil.TestAccPreCheck(t) + if preCheck != nil { + preCheck(t) + } + }, + CheckDestroy: testCheckMountDestroyed("vault_mount", consts.MountTypePKI, consts.FieldPath), Steps: []resource.TestStep{ { - Config: testPkiSecretBackendRootCertificateConfig_basic(path), + Config: config, Check: resource.ComposeTestCheckFunc( append(checks, testCapturePKICert(resourceName, store), @@ -67,7 +187,7 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { t.Fatal(err) } }, - Config: testPkiSecretBackendRootCertificateConfig_basic(path), + Config: config, Check: resource.ComposeTestCheckFunc( append(checks, testPKICertReIssued(resourceName, store), @@ -100,7 +220,7 @@ func TestPkiSecretBackendRootCertificate_basic(t *testing.T) { t.Fatalf("empty response for write on path %s", genPath) } }, - Config: testPkiSecretBackendRootCertificateConfig_basic(path), + Config: config, Check: resource.ComposeTestCheckFunc( append(checks, testPKICertReIssued(resourceName, store), @@ -271,6 +391,45 @@ resource "vault_pki_secret_backend_root_cert" "test" { return config } +func testPkiSecretBackendRootCertificateConfig_name_constraints(path string) string { + config := fmt.Sprintf(` +resource "vault_mount" "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" { + 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" + permitted_dns_domains = ["example.com",".example.com"] + excluded_dns_domains = ["bad.example.com"] + permitted_ip_ranges = ["192.0.2.0/24", "2001:db8::/32"] + excluded_ip_ranges = ["192.0.3.0/24", "2002::/16"] + permitted_email_addresses = ["admin@example.com","info@example.com"] + excluded_email_addresses = ["root@example.com"] + permitted_uri_domains = ["https://example.com", "https://www.example.com"] + excluded_uri_domains = ["ftp://example.com"] +} +`, path) + + return config +} + func testPkiSecretBackendRootCertificateConfig_multiIssuerInternal(path, issuer, key string) string { config := fmt.Sprintf(` resource "vault_mount" "test" { diff --git a/vault/resource_pki_secret_backend_root_sign_intermediate.go b/vault/resource_pki_secret_backend_root_sign_intermediate.go index f11848e59..5516e8106 100644 --- a/vault/resource_pki_secret_backend_root_sign_intermediate.go +++ b/vault/resource_pki_secret_backend_root_sign_intermediate.go @@ -137,6 +137,69 @@ func pkiSecretBackendRootSignIntermediateResource() *schema.Resource { Type: schema.TypeString, }, }, + consts.FieldExcludedDNSDomains: { + Type: schema.TypeList, + Optional: true, + Description: "List of domains for which certificates are not allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldPermittedIPRanges: { + Type: schema.TypeList, + Optional: true, + Description: "List of IP ranges for which certificates are allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldExcludedIPRanges: { + Type: schema.TypeList, + Optional: true, + Description: "List of IP ranges for which certificates are NOT allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldPermittedEmailAddresses: { + Type: schema.TypeList, + Optional: true, + Description: "List of email addresses for which certificates are allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldExcludedEmailAddresses: { + Type: schema.TypeList, + Optional: true, + Description: "List of email addresses for which certificates are not allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldPermittedURIDomains: { + Type: schema.TypeList, + Optional: true, + Description: "List of URI domains for which certificates are allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + consts.FieldExcludedURIDomains: { + Type: schema.TypeList, + Optional: true, + Description: "List of URI domains for which certificates are not allowed to be issued.", + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, consts.FieldOu: { Type: schema.TypeString, Optional: true, @@ -262,6 +325,21 @@ func pkiSecretBackendRootSignIntermediateCreate(ctx context.Context, d *schema.R consts.FieldPermittedDNSDomains, } + // Whether name constraints fields (other than permitted_dns_domains), are supproted, + // See VAULT-32141. + isNameConstraintsExtensionSupported := provider.IsAPISupported(meta, provider.VaultVersion119) + if isNameConstraintsExtensionSupported { + intermediateSignStringArrayFields = append(intermediateSignStringArrayFields, + consts.FieldExcludedDNSDomains, + consts.FieldPermittedIPRanges, + consts.FieldExcludedIPRanges, + consts.FieldPermittedEmailAddresses, + consts.FieldExcludedEmailAddresses, + consts.FieldPermittedURIDomains, + consts.FieldExcludedURIDomains, + ) + } + data := map[string]interface{}{} rawConfig := d.GetRawConfig() for _, k := range intermediateSignAPIFields { diff --git a/vault/resource_pki_secret_backend_root_sign_intermediate_test.go b/vault/resource_pki_secret_backend_root_sign_intermediate_test.go index d520936fe..d0d25dcf7 100644 --- a/vault/resource_pki_secret_backend_root_sign_intermediate_test.go +++ b/vault/resource_pki_secret_backend_root_sign_intermediate_test.go @@ -123,7 +123,7 @@ func TestPkiSecretBackendRootSignIntermediate_basic_default(t *testing.T) { store := &testPKICertStore{} resourceName := "vault_pki_secret_backend_root_sign_intermediate.test" - checks := testCheckPKISecretRootSignIntermediate(resourceName, rootPath, commonName, format) + checks := testCheckPKISecretRootSignIntermediate(resourceName, rootPath, commonName, format, false) resource.Test(t, resource.TestCase{ ProviderFactories: providerFactories, PreCheck: func() { testutil.TestAccPreCheck(t) }, @@ -170,7 +170,7 @@ func TestPkiSecretBackendRootSignIntermediate_basic_pem(t *testing.T) { Steps: []resource.TestStep{ { Config: testPkiSecretBackendRootSignIntermediateConfig_basic(rootPath, intermediatePath, format, false, ""), - Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.test", rootPath, commonName, format), + Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.test", rootPath, commonName, format, false), }, }, }) @@ -189,7 +189,7 @@ func TestPkiSecretBackendRootSignIntermediate_basic_der(t *testing.T) { Steps: []resource.TestStep{ { Config: testPkiSecretBackendRootSignIntermediateConfig_basic(rootPath, intermediatePath, format, false, ""), - Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.test", rootPath, commonName, format), + Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.test", rootPath, commonName, format, false), }, }, }) @@ -208,7 +208,29 @@ func TestPkiSecretBackendRootSignIntermediate_basic_pem_bundle(t *testing.T) { Steps: []resource.TestStep{ { Config: testPkiSecretBackendRootSignIntermediateConfig_basic(rootPath, intermediatePath, format, false, ""), - Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.test", rootPath, commonName, format), + Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.test", rootPath, commonName, format, false), + }, + }, + }) +} + +func TestPkiSecretBackendRootSignIntermediate_name_constraints_pem_bundle(t *testing.T) { + rootPath := "pki-root-" + strconv.Itoa(acctest.RandInt()) + intermediatePath := "pki-intermediate-" + strconv.Itoa(acctest.RandInt()) + format := "pem_bundle" + commonName := "SubOrg Intermediate CA" + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { + testutil.TestAccPreCheck(t) + SkipIfAPIVersionLT(t, testProvider.Meta(), provider.VaultVersion119) + }, + CheckDestroy: testCheckMountDestroyed("vault_mount", consts.MountTypePKI, consts.FieldPath), + Steps: []resource.TestStep{ + { + Config: testPkiSecretBackendRootSignIntermediateConfig_name_constraints(rootPath, intermediatePath, format, false, ""), + Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.test", rootPath, commonName, format, true), }, }, }) @@ -231,15 +253,14 @@ func TestPkiSecretBackendRootSignIntermediate_basic_pem_bundle_multiple_intermed Steps: []resource.TestStep{ { Config: testPkiSecretBackendRootSignIntermediateConfig_multiple_inter(rootPath, intermediate1Path, intermediate2Path, format), - Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.two", intermediate1Path, commonName, format), + Check: testCheckPKISecretRootSignIntermediate("vault_pki_secret_backend_root_sign_intermediate.two", intermediate1Path, commonName, format, false), }, }, }) } -func testCheckPKISecretRootSignIntermediate(res, path, commonName, format string) resource.TestCheckFunc { - return resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(res, "backend", path), +func testCheckPKISecretRootSignIntermediate(res, path, commonName, format string, checkNameConstraintsAttrs bool) resource.TestCheckFunc { + checks := []resource.TestCheckFunc{resource.TestCheckResourceAttr(res, "backend", path), resource.TestCheckResourceAttr(res, "common_name", commonName), resource.TestCheckResourceAttr(res, "ou", "SubUnit"), resource.TestCheckResourceAttr(res, "organization", "SubOrg"), @@ -251,7 +272,32 @@ func testCheckPKISecretRootSignIntermediate(res, path, commonName, format string assertPKICertificateBundle(res, format), assertPKICAChain(res), assertCertificateAttributes(res), - ) + } + if checkNameConstraintsAttrs { + // Note that the name constraints extension field values are the same as in resource_pki_secret_backend_root_cert_test.go + // only for dev convinience (i.e. laziness). + checks = append(checks, + resource.TestCheckResourceAttr(res, consts.FieldPermittedDNSDomains+".0", "example.com"), + resource.TestCheckResourceAttr(res, consts.FieldPermittedDNSDomains+".1", ".example.com"), + resource.TestCheckResourceAttr(res, consts.FieldExcludedDNSDomains+".0", "bad.example.com"), + + resource.TestCheckResourceAttr(res, consts.FieldPermittedIPRanges+".0", "192.0.2.0/24"), + resource.TestCheckResourceAttr(res, consts.FieldPermittedIPRanges+".1", "2001:db8::/32"), + resource.TestCheckResourceAttr(res, consts.FieldExcludedEmailAddresses+".0", "root@example.com"), + + resource.TestCheckResourceAttr(res, consts.FieldPermittedEmailAddresses+".0", "admin@example.com"), + resource.TestCheckResourceAttr(res, consts.FieldPermittedEmailAddresses+".1", "info@example.com"), + resource.TestCheckResourceAttr(res, consts.FieldExcludedEmailAddresses+".0", "root@example.com"), + + resource.TestCheckResourceAttr(res, consts.FieldPermittedURIDomains+".0", "https://example.com"), + resource.TestCheckResourceAttr(res, consts.FieldPermittedURIDomains+".1", "https://www.example.com"), + resource.TestCheckResourceAttr(res, consts.FieldExcludedURIDomains+".0", "ftp://example.com"), + func(s *terraform.State) error { + return checkCertificateNameConstraints(res, s) + }, + ) + } + return resource.ComposeTestCheckFunc(checks...) } func assertPKICertificateBundle(res, expectedFormat string) resource.TestCheckFunc { @@ -432,6 +478,84 @@ resource "vault_pki_secret_backend_root_sign_intermediate" "test" { return config + "}" } +func testPkiSecretBackendRootSignIntermediateConfig_name_constraints(rootPath, path, format string, revoke bool, issuerRef string) string { + config := fmt.Sprintf(` +resource "vault_mount" "test-root" { + path = "%s" + type = "pki" + description = "test root" + default_lease_ttl_seconds = "8640000" + max_lease_ttl_seconds = "8640000" +} + +resource "vault_mount" "test-intermediate" { + path = "%s" + type = vault_mount.test-root.type + description = "test intermediate" + default_lease_ttl_seconds = "86400" + max_lease_ttl_seconds = "86400" +} + +resource "vault_pki_secret_backend_root_cert" "test" { + backend = vault_mount.test-root.path + type = "internal" + common_name = "RootOrg Root CA" + ttl = "86400" + format = "pem" + private_key_format = "der" + key_type = "rsa" + key_bits = 4096 + exclude_cn_from_sans = true + ou = "Organizational Unit" + organization = "RootOrg" + country = "US" + locality = "San Francisco" + province = "CA" +} + +resource "vault_pki_secret_backend_intermediate_cert_request" "test" { + depends_on = [vault_pki_secret_backend_root_cert.test] + backend = vault_mount.test-intermediate.path + type = "internal" + common_name = "SubOrg Intermediate CA" +} + +resource "vault_pki_secret_backend_root_sign_intermediate" "test" { + backend = vault_mount.test-root.path + csr = vault_pki_secret_backend_intermediate_cert_request.test.csr + common_name = "SubOrg Intermediate CA" + exclude_cn_from_sans = true + ou = "SubUnit" + organization = "SubOrg" + country = "US" + locality = "San Francisco" + province = "CA" + permitted_dns_domains = ["example.com",".example.com"] + excluded_dns_domains = ["bad.example.com"] + permitted_ip_ranges = ["192.0.2.0/24", "2001:db8::/32"] + excluded_ip_ranges = ["192.0.3.0/24", "2002:db8::/16"] + permitted_email_addresses = ["admin@example.com","info@example.com"] + excluded_email_addresses = ["root@example.com"] + permitted_uri_domains = ["https://example.com", "https://www.example.com"] + excluded_uri_domains = ["ftp://example.com"] + revoke = %t +`, rootPath, path, revoke) + + if format != "" { + config += fmt.Sprintf(` + format = %q +`, format) + } + + if issuerRef != "" { + config += fmt.Sprintf(` + issuer_ref = "%s" +`, issuerRef) + } + + return config + "}" +} + func testPkiSecretBackendRootSignIntermediateConfig_multiple_inter(rootPath, prePath, path, format string) string { config := fmt.Sprintf(` resource "vault_mount" "root" { 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 ddabac1c3..b4024fa7c 100644 --- a/website/docs/r/pki_secret_backend_root_cert.html.md +++ b/website/docs/r/pki_secret_backend_root_cert.html.md @@ -80,6 +80,20 @@ The following arguments are supported: * `permitted_dns_domains` - (Optional) List of domains for which certificates are allowed to be issued +* `excluded_dns_domains` - (Optional) List of domains for which certificates are not allowed to be issued. Requires Vault version 1.19+. + +* `permitted_ip_ranges` - (Optional) List of IP ranges for which certificates are allowed to be issued. Requires Vault version 1.19+. + +* `excluded_ip_ranges` - (Optional) List of IP ranges for which certificates are not allowed to be issued. Requires Vault version 1.19+. + +* `permitted_email_addresses` - (Optional) List of email addresses for which certificates are allowed to be issued. Requires Vault version 1.19+. + +* `excluded_email_addresses` - (Optional) List of email addresses for which certificates are not allowed to be issued. Requires Vault version 1.19+. + +* `permitted_uri_domains` - (Optional) List of URI domains for which certificates are allowed to be issued. Requires Vault version 1.19+. + +* `excluded_uri_domains` - (Optional) List of URI domains for which certificates are not allowed to be issued. Requires Vault version 1.19+. + * `ou` - (Optional) The organization unit * `organization` - (Optional) The organization diff --git a/website/docs/r/pki_secret_backend_root_sign_intermediate.html.md b/website/docs/r/pki_secret_backend_root_sign_intermediate.html.md index ae77367c6..819025287 100644 --- a/website/docs/r/pki_secret_backend_root_sign_intermediate.html.md +++ b/website/docs/r/pki_secret_backend_root_sign_intermediate.html.md @@ -59,6 +59,20 @@ The following arguments are supported: * `permitted_dns_domains` - (Optional) List of domains for which certificates are allowed to be issued +* `excluded_dns_domains` - (Optional) List of domains for which certificates are not allowed to be issued. Requires Vault version 1.19+. + +* `permitted_ip_ranges` - (Optional) List of IP ranges for which certificates are allowed to be issued. Requires Vault version 1.19+. + +* `excluded_ip_ranges` - (Optional) List of IP ranges for which certificates are not allowed to be issued. Requires Vault version 1.19+. + +* `permitted_email_addresses` - (Optional) List of email addresses for which certificates are allowed to be issued. Requires Vault version 1.19+. + +* `excluded_email_addresses` - (Optional) List of email addresses for which certificates are not allowed to be issued. Requires Vault version 1.19+. + +* `permitted_uri_domains` - (Optional) List of URI domains for which certificates are allowed to be issued. Requires Vault version 1.19+. + +* `excluded_uri_domains` - (Optional) List of URI domains for which certificates are not allowed to be issued. Requires Vault version 1.19+. + * `ou` - (Optional) The organization unit * `organization` - (Optional) The organization