diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 6e07ccfee..ad82a8add 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -30,6 +30,8 @@ import ( "testing" "time" + "github.com/hashicorp/vault/sdk/helper/testhelpers/schema" + "github.com/stretchr/testify/require" "github.com/armon/go-metrics" @@ -550,7 +552,6 @@ func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[s if err != nil { return err } - if !reflect.DeepEqual(entries, expected) { return fmt.Errorf("expected urls\n%#v\ndoes not match provided\n%#v\n", expected, entries) } @@ -1983,6 +1984,7 @@ func TestBackend_PathFetchCertList(t *testing.T) { Data: rootData, MountPoint: "pki/", }) + if resp != nil && resp.IsError() { t.Fatalf("failed to generate root, %#v", resp) } @@ -2003,6 +2005,16 @@ func TestBackend_PathFetchCertList(t *testing.T) { Data: urlsData, MountPoint: "pki/", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/urls"), logical.UpdateOperation), resp, true) + + resp, err = b.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.ReadOperation, + Path: "config/urls", + Storage: storage, + MountPoint: "pki/", + }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/urls"), logical.ReadOperation), resp, true) + if resp != nil && resp.IsError() { t.Fatalf("failed to config urls, %#v", resp) } @@ -2410,6 +2422,8 @@ func TestBackend_Root_Idempotency(t *testing.T) { resp, err = CBWrite(b, s, "config/ca", map[string]interface{}{ "pem_bundle": pemBundleRootCA, }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/ca"), logical.UpdateOperation), resp, true) + require.NoError(t, err) require.NotNil(t, resp, "expected ca info") firstImportedKeys := resp.Data["imported_keys"].([]string) @@ -6013,11 +6027,16 @@ func TestPKI_TemplatedAIAs(t *testing.T) { b, s := CreateBackendWithStorage(t) // Setting templated AIAs should succeed. - _, err := CBWrite(b, s, "config/cluster", map[string]interface{}{ + resp, err := CBWrite(b, s, "config/cluster", map[string]interface{}{ "path": "http://localhost:8200/v1/pki", "aia_path": "http://localhost:8200/cdn/pki", }) require.NoError(t, err) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/cluster"), logical.UpdateOperation), resp, true) + + resp, err = CBRead(b, s, "config/cluster") + require.NoError(t, err) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/cluster"), logical.ReadOperation), resp, true) aiaData := map[string]interface{}{ "crl_distribution_points": "{{cluster_path}}/issuer/{{issuer_id}}/crl/der", @@ -6046,7 +6065,7 @@ func TestPKI_TemplatedAIAs(t *testing.T) { "enable_templating": false, }) require.NoError(t, err) - resp, err := CBWrite(b, s, "root/generate/internal", rootData) + resp, err = CBWrite(b, s, "root/generate/internal", rootData) requireSuccessNonNilResponse(t, resp, err) issuerId := string(resp.Data["issuer_id"].(issuerID)) diff --git a/builtin/logical/pki/crl_test.go b/builtin/logical/pki/crl_test.go index a5f83b2f8..3490114b9 100644 --- a/builtin/logical/pki/crl_test.go +++ b/builtin/logical/pki/crl_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/hashicorp/vault/sdk/helper/testhelpers/schema" + "github.com/hashicorp/vault/api" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/sdk/logical" @@ -90,8 +92,11 @@ func TestBackend_CRLConfig(t *testing.T) { "auto_rebuild_grace_period": tc.autoRebuildGracePeriod, }) requireSuccessNonNilResponse(t, resp, err) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/crl"), logical.UpdateOperation), resp, true) resp, err = CBRead(b, s, "config/crl") + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/crl"), logical.ReadOperation), resp, true) + requireSuccessNonNilResponse(t, resp, err) requireFieldsSetInResp(t, resp, "disable", "expiry", "ocsp_disable", "auto_rebuild", "auto_rebuild_grace_period") diff --git a/builtin/logical/pki/integration_test.go b/builtin/logical/pki/integration_test.go index c2bdffbde..91d0e96e6 100644 --- a/builtin/logical/pki/integration_test.go +++ b/builtin/logical/pki/integration_test.go @@ -11,6 +11,8 @@ import ( "fmt" "testing" + "github.com/hashicorp/vault/sdk/helper/testhelpers/schema" + "github.com/hashicorp/vault/sdk/logical" "github.com/stretchr/testify/require" ) @@ -371,6 +373,7 @@ func TestIntegration_AutoIssuer(t *testing.T) { "issuer_name": "root-1", "key_type": "ec", }) + requireSuccessNonNilResponse(t, resp, err) issuerIdOne := resp.Data["issuer_id"] require.NotEmpty(t, issuerIdOne) @@ -381,12 +384,15 @@ func TestIntegration_AutoIssuer(t *testing.T) { requireSuccessNonNilResponse(t, resp, err) require.Equal(t, issuerIdOne, resp.Data["default"]) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.ReadOperation), resp, true) + // Enable the new config option. - _, err = CBWrite(b, s, "config/issuers", map[string]interface{}{ + resp, err = CBWrite(b, s, "config/issuers", map[string]interface{}{ "default": issuerIdOne, "default_follows_latest_issuer": true, }) require.NoError(t, err) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("config/issuers"), logical.UpdateOperation), resp, true) // Now generate the second root; it should become default. resp, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{ diff --git a/builtin/logical/pki/path_config_ca.go b/builtin/logical/pki/path_config_ca.go index 2399db4e5..3dcf4e981 100644 --- a/builtin/logical/pki/path_config_ca.go +++ b/builtin/logical/pki/path_config_ca.go @@ -2,6 +2,7 @@ package pki import ( "context" + "net/http" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" @@ -21,6 +22,28 @@ secret key and certificate.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathImportIssuers, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "mapping": { + Type: framework.TypeMap, + Description: "A mapping of issuer_id to key_id for all issuers included in this request", + Required: true, + }, + "imported_keys": { + Type: framework.TypeCommaStringSlice, + Description: "Net-new keys imported as a part of this request", + Required: true, + }, + "imported_issuers": { + Type: framework.TypeCommaStringSlice, + Description: "Net-new issuers imported as a part of this request", + Required: true, + }, + }, + }}, + }, // Read more about why these flags are set in backend.go. ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, @@ -58,13 +81,44 @@ func pathConfigIssuers(b *backend) *framework.Path { Default: false, }, }, - Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.pathCAIssuersRead, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "default": { + Type: framework.TypeString, + Description: `Reference (name or identifier) to the default issuer.`, + Required: true, + }, + "default_follows_latest_issuer": { + Type: framework.TypeBool, + Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`, + Required: true, + }, + }, + }}, + }, }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathCAIssuersWrite, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "default": { + Type: framework.TypeString, + Description: `Reference (name or identifier) to the default issuer.`, + }, + "default_follows_latest_issuer": { + Type: framework.TypeBool, + Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`, + }, + }, + }}, + }, // Read more about why these flags are set in backend.go. ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, @@ -90,6 +144,23 @@ func pathReplaceRoot(b *backend) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathCAIssuersWrite, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "default": { + Type: framework.TypeString, + Description: `Reference (name or identifier) to the default issuer.`, + Required: true, + }, + "default_follows_latest_issuer": { + Type: framework.TypeBool, + Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`, + Required: true, + }, + }, + }}, + }, // Read more about why these flags are set in backend.go. ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, @@ -208,12 +279,35 @@ func pathConfigKeys(b *backend) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ - Callback: b.pathKeyDefaultWrite, + Callback: b.pathKeyDefaultWrite, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "default": { + Type: framework.TypeString, + Description: `Reference (name or identifier) to the default issuer.`, + Required: true, + }, + }, + }}, + }, ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, logical.ReadOperation: &framework.PathOperation{ - Callback: b.pathKeyDefaultRead, + Callback: b.pathKeyDefaultRead, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "default": { + Type: framework.TypeString, + Description: `Reference (name or identifier) to the default issuer.`, + }, + }, + }}, + }, ForwardPerformanceStandby: false, ForwardPerformanceSecondary: false, }, diff --git a/builtin/logical/pki/path_config_cluster.go b/builtin/logical/pki/path_config_cluster.go index c887db7b7..6d72c2674 100644 --- a/builtin/logical/pki/path_config_cluster.go +++ b/builtin/logical/pki/path_config_cluster.go @@ -3,6 +3,7 @@ package pki import ( "context" "fmt" + "net/http" "github.com/asaskevich/govalidator" "github.com/hashicorp/vault/sdk/framework" @@ -41,9 +42,70 @@ For example: http://cdn.example.com/pr1/pki`, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathWriteCluster, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "path": { + Type: framework.TypeString, + Description: `Canonical URI to this mount on this performance +replication cluster's external address. This is for resolving AIA URLs and +providing the {{cluster_path}} template parameter but might be used for other +purposes in the future. + +This should only point back to this particular PR replica and should not ever +point to another PR cluster. It may point to any node in the PR replica, +including standby nodes, and need not always point to the active node. + +For example: https://pr1.vault.example.com:8200/v1/pki`, + }, + "aia_path": { + Type: framework.TypeString, + Description: `Optional URI to this mount's AIA distribution +point; may refer to an external non-Vault responder. This is for resolving AIA +URLs and providing the {{cluster_aia_path}} template parameter and will not +be used for other purposes. As such, unlike path above, this could safely +be an insecure transit mechanism (like HTTP without TLS). + +For example: http://cdn.example.com/pr1/pki`, + }, + }, + }}, + }, }, logical.ReadOperation: &framework.PathOperation{ Callback: b.pathReadCluster, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "path": { + Type: framework.TypeString, + Description: `Canonical URI to this mount on this performance +replication cluster's external address. This is for resolving AIA URLs and +providing the {{cluster_path}} template parameter but might be used for other +purposes in the future. + +This should only point back to this particular PR replica and should not ever +point to another PR cluster. It may point to any node in the PR replica, +including standby nodes, and need not always point to the active node. + +For example: https://pr1.vault.example.com:8200/v1/pki`, + Required: true, + }, + "aia_path": { + Type: framework.TypeString, + Description: `Optional URI to this mount's AIA distribution +point; may refer to an external non-Vault responder. This is for resolving AIA +URLs and providing the {{cluster_aia_path}} template parameter and will not +be used for other purposes. As such, unlike path above, this could safely +be an insecure transit mechanism (like HTTP without TLS). + +For example: http://cdn.example.com/pr1/pki`, + }, + }, + }}, + }, }, }, diff --git a/builtin/logical/pki/path_config_crl.go b/builtin/logical/pki/path_config_crl.go index 9cf57aa1e..81de70566 100644 --- a/builtin/logical/pki/path_config_crl.go +++ b/builtin/logical/pki/path_config_crl.go @@ -3,6 +3,7 @@ package pki import ( "context" "fmt" + "net/http" "time" "github.com/hashicorp/vault/helper/constants" @@ -110,9 +111,141 @@ existing CRL and OCSP paths will return the unified CRL instead of a response ba Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.pathCRLRead, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "expiry": { + Type: framework.TypeString, + Description: `The amount of time the generated CRL should be +valid; defaults to 72 hours`, + Required: true, + }, + "disable": { + Type: framework.TypeBool, + Description: `If set to true, disables generating the CRL entirely.`, + Required: true, + }, + "ocsp_disable": { + Type: framework.TypeBool, + Description: `If set to true, ocsp unauthorized responses will be returned.`, + Required: true, + }, + "ocsp_expiry": { + Type: framework.TypeString, + Description: `The amount of time an OCSP response will be valid (controls +the NextUpdate field); defaults to 12 hours`, + Required: true, + }, + "auto_rebuild": { + Type: framework.TypeBool, + Description: `If set to true, enables automatic rebuilding of the CRL`, + Required: true, + }, + "auto_rebuild_grace_period": { + Type: framework.TypeString, + Description: `The time before the CRL expires to automatically rebuild it, when enabled. Must be shorter than the CRL expiry. Defaults to 12h.`, + Required: true, + }, + "enable_delta": { + Type: framework.TypeBool, + Description: `Whether to enable delta CRLs between authoritative CRL rebuilds`, + Required: true, + }, + "delta_rebuild_interval": { + Type: framework.TypeString, + Description: `The time between delta CRL rebuilds if a new revocation has occurred. Must be shorter than the CRL expiry. Defaults to 15m.`, + Required: true, + }, + "cross_cluster_revocation": { + Type: framework.TypeBool, + Description: `Whether to enable a global, cross-cluster revocation queue. +Must be used with auto_rebuild=true.`, + Required: true, + }, + "unified_crl": { + Type: framework.TypeBool, + Description: `If set to true enables global replication of revocation entries, +also enabling unified versions of OCSP and CRLs if their respective features are enabled. +disable for CRLs and ocsp_disable for OCSP.`, + Required: true, + }, + "unified_crl_on_existing_paths": { + Type: framework.TypeBool, + Description: `If set to true, +existing CRL and OCSP paths will return the unified CRL instead of a response based on cluster-local data`, + Required: true, + }, + }, + }}, + }, }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathCRLWrite, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "expiry": { + Type: framework.TypeString, + Description: `The amount of time the generated CRL should be +valid; defaults to 72 hours`, + Default: "72h", + }, + "disable": { + Type: framework.TypeBool, + Description: `If set to true, disables generating the CRL entirely.`, + }, + "ocsp_disable": { + Type: framework.TypeBool, + Description: `If set to true, ocsp unauthorized responses will be returned.`, + }, + "ocsp_expiry": { + Type: framework.TypeString, + Description: `The amount of time an OCSP response will be valid (controls +the NextUpdate field); defaults to 12 hours`, + Default: "1h", + }, + "auto_rebuild": { + Type: framework.TypeBool, + Description: `If set to true, enables automatic rebuilding of the CRL`, + }, + "auto_rebuild_grace_period": { + Type: framework.TypeString, + Description: `The time before the CRL expires to automatically rebuild it, when enabled. Must be shorter than the CRL expiry. Defaults to 12h.`, + Default: "12h", + }, + "enable_delta": { + Type: framework.TypeBool, + Description: `Whether to enable delta CRLs between authoritative CRL rebuilds`, + }, + "delta_rebuild_interval": { + Type: framework.TypeString, + Description: `The time between delta CRL rebuilds if a new revocation has occurred. Must be shorter than the CRL expiry. Defaults to 15m.`, + Default: "15m", + }, + "cross_cluster_revocation": { + Type: framework.TypeBool, + Description: `Whether to enable a global, cross-cluster revocation queue. +Must be used with auto_rebuild=true.`, + Required: false, + }, + "unified_crl": { + Type: framework.TypeBool, + Description: `If set to true enables global replication of revocation entries, +also enabling unified versions of OCSP and CRLs if their respective features are enabled. +disable for CRLs and ocsp_disable for OCSP.`, + Required: false, + }, + "unified_crl_on_existing_paths": { + Type: framework.TypeBool, + Description: `If set to true, +existing CRL and OCSP paths will return the unified CRL instead of a response based on cluster-local data`, + Required: false, + }, + }, + }}, + }, // Read more about why these flags are set in backend.go. ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, diff --git a/builtin/logical/pki/path_config_urls.go b/builtin/logical/pki/path_config_urls.go index 5c81f9336..7ab24c8a6 100644 --- a/builtin/logical/pki/path_config_urls.go +++ b/builtin/logical/pki/path_config_urls.go @@ -3,6 +3,7 @@ package pki import ( "context" "fmt" + "net/http" "strings" "github.com/asaskevich/govalidator" @@ -48,9 +49,74 @@ to be set on all PR secondary clusters.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathWriteURL, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "issuing_certificates": { + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the issuing certificate attribute. See also RFC 5280 Section 4.2.2.1.`, + }, + "crl_distribution_points": { + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the CRL distribution points attribute. See also RFC 5280 Section 4.2.1.13.`, + }, + "ocsp_servers": { + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the OCSP servers attribute. See also RFC 5280 Section 4.2.2.1.`, + }, + "enable_templating": { + Type: framework.TypeBool, + Description: `Whether or not to enabling templating of the +above AIA fields. When templating is enabled the special values '{{issuer_id}}' +and '{{cluster_path}}' are available, but the addresses are not checked for +URI validity until issuance time. This requires /config/cluster's path to be +set on all PR Secondary clusters.`, + Default: false, + }, + }, + }}, + }, }, logical.ReadOperation: &framework.PathOperation{ Callback: b.pathReadURL, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "issuing_certificates": { + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the issuing certificate attribute. See also RFC 5280 Section 4.2.2.1.`, + Required: true, + }, + "crl_distribution_points": { + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the CRL distribution points attribute. See also RFC 5280 Section 4.2.1.13.`, + Required: true, + }, + "ocsp_servers": { + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the OCSP servers attribute. See also RFC 5280 Section 4.2.2.1.`, + Required: true, + }, + "enable_templating": { + Type: framework.TypeBool, + Description: `Whether or not to enable templating of the +above AIA fields. When templating is enabled the special values '{{issuer_id}}' +and '{{cluster_path}}' are available, but the addresses are not checked for +URI validity until issuance time. This requires /config/cluster's path to be +set on all PR Secondary clusters.`, + Required: true, + }, + }, + }}, + }, }, }, diff --git a/changelog/18376.txt b/changelog/18376.txt new file mode 100644 index 000000000..1edc3df5a --- /dev/null +++ b/changelog/18376.txt @@ -0,0 +1,3 @@ +```release-note:improvement +openapi: Add openapi response definitions to pki/config_*.go +```