diff --git a/builtin/logical/pki/backend.go b/builtin/logical/pki/backend.go index 98257e0c51eb..da60099b358a 100644 --- a/builtin/logical/pki/backend.go +++ b/builtin/logical/pki/backend.go @@ -117,6 +117,7 @@ func Backend(conf *logical.BackendConfig) *backend { pathIssuerSignSelfIssued(&b), pathIssuerSignVerbatim(&b), pathIssuerGenerateRoot(&b), + pathIssuerGenerateIntermediate(&b), pathConfigIssuers(&b), // Fetch APIs have been lowered to favor the newer issuer API endpoints diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 8120936d4af8..e5f59d41038c 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -4708,6 +4708,82 @@ func TestRootWithExistingKey(t *testing.T) { require.Contains(t, resp.Data["keys"], myIssuerId3) } +func TestIntermediateWithExistingKey(t *testing.T) { + coreConfig := &vault.CoreConfig{ + LogicalBackends: map[string]logical.Factory{ + "pki": Factory, + }, + } + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: vaulthttp.Handler, + }) + cluster.Start() + defer cluster.Cleanup() + + client := cluster.Cores[0].Client + var err error + + mountPKIEndpoint(t, client, "pki-root") + + // Fail requests if type is existing, and we specify the key_type param + ctx := context.Background() + _, err = client.Logical().WriteWithContext(ctx, "pki-root/intermediate/generate/existing", map[string]interface{}{ + "common_name": "root myvault.com", + "key_type": "rsa", + }) + require.Error(t, err) + require.Contains(t, err.Error(), "key_type nor key_bits arguments can be set in this mode") + + // Fail requests if type is existing, and we specify the key_bits param + _, err = client.Logical().WriteWithContext(ctx, "pki-root/intermediate/generate/existing", map[string]interface{}{ + "common_name": "root myvault.com", + "key_bits": "2048", + }) + require.Error(t, err) + require.Contains(t, err.Error(), "key_type nor key_bits arguments can be set in this mode") + + // Fail if the specified key does not exist. + _, err = client.Logical().WriteWithContext(ctx, "pki-root/issuers/generate/intermediate/existing", map[string]interface{}{ + "common_name": "root myvault.com", + "key_id": "my-key1", + }) + require.Error(t, err) + require.Contains(t, err.Error(), "unable to find PKI key for reference: my-key1") + + // Create the first intermediate CA + resp, err := client.Logical().WriteWithContext(ctx, "pki-root/issuers/generate/intermediate/internal", map[string]interface{}{ + "common_name": "root myvault.com", + "key_type": "rsa", + }) + require.NoError(t, err) + // csr1 := resp.Data["csr"] + myKeyId1 := resp.Data["key_id"] + require.NotEmpty(t, myKeyId1) + + // Create the second intermediate CA + resp, err = client.Logical().WriteWithContext(ctx, "pki-root/issuers/generate/intermediate/internal", map[string]interface{}{ + "common_name": "root myvault.com", + "key_type": "rsa", + }) + require.NoError(t, err) + // csr2 := resp.Data["csr"] + myKeyId2 := resp.Data["key_id"] + require.NotEmpty(t, myKeyId2) + + // Create a third intermediate CA re-using key from intermediate CA 1 + resp, err = client.Logical().WriteWithContext(ctx, "pki-root/issuers/generate/intermediate/existing", map[string]interface{}{ + "common_name": "root myvault.com", + "key_id": myKeyId1, + }) + require.NoError(t, err) + // csr3 := resp.Data["csr"] + myKeyId3 := resp.Data["key_id"] + require.NotEmpty(t, myKeyId3) + + require.NotEqual(t, myKeyId1, myKeyId2) + require.Equal(t, myKeyId1, myKeyId3) +} + var ( initTest sync.Once rsaCAKey string diff --git a/builtin/logical/pki/fields.go b/builtin/logical/pki/fields.go index fa94751451b7..5e9d29f8cfb0 100644 --- a/builtin/logical/pki/fields.go +++ b/builtin/logical/pki/fields.go @@ -321,11 +321,6 @@ SHA-2-512. Defaults to 0 to automatically detect based on key length for the configured default key, an identifier or the name assigned to the key. Note this is only used for the existing generation type.`, } - - fields["id"] = &framework.FieldSchema{ - Type: framework.TypeString, - Description: `Assign a name to the generated issuer.`, - } return fields } diff --git a/builtin/logical/pki/managed_key_util.go b/builtin/logical/pki/managed_key_util.go index acb11e624065..caff650958d7 100644 --- a/builtin/logical/pki/managed_key_util.go +++ b/builtin/logical/pki/managed_key_util.go @@ -28,10 +28,17 @@ func generateCABundle(ctx context.Context, _ *backend, input *inputBundle, data return certutil.CreateCertificateWithRandomSource(data, randomSource) } -func generateCSRBundle(_ context.Context, _ *backend, input *inputBundle, data *certutil.CreationBundle, addBasicConstraints bool, randomSource io.Reader) (*certutil.ParsedCSRBundle, error) { +func generateCSRBundle(ctx context.Context, _ *backend, input *inputBundle, data *certutil.CreationBundle, addBasicConstraints bool, randomSource io.Reader) (*certutil.ParsedCSRBundle, error) { if kmsRequested(input) { return nil, errEntOnly } + if existingKeyRequested(input) { + keyRef, err := getExistingKeyRef(input.apiData) + if err != nil { + return nil, err + } + return certutil.CreateCSRWithKeyGenerator(data, addBasicConstraints, randomSource, existingGeneratePrivateKey(ctx, input.req.Storage, keyRef)) + } return certutil.CreateCSRWithRandomSource(data, addBasicConstraints, randomSource) } diff --git a/builtin/logical/pki/path_intermediate.go b/builtin/logical/pki/path_intermediate.go index 4e1e766eae35..6828c20b0a00 100644 --- a/builtin/logical/pki/path_intermediate.go +++ b/builtin/logical/pki/path_intermediate.go @@ -12,32 +12,7 @@ import ( ) func pathGenerateIntermediate(b *backend) *framework.Path { - ret := &framework.Path{ - Pattern: "intermediate/generate/" + framework.GenericNameRegex("exported"), - Operations: map[logical.Operation]framework.OperationHandler{ - logical.UpdateOperation: &framework.PathOperation{ - Callback: b.pathGenerateIntermediate, - // Read more about why these flags are set in backend.go - ForwardPerformanceStandby: true, - ForwardPerformanceSecondary: true, - }, - }, - - HelpSynopsis: pathGenerateIntermediateHelpSyn, - HelpDescription: pathGenerateIntermediateHelpDesc, - } - - ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{}) - ret.Fields = addCAKeyGenerationFields(ret.Fields) - ret.Fields["add_basic_constraints"] = &framework.FieldSchema{ - Type: framework.TypeBool, - Description: `Whether to add a Basic Constraints -extension with CA: true. Only needed as a -workaround in some compatibility scenarios -with Active Directory Certificate Services.`, - } - - return ret + return commonGenerateIntermediate(b, "intermediate/generate/"+framework.GenericNameRegex("exported")) } func pathSetSignedIntermediate(b *backend) *framework.Path { @@ -135,18 +110,11 @@ func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Req } } - cb := &certutil.CertBundle{} - cb.PrivateKey = csrb.PrivateKey - cb.PrivateKeyType = csrb.PrivateKeyType - - entry, err := logical.StorageEntryJSON("config/ca_bundle", cb) - if err != nil { - return nil, err - } - err = req.Storage.Put(ctx, entry) + myKey, _, err := importKey(ctx, req.Storage, csrb.PrivateKey) if err != nil { return nil, err } + resp.Data["key_id"] = myKey.ID return resp, nil } diff --git a/builtin/logical/pki/path_manage_issuers.go b/builtin/logical/pki/path_manage_issuers.go index b9ac8bc5740b..58f2a3ade714 100644 --- a/builtin/logical/pki/path_manage_issuers.go +++ b/builtin/logical/pki/path_manage_issuers.go @@ -12,8 +12,12 @@ import ( ) func pathIssuerGenerateRoot(b *backend) *framework.Path { + return commonGenerateRoot(b, "issuers/generate/root/"+framework.GenericNameRegex("exported")) +} + +func commonGenerateRoot(b *backend, pattern string) *framework.Path { ret := &framework.Path{ - Pattern: "issuers/generate/root/" + framework.GenericNameRegex("exported"), + Pattern: pattern, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ @@ -32,6 +36,45 @@ func pathIssuerGenerateRoot(b *backend) *framework.Path { ret.Fields = addCAKeyGenerationFields(ret.Fields) ret.Fields = addCAIssueFields(ret.Fields) + ret.Fields["id"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `Assign a name to the generated issuer.`, + } + + return ret +} + +func pathIssuerGenerateIntermediate(b *backend) *framework.Path { + return commonGenerateIntermediate(b, + "issuers/generate/intermediate/"+framework.GenericNameRegex("exported")) +} + +func commonGenerateIntermediate(b *backend, pattern string) *framework.Path { + ret := &framework.Path{ + Pattern: pattern, + Operations: map[logical.Operation]framework.OperationHandler{ + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.pathGenerateIntermediate, + // Read more about why these flags are set in backend.go + ForwardPerformanceStandby: true, + ForwardPerformanceSecondary: true, + }, + }, + + HelpSynopsis: pathGenerateIntermediateHelpSyn, + HelpDescription: pathGenerateIntermediateHelpDesc, + } + + ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{}) + ret.Fields = addCAKeyGenerationFields(ret.Fields) + ret.Fields["add_basic_constraints"] = &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `Whether to add a Basic Constraints +extension with CA: true. Only needed as a +workaround in some compatibility scenarios +with Active Directory Certificate Services.`, + } + return ret } diff --git a/builtin/logical/pki/path_root.go b/builtin/logical/pki/path_root.go index 5f84c1152358..18ee65f96ed6 100644 --- a/builtin/logical/pki/path_root.go +++ b/builtin/logical/pki/path_root.go @@ -26,27 +26,7 @@ import ( ) func pathGenerateRoot(b *backend) *framework.Path { - ret := &framework.Path{ - Pattern: "root/generate/" + framework.GenericNameRegex("exported"), - - Operations: map[logical.Operation]framework.OperationHandler{ - logical.UpdateOperation: &framework.PathOperation{ - Callback: b.pathCAGenerateRoot, - // Read more about why these flags are set in backend.go - ForwardPerformanceStandby: true, - ForwardPerformanceSecondary: true, - }, - }, - - HelpSynopsis: pathGenerateRootHelpSyn, - HelpDescription: pathGenerateRootHelpDesc, - } - - ret.Fields = addCACommonFields(map[string]*framework.FieldSchema{}) - ret.Fields = addCAKeyGenerationFields(ret.Fields) - ret.Fields = addCAIssueFields(ret.Fields) - - return ret + return commonGenerateRoot(b, "root/generate/"+framework.GenericNameRegex("exported")) } func pathDeleteRoot(b *backend) *framework.Path {