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{ Paths: []*framework.Path{
pathRoles(&b), pathRoles(&b),
pathGenerateCA(&b), pathGenerateRootCA(&b),
pathSignCA(&b), pathGenerateIntermediateCA(&b),
pathSignIntermediateCA(&b),
pathSetCA(&b), pathSetCA(&b),
pathConfigCA(&b), pathConfigCA(&b),
pathConfigCRL(&b), pathConfigCRL(&b),

View File

@@ -170,10 +170,16 @@ func validateCommonNames(req *logical.Request, commonNames []string, role *roleE
func generateCert(b *backend, func generateCert(b *backend,
role *roleEntry, role *roleEntry,
signingBundle *certutil.ParsedCertBundle, signingBundle *certutil.ParsedCertBundle,
isCA bool,
req *logical.Request, req *logical.Request,
data *framework.FieldData) (*certutil.ParsedCertBundle, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@@ -189,10 +195,11 @@ func generateCert(b *backend,
func generateCSR(b *backend, func generateCSR(b *backend,
role *roleEntry, role *roleEntry,
signingBundle *certutil.ParsedCertBundle, signingBundle *certutil.ParsedCertBundle,
isCA bool,
req *logical.Request, req *logical.Request,
data *framework.FieldData) (*certutil.ParsedCSRBundle, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@@ -209,10 +216,16 @@ func signCert(b *backend,
role *roleEntry, role *roleEntry,
signingBundle *certutil.ParsedCertBundle, signingBundle *certutil.ParsedCertBundle,
csr *x509.CertificateRequest, csr *x509.CertificateRequest,
isCA bool,
req *logical.Request, req *logical.Request,
data *framework.FieldData) (*certutil.ParsedCertBundle, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@@ -228,29 +241,34 @@ func signCert(b *backend,
func generateCreationBundle(b *backend, func generateCreationBundle(b *backend,
role *roleEntry, role *roleEntry,
signingBundle *certutil.ParsedCertBundle, signingBundle *certutil.ParsedCertBundle,
isCA bool,
req *logical.Request, req *logical.Request,
data *framework.FieldData) (*certCreationBundle, error) { data *framework.FieldData) (*certCreationBundle, error) {
var err error var err error
// Get the common name(s) // Get the common name(s)
var commonNames []string var cn string
cn := data.Get("common_name").(string) cnInt, ok := data.GetOk("common_name")
if len(cn) == 0 { if ok {
return nil, certutil.UserError{Err: "The common_name field is required"} cn = cnInt.(string)
} }
commonNames = []string{cn}
cnAlt := data.Get("alt_names").(string) commonNames := []string{cn}
cnAltInt, ok := data.GetOk("alt_names")
if ok {
cnAlt := cnAltInt.(string)
if len(cnAlt) != 0 { if len(cnAlt) != 0 {
for _, v := range strings.Split(cnAlt, ",") { for _, v := range strings.Split(cnAlt, ",") {
commonNames = append(commonNames, v) commonNames = append(commonNames, v)
} }
} }
}
// Get any IP SANs // Get any IP SANs
ipSANs := []net.IP{} ipSANs := []net.IP{}
ipAltInt, ok := data.GetOk("ip_sans")
ipAlt := data.Get("ip_sans").(string) if ok {
ipAlt := ipAltInt.(string)
if len(ipAlt) != 0 { if len(ipAlt) != 0 {
if !role.AllowIPSANs { if !role.AllowIPSANs {
return nil, certutil.UserError{Err: fmt.Sprintf( return nil, certutil.UserError{Err: fmt.Sprintf(
@@ -265,10 +283,14 @@ func generateCreationBundle(b *backend,
ipSANs = append(ipSANs, parsedIP) ipSANs = append(ipSANs, parsedIP)
} }
} }
}
ttlField := data.Get("ttl").(string) var ttlField string
if len(ttlField) == 0 { ttlFieldInt, ok := data.GetOk("ttl")
if !ok {
ttlField = role.TTL ttlField = role.TTL
} else {
ttlField = ttlFieldInt.(string)
} }
var ttl time.Duration var ttl time.Duration
@@ -340,12 +362,14 @@ func generateCreationBundle(b *backend,
Usage: usage, Usage: usage,
} }
if isCA {
if _, ok := req.Data["ca_type"]; ok { if _, ok := req.Data["ca_type"]; ok {
creationBundle.CAType = req.Data["ca_type"].(string) creationBundle.CAType = req.Data["ca_type"].(string)
} }
if _, ok := req.Data["pki_address"]; ok { if _, ok := req.Data["pki_address"]; ok {
creationBundle.PKIAddress = req.Data["pki_address"].(string) creationBundle.PKIAddress = req.Data["pki_address"].(string)
} }
}
return creationBundle, nil return creationBundle, nil
} }

View File

@@ -14,43 +14,7 @@ import (
"github.com/hashicorp/vault/logical/framework" "github.com/hashicorp/vault/logical/framework"
) )
var generateAndSignSchema = map[string]*framework.FieldSchema{ var rootAndSignSchema = 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.`,
},
"common_name": &framework.FieldSchema{ "common_name": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: `The requested common name; if you want more than Description: `The requested common name; if you want more than
@@ -70,26 +34,16 @@ in a comma-delimited list`,
common-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{ "ttl": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: `The requested Time To Live for the certificate; Description: `The requested Time To Live for the certificate;
sets the expiration date. If not specified sets the expiration date. If not specified
the role default, backend default, or system the role default, backend default, or system
default TTL is used, in that order. Cannot default TTL is used, in that order. Cannot
be larger than the mount max TTL.`, 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
"csr": &framework.FieldSchema{ creating a CSR for an intermediate CA.`,
Type: framework.TypeString,
Description: `PEM-format CSR to be signed.`,
}, },
} }
@@ -134,26 +88,88 @@ endpoint, just the signed certificate.`,
} }
} }
func pathGenerateCA(b *backend) *framework.Path { func pathGenerateRootCA(b *backend) *framework.Path {
return &framework.Path{ ret := &framework.Path{
Pattern: "config/ca/generate/" + framework.GenericNameRegex("type") + "/" + framework.GenericNameRegex("exported"), Pattern: "config/ca/generate/root/" + framework.GenericNameRegex("exported"),
Fields: generateAndSignSchema, Fields: rootAndSignSchema,
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCAGenerateWrite, logical.WriteOperation: b.pathCAGenerateRoot,
}, },
HelpSynopsis: pathConfigCAGenerateHelpSyn, HelpSynopsis: pathConfigCAGenerateHelpSyn,
HelpDescription: pathConfigCAGenerateHelpDesc, 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 { func pathGenerateIntermediateCA(b *backend) *framework.Path {
return &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", Pattern: "config/ca/sign",
Fields: generateAndSignSchema, Fields: rootAndSignSchema,
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathCASignWrite, logical.WriteOperation: b.pathCASignWrite,
@@ -162,9 +178,28 @@ func pathSignCA(b *backend) *framework.Path {
HelpSynopsis: pathConfigCASignHelpSyn, HelpSynopsis: pathConfigCASignHelpSyn,
HelpDescription: pathConfigCASignHelpDesc, 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) { req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
var err error var err error
exported := data.Get("exported").(string) exported := data.Get("exported").(string)
@@ -176,18 +211,8 @@ func (b *backend) pathCAGenerateWrite(
"The \"exported\" path parameter must be \"internal\" or \"exported\"")), nil "The \"exported\" path parameter must be \"internal\" or \"exported\"")), nil
} }
genType := data.Get("type").(string) req.Data["ca_type"] = "root"
switch genType {
case "self-signed":
case "intermediate":
default:
return logical.ErrorResponse(fmt.Sprintf(
"The \"type\" path parameter must be \"self-signed\" or \"intermediate\"")), nil
}
req.Data["ca_type"] = genType
if genType == "self-signed" {
pkiAddress := strings.ToLower(data.Get("pki_address").(string)) pkiAddress := strings.ToLower(data.Get("pki_address").(string))
switch { switch {
case len(pkiAddress) == 0: case len(pkiAddress) == 0:
@@ -208,7 +233,6 @@ func (b *backend) pathCAGenerateWrite(
} }
req.Data["pki_address"] = pkiAddress req.Data["pki_address"] = pkiAddress
}
role := &roleEntry{ role := &roleEntry{
TTL: data.Get("ttl").(string), TTL: data.Get("ttl").(string),
@@ -219,21 +243,6 @@ func (b *backend) pathCAGenerateWrite(
EnforceHostnames: false, 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 { switch role.KeyBits {
case 0: case 0:
role.KeyBits = 2048 role.KeyBits = 2048
@@ -246,9 +255,7 @@ func (b *backend) pathCAGenerateWrite(
} }
var resp *logical.Response var resp *logical.Response
switch genType { parsedBundle, err := generateCert(b, role, nil, true, req, data)
case "self-signed":
parsedBundle, err := generateCert(b, role, nil, req, data)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case certutil.UserError: case certutil.UserError:
@@ -302,8 +309,44 @@ func (b *backend) pathCAGenerateWrite(
return nil, err return nil, err
} }
case "intermediate": return resp, nil
parsedBundle, err := generateCSR(b, role, nil, req, data) }
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(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 { if err != nil {
switch err.(type) { switch err.(type) {
case certutil.UserError: case certutil.UserError:
@@ -343,10 +386,6 @@ func (b *backend) pathCAGenerateWrite(
return nil, err return nil, err
} }
default:
return logical.ErrorResponse("Unknown generation type"), nil
}
return resp, nil return resp, nil
} }
@@ -425,7 +464,7 @@ func (b *backend) pathCASignWrite(
"Error fetching CA certificate: %s", caErr)} "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 { if err != nil {
switch err.(type) { switch err.(type) {
case certutil.UserError: case certutil.UserError:

View File

@@ -10,10 +10,7 @@ import (
"github.com/hashicorp/vault/logical/framework" "github.com/hashicorp/vault/logical/framework"
) )
func pathIssue(b *backend) *framework.Path { var issueAndSignSchema = map[string]*framework.FieldSchema{
return &framework.Path{
Pattern: "issue/" + framework.GenericNameRegex("role"),
Fields: map[string]*framework.FieldSchema{
"role": &framework.FieldSchema{ "role": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: `The desired role with configuration for this Description: `The desired role with configuration for this
@@ -47,7 +44,30 @@ the role default, backend default, or system
default TTL is used, in that order. Cannot default TTL is used, in that order. Cannot
be later than the role max TTL.`, 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{ Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathIssueCert, logical.WriteOperation: b.pathIssueCert,
@@ -82,13 +102,7 @@ func (b *backend) pathIssueCert(
"Error fetching CA certificate: %s", caErr)} "Error fetching CA certificate: %s", caErr)}
} }
// Don't allow these on the standard path. Ideally we should determine parsedBundle, err := generateCert(b, role, signingBundle, false, req, data)
// 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)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case certutil.UserError: case certutil.UserError: