Don't show field names when not needed

This commit is contained in:
Jeff Mitchell
2015-09-29 16:13:54 -07:00
parent 55fc4ba898
commit 667d5cafd3
4 changed files with 336 additions and 258 deletions

View File

@@ -37,8 +37,9 @@ func Backend() *framework.Backend {
Paths: []*framework.Path{
pathRoles(&b),
pathGenerateCA(&b),
pathSignCA(&b),
pathGenerateRootCA(&b),
pathGenerateIntermediateCA(&b),
pathSignIntermediateCA(&b),
pathSetCA(&b),
pathConfigCA(&b),
pathConfigCRL(&b),

View File

@@ -170,10 +170,16 @@ func validateCommonNames(req *logical.Request, commonNames []string, role *roleE
func generateCert(b *backend,
role *roleEntry,
signingBundle *certutil.ParsedCertBundle,
isCA bool,
req *logical.Request,
data *framework.FieldData) (*certutil.ParsedCertBundle, error) {
creationBundle, err := generateCreationBundle(b, role, signingBundle, req, data)
_, ok := data.GetOk("common_name")
if !ok {
return nil, certutil.UserError{Err: "The common_name field is required"}
}
creationBundle, err := generateCreationBundle(b, role, signingBundle, isCA, req, data)
if err != nil {
return nil, err
}
@@ -189,10 +195,11 @@ func generateCert(b *backend,
func generateCSR(b *backend,
role *roleEntry,
signingBundle *certutil.ParsedCertBundle,
isCA bool,
req *logical.Request,
data *framework.FieldData) (*certutil.ParsedCSRBundle, error) {
creationBundle, err := generateCreationBundle(b, role, signingBundle, req, data)
creationBundle, err := generateCreationBundle(b, role, signingBundle, isCA, req, data)
if err != nil {
return nil, err
}
@@ -209,10 +216,16 @@ func signCert(b *backend,
role *roleEntry,
signingBundle *certutil.ParsedCertBundle,
csr *x509.CertificateRequest,
isCA bool,
req *logical.Request,
data *framework.FieldData) (*certutil.ParsedCertBundle, error) {
creationBundle, err := generateCreationBundle(b, role, signingBundle, req, data)
_, ok := data.GetOk("common_name")
if !ok {
return nil, certutil.UserError{Err: "The common_name field is required"}
}
creationBundle, err := generateCreationBundle(b, role, signingBundle, isCA, req, data)
if err != nil {
return nil, err
}
@@ -228,47 +241,56 @@ func signCert(b *backend,
func generateCreationBundle(b *backend,
role *roleEntry,
signingBundle *certutil.ParsedCertBundle,
isCA bool,
req *logical.Request,
data *framework.FieldData) (*certCreationBundle, error) {
var err error
// Get the common name(s)
var commonNames []string
cn := data.Get("common_name").(string)
if len(cn) == 0 {
return nil, certutil.UserError{Err: "The common_name field is required"}
var cn string
cnInt, ok := data.GetOk("common_name")
if ok {
cn = cnInt.(string)
}
commonNames = []string{cn}
cnAlt := data.Get("alt_names").(string)
if len(cnAlt) != 0 {
for _, v := range strings.Split(cnAlt, ",") {
commonNames = append(commonNames, v)
commonNames := []string{cn}
cnAltInt, ok := data.GetOk("alt_names")
if ok {
cnAlt := cnAltInt.(string)
if len(cnAlt) != 0 {
for _, v := range strings.Split(cnAlt, ",") {
commonNames = append(commonNames, v)
}
}
}
// Get any IP SANs
ipSANs := []net.IP{}
ipAlt := data.Get("ip_sans").(string)
if len(ipAlt) != 0 {
if !role.AllowIPSANs {
return nil, certutil.UserError{Err: fmt.Sprintf(
"IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)}
}
for _, v := range strings.Split(ipAlt, ",") {
parsedIP := net.ParseIP(v)
if parsedIP == nil {
ipAltInt, ok := data.GetOk("ip_sans")
if ok {
ipAlt := ipAltInt.(string)
if len(ipAlt) != 0 {
if !role.AllowIPSANs {
return nil, certutil.UserError{Err: fmt.Sprintf(
"the value '%s' is not a valid IP address", v)}
"IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)}
}
for _, v := range strings.Split(ipAlt, ",") {
parsedIP := net.ParseIP(v)
if parsedIP == nil {
return nil, certutil.UserError{Err: fmt.Sprintf(
"the value '%s' is not a valid IP address", v)}
}
ipSANs = append(ipSANs, parsedIP)
}
ipSANs = append(ipSANs, parsedIP)
}
}
ttlField := data.Get("ttl").(string)
if len(ttlField) == 0 {
var ttlField string
ttlFieldInt, ok := data.GetOk("ttl")
if !ok {
ttlField = role.TTL
} else {
ttlField = ttlFieldInt.(string)
}
var ttl time.Duration
@@ -340,11 +362,13 @@ func generateCreationBundle(b *backend,
Usage: usage,
}
if _, ok := req.Data["ca_type"]; ok {
creationBundle.CAType = req.Data["ca_type"].(string)
}
if _, ok := req.Data["pki_address"]; ok {
creationBundle.PKIAddress = req.Data["pki_address"].(string)
if isCA {
if _, ok := req.Data["ca_type"]; ok {
creationBundle.CAType = req.Data["ca_type"].(string)
}
if _, ok := req.Data["pki_address"]; ok {
creationBundle.PKIAddress = req.Data["pki_address"].(string)
}
}
return creationBundle, nil

View File

@@ -14,43 +14,7 @@ import (
"github.com/hashicorp/vault/logical/framework"
)
var generateAndSignSchema = map[string]*framework.FieldSchema{
"type": &framework.FieldSchema{
Type: framework.TypeString,
Description: `Must be "self-signed" or "intermediate".
If set to "self-signed", a self-signed root CA
will be generated. If set to "intermediate", a
CSR will be returned for signing by the root.`,
},
"exported": &framework.FieldSchema{
Type: framework.TypeString,
Description: `Must be "internal" or "exported".
If set to "exported", the generated private
key will be returned. This is your *only*
chance to retrieve the private key!`,
},
"pki_address": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The base URL of the PKI mount. For
"self-signed"; this is needed when
calling '/generate/' and should be
the base URL of the mount where you
are running this command, e.g.
"https://vault.example.com/v1/root_pki".
For "intermediate", this is needed
when calling '/sign/' and should be
the base URL of the destination
mount of the certificate, e.g.
"https://vault.example.com/v1/intermediate_pki".
For HA setups, the given host name
should be the address that can always
be used to contact the leader. This is
is used for generating the CA/CRL URLs in
the certificate. Required as specified.`,
},
var rootAndSignSchema = map[string]*framework.FieldSchema{
"common_name": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested common name; if you want more than
@@ -70,26 +34,16 @@ in a comma-delimited list`,
common-delimited list`,
},
"key_bits": &framework.FieldSchema{
Type: framework.TypeInt,
Default: 2048,
Description: `The number of bits to use. You will almost
certainly want to change this if you adjust
the key_type.`,
},
"ttl": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested Time To Live for the certificate;
sets the expiration date. If not specified
the role default, backend default, or system
default TTL is used, in that order. Cannot
be larger than the mount max TTL.`,
},
"csr": &framework.FieldSchema{
Type: framework.TypeString,
Description: `PEM-format CSR to be signed.`,
be larger than the mount max TTL. Note:
this only has an effect when generating
a CA cert or signing a CA cert, not when
creating a CSR for an intermediate CA.`,
},
}
@@ -134,26 +88,88 @@ endpoint, just the signed certificate.`,
}
}
func pathGenerateCA(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/ca/generate/" + framework.GenericNameRegex("type") + "/" + framework.GenericNameRegex("exported"),
func pathGenerateRootCA(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "config/ca/generate/root/" + framework.GenericNameRegex("exported"),
Fields: generateAndSignSchema,
Fields: rootAndSignSchema,
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCAGenerateWrite,
logical.WriteOperation: b.pathCAGenerateRoot,
},
HelpSynopsis: pathConfigCAGenerateHelpSyn,
HelpDescription: pathConfigCAGenerateHelpDesc,
}
ret.Fields["pki_address"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `The base URL of the PKI mount, e.g.
"https://vault.example.com/v1/root_pki".
For HA setups, the given host name
should be the address that can always
be used to contact the leader, as this is
is used for generating the CA/CRL URLs in
the certificate.`,
}
ret.Fields["exported"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Must be "internal" or "exported".
If set to "exported", the generated private
key will be returned. This is your *only*
chance to retrieve the private key!`,
}
ret.Fields["key_bits"] = &framework.FieldSchema{
Type: framework.TypeInt,
Default: 2048,
Description: `The number of bits to use. You will almost
certainly want to change this if you adjust
the key_type.`,
}
return ret
}
func pathSignCA(b *backend) *framework.Path {
return &framework.Path{
func pathGenerateIntermediateCA(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "config/ca/generate/intermediate/" + framework.GenericNameRegex("exported"),
Fields: map[string]*framework.FieldSchema{},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCAGenerateIntermediate,
},
HelpSynopsis: pathConfigCAGenerateHelpSyn,
HelpDescription: pathConfigCAGenerateHelpDesc,
}
ret.Fields["exported"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `Must be "internal" or "exported".
If set to "exported", the generated private
key will be returned. This is your *only*
chance to retrieve the private key!`,
}
ret.Fields["key_bits"] = &framework.FieldSchema{
Type: framework.TypeInt,
Default: 2048,
Description: `The number of bits to use. You will almost
certainly want to change this if you adjust
the key_type.`,
}
return ret
}
func pathSignIntermediateCA(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "config/ca/sign",
Fields: generateAndSignSchema,
Fields: rootAndSignSchema,
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCASignWrite,
@@ -162,9 +178,28 @@ func pathSignCA(b *backend) *framework.Path {
HelpSynopsis: pathConfigCASignHelpSyn,
HelpDescription: pathConfigCASignHelpDesc,
}
ret.Fields["pki_address"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `The base URL of the *destination*
PKI mount, e.g.
"https://vault.example.com/v1/intermediate_pki".
For HA setups, the given host name
should be the address that can always
be used to contact the leader, as this is
is used for generating the CA/CRL URLs in
the certificate.`,
}
ret.Fields["csr"] = &framework.FieldSchema{
Type: framework.TypeString,
Description: `PEM-format CSR to be signed.`,
}
return ret
}
func (b *backend) pathCAGenerateWrite(
func (b *backend) pathCAGenerateRoot(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
var err error
exported := data.Get("exported").(string)
@@ -176,39 +211,28 @@ func (b *backend) pathCAGenerateWrite(
"The \"exported\" path parameter must be \"internal\" or \"exported\"")), nil
}
genType := data.Get("type").(string)
switch genType {
case "self-signed":
case "intermediate":
default:
req.Data["ca_type"] = "root"
pkiAddress := strings.ToLower(data.Get("pki_address").(string))
switch {
case len(pkiAddress) == 0:
return logical.ErrorResponse(fmt.Sprintf(
"The \"type\" path parameter must be \"self-signed\" or \"intermediate\"")), nil
"\"pki_address\" cannot be empty")), nil
case !strings.HasPrefix(pkiAddress, "http"):
return logical.ErrorResponse(fmt.Sprintf(
"\"pki_address\" must be a URL")), nil
case !strings.Contains(pkiAddress, "/v1/"):
return logical.ErrorResponse(fmt.Sprintf(
"\"pki_address\" needs to be the path to the PKI mount, not the base Vault path")), nil
case !strings.Contains(pkiAddress, "/v1/"+req.MountPoint[:len(req.MountPoint)-1]):
return logical.ErrorResponse(fmt.Sprintf(
"\"pki_address\" needs to be the path to this mount")), nil
}
if strings.HasSuffix(pkiAddress, "/") {
pkiAddress = pkiAddress[:len(pkiAddress)-1]
}
req.Data["ca_type"] = genType
if genType == "self-signed" {
pkiAddress := strings.ToLower(data.Get("pki_address").(string))
switch {
case len(pkiAddress) == 0:
return logical.ErrorResponse(fmt.Sprintf(
"\"pki_address\" cannot be empty")), nil
case !strings.HasPrefix(pkiAddress, "http"):
return logical.ErrorResponse(fmt.Sprintf(
"\"pki_address\" must be a URL")), nil
case !strings.Contains(pkiAddress, "/v1/"):
return logical.ErrorResponse(fmt.Sprintf(
"\"pki_address\" needs to be the path to the PKI mount, not the base Vault path")), nil
case !strings.Contains(pkiAddress, "/v1/"+req.MountPoint[:len(req.MountPoint)-1]):
return logical.ErrorResponse(fmt.Sprintf(
"\"pki_address\" needs to be the path to this mount")), nil
}
if strings.HasSuffix(pkiAddress, "/") {
pkiAddress = pkiAddress[:len(pkiAddress)-1]
}
req.Data["pki_address"] = pkiAddress
}
req.Data["pki_address"] = pkiAddress
role := &roleEntry{
TTL: data.Get("ttl").(string),
@@ -219,21 +243,6 @@ func (b *backend) pathCAGenerateWrite(
EnforceHostnames: false,
}
maxSystemTTL := b.System().MaxLeaseTTL()
ttl := b.System().DefaultLeaseTTL()
if len(role.TTL) != 0 {
ttl, err = time.ParseDuration(role.TTL)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"Invalid ttl: %s", err)), nil
}
}
if ttl > maxSystemTTL {
return logical.ErrorResponse(fmt.Sprintf(
"\"ttl\" value must be less than mount max of %d seconds", maxSystemTTL/time.Second)), nil
}
switch role.KeyBits {
case 0:
role.KeyBits = 2048
@@ -246,105 +255,135 @@ func (b *backend) pathCAGenerateWrite(
}
var resp *logical.Response
switch genType {
case "self-signed":
parsedBundle, err := generateCert(b, role, nil, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case certutil.InternalError:
return nil, err
}
}
cb, err := parsedBundle.ToCertBundle()
if err != nil {
return nil, fmt.Errorf("Error converting raw cert bundle to cert bundle: %s", err)
}
resp = &logical.Response{
Data: map[string]interface{}{
"serial_number": cb.SerialNumber,
"certificate": cb.Certificate,
"issuing_ca": cb.IssuingCA,
"expiration": int(parsedBundle.Certificate.NotAfter.Unix()),
},
}
if exported == "exported" {
resp.Data["private_key"] = cb.PrivateKey
resp.Data["private_key_type"] = cb.PrivateKeyType
}
entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
return nil, err
}
err = req.Storage.Put(entry)
if err != nil {
parsedBundle, err := generateCert(b, role, nil, true, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case certutil.InternalError:
return nil, err
}
}
// For ease of later use, also store just the certificate at a known
// location, plus a blank CRL
entry.Key = "ca"
entry.Value = parsedBundle.CertificateBytes
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
cb, err := parsedBundle.ToCertBundle()
if err != nil {
return nil, fmt.Errorf("Error converting raw cert bundle to cert bundle: %s", err)
}
entry.Key = "crl"
entry.Value = []byte{}
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
resp = &logical.Response{
Data: map[string]interface{}{
"serial_number": cb.SerialNumber,
"certificate": cb.Certificate,
"issuing_ca": cb.IssuingCA,
"expiration": int(parsedBundle.Certificate.NotAfter.Unix()),
},
}
case "intermediate":
parsedBundle, err := generateCSR(b, role, nil, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case certutil.InternalError:
return nil, err
}
}
if exported == "exported" {
resp.Data["private_key"] = cb.PrivateKey
resp.Data["private_key_type"] = cb.PrivateKeyType
}
csrb, err := parsedBundle.ToCSRBundle()
if err != nil {
return nil, fmt.Errorf("Error converting raw CSR bundle to CSR bundle: %s", err)
}
entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
return nil, err
}
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
resp = &logical.Response{
Data: map[string]interface{}{
"csr": csrb.CSR,
},
}
// For ease of later use, also store just the certificate at a known
// location, plus a blank CRL
entry.Key = "ca"
entry.Value = parsedBundle.CertificateBytes
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
if exported == "exported" {
resp.Data["private_key"] = csrb.PrivateKey
resp.Data["private_key_type"] = csrb.PrivateKeyType
}
entry.Key = "crl"
entry.Value = []byte{}
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
cb := &certutil.CertBundle{
PrivateKey: csrb.PrivateKey,
PrivateKeyType: csrb.PrivateKeyType,
}
entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
return nil, err
}
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
return resp, nil
}
func (b *backend) pathCAGenerateIntermediate(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
var err error
exported := data.Get("exported").(string)
switch exported {
case "exported":
case "internal":
default:
return logical.ErrorResponse("Unknown generation type"), nil
return logical.ErrorResponse(fmt.Sprintf(
"The \"exported\" path parameter must be \"internal\" or \"exported\"")), nil
}
req.Data["ca_type"] = "intermediate"
role := &roleEntry{
KeyType: "rsa",
KeyBits: data.Get("key_bits").(int),
AllowLocalhost: true,
AllowAnyName: true,
EnforceHostnames: false,
}
switch role.KeyBits {
case 0:
role.KeyBits = 2048
case 1024:
case 2048:
case 4096:
default:
return logical.ErrorResponse(fmt.Sprintf(
"\"key_bits\" must be 1024, 2048, or 4096")), nil
}
var resp *logical.Response
parsedBundle, err := generateCSR(b, role, nil, true, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError:
return logical.ErrorResponse(err.Error()), nil
case certutil.InternalError:
return nil, err
}
}
csrb, err := parsedBundle.ToCSRBundle()
if err != nil {
return nil, fmt.Errorf("Error converting raw CSR bundle to CSR bundle: %s", err)
}
resp = &logical.Response{
Data: map[string]interface{}{
"csr": csrb.CSR,
},
}
if exported == "exported" {
resp.Data["private_key"] = csrb.PrivateKey
resp.Data["private_key_type"] = csrb.PrivateKeyType
}
cb := &certutil.CertBundle{
PrivateKey: csrb.PrivateKey,
PrivateKeyType: csrb.PrivateKeyType,
}
entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
return nil, err
}
err = req.Storage.Put(entry)
if err != nil {
return nil, err
}
return resp, nil
@@ -425,7 +464,7 @@ func (b *backend) pathCASignWrite(
"Error fetching CA certificate: %s", caErr)}
}
parsedBundle, err := signCert(b, role, signingBundle, csr, req, data)
parsedBundle, err := signCert(b, role, signingBundle, csr, true, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError:

View File

@@ -10,45 +10,65 @@ import (
"github.com/hashicorp/vault/logical/framework"
)
func pathIssue(b *backend) *framework.Path {
return &framework.Path{
Pattern: "issue/" + framework.GenericNameRegex("role"),
Fields: map[string]*framework.FieldSchema{
"role": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The desired role with configuration for this
var issueAndSignSchema = map[string]*framework.FieldSchema{
"role": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The desired role with configuration for this
request`,
},
},
"common_name": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested common name; if you want more than
"common_name": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested common name; if you want more than
one, specify the alternative names in the
alt_names map`,
},
},
"alt_names": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested Subject Alternative Names, if any,
"alt_names": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested Subject Alternative Names, if any,
in a comma-delimited list`,
},
},
"ip_sans": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested IP SANs, if any, in a
"ip_sans": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested IP SANs, if any, in a
common-delimited list`,
},
},
"ttl": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested Time To Live for the certificate;
"ttl": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The requested Time To Live for the certificate;
sets the expiration date. If not specified
the role default, backend default, or system
default TTL is used, in that order. Cannot
be later than the role max TTL.`,
},
},
"csr": &framework.FieldSchema{
Type: framework.TypeString,
Description: `PEM-format CSR to be signed.`,
},
}
func pathIssue(b *backend) *framework.Path {
return &framework.Path{
Pattern: "issue/" + framework.GenericNameRegex("role"),
Fields: issueAndSignSchema,
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathIssueCert,
},
HelpSynopsis: pathIssueCertHelpSyn,
HelpDescription: pathIssueCertHelpDesc,
}
}
func pathSign(b *backend) *framework.Path {
return &framework.Path{
Pattern: "sign/" + framework.GenericNameRegex("role"),
Fields: issueAndSignSchema,
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathIssueCert,
},
@@ -82,13 +102,7 @@ func (b *backend) pathIssueCert(
"Error fetching CA certificate: %s", caErr)}
}
// Don't allow these on the standard path. Ideally we should determine
// this internally once we get SudoPrivilege from System() working
// for non-TokenStore
delete(req.Data, "ca_type")
delete(req.Data, "pki_address")
parsedBundle, err := generateCert(b, role, signingBundle, req, data)
parsedBundle, err := generateCert(b, role, signingBundle, false, req, data)
if err != nil {
switch err.(type) {
case certutil.UserError: