2015-05-15 16:13:05 +00:00
|
|
|
package pki
|
|
|
|
|
|
|
|
import (
|
2015-11-12 16:24:32 +00:00
|
|
|
"encoding/base64"
|
2015-05-15 16:13:05 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2017-10-27 16:02:18 +00:00
|
|
|
"github.com/hashicorp/errwrap"
|
2015-06-19 16:48:18 +00:00
|
|
|
"github.com/hashicorp/vault/helper/certutil"
|
2016-07-28 19:19:27 +00:00
|
|
|
"github.com/hashicorp/vault/helper/errutil"
|
2015-05-15 16:13:05 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
|
|
)
|
|
|
|
|
2015-09-29 23:13:54 +00:00
|
|
|
func pathIssue(b *backend) *framework.Path {
|
2015-11-16 16:42:06 +00:00
|
|
|
ret := &framework.Path{
|
2015-09-29 23:13:54 +00:00
|
|
|
Pattern: "issue/" + framework.GenericNameRegex("role"),
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
2016-01-07 15:30:47 +00:00
|
|
|
logical.UpdateOperation: b.pathIssue,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
2015-10-02 19:47:45 +00:00
|
|
|
HelpSynopsis: pathIssueHelpSyn,
|
|
|
|
HelpDescription: pathIssueHelpDesc,
|
2015-09-29 23:13:54 +00:00
|
|
|
}
|
2015-11-16 16:42:06 +00:00
|
|
|
|
|
|
|
ret.Fields = addNonCACommonFields(map[string]*framework.FieldSchema{})
|
|
|
|
return ret
|
2015-09-29 23:13:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func pathSign(b *backend) *framework.Path {
|
2015-09-30 01:48:31 +00:00
|
|
|
ret := &framework.Path{
|
2015-09-29 23:13:54 +00:00
|
|
|
Pattern: "sign/" + framework.GenericNameRegex("role"),
|
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
2016-01-07 15:30:47 +00:00
|
|
|
logical.UpdateOperation: b.pathSign,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
2015-10-02 19:47:45 +00:00
|
|
|
HelpSynopsis: pathSignHelpSyn,
|
|
|
|
HelpDescription: pathSignHelpDesc,
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
2015-09-30 01:48:31 +00:00
|
|
|
|
2015-11-16 16:42:06 +00:00
|
|
|
ret.Fields = addNonCACommonFields(map[string]*framework.FieldSchema{})
|
|
|
|
|
2015-09-30 01:48:31 +00:00
|
|
|
ret.Fields["csr"] = &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
2015-11-12 16:24:32 +00:00
|
|
|
Default: "",
|
2015-09-30 01:48:31 +00:00
|
|
|
Description: `PEM-format CSR to be signed.`,
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
func pathSignVerbatim(b *backend) *framework.Path {
|
|
|
|
ret := &framework.Path{
|
2017-01-10 14:47:59 +00:00
|
|
|
Pattern: "sign-verbatim" + framework.OptionalParamRegex("role"),
|
2015-11-18 15:16:09 +00:00
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
2016-01-07 15:30:47 +00:00
|
|
|
logical.UpdateOperation: b.pathSignVerbatim,
|
2015-11-18 15:16:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathSignHelpSyn,
|
|
|
|
HelpDescription: pathSignHelpDesc,
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.Fields = addNonCACommonFields(map[string]*framework.FieldSchema{})
|
|
|
|
|
|
|
|
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.`,
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// pathIssue issues a certificate and private key from given parameters,
|
|
|
|
// subject to role restrictions
|
2015-10-02 19:47:45 +00:00
|
|
|
func (b *backend) pathIssue(
|
2015-05-15 16:13:05 +00:00
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-11-18 15:16:09 +00:00
|
|
|
roleName := data.Get("role").(string)
|
|
|
|
|
|
|
|
// Get the role
|
|
|
|
role, err := b.getRole(req.Storage, roleName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if role == nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.pathIssueSignCert(req, data, role, false, false)
|
2015-09-30 01:48:31 +00:00
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// pathSign issues a certificate from a submitted CSR, subject to role
|
|
|
|
// restrictions
|
2015-10-02 19:47:45 +00:00
|
|
|
func (b *backend) pathSign(
|
2015-09-30 01:48:31 +00:00
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
roleName := data.Get("role").(string)
|
|
|
|
|
|
|
|
// Get the role
|
|
|
|
role, err := b.getRole(req.Storage, roleName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if role == nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil
|
|
|
|
}
|
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
return b.pathIssueSignCert(req, data, role, true, false)
|
|
|
|
}
|
|
|
|
|
2015-11-19 21:51:27 +00:00
|
|
|
// pathSignVerbatim issues a certificate from a submitted CSR, *not* subject to
|
|
|
|
// role restrictions
|
2015-11-18 15:16:09 +00:00
|
|
|
func (b *backend) pathSignVerbatim(
|
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
|
2017-04-18 19:54:31 +00:00
|
|
|
roleName := data.Get("role").(string)
|
|
|
|
|
|
|
|
// Get the role if one was specified
|
|
|
|
role, err := b.getRole(req.Storage, roleName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-11-18 15:16:09 +00:00
|
|
|
ttl := b.System().DefaultLeaseTTL()
|
2017-04-18 19:54:31 +00:00
|
|
|
maxTTL := b.System().MaxLeaseTTL()
|
|
|
|
|
|
|
|
entry := &roleEntry{
|
2015-11-18 15:16:09 +00:00
|
|
|
TTL: ttl.String(),
|
2017-04-18 19:54:31 +00:00
|
|
|
MaxTTL: maxTTL.String(),
|
2015-11-18 15:16:09 +00:00
|
|
|
AllowLocalhost: true,
|
|
|
|
AllowAnyName: true,
|
|
|
|
AllowIPSANs: true,
|
|
|
|
EnforceHostnames: false,
|
2016-02-19 23:50:51 +00:00
|
|
|
KeyType: "any",
|
2017-01-10 14:47:59 +00:00
|
|
|
UseCSRCommonName: true,
|
2017-03-15 18:38:18 +00:00
|
|
|
UseCSRSANs: true,
|
2017-04-18 19:54:31 +00:00
|
|
|
GenerateLease: new(bool),
|
|
|
|
}
|
|
|
|
|
|
|
|
if role != nil {
|
|
|
|
if role.TTL != "" {
|
|
|
|
entry.TTL = role.TTL
|
|
|
|
}
|
|
|
|
if role.MaxTTL != "" {
|
|
|
|
entry.MaxTTL = role.MaxTTL
|
|
|
|
}
|
|
|
|
entry.NoStore = role.NoStore
|
|
|
|
}
|
|
|
|
|
|
|
|
*entry.GenerateLease = false
|
|
|
|
if role != nil && role.GenerateLease != nil {
|
|
|
|
*entry.GenerateLease = *role.GenerateLease
|
2015-11-18 15:16:09 +00:00
|
|
|
}
|
|
|
|
|
2017-04-18 19:54:31 +00:00
|
|
|
return b.pathIssueSignCert(req, data, entry, true, true)
|
2015-11-18 15:16:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) pathIssueSignCert(
|
|
|
|
req *logical.Request, data *framework.FieldData, role *roleEntry, useCSR, useCSRValues bool) (*logical.Response, error) {
|
2015-11-16 21:32:49 +00:00
|
|
|
format := getFormat(data)
|
|
|
|
if format == "" {
|
2015-11-12 16:24:32 +00:00
|
|
|
return logical.ErrorResponse(
|
2017-10-27 16:02:18 +00:00
|
|
|
`the "format" path parameter must be "pem", "der", or "pem_bundle"`), nil
|
2015-11-12 16:24:32 +00:00
|
|
|
}
|
|
|
|
|
2015-09-30 01:48:31 +00:00
|
|
|
var caErr error
|
|
|
|
signingBundle, caErr := fetchCAInfo(req)
|
|
|
|
switch caErr.(type) {
|
2016-07-28 19:19:27 +00:00
|
|
|
case errutil.UserError:
|
|
|
|
return nil, errutil.UserError{Err: fmt.Sprintf(
|
2017-10-27 16:02:18 +00:00
|
|
|
"could not fetch the CA certificate (was one set?): %s", caErr)}
|
2016-07-28 19:19:27 +00:00
|
|
|
case errutil.InternalError:
|
|
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf(
|
2017-10-27 16:02:18 +00:00
|
|
|
"error fetching CA certificate: %s", caErr)}
|
2015-09-30 01:48:31 +00:00
|
|
|
}
|
|
|
|
|
2015-10-02 19:47:45 +00:00
|
|
|
var parsedBundle *certutil.ParsedCertBundle
|
2015-11-18 15:16:09 +00:00
|
|
|
var err error
|
2015-10-02 19:47:45 +00:00
|
|
|
if useCSR {
|
2015-11-18 15:16:09 +00:00
|
|
|
parsedBundle, err = signCert(b, role, signingBundle, false, useCSRValues, req, data)
|
2015-10-02 19:47:45 +00:00
|
|
|
} else {
|
2015-10-14 01:13:40 +00:00
|
|
|
parsedBundle, err = generateCert(b, role, signingBundle, false, req, data)
|
2015-10-02 19:47:45 +00:00
|
|
|
}
|
2015-09-30 01:48:31 +00:00
|
|
|
if err != nil {
|
|
|
|
switch err.(type) {
|
2016-07-28 19:19:27 +00:00
|
|
|
case errutil.UserError:
|
2015-09-30 01:48:31 +00:00
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
2016-07-28 19:19:27 +00:00
|
|
|
case errutil.InternalError:
|
2015-08-29 13:03:02 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2016-09-28 00:50:17 +00:00
|
|
|
signingCB, err := signingBundle.ToCertBundle()
|
|
|
|
if err != nil {
|
2017-10-27 16:02:18 +00:00
|
|
|
return nil, errwrap.Wrapf("error converting raw signing bundle to cert bundle: {{err}}", err)
|
2016-09-28 00:50:17 +00:00
|
|
|
}
|
|
|
|
|
2015-06-17 16:43:36 +00:00
|
|
|
cb, err := parsedBundle.ToCertBundle()
|
2015-06-15 17:33:23 +00:00
|
|
|
if err != nil {
|
2017-10-27 16:02:18 +00:00
|
|
|
return nil, errwrap.Wrapf("error converting raw cert bundle to cert bundle: {{err}}", err)
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2017-02-24 17:12:40 +00:00
|
|
|
respData := map[string]interface{}{
|
|
|
|
"serial_number": cb.SerialNumber,
|
|
|
|
}
|
2015-05-15 16:13:05 +00:00
|
|
|
|
2016-02-01 18:19:41 +00:00
|
|
|
switch format {
|
|
|
|
case "pem":
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["issuing_ca"] = signingCB.Certificate
|
|
|
|
respData["certificate"] = cb.Certificate
|
2016-09-28 00:50:17 +00:00
|
|
|
if cb.CAChain != nil && len(cb.CAChain) > 0 {
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["ca_chain"] = cb.CAChain
|
2016-09-28 00:50:17 +00:00
|
|
|
}
|
2016-02-01 18:19:41 +00:00
|
|
|
if !useCSR {
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["private_key"] = cb.PrivateKey
|
|
|
|
respData["private_key_type"] = cb.PrivateKeyType
|
2016-02-01 18:19:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case "pem_bundle":
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["issuing_ca"] = signingCB.Certificate
|
|
|
|
respData["certificate"] = cb.ToPEMBundle()
|
2016-09-28 00:50:17 +00:00
|
|
|
if cb.CAChain != nil && len(cb.CAChain) > 0 {
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["ca_chain"] = cb.CAChain
|
2016-09-28 00:50:17 +00:00
|
|
|
}
|
2016-02-01 18:19:41 +00:00
|
|
|
if !useCSR {
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["private_key"] = cb.PrivateKey
|
|
|
|
respData["private_key_type"] = cb.PrivateKeyType
|
2016-02-01 18:19:41 +00:00
|
|
|
}
|
2015-11-19 16:32:18 +00:00
|
|
|
|
2016-02-01 18:19:41 +00:00
|
|
|
case "der":
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
|
|
|
|
respData["issuing_ca"] = base64.StdEncoding.EncodeToString(signingBundle.CertificateBytes)
|
2016-09-28 00:50:17 +00:00
|
|
|
|
|
|
|
var caChain []string
|
|
|
|
for _, caCert := range parsedBundle.CAChain {
|
|
|
|
caChain = append(caChain, base64.StdEncoding.EncodeToString(caCert.Bytes))
|
|
|
|
}
|
|
|
|
if caChain != nil && len(caChain) > 0 {
|
2017-02-24 17:12:40 +00:00
|
|
|
respData["ca_chain"] = caChain
|
2016-09-28 00:50:17 +00:00
|
|
|
}
|
|
|
|
|
2015-11-19 16:32:18 +00:00
|
|
|
if !useCSR {
|
2017-02-24 17:41:20 +00:00
|
|
|
respData["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes)
|
|
|
|
respData["private_key_type"] = cb.PrivateKeyType
|
2015-11-19 16:32:18 +00:00
|
|
|
}
|
2015-11-12 16:24:32 +00:00
|
|
|
}
|
|
|
|
|
2017-02-24 17:12:40 +00:00
|
|
|
var resp *logical.Response
|
|
|
|
switch {
|
|
|
|
case role.GenerateLease == nil:
|
|
|
|
return nil, fmt.Errorf("generate lease in role is nil")
|
|
|
|
case *role.GenerateLease == false:
|
|
|
|
// If lease generation is disabled do not populate `Secret` field in
|
|
|
|
// the response
|
|
|
|
resp = &logical.Response{
|
|
|
|
Data: respData,
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
resp = b.Secret(SecretCertsType).Response(
|
|
|
|
respData,
|
|
|
|
map[string]interface{}{
|
|
|
|
"serial_number": cb.SerialNumber,
|
|
|
|
})
|
|
|
|
resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now())
|
|
|
|
}
|
2015-05-15 16:13:05 +00:00
|
|
|
|
2017-04-07 18:25:47 +00:00
|
|
|
if !role.NoStore {
|
|
|
|
err = req.Storage.Put(&logical.StorageEntry{
|
2017-05-03 14:12:58 +00:00
|
|
|
Key: "certs/" + normalizeSerial(cb.SerialNumber),
|
2017-04-07 18:25:47 +00:00
|
|
|
Value: parsedBundle.CertificateBytes,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2017-04-06 21:00:50 +00:00
|
|
|
return nil, fmt.Errorf("unable to store certificate locally: %v", err)
|
2017-04-07 18:25:47 +00:00
|
|
|
}
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
2017-08-02 14:02:40 +00:00
|
|
|
if useCSR {
|
|
|
|
if role.UseCSRCommonName && data.Get("common_name").(string) != "" {
|
|
|
|
resp.AddWarning("the common_name field was provided but the role is set with \"use_csr_common_name\" set to true")
|
|
|
|
}
|
|
|
|
if role.UseCSRSANs && data.Get("alt_names").(string) != "" {
|
|
|
|
resp.AddWarning("the alt_names field was provided but the role is set with \"use_csr_sans\" set to true")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2015-10-02 19:47:45 +00:00
|
|
|
const pathIssueHelpSyn = `
|
|
|
|
Request a certificate using a certain role with the provided details.
|
2015-05-15 16:13:05 +00:00
|
|
|
`
|
|
|
|
|
2015-10-02 19:47:45 +00:00
|
|
|
const pathIssueHelpDesc = `
|
|
|
|
This path allows requesting a certificate to be issued according to the
|
|
|
|
policy of the given role. The certificate will only be issued if the
|
|
|
|
requested details are allowed by the role policy.
|
|
|
|
|
|
|
|
This path returns a certificate and a private key. If you want a workflow
|
|
|
|
that does not expose a private key, generate a CSR locally and use the
|
|
|
|
sign path instead.
|
|
|
|
`
|
|
|
|
|
|
|
|
const pathSignHelpSyn = `
|
|
|
|
Request certificates using a certain role with the provided details.
|
|
|
|
`
|
|
|
|
|
|
|
|
const pathSignHelpDesc = `
|
2015-05-15 16:13:05 +00:00
|
|
|
This path allows requesting certificates to be issued according to the
|
|
|
|
policy of the given role. The certificate will only be issued if the
|
|
|
|
requested common name is allowed by the role policy.
|
2015-10-02 19:47:45 +00:00
|
|
|
|
|
|
|
This path requires a CSR; if you want Vault to generate a private key
|
|
|
|
for you, use the issue path instead.
|
2015-05-15 16:13:05 +00:00
|
|
|
`
|