2015-05-15 16:13:05 +00:00
|
|
|
package pki
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2015-06-15 17:33:23 +00:00
|
|
|
"github.com/fatih/structs"
|
2015-05-15 16:13:05 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
|
|
)
|
|
|
|
|
|
|
|
func pathRoles(b *backend) *framework.Path {
|
|
|
|
return &framework.Path{
|
2015-08-21 07:56:13 +00:00
|
|
|
Pattern: "roles/" + framework.GenericNameRegex("name"),
|
2015-05-15 16:13:05 +00:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"name": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
2015-06-18 14:44:02 +00:00
|
|
|
Description: "Name of the role",
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"lease": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Default: "",
|
|
|
|
Description: `The lease length if no specific lease length is
|
|
|
|
requested. The lease length controls the expiration
|
|
|
|
of certificates issued by this backend. Defaults to
|
|
|
|
the value of lease_max.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"lease_max": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Default: "",
|
2015-06-18 14:44:02 +00:00
|
|
|
Description: "The maximum allowed lease length",
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"allow_localhost": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: true,
|
|
|
|
Description: `Whether to allow "localhost" as a valid common
|
|
|
|
name in a request`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"allowed_base_domain": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Default: "",
|
|
|
|
Description: `If set, clients can request certificates for
|
|
|
|
subdomains directly beneath this base domain, including
|
|
|
|
the wildcard subdomain. See the documentation for more
|
|
|
|
information.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"allow_token_displayname": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: false,
|
|
|
|
Description: `If set, clients can request certificates for
|
|
|
|
matching the value of the Display Name on the requesting
|
|
|
|
token. See the documentation for more information.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"allow_subdomains": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: false,
|
|
|
|
Description: `If set, clients can request certificates for
|
|
|
|
subdomains of the CNs allowed by the other role options,
|
|
|
|
including wildcard subdomains. See the documentation for
|
|
|
|
more information.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"allow_any_name": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: false,
|
|
|
|
Description: `If set, clients can request certificates for
|
|
|
|
any CN they like. See the documentation for more
|
|
|
|
information.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
2015-08-20 21:33:37 +00:00
|
|
|
"enforce_hostnames": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: false,
|
|
|
|
Description: `If set, only valid host names are allowed for
|
|
|
|
CN and SANs.`,
|
|
|
|
},
|
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
"allow_ip_sans": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: true,
|
|
|
|
Description: `If set, IP Subject Alternative Names are allowed.
|
|
|
|
Any valid IP is accepted.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"server_flag": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: true,
|
|
|
|
Description: `If set, certificates are flagged for server use.
|
|
|
|
Defaults to true.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"client_flag": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: true,
|
|
|
|
Description: `If set, certificates are flagged for client use.
|
|
|
|
Defaults to true.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"code_signing_flag": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeBool,
|
|
|
|
Default: false,
|
|
|
|
Description: `If set, certificates are flagged for code signing
|
|
|
|
use. Defaults to false.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"key_type": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Default: "rsa",
|
|
|
|
Description: `The type of key to use; defaults to RSA. "rsa"
|
|
|
|
and "ec" are the only valid values.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"key_bits": &framework.FieldSchema{
|
2015-06-18 14:44:02 +00:00
|
|
|
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.`,
|
2015-05-15 16:13:05 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
|
|
logical.ReadOperation: b.pathRoleRead,
|
|
|
|
logical.WriteOperation: b.pathRoleCreate,
|
|
|
|
logical.DeleteOperation: b.pathRoleDelete,
|
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathRoleHelpSyn,
|
|
|
|
HelpDescription: pathRoleHelpDesc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) getRole(s logical.Storage, n string) (*roleEntry, error) {
|
|
|
|
entry, err := s.Get("role/" + n)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if entry == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var result roleEntry
|
|
|
|
if err := entry.DecodeJSON(&result); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) pathRoleDelete(
|
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
err := req.Storage.Delete("role/" + data.Get("name").(string))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) pathRoleRead(
|
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
role, err := b.getRole(req.Storage, data.Get("name").(string))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if role == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
resp := &logical.Response{
|
2015-06-15 17:33:23 +00:00
|
|
|
Data: structs.New(role).Map(),
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) pathRoleCreate(
|
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
|
|
name := data.Get("name").(string)
|
|
|
|
|
|
|
|
entry := &roleEntry{
|
|
|
|
LeaseMax: data.Get("lease_max").(string),
|
|
|
|
Lease: data.Get("lease").(string),
|
|
|
|
AllowLocalhost: data.Get("allow_localhost").(bool),
|
|
|
|
AllowedBaseDomain: data.Get("allowed_base_domain").(string),
|
|
|
|
AllowTokenDisplayName: data.Get("allow_token_displayname").(bool),
|
|
|
|
AllowSubdomains: data.Get("allow_subdomains").(bool),
|
|
|
|
AllowAnyName: data.Get("allow_any_name").(bool),
|
2015-08-20 21:33:37 +00:00
|
|
|
EnforceHostnames: data.Get("enforce_hostnames").(bool),
|
2015-05-15 16:13:05 +00:00
|
|
|
AllowIPSANs: data.Get("allow_ip_sans").(bool),
|
|
|
|
ServerFlag: data.Get("server_flag").(bool),
|
|
|
|
ClientFlag: data.Get("client_flag").(bool),
|
|
|
|
CodeSigningFlag: data.Get("code_signing_flag").(bool),
|
|
|
|
KeyType: data.Get("key_type").(string),
|
|
|
|
KeyBits: data.Get("key_bits").(int),
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(entry.LeaseMax) == 0 {
|
|
|
|
return logical.ErrorResponse("\"lease_max\" value must be supplied"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
leaseMax, err := time.ParseDuration(entry.LeaseMax)
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"Invalid lease: %s", err)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch len(entry.Lease) {
|
|
|
|
case 0:
|
|
|
|
entry.Lease = entry.LeaseMax
|
|
|
|
default:
|
|
|
|
lease, err := time.ParseDuration(entry.Lease)
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"Invalid lease: %s", err)), nil
|
|
|
|
}
|
|
|
|
if lease > leaseMax {
|
|
|
|
return logical.ErrorResponse("\"lease\" value must be less than \"lease_max\" value"), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-05 18:03:57 +00:00
|
|
|
if len(entry.KeyType) == 0 {
|
|
|
|
entry.KeyType = "rsa"
|
|
|
|
}
|
|
|
|
if entry.KeyBits == 0 {
|
|
|
|
entry.KeyBits = 2048
|
|
|
|
}
|
|
|
|
|
2015-05-15 16:13:05 +00:00
|
|
|
switch entry.KeyType {
|
|
|
|
case "rsa":
|
2015-06-05 18:03:57 +00:00
|
|
|
case "ec":
|
2015-05-15 16:13:05 +00:00
|
|
|
switch entry.KeyBits {
|
|
|
|
case 224:
|
|
|
|
case 256:
|
|
|
|
case 384:
|
|
|
|
case 521:
|
|
|
|
default:
|
2015-06-05 18:03:57 +00:00
|
|
|
return logical.ErrorResponse(fmt.Sprintf("Unsupported bit length for EC key: %d", entry.KeyBits)), nil
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf("Unknown key type %s", entry.KeyType)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store it
|
|
|
|
jsonEntry, err := logical.StorageEntryJSON("role/"+name, entry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := req.Storage.Put(jsonEntry); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type roleEntry struct {
|
2015-06-05 18:03:57 +00:00
|
|
|
LeaseMax string `json:"lease_max" structs:"lease_max" mapstructure:"lease_max"`
|
|
|
|
Lease string `json:"lease" structs:"lease" mapstructure:"lease"`
|
|
|
|
AllowLocalhost bool `json:"allow_localhost" structs:"allow_localhost" mapstructure:"allow_localhost"`
|
|
|
|
AllowedBaseDomain string `json:"allowed_base_domain" structs:"allowed_base_domain" mapstructure:"allowed_base_domain"`
|
|
|
|
AllowTokenDisplayName bool `json:"allow_token_displayname" structs:"allow_token_displayname" mapstructure:"allow_token_displayname"`
|
|
|
|
AllowSubdomains bool `json:"allow_subdomains" structs:"allow_subdomains" mapstructure:"allow_subdomains"`
|
|
|
|
AllowAnyName bool `json:"allow_any_name" structs:"allow_any_name" mapstructure:"allow_any_name"`
|
2015-08-20 21:33:37 +00:00
|
|
|
EnforceHostnames bool `json:"enforce_hostnames" structs:"enforce_hostnames" mapstructure:"enforce_hostnames"`
|
2015-06-05 18:03:57 +00:00
|
|
|
AllowIPSANs bool `json:"allow_ip_sans" structs:"allow_ip_sans" mapstructure:"allow_ip_sans"`
|
|
|
|
ServerFlag bool `json:"server_flag" structs:"server_flag" mapstructure:"server_flag"`
|
|
|
|
ClientFlag bool `json:"client_flag" structs:"client_flag" mapstructure:"client_flag"`
|
|
|
|
CodeSigningFlag bool `json:"code_signing_flag" structs:"code_signing_flag" mapstructure:"code_signing_flag"`
|
|
|
|
KeyType string `json:"key_type" structs:"key_type" mapstructure:"key_type"`
|
|
|
|
KeyBits int `json:"key_bits" structs:"key_bits" mapstructure:"key_bits"`
|
2015-05-15 16:13:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const pathRoleHelpSyn = `
|
|
|
|
Manage the roles that can be created with this backend.
|
|
|
|
`
|
|
|
|
|
|
|
|
const pathRoleHelpDesc = `
|
|
|
|
This path lets you manage the roles that can be created with this backend.
|
|
|
|
`
|