Add support to load roles and issuers within ACME wrapper (#20333)
* Add support to load roles and issuers within ACME wrapper * Add missing go doc to new test * PR feedback - Move field definitions into fields.go - Update wording and associated errors to some role failures. - Add missing ':' to error messages
This commit is contained in:
parent
7d631cb44f
commit
47605c0d48
|
@ -19,6 +19,8 @@ type acmeContext struct {
|
|||
// baseUrl is the combination of the configured cluster local URL and the acmePath up to /acme/
|
||||
baseUrl *url.URL
|
||||
sc *storageContext
|
||||
role *roleEntry
|
||||
issuer *issuerEntry
|
||||
}
|
||||
|
||||
type (
|
||||
|
@ -47,20 +49,99 @@ func (b *backend) acmeWrapper(op acmeOperation) framework.OperationFunc {
|
|||
return nil, fmt.Errorf("ACME is disabled in configuration: %w", ErrServerInternal)
|
||||
}
|
||||
|
||||
if b.useLegacyBundleCaStorage() {
|
||||
return nil, fmt.Errorf("%w: Can not perform ACME operations until migration has completed", ErrServerInternal)
|
||||
}
|
||||
|
||||
acmeBaseUrl, err := getAcmeBaseUrl(sc, r.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role, issuer, err := getAcmeRoleAndIssuer(sc, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acmeCtx := &acmeContext{
|
||||
baseUrl: acmeBaseUrl,
|
||||
sc: sc,
|
||||
role: role,
|
||||
issuer: issuer,
|
||||
}
|
||||
|
||||
return op(acmeCtx, r, data)
|
||||
})
|
||||
}
|
||||
|
||||
func getAcmeIssuer(sc *storageContext, issuerName string) (*issuerEntry, error) {
|
||||
issuerId, err := sc.resolveIssuerReference(issuerName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: issuer does not exist", ErrMalformed)
|
||||
}
|
||||
|
||||
issuer, err := sc.fetchIssuerById(issuerId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("issuer failed to load: %w", err)
|
||||
}
|
||||
|
||||
if issuer.Usage.HasUsage(IssuanceUsage) && len(issuer.KeyID) > 0 {
|
||||
return issuer, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: issuer missing proper issuance usage or key", ErrServerInternal)
|
||||
}
|
||||
|
||||
func getAcmeRoleAndIssuer(sc *storageContext, data *framework.FieldData) (*roleEntry, *issuerEntry, error) {
|
||||
requestedIssuer := defaultRef
|
||||
requestedIssuerRaw, present := data.GetOk("issuer")
|
||||
if present {
|
||||
requestedIssuer = requestedIssuerRaw.(string)
|
||||
}
|
||||
|
||||
var role *roleEntry
|
||||
roleNameRaw, present := data.GetOk("role")
|
||||
if present {
|
||||
roleName := roleNameRaw.(string)
|
||||
if len(roleName) > 0 {
|
||||
var err error
|
||||
role, err = sc.Backend.getRole(sc.Context, sc.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: err loading role", ErrServerInternal)
|
||||
}
|
||||
|
||||
if role == nil {
|
||||
return nil, nil, fmt.Errorf("%w: role does not exist", ErrMalformed)
|
||||
}
|
||||
|
||||
if role.NoStore {
|
||||
return nil, nil, fmt.Errorf("%w: role can not be used as NoStore is set to true", ErrServerInternal)
|
||||
}
|
||||
|
||||
if len(role.Issuer) > 0 {
|
||||
requestedIssuer = role.Issuer
|
||||
}
|
||||
}
|
||||
} else {
|
||||
role = buildSignVerbatimRoleWithNoData(&roleEntry{
|
||||
Issuer: requestedIssuer,
|
||||
NoStore: false,
|
||||
Name: "",
|
||||
})
|
||||
}
|
||||
|
||||
issuer, err := getAcmeIssuer(sc, requestedIssuer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
role.Issuer = requestedIssuer
|
||||
|
||||
// TODO: Need additional configuration validation here, for allowed roles/issuers.
|
||||
|
||||
return role, issuer, nil
|
||||
}
|
||||
|
||||
func (b *backend) acmeParsedWrapper(op acmeParsedOperation) framework.OperationFunc {
|
||||
return b.acmeWrapper(func(acmeCtx *acmeContext, r *logical.Request, fields *framework.FieldData) (*logical.Response, error) {
|
||||
user, data, err := b.acmeState.ParseRequestParams(acmeCtx, fields)
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package pki
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestACMEIssuerRoleLoading validates the role and issuer loading logic within the base
|
||||
// ACME wrapper is correct.
|
||||
func TestACMEIssuerRoleLoading(t *testing.T) {
|
||||
b, s := CreateBackendWithStorage(t)
|
||||
|
||||
_, 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)
|
||||
|
||||
_, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{
|
||||
"common_name": "myvault1.com",
|
||||
"issuer_name": "issuer-1",
|
||||
"key_type": "ec",
|
||||
})
|
||||
require.NoError(t, err, "failed creating issuer issuer-1")
|
||||
|
||||
_, err = CBWrite(b, s, "root/generate/internal", map[string]interface{}{
|
||||
"common_name": "myvault2.com",
|
||||
"issuer_name": "issuer-2",
|
||||
"key_type": "ec",
|
||||
})
|
||||
require.NoError(t, err, "failed creating issuer issuer-2")
|
||||
|
||||
_, err = CBWrite(b, s, "roles/role-bad-issuer", map[string]interface{}{
|
||||
issuerRefParam: "non-existant",
|
||||
"no_store": "false",
|
||||
})
|
||||
require.NoError(t, err, "failed creating role role-bad-issuer")
|
||||
|
||||
_, err = CBWrite(b, s, "roles/role-no-store-enabled", map[string]interface{}{
|
||||
issuerRefParam: "issuer-2",
|
||||
"no_store": "true",
|
||||
})
|
||||
require.NoError(t, err, "failed creating role role-no-store-enabled")
|
||||
|
||||
_, err = CBWrite(b, s, "roles/role-issuer-2", map[string]interface{}{
|
||||
issuerRefParam: "issuer-2",
|
||||
"no_store": "false",
|
||||
})
|
||||
require.NoError(t, err, "failed creating role role-issuer-2")
|
||||
|
||||
tc := []struct {
|
||||
name string
|
||||
roleName string
|
||||
issuerName string
|
||||
expectedIssuerName string
|
||||
expectErr bool
|
||||
}{
|
||||
{name: "pass-default-use-default", roleName: "", issuerName: "", expectedIssuerName: "issuer-1", expectErr: false},
|
||||
{name: "pass-role-issuer-2", roleName: "role-issuer-2", issuerName: "", expectedIssuerName: "issuer-2", expectErr: false},
|
||||
{name: "pass-issuer-1-no-role", roleName: "", issuerName: "issuer-1", expectedIssuerName: "issuer-1", expectErr: false},
|
||||
{name: "fail-role-has-bad-issuer", roleName: "role-bad-issuer", issuerName: "", expectedIssuerName: "", expectErr: true},
|
||||
{name: "fail-role-no-store-enabled", roleName: "role-no-store-enabled", issuerName: "", expectedIssuerName: "", expectErr: true},
|
||||
{name: "fail-role-no-store-enabled", roleName: "role-no-store-enabled", issuerName: "", expectedIssuerName: "", expectErr: true},
|
||||
{name: "fail-role-does-not-exist", roleName: "non-existant", issuerName: "", expectedIssuerName: "", expectErr: true},
|
||||
{name: "fail-issuer-does-not-exist", roleName: "", issuerName: "non-existant", expectedIssuerName: "", expectErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := b.acmeWrapper(func(acmeCtx *acmeContext, r *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
if tt.roleName != acmeCtx.role.Name {
|
||||
return nil, fmt.Errorf("expected role %s but got %s", tt.roleName, acmeCtx.role.Name)
|
||||
}
|
||||
|
||||
if tt.expectedIssuerName != acmeCtx.issuer.Name {
|
||||
return nil, fmt.Errorf("expected issuer %s but got %s", tt.expectedIssuerName, acmeCtx.issuer.Name)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
fieldRaw := map[string]interface{}{}
|
||||
if tt.roleName != "" {
|
||||
fieldRaw["role"] = tt.roleName
|
||||
}
|
||||
if tt.issuerName != "" {
|
||||
fieldRaw[issuerRefParam] = tt.issuerName
|
||||
}
|
||||
|
||||
resp, err := f(context.Background(), &logical.Request{Storage: s}, &framework.FieldData{
|
||||
Raw: fieldRaw,
|
||||
Schema: getCsrSignVerbatimSchemaFields(),
|
||||
})
|
||||
require.NoError(t, err, "all errors should be re-encoded")
|
||||
|
||||
if tt.expectErr {
|
||||
require.NotEqual(t, 200, resp.Data[logical.HTTPStatusCode])
|
||||
require.Equal(t, ErrorContentType, resp.Data[logical.HTTPContentType])
|
||||
} else {
|
||||
if resp != nil {
|
||||
t.Fatalf("expected no error got %s", string(resp.Data[logical.HTTPRawBody].([]uint8)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -264,3 +264,61 @@ func existingKeyGeneratorFromBytes(key *keyEntry) certutil.KeyGenerator {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildSignVerbatimRoleWithNoData(role *roleEntry) *roleEntry {
|
||||
data := &framework.FieldData{
|
||||
Raw: map[string]interface{}{},
|
||||
Schema: addSignVerbatimRoleFields(map[string]*framework.FieldSchema{}),
|
||||
}
|
||||
return buildSignVerbatimRole(data, role)
|
||||
}
|
||||
|
||||
func buildSignVerbatimRole(data *framework.FieldData, role *roleEntry) *roleEntry {
|
||||
entry := &roleEntry{
|
||||
AllowLocalhost: true,
|
||||
AllowAnyName: true,
|
||||
AllowIPSANs: true,
|
||||
AllowWildcardCertificates: new(bool),
|
||||
EnforceHostnames: false,
|
||||
KeyType: "any",
|
||||
UseCSRCommonName: true,
|
||||
UseCSRSANs: true,
|
||||
AllowedOtherSANs: []string{"*"},
|
||||
AllowedSerialNumbers: []string{"*"},
|
||||
AllowedURISANs: []string{"*"},
|
||||
AllowedUserIDs: []string{"*"},
|
||||
CNValidations: []string{"disabled"},
|
||||
GenerateLease: new(bool),
|
||||
// If adding new fields to be read, update the field list within addSignVerbatimRoleFields
|
||||
KeyUsage: data.Get("key_usage").([]string),
|
||||
ExtKeyUsage: data.Get("ext_key_usage").([]string),
|
||||
ExtKeyUsageOIDs: data.Get("ext_key_usage_oids").([]string),
|
||||
SignatureBits: data.Get("signature_bits").(int),
|
||||
UsePSS: data.Get("use_pss").(bool),
|
||||
}
|
||||
*entry.AllowWildcardCertificates = true
|
||||
*entry.GenerateLease = false
|
||||
|
||||
if role != nil {
|
||||
if role.TTL > 0 {
|
||||
entry.TTL = role.TTL
|
||||
}
|
||||
if role.MaxTTL > 0 {
|
||||
entry.MaxTTL = role.MaxTTL
|
||||
}
|
||||
if role.GenerateLease != nil {
|
||||
*entry.GenerateLease = *role.GenerateLease
|
||||
}
|
||||
if role.NotBeforeDuration > 0 {
|
||||
entry.NotBeforeDuration = role.NotBeforeDuration
|
||||
}
|
||||
entry.NoStore = role.NoStore
|
||||
entry.Issuer = role.Issuer
|
||||
}
|
||||
|
||||
if len(entry.Issuer) == 0 {
|
||||
entry.Issuer = defaultRef
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
|
|
@ -565,3 +565,72 @@ primary node.`,
|
|||
|
||||
return fields
|
||||
}
|
||||
|
||||
// generate the entire list of schema fields we need for CSR sign verbatim, this is also
|
||||
// leveraged by ACME internally.
|
||||
func getCsrSignVerbatimSchemaFields() map[string]*framework.FieldSchema {
|
||||
fields := map[string]*framework.FieldSchema{}
|
||||
fields = addNonCACommonFields(fields)
|
||||
fields = addSignVerbatimRoleFields(fields)
|
||||
|
||||
fields["csr"] = &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "",
|
||||
Description: `PEM-format CSR to be signed. Values will be
|
||||
taken verbatim from the CSR, except for
|
||||
basic constraints.`,
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// addSignVerbatimRoleFields provides the fields and defaults to be used by anything that is building up the fields
|
||||
// and their corresponding default values when generating/using a sign-verbatim type role such as buildSignVerbatimRole.
|
||||
func addSignVerbatimRoleFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
||||
fields["key_usage"] = &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Default: []string{"DigitalSignature", "KeyAgreement", "KeyEncipherment"},
|
||||
Description: `A comma-separated string or list of key usages (not extended
|
||||
key usages). Valid values can be found at
|
||||
https://golang.org/pkg/crypto/x509/#KeyUsage
|
||||
-- simply drop the "KeyUsage" part of the name.
|
||||
To remove all key usages from being set, set
|
||||
this value to an empty list.`,
|
||||
}
|
||||
|
||||
fields["ext_key_usage"] = &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Default: []string{},
|
||||
Description: `A comma-separated string or list of extended key usages. Valid values can be found at
|
||||
https://golang.org/pkg/crypto/x509/#ExtKeyUsage
|
||||
-- simply drop the "ExtKeyUsage" part of the name.
|
||||
To remove all key usages from being set, set
|
||||
this value to an empty list.`,
|
||||
}
|
||||
|
||||
fields["ext_key_usage_oids"] = &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated string or list of extended key usage oids.`,
|
||||
}
|
||||
|
||||
fields["signature_bits"] = &framework.FieldSchema{
|
||||
Type: framework.TypeInt,
|
||||
Default: 0,
|
||||
Description: `The number of bits to use in the signature
|
||||
algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for
|
||||
SHA-2-512. Defaults to 0 to automatically detect based on key length
|
||||
(SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Value: 0,
|
||||
},
|
||||
}
|
||||
|
||||
fields["use_pss"] = &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Default: false,
|
||||
Description: `Whether or not to use PSS signatures when using a
|
||||
RSA key-type issuer. Defaults to false.`,
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
|
|
@ -404,31 +404,6 @@ func storeCertificate(sc *storageContext, signedCertBundle *certutil.ParsedCertB
|
|||
}
|
||||
|
||||
func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, issuerID, error) {
|
||||
entry := &roleEntry{
|
||||
AllowLocalhost: true,
|
||||
AllowAnyName: true,
|
||||
AllowIPSANs: true,
|
||||
AllowWildcardCertificates: new(bool),
|
||||
EnforceHostnames: false,
|
||||
KeyType: "any",
|
||||
UseCSRCommonName: true,
|
||||
UseCSRSANs: true,
|
||||
AllowedOtherSANs: []string{"*"},
|
||||
AllowedSerialNumbers: []string{"*"},
|
||||
AllowedURISANs: []string{"*"},
|
||||
AllowedUserIDs: []string{"*"},
|
||||
CNValidations: []string{"disabled"},
|
||||
GenerateLease: new(bool),
|
||||
KeyUsage: []string{},
|
||||
ExtKeyUsage: []string{},
|
||||
ExtKeyUsageOIDs: []string{},
|
||||
SignatureBits: 0,
|
||||
UsePSS: false,
|
||||
Issuer: defaultRef,
|
||||
}
|
||||
*entry.AllowWildcardCertificates = true
|
||||
*entry.GenerateLease = false
|
||||
|
||||
pemBlock := &pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Headers: nil,
|
||||
|
@ -436,27 +411,22 @@ func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil.
|
|||
}
|
||||
pemCsr := string(pem.EncodeToMemory(pemBlock))
|
||||
|
||||
signingBundle, issuerId, err := ac.sc.fetchCAInfoWithIssuer(entry.Issuer, IssuanceUsage)
|
||||
data := &framework.FieldData{
|
||||
Raw: map[string]interface{}{
|
||||
"csr": pemCsr,
|
||||
},
|
||||
Schema: getCsrSignVerbatimSchemaFields(),
|
||||
}
|
||||
|
||||
signingBundle, issuerId, err := ac.sc.fetchCAInfoWithIssuer(ac.issuer.ID.String(), IssuanceUsage)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed loading CA %s: %w", entry.Issuer, err)
|
||||
return nil, "", fmt.Errorf("failed loading CA %s: %w", ac.issuer.ID.String(), err)
|
||||
}
|
||||
|
||||
input := &inputBundle{
|
||||
req: &logical.Request{},
|
||||
apiData: &framework.FieldData{
|
||||
Raw: map[string]interface{}{
|
||||
"csr": pemCsr,
|
||||
"ttl": "1h",
|
||||
},
|
||||
Schema: map[string]*framework.FieldSchema{
|
||||
"csr": {Type: framework.TypeString},
|
||||
"serial_number": {Type: framework.TypeString},
|
||||
"exclude_cn_from_sans": {Type: framework.TypeBool},
|
||||
"other_sans": {Type: framework.TypeCommaStringSlice},
|
||||
"ttl": {Type: framework.TypeDurationSecond},
|
||||
},
|
||||
},
|
||||
role: entry,
|
||||
req: &logical.Request{},
|
||||
apiData: data,
|
||||
role: ac.role,
|
||||
}
|
||||
|
||||
if csr.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm || csr.PublicKey == nil {
|
||||
|
|
|
@ -384,19 +384,22 @@ func setupAcmeBackend(t *testing.T) (*vault.TestCluster, *api.Client, string) {
|
|||
// Allow certain headers to pass through for ACME support
|
||||
_, err = client.Logical().WriteWithContext(context.Background(), "sys/mounts/pki/tune", map[string]interface{}{
|
||||
"allowed_response_headers": []string{"Last-Modified", "Replay-Nonce", "Link", "Location"},
|
||||
"max_lease_ttl": "920000h",
|
||||
})
|
||||
require.NoError(t, err, "failed tuning mount response headers")
|
||||
|
||||
_, err = client.Logical().WriteWithContext(context.Background(), "/pki/issuers/generate/root/internal", map[string]interface{}{
|
||||
"issuer_name": "root-ca",
|
||||
"key_name": "root-key",
|
||||
"key_type": "ec",
|
||||
"common_name": "root.com",
|
||||
"ttl": "10h",
|
||||
})
|
||||
resp, err := client.Logical().WriteWithContext(context.Background(), "/pki/issuers/generate/root/internal",
|
||||
map[string]interface{}{
|
||||
"issuer_name": "root-ca",
|
||||
"key_name": "root-key",
|
||||
"key_type": "ec",
|
||||
"common_name": "root.com",
|
||||
"ttl": "7200h",
|
||||
"max_ttl": "920000h",
|
||||
})
|
||||
require.NoError(t, err, "failed creating root CA")
|
||||
|
||||
resp, err := client.Logical().WriteWithContext(context.Background(), "/pki/issuers/generate/intermediate/internal",
|
||||
resp, err = client.Logical().WriteWithContext(context.Background(), "/pki/issuers/generate/intermediate/internal",
|
||||
map[string]interface{}{
|
||||
"key_name": "int-key",
|
||||
"key_type": "ec",
|
||||
|
@ -407,8 +410,9 @@ func setupAcmeBackend(t *testing.T) (*vault.TestCluster, *api.Client, string) {
|
|||
|
||||
// Sign the intermediate CSR using /pki
|
||||
resp, err = client.Logical().Write("pki/issuer/root-ca/sign-intermediate", map[string]interface{}{
|
||||
"csr": intermediateCSR,
|
||||
"ttl": "5h",
|
||||
"csr": intermediateCSR,
|
||||
"ttl": "720h",
|
||||
"max_ttl": "7200h",
|
||||
})
|
||||
require.NoError(t, err, "failed signing intermediary CSR")
|
||||
intermediateCertPEM := resp.Data["certificate"].(string)
|
||||
|
@ -425,10 +429,26 @@ func setupAcmeBackend(t *testing.T) (*vault.TestCluster, *api.Client, string) {
|
|||
_, err = client.Logical().Write("/pki/issuer/"+intCaUuid, map[string]interface{}{
|
||||
"issuer_name": "int-ca",
|
||||
})
|
||||
require.NoError(t, err, "failed updating issuer name")
|
||||
|
||||
_, err = client.Logical().Write("/pki/config/issuers", map[string]interface{}{
|
||||
"default": "int-ca",
|
||||
})
|
||||
require.NoError(t, err, "failed updating default issuer")
|
||||
|
||||
_, err = client.Logical().Write("/pki/roles/test-role", map[string]interface{}{
|
||||
"ttl_duration": "365h",
|
||||
"max_ttl_duration": "720h",
|
||||
"key_type": "any",
|
||||
})
|
||||
require.NoError(t, err, "failed creating role test-role")
|
||||
|
||||
_, err = client.Logical().Write("/pki/roles/acme", map[string]interface{}{
|
||||
"ttl_duration": "365h",
|
||||
"max_ttl_duration": "720h",
|
||||
"key_type": "any",
|
||||
})
|
||||
require.NoError(t, err, "failed creating role acme")
|
||||
|
||||
return cluster, client, pathConfig
|
||||
}
|
||||
|
|
|
@ -226,7 +226,7 @@ func buildPathIssuerSignVerbatim(b *backend, pattern string, displayAttrs *frame
|
|||
ret := &framework.Path{
|
||||
Pattern: pattern,
|
||||
DisplayAttrs: displayAttrs,
|
||||
Fields: map[string]*framework.FieldSchema{},
|
||||
Fields: getCsrSignVerbatimSchemaFields(),
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
|
@ -280,61 +280,6 @@ func buildPathIssuerSignVerbatim(b *backend, pattern string, displayAttrs *frame
|
|||
HelpDescription: pathIssuerSignVerbatimHelpDesc,
|
||||
}
|
||||
|
||||
ret.Fields = addNonCACommonFields(ret.Fields)
|
||||
|
||||
ret.Fields["csr"] = &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "",
|
||||
Description: `PEM-format CSR to be signed. Values will be
|
||||
taken verbatim from the CSR, except for
|
||||
basic constraints.`,
|
||||
}
|
||||
|
||||
ret.Fields["key_usage"] = &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Default: []string{"DigitalSignature", "KeyAgreement", "KeyEncipherment"},
|
||||
Description: `A comma-separated string or list of key usages (not extended
|
||||
key usages). Valid values can be found at
|
||||
https://golang.org/pkg/crypto/x509/#KeyUsage
|
||||
-- simply drop the "KeyUsage" part of the name.
|
||||
To remove all key usages from being set, set
|
||||
this value to an empty list.`,
|
||||
}
|
||||
|
||||
ret.Fields["ext_key_usage"] = &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Default: []string{},
|
||||
Description: `A comma-separated string or list of extended key usages. Valid values can be found at
|
||||
https://golang.org/pkg/crypto/x509/#ExtKeyUsage
|
||||
-- simply drop the "ExtKeyUsage" part of the name.
|
||||
To remove all key usages from being set, set
|
||||
this value to an empty list.`,
|
||||
}
|
||||
|
||||
ret.Fields["ext_key_usage_oids"] = &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated string or list of extended key usage oids.`,
|
||||
}
|
||||
|
||||
ret.Fields["signature_bits"] = &framework.FieldSchema{
|
||||
Type: framework.TypeInt,
|
||||
Default: 0,
|
||||
Description: `The number of bits to use in the signature
|
||||
algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for
|
||||
SHA-2-512. Defaults to 0 to automatically detect based on key length
|
||||
(SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).`,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
Value: 0,
|
||||
},
|
||||
}
|
||||
|
||||
ret.Fields["use_pss"] = &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Default: false,
|
||||
Description: `Whether or not to use PSS signatures when using a
|
||||
RSA key-type issuer. Defaults to false.`,
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
|
@ -377,51 +322,7 @@ func (b *backend) pathSign(ctx context.Context, req *logical.Request, data *fram
|
|||
// pathSignVerbatim issues a certificate from a submitted CSR, *not* subject to
|
||||
// role restrictions
|
||||
func (b *backend) pathSignVerbatim(ctx context.Context, req *logical.Request, data *framework.FieldData, role *roleEntry) (*logical.Response, error) {
|
||||
entry := &roleEntry{
|
||||
AllowLocalhost: true,
|
||||
AllowAnyName: true,
|
||||
AllowIPSANs: true,
|
||||
AllowWildcardCertificates: new(bool),
|
||||
EnforceHostnames: false,
|
||||
KeyType: "any",
|
||||
UseCSRCommonName: true,
|
||||
UseCSRSANs: true,
|
||||
AllowedOtherSANs: []string{"*"},
|
||||
AllowedSerialNumbers: []string{"*"},
|
||||
AllowedURISANs: []string{"*"},
|
||||
AllowedUserIDs: []string{"*"},
|
||||
CNValidations: []string{"disabled"},
|
||||
GenerateLease: new(bool),
|
||||
KeyUsage: data.Get("key_usage").([]string),
|
||||
ExtKeyUsage: data.Get("ext_key_usage").([]string),
|
||||
ExtKeyUsageOIDs: data.Get("ext_key_usage_oids").([]string),
|
||||
SignatureBits: data.Get("signature_bits").(int),
|
||||
UsePSS: data.Get("use_pss").(bool),
|
||||
}
|
||||
*entry.AllowWildcardCertificates = true
|
||||
|
||||
*entry.GenerateLease = false
|
||||
|
||||
if role != nil {
|
||||
if role.TTL > 0 {
|
||||
entry.TTL = role.TTL
|
||||
}
|
||||
if role.MaxTTL > 0 {
|
||||
entry.MaxTTL = role.MaxTTL
|
||||
}
|
||||
if role.GenerateLease != nil {
|
||||
*entry.GenerateLease = *role.GenerateLease
|
||||
}
|
||||
if role.NotBeforeDuration > 0 {
|
||||
entry.NotBeforeDuration = role.NotBeforeDuration
|
||||
}
|
||||
entry.NoStore = role.NoStore
|
||||
entry.Issuer = role.Issuer
|
||||
}
|
||||
|
||||
if len(entry.Issuer) == 0 {
|
||||
entry.Issuer = defaultRef
|
||||
}
|
||||
entry := buildSignVerbatimRole(data, role)
|
||||
|
||||
return b.pathIssueSignCert(ctx, req, data, entry, true, true)
|
||||
}
|
||||
|
|
|
@ -1014,6 +1014,8 @@ func (b *backend) getRole(ctx context.Context, s logical.Storage, n string) (*ro
|
|||
}
|
||||
}
|
||||
|
||||
result.Name = n
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
|
@ -1105,6 +1107,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
|
|||
NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second,
|
||||
NotAfter: data.Get("not_after").(string),
|
||||
Issuer: data.Get("issuer_ref").(string),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
allowedOtherSANs := data.Get("allowed_other_sans").([]string)
|
||||
|
@ -1510,6 +1513,8 @@ type roleEntry struct {
|
|||
NotBeforeDuration time.Duration `json:"not_before_duration"`
|
||||
NotAfter string `json:"not_after"`
|
||||
Issuer string `json:"issuer"`
|
||||
// Name is only set when the role has been stored, on the fly roles have a blank name
|
||||
Name string `json:"-"`
|
||||
}
|
||||
|
||||
func (r *roleEntry) ToResponseData() map[string]interface{} {
|
||||
|
|
Loading…
Reference in New Issue