From 8651d6de38890ae5d74903d21971386b915b81e5 Mon Sep 17 00:00:00 2001 From: AnPucel Date: Wed, 15 Feb 2023 15:09:57 -0800 Subject: [PATCH] PKI Response Structures Part 2 (#18479) Response structures from intermediate --> manage_keys --- builtin/logical/pki/backend_test.go | 11 +- builtin/logical/pki/crl_test.go | 8 +- builtin/logical/pki/path_intermediate.go | 23 +++ builtin/logical/pki/path_issue_sign.go | 127 +++++++++++++ builtin/logical/pki/path_manage_issuers.go | 184 +++++++++++++++++++ builtin/logical/pki/path_manage_keys.go | 57 +++++- builtin/logical/pki/path_manage_keys_test.go | 7 + 7 files changed, 413 insertions(+), 4 deletions(-) diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index ad82a8add99b..ec4733af0744 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -130,9 +130,10 @@ func TestPKI_RequireCN(t *testing.T) { // Issue a cert with require_cn set to true and with common name supplied. // It should succeed. - _, err = CBWrite(b, s, "issue/example", map[string]interface{}{ + resp, err = CBWrite(b, s, "issue/example", map[string]interface{}{ "common_name": "foobar.com", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("issue/example"), logical.UpdateOperation), resp, true) if err != nil { t.Fatal(err) } @@ -2194,6 +2195,8 @@ func runTestSignVerbatim(t *testing.T, keyType string) { Data: signVerbatimData, MountPoint: "pki/", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("sign-verbatim"), logical.UpdateOperation), resp, true) + if resp != nil && resp.IsError() { t.Fatalf("failed to sign-verbatim basic CSR: %#v", *resp) } @@ -2510,6 +2513,8 @@ func TestBackend_SignIntermediate_AllowedPastCA(t *testing.T) { resp, err := CBWrite(b_int, s_int, "intermediate/generate/internal", map[string]interface{}{ "common_name": "myint.com", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b_root.Route("intermediate/generate/internal"), logical.UpdateOperation), resp, true) + if err != nil { t.Fatal(err) } @@ -4789,6 +4794,7 @@ func TestRootWithExistingKey(t *testing.T) { "key_type": "rsa", "issuer_name": "my-issuer1", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("issuers/generate/root/internal"), logical.UpdateOperation), resp, true) require.NoError(t, err) require.NotNil(t, resp.Data["certificate"]) myIssuerId1 := resp.Data["issuer_id"] @@ -4904,6 +4910,7 @@ func TestIntermediateWithExistingKey(t *testing.T) { "common_name": "root myvault.com", "key_type": "rsa", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("issuers/generate/intermediate/internal"), logical.UpdateOperation), resp, true) require.NoError(t, err) // csr1 := resp.Data["csr"] myKeyId1 := resp.Data["key_id"] @@ -5192,6 +5199,7 @@ TgM7RZnmEjNdeaa4M52o7VY= resp, err := CBWrite(b, s, "issuers/import/bundle", map[string]interface{}{ "pem_bundle": customBundleWithoutCRLBits, }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("issuers/import/bundle"), logical.UpdateOperation), resp, true) require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.Data) @@ -6377,6 +6385,7 @@ func TestUserIDsInLeafCerts(t *testing.T) { resp, err = CBWrite(b, s, "sign/testing", map[string]interface{}{ "csr": csrPem, }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("sign/testing"), logical.UpdateOperation), resp, true) requireSuccessNonNilResponse(t, resp, err, "failed issuing leaf cert") requireSubjectUserIDAttr(t, resp.Data["certificate"].(string), "humanoid") diff --git a/builtin/logical/pki/crl_test.go b/builtin/logical/pki/crl_test.go index 3490114b90a5..fc3436b16681 100644 --- a/builtin/logical/pki/crl_test.go +++ b/builtin/logical/pki/crl_test.go @@ -771,6 +771,8 @@ func TestIssuerRevocation(t *testing.T) { // Revoke it. resp, err = CBWrite(b, s, "issuer/root2/revoke", map[string]interface{}{}) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("issuer/root2/revoke"), logical.UpdateOperation), resp, true) + require.NoError(t, err) require.NotNil(t, resp) require.NotZero(t, resp.Data["revocation_time"]) @@ -801,7 +803,7 @@ func TestIssuerRevocation(t *testing.T) { require.NoError(t, err) // Issue a leaf cert and ensure it fails (because the issuer is revoked). - _, err = CBWrite(b, s, "issuer/root2/issue/local-testing", map[string]interface{}{ + resp, err = CBWrite(b, s, "issuer/root2/issue/local-testing", map[string]interface{}{ "common_name": "testing", }) require.Error(t, err) @@ -827,6 +829,8 @@ func TestIssuerRevocation(t *testing.T) { resp, err = CBWrite(b, s, "intermediate/set-signed", map[string]interface{}{ "certificate": intCert, }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("intermediate/set-signed"), logical.UpdateOperation), resp, true) + require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.Data["imported_issuers"]) @@ -842,6 +846,8 @@ func TestIssuerRevocation(t *testing.T) { resp, err = CBWrite(b, s, "issuer/int1/issue/local-testing", map[string]interface{}{ "common_name": "testing", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("issuer/int1/issue/local-testing"), logical.UpdateOperation), resp, true) + require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.Data["certificate"]) diff --git a/builtin/logical/pki/path_intermediate.go b/builtin/logical/pki/path_intermediate.go index cfcff87b04f3..e83e8bc614c7 100644 --- a/builtin/logical/pki/path_intermediate.go +++ b/builtin/logical/pki/path_intermediate.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "fmt" + "net/http" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/errutil" @@ -31,6 +32,28 @@ appended to the bundle.`, 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, diff --git a/builtin/logical/pki/path_issue_sign.go b/builtin/logical/pki/path_issue_sign.go index c8bc0088cb13..d7cac0bfaec9 100644 --- a/builtin/logical/pki/path_issue_sign.go +++ b/builtin/logical/pki/path_issue_sign.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/pem" "fmt" + "net/http" "strings" "time" @@ -34,6 +35,48 @@ func buildPathIssue(b *backend, pattern string) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.metricsWrap("issue", roleRequired, b.pathIssue), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "certificate": { + Type: framework.TypeString, + Description: `Certificate`, + Required: true, + }, + "issuing_ca": { + Type: framework.TypeString, + Description: `Issuing Certificate Authority`, + Required: true, + }, + "ca_chain": { + Type: framework.TypeCommaStringSlice, + Description: `Certificate Chain`, + Required: false, + }, + "serial_number": { + Type: framework.TypeString, + Description: `Serial Number`, + Required: false, + }, + "expiration": { + Type: framework.TypeString, + Description: `Time of expiration`, + Required: false, + }, + "private_key": { + Type: framework.TypeString, + Description: `Private key`, + Required: false, + }, + "private_key_type": { + Type: framework.TypeString, + Description: `Private key type`, + Required: false, + }, + }, + }}, + }, }, }, @@ -62,6 +105,48 @@ func buildPathSign(b *backend, pattern string) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.metricsWrap("sign", roleRequired, b.pathSign), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "certificate": { + Type: framework.TypeString, + Description: `Certificate`, + Required: true, + }, + "issuing_ca": { + Type: framework.TypeString, + Description: `Issuing Certificate Authority`, + Required: true, + }, + "ca_chain": { + Type: framework.TypeCommaStringSlice, + Description: `Certificate Chain`, + Required: false, + }, + "serial_number": { + Type: framework.TypeString, + Description: `Serial Number`, + Required: true, + }, + "expiration": { + Type: framework.TypeString, + Description: `Time of expiration`, + Required: true, + }, + "private_key": { + Type: framework.TypeString, + Description: `Private key`, + Required: false, + }, + "private_key_type": { + Type: framework.TypeString, + Description: `Private key type`, + Required: false, + }, + }, + }}, + }, }, }, @@ -98,6 +183,48 @@ func buildPathIssuerSignVerbatim(b *backend, pattern string) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.metricsWrap("sign-verbatim", roleOptional, b.pathSignVerbatim), + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "certificate": { + Type: framework.TypeString, + Description: `Certificate`, + Required: true, + }, + "issuing_ca": { + Type: framework.TypeString, + Description: `Issuing Certificate Authority`, + Required: true, + }, + "ca_chain": { + Type: framework.TypeCommaStringSlice, + Description: `Certificate Chain`, + Required: false, + }, + "serial_number": { + Type: framework.TypeString, + Description: `Serial Number`, + Required: false, + }, + "expiration": { + Type: framework.TypeString, + Description: `Time of expiration`, + Required: false, + }, + "private_key": { + Type: framework.TypeString, + Description: `Private key`, + Required: false, + }, + "private_key_type": { + Type: framework.TypeString, + Description: `Private key type`, + Required: false, + }, + }, + }}, + }, }, }, diff --git a/builtin/logical/pki/path_manage_issuers.go b/builtin/logical/pki/path_manage_issuers.go index 689b3a716619..b0af377a539c 100644 --- a/builtin/logical/pki/path_manage_issuers.go +++ b/builtin/logical/pki/path_manage_issuers.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "net/http" "strings" "time" @@ -29,6 +30,58 @@ func buildPathGenerateRoot(b *backend, pattern string) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathCAGenerateRoot, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "expiration": { + Type: framework.TypeString, + Description: `The expiration of the given.`, + Required: true, + }, + "serial_number": { + Type: framework.TypeString, + Description: `The requested Subject's named serial number.`, + Required: true, + }, + "certificate": { + Type: framework.TypeString, + Description: `The generated self-signed CA certificate.`, + Required: true, + }, + "issuing_ca": { + Type: framework.TypeString, + Description: `The issuing certificate authority.`, + Required: true, + }, + "issuer_id": { + Type: framework.TypeString, + Description: `The ID of the issuer`, + Required: true, + }, + "issuer_name": { + Type: framework.TypeString, + Description: `The name of the issuer.`, + Required: true, + }, + "key_id": { + Type: framework.TypeString, + Description: `The ID of the key.`, + Required: true, + }, + "key_name": { + Type: framework.TypeString, + Description: `The key name if given.`, + Required: true, + }, + "private_key": { + Type: framework.TypeString, + Description: `The private key if exported was specified.`, + Required: false, + }, + }, + }}, + }, // Read more about why these flags are set in backend.go ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, @@ -60,6 +113,33 @@ func buildPathGenerateIntermediate(b *backend, pattern string) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathGenerateIntermediate, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "csr": { + Type: framework.TypeString, + Description: `Certificate signing request.`, + Required: true, + }, + "key_id": { + Type: framework.TypeString, + Description: `Id of the key.`, + Required: true, + }, + "private_key": { + Type: framework.TypeString, + Description: `Generated private key.`, + Required: false, + }, + "private_key_type": { + Type: framework.TypeString, + Description: `Specifies the format used for marshaling the private key.`, + Required: false, + }, + }, + }}, + }, // Read more about why these flags are set in backend.go ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, @@ -101,6 +181,28 @@ secret-key (optional) and certificates.`, 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, @@ -354,6 +456,88 @@ func pathRevokeIssuer(b *backend) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathRevokeIssuer, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "issuer_id": { + Type: framework.TypeString, + Description: `ID of the issuer`, + Required: true, + }, + "issuer_name": { + Type: framework.TypeString, + Description: `Name of the issuer`, + Required: true, + }, + "key_id": { + Type: framework.TypeString, + Description: `ID of the Key`, + Required: true, + }, + "certificate": { + Type: framework.TypeString, + Description: `Certificate`, + Required: true, + }, + "manual_chain": { + Type: framework.TypeCommaStringSlice, + Description: `Manual Chain`, + Required: true, + }, + "ca_chain": { + Type: framework.TypeCommaStringSlice, + Description: `Certificate Authority Chain`, + Required: true, + }, + "leaf_not_after_behavior": { + Type: framework.TypeString, + Description: ``, + Required: true, + }, + "usage": { + Type: framework.TypeString, + Description: `Allowed usage`, + Required: true, + }, + "revocation_signature_algorithm": { + Type: framework.TypeString, + Description: `Which signature algorithm to use when building CRLs`, + Required: true, + }, + "revoked": { + Type: framework.TypeBool, + Description: `Whether the issuer was revoked`, + Required: true, + }, + "issuing_certificates": { + Type: framework.TypeCommaStringSlice, + Description: `Specifies the URL values for the Issuing Certificate field`, + Required: true, + }, + "crl_distribution_points": { + Type: framework.TypeStringSlice, + Description: `Specifies the URL values for the CRL Distribution Points field`, + Required: true, + }, + "ocsp_servers": { + Type: framework.TypeStringSlice, + Description: `Specifies the URL values for the OCSP Servers field`, + Required: true, + }, + "revocation_time": { + Type: framework.TypeInt64, + Description: `Time of revocation`, + Required: false, + }, + "revocation_time_rfc3339": { + Type: framework.TypeTime, + Description: `RFC formatted time of revocation`, + Required: false, + }, + }, + }}, + }, // Read more about why these flags are set in backend.go ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, diff --git a/builtin/logical/pki/path_manage_keys.go b/builtin/logical/pki/path_manage_keys.go index 90119ce4e8a1..a722f90d3462 100644 --- a/builtin/logical/pki/path_manage_keys.go +++ b/builtin/logical/pki/path_manage_keys.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/pem" + "net/http" "strings" "github.com/hashicorp/vault/sdk/framework" @@ -54,7 +55,36 @@ is required. Ignored for other types.`, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ - Callback: b.pathGenerateKeyHandler, + Callback: b.pathGenerateKeyHandler, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "key_id": { + Type: framework.TypeString, + Description: `ID assigned to this key.`, + Required: true, + }, + "key_name": { + Type: framework.TypeString, + Description: `Name assigned to this key.`, + Required: true, + }, + "key_type": { + Type: framework.TypeString, + Description: `The type of key to use; defaults to RSA. "rsa" + "ec" and "ed25519" are the only valid values.`, + Required: true, + }, + "private_key": { + Type: framework.TypeString, + Description: `The private key string`, + Required: false, + }, + }, + }}, + }, + ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, @@ -162,7 +192,30 @@ func pathImportKey(b *backend) *framework.Path { Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ - Callback: b.pathImportKeyHandler, + Callback: b.pathImportKeyHandler, + Responses: map[int][]framework.Response{ + http.StatusOK: {{ + Description: "OK", + Fields: map[string]*framework.FieldSchema{ + "key_id": { + Type: framework.TypeString, + Description: `ID assigned to this key.`, + Required: true, + }, + "key_name": { + Type: framework.TypeString, + Description: `Name assigned to this key.`, + Required: true, + }, + "key_type": { + Type: framework.TypeString, + Description: `The type of key to use; defaults to RSA. "rsa" + "ec" and "ed25519" are the only valid values.`, + Required: true, + }, + }, + }}, + }, ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, diff --git a/builtin/logical/pki/path_manage_keys_test.go b/builtin/logical/pki/path_manage_keys_test.go index 7b53ae836ee6..26f43912b169 100644 --- a/builtin/logical/pki/path_manage_keys_test.go +++ b/builtin/logical/pki/path_manage_keys_test.go @@ -9,6 +9,8 @@ import ( "fmt" "testing" + "github.com/hashicorp/vault/sdk/helper/testhelpers/schema" + "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/logical" @@ -95,6 +97,8 @@ func TestPKI_PathManageKeys_GenerateExportedKeys(t *testing.T) { }, MountPoint: "pki/", }) + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("keys/generate/exported"), logical.UpdateOperation), resp, true) + require.NoError(t, err, "Failed generating exported key") require.NotNil(t, resp, "Got nil response generating exported key") require.Equal(t, "ec", resp.Data["key_type"], "key_type field contained an invalid type") @@ -136,6 +140,9 @@ func TestPKI_PathManageKeys_ImportKeyBundle(t *testing.T) { }, MountPoint: "pki/", }) + + schema.ValidateResponse(t, schema.GetResponseSchema(t, b.Route("keys/import"), logical.UpdateOperation), resp, true) + require.NoError(t, err, "Failed importing ec key") require.NotNil(t, resp, "Got nil response importing ec key") require.False(t, resp.IsError(), "received an error response: %v", resp.Error())