2015-06-16 20:58:54 +00:00
|
|
|
package ssh
|
|
|
|
|
|
|
|
import (
|
2015-06-30 20:30:13 +00:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
2015-06-16 20:58:54 +00:00
|
|
|
|
2017-03-01 20:50:23 +00:00
|
|
|
"time"
|
|
|
|
|
2016-09-19 18:40:43 +00:00
|
|
|
"github.com/hashicorp/vault/helper/cidrutil"
|
2017-03-07 16:21:22 +00:00
|
|
|
"github.com/hashicorp/vault/helper/parseutil"
|
2015-06-16 20:58:54 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
|
|
)
|
|
|
|
|
2015-08-13 21:18:30 +00:00
|
|
|
const (
|
|
|
|
KeyTypeOTP = "otp"
|
|
|
|
KeyTypeDynamic = "dynamic"
|
2016-12-26 14:03:27 +00:00
|
|
|
KeyTypeCA = "ca"
|
2015-08-13 21:18:30 +00:00
|
|
|
)
|
|
|
|
|
2015-08-18 01:22:03 +00:00
|
|
|
// Structure that represents a role in SSH backend. This is a common role structure
|
|
|
|
// for both OTP and Dynamic roles. Not all the fields are mandatory for both type.
|
|
|
|
// Some are applicable for one and not for other. It doesn't matter.
|
2015-08-13 21:18:30 +00:00
|
|
|
type sshRole struct {
|
2016-12-26 14:03:27 +00:00
|
|
|
KeyType string `mapstructure:"key_type" json:"key_type"`
|
|
|
|
KeyName string `mapstructure:"key" json:"key"`
|
|
|
|
KeyBits int `mapstructure:"key_bits" json:"key_bits"`
|
|
|
|
AdminUser string `mapstructure:"admin_user" json:"admin_user"`
|
|
|
|
DefaultUser string `mapstructure:"default_user" json:"default_user"`
|
|
|
|
CIDRList string `mapstructure:"cidr_list" json:"cidr_list"`
|
|
|
|
ExcludeCIDRList string `mapstructure:"exclude_cidr_list" json:"exclude_cidr_list"`
|
|
|
|
Port int `mapstructure:"port" json:"port"`
|
|
|
|
InstallScript string `mapstructure:"install_script" json:"install_script"`
|
|
|
|
AllowedUsers string `mapstructure:"allowed_users" json:"allowed_users"`
|
2017-02-28 08:24:31 +00:00
|
|
|
AllowedDomains string `mapstructure:"allowed_domains" json:"allowed_domains"`
|
2016-12-26 14:03:27 +00:00
|
|
|
KeyOptionSpecs string `mapstructure:"key_option_specs" json:"key_option_specs"`
|
|
|
|
MaxTTL string `mapstructure:"max_ttl" json:"max_ttl"`
|
|
|
|
TTL string `mapstructure:"ttl" json:"ttl"`
|
|
|
|
DefaultCriticalOptions map[string]string `mapstructure:"default_critical_options" json:"default_critical_options"`
|
|
|
|
DefaultExtensions map[string]string `mapstructure:"default_extensions" json:"default_extensions"`
|
|
|
|
AllowedCriticalOptions string `mapstructure:"allowed_critical_options" json:"allowed_critical_options"`
|
|
|
|
AllowedExtensions string `mapstructure:"allowed_extensions" json:"allowed_extensions"`
|
|
|
|
AllowUserCertificates bool `mapstructure:"allow_user_certificates" json:"allow_user_certificates"`
|
|
|
|
AllowHostCertificates bool `mapstructure:"allow_host_certificates" json:"allow_host_certificates"`
|
|
|
|
AllowBareDomains bool `mapstructure:"allow_bare_domains" json:"allow_bare_domains"`
|
|
|
|
AllowSubdomains bool `mapstructure:"allow_subdomains" json:"allow_subdomains"`
|
2015-08-13 21:18:30 +00:00
|
|
|
}
|
2015-07-29 18:21:36 +00:00
|
|
|
|
2016-01-28 17:48:00 +00:00
|
|
|
func pathListRoles(b *backend) *framework.Path {
|
|
|
|
return &framework.Path{
|
|
|
|
Pattern: "roles/?$",
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
|
|
logical.ListOperation: b.pathRoleList,
|
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathRoleHelpSyn,
|
|
|
|
HelpDescription: pathRoleHelpDesc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-16 20:58:54 +00:00
|
|
|
func pathRoles(b *backend) *framework.Path {
|
|
|
|
return &framework.Path{
|
2015-08-21 07:56:13 +00:00
|
|
|
Pattern: "roles/" + framework.GenericNameRegex("role"),
|
2015-06-16 20:58:54 +00:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
2015-08-13 00:36:27 +00:00
|
|
|
"role": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Required for all types]
|
2015-08-13 17:36:31 +00:00
|
|
|
Name of the role being created.`,
|
2015-06-16 20:58:54 +00:00
|
|
|
},
|
2015-06-24 22:13:12 +00:00
|
|
|
"key": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Required for Dynamic type] [Not applicable for OTP type] [Not applicable for CA type]
|
2015-08-13 17:36:31 +00:00
|
|
|
Name of the registered key in Vault. Before creating the role, use the
|
|
|
|
'keys/' endpoint to create a named key.`,
|
2015-06-24 22:13:12 +00:00
|
|
|
},
|
|
|
|
"admin_user": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Required for Dynamic type] [Not applicable for OTP type] [Not applicable for CA type]
|
2015-08-13 17:36:31 +00:00
|
|
|
Admin user at remote host. The shared key being registered should be
|
|
|
|
for this user and should have root privileges. Everytime a dynamic
|
|
|
|
credential is being generated for other users, Vault uses this admin
|
|
|
|
username to login to remote host and install the generated credential
|
|
|
|
for the other user.`,
|
2015-06-24 22:13:12 +00:00
|
|
|
},
|
|
|
|
"default_user": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Required for Dynamic type] [Required for OTP type] [Optional for CA type]
|
2015-08-13 17:36:31 +00:00
|
|
|
Default username for which a credential will be generated.
|
|
|
|
When the endpoint 'creds/' is used without a username, this
|
|
|
|
value will be used as default username.`,
|
2015-06-24 22:13:12 +00:00
|
|
|
},
|
2015-08-13 15:46:55 +00:00
|
|
|
"cidr_list": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Optional for Dynamic type] [Optional for OTP type] [Not applicable for CA type]
|
2015-08-13 17:36:31 +00:00
|
|
|
Comma separated list of CIDR blocks for which the role is applicable for.
|
2016-02-25 01:34:07 +00:00
|
|
|
CIDR blocks can belong to more than one role.`,
|
2015-06-16 20:58:54 +00:00
|
|
|
},
|
2015-08-28 03:19:55 +00:00
|
|
|
"exclude_cidr_list": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Optional for Dynamic type] [Optional for OTP type] [Not applicable for CA type]
|
2015-08-28 03:19:55 +00:00
|
|
|
Comma separated list of CIDR blocks. IP addresses belonging to these blocks are not
|
|
|
|
accepted by the role. This is particularly useful when big CIDR blocks are being used
|
|
|
|
by the role and certain parts of it needs to be kept out.`,
|
|
|
|
},
|
2015-07-06 20:56:45 +00:00
|
|
|
"port": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeInt,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Optional for Dynamic type] [Optional for OTP type] [Not applicable for CA type]
|
2015-08-13 23:55:47 +00:00
|
|
|
Port number for SSH connection. Default is '22'. Port number does not
|
|
|
|
play any role in creation of OTP. For 'otp' type, this is just a way
|
|
|
|
to inform client about the port number to use. Port number will be
|
|
|
|
returned to client by Vault server along with OTP.`,
|
2015-07-06 20:56:45 +00:00
|
|
|
},
|
2015-07-22 18:15:19 +00:00
|
|
|
"key_type": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Required for all types]
|
|
|
|
Type of key used to login to hosts. It can be either 'otp', 'dynamic' or 'ca'.
|
2015-08-13 17:36:31 +00:00
|
|
|
'otp' type requires agent to be installed in remote hosts.`,
|
2015-07-22 18:15:19 +00:00
|
|
|
},
|
2015-07-29 18:21:36 +00:00
|
|
|
"key_bits": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeInt,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Optional for Dynamic type] [Not applicable for OTP type] [Not applicable for CA type]
|
2015-08-19 19:51:33 +00:00
|
|
|
Length of the RSA dynamic key in bits. It is 1024 by default or it can be 2048.`,
|
2015-07-29 18:21:36 +00:00
|
|
|
},
|
2015-08-06 18:48:19 +00:00
|
|
|
"install_script": &framework.FieldSchema{
|
2015-08-13 17:36:31 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Optional for Dynamic type] [Not-applicable for OTP type] [Not applicable for CA type]
|
2015-08-13 17:36:31 +00:00
|
|
|
Script used to install and uninstall public keys in the target machine.
|
|
|
|
The inbuilt default install script will be for Linux hosts. For sample
|
2015-08-14 19:41:26 +00:00
|
|
|
script, refer the project documentation website.`,
|
2015-08-06 18:48:19 +00:00
|
|
|
},
|
2015-08-13 21:18:30 +00:00
|
|
|
"allowed_users": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Optional for all types]
|
2017-03-09 15:34:55 +00:00
|
|
|
If this option is not specified, client can request for a
|
|
|
|
credential for any valid user at the remote host, including the
|
|
|
|
admin user. If only certain usernames are to be allowed, then
|
|
|
|
this list enforces it. If this field is set, then credentials
|
|
|
|
can only be created for default_user and usernames present in
|
|
|
|
this list. Setting this option will enable all the users with
|
|
|
|
access this role to fetch credentials for all other usernames
|
|
|
|
in this list. Use with caution.
|
2017-02-28 08:24:31 +00:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"allowed_domains": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
If this option is not specified, client can request for a signed certificate for any
|
|
|
|
valid host. If only certain domains are allowed, then this list enforces it.
|
2015-08-13 21:18:30 +00:00
|
|
|
`,
|
|
|
|
},
|
2015-08-27 15:41:29 +00:00
|
|
|
"key_option_specs": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
2016-12-26 14:03:27 +00:00
|
|
|
[Optional for Dynamic type] [Not applicable for OTP type] [Not applicable for CA type]
|
2015-08-27 15:41:29 +00:00
|
|
|
Comma separated option specifications which will be prefixed to RSA key in
|
|
|
|
authorized_keys file. Options should be valid and comply with authorized_keys
|
|
|
|
file format and should not contain spaces.
|
|
|
|
`,
|
|
|
|
},
|
2016-12-26 14:03:27 +00:00
|
|
|
"ttl": &framework.FieldSchema{
|
2017-03-01 20:50:23 +00:00
|
|
|
Type: framework.TypeString,
|
2016-12-26 14:03:27 +00:00
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
The lease duration if no specific lease duration is
|
|
|
|
requested. The lease duration controls the expiration
|
|
|
|
of certificates issued by this backend. Defaults to
|
|
|
|
the value of max_ttl.`,
|
|
|
|
},
|
|
|
|
"max_ttl": &framework.FieldSchema{
|
2017-03-01 20:50:23 +00:00
|
|
|
Type: framework.TypeString,
|
2016-12-26 14:03:27 +00:00
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
The maximum allowed lease duration
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"allowed_critical_options": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
A comma-separated list of critical options that certificates can have when signed.
|
|
|
|
To allow any critical options, set this to an empty string.
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"allowed_extensions": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
A comma-separated list of extensions that certificates can have when signed.
|
2017-02-28 22:08:10 +00:00
|
|
|
To allow any extensions, set this to an empty string.
|
2016-12-26 14:03:27 +00:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"default_critical_options": &framework.FieldSchema{
|
2017-03-01 20:50:23 +00:00
|
|
|
Type: framework.TypeMap,
|
2016-12-26 14:03:27 +00:00
|
|
|
Description: `
|
2017-03-01 20:50:23 +00:00
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type]
|
|
|
|
[Optional for CA type] Critical options certificates should
|
|
|
|
have if none are provided when signing. This field takes in key
|
|
|
|
value pairs in JSON format. Note that these are not restricted
|
|
|
|
by "allowed_critical_options". Defaults to none.
|
|
|
|
`,
|
2016-12-26 14:03:27 +00:00
|
|
|
},
|
|
|
|
"default_extensions": &framework.FieldSchema{
|
2017-03-01 20:50:23 +00:00
|
|
|
Type: framework.TypeMap,
|
2016-12-26 14:03:27 +00:00
|
|
|
Description: `
|
2017-03-01 20:50:23 +00:00
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type]
|
|
|
|
[Optional for CA type] Extensions certificates should have if
|
|
|
|
none are provided when signing. This field takes in key value
|
|
|
|
pairs in JSON format. Note that these are not restricted by
|
|
|
|
"allowed_extensions". Defaults to none.
|
2016-12-26 14:03:27 +00:00
|
|
|
`,
|
|
|
|
},
|
|
|
|
"allow_user_certificates": &framework.FieldSchema{
|
2017-03-01 20:50:23 +00:00
|
|
|
Type: framework.TypeBool,
|
2016-12-26 14:03:27 +00:00
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
If set, certificates are allowed to be signed for use as a 'user'.
|
|
|
|
`,
|
2017-03-01 20:50:23 +00:00
|
|
|
Default: false,
|
2016-12-26 14:03:27 +00:00
|
|
|
},
|
|
|
|
"allow_host_certificates": &framework.FieldSchema{
|
2017-03-01 20:50:23 +00:00
|
|
|
Type: framework.TypeBool,
|
2016-12-26 14:03:27 +00:00
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
If set, certificates are allowed to be signed for use as a 'host'.
|
|
|
|
`,
|
2017-03-01 20:50:23 +00:00
|
|
|
Default: false,
|
2016-12-26 14:03:27 +00:00
|
|
|
},
|
|
|
|
"allow_bare_domains": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeBool,
|
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
|
|
|
If set, host certificates that are requested are allowed to use the base domains listed in
|
2017-02-28 22:08:10 +00:00
|
|
|
"allowed_domains", e.g. "example.com".
|
2016-12-26 14:03:27 +00:00
|
|
|
This is a separate option as in some cases this can be considered a security threat.
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
"allow_subdomains": &framework.FieldSchema{
|
2017-03-01 20:50:23 +00:00
|
|
|
Type: framework.TypeBool,
|
2016-12-26 14:03:27 +00:00
|
|
|
Description: `
|
|
|
|
[Not applicable for Dynamic type] [Not applicable for OTP type] [Optional for CA type]
|
2017-02-28 22:08:10 +00:00
|
|
|
If set, host certificates that are requested are allowed to use subdomains of those listed in "allowed_domains".
|
2016-12-26 14:03:27 +00:00
|
|
|
`,
|
|
|
|
},
|
2015-06-16 20:58:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
|
|
logical.ReadOperation: b.pathRoleRead,
|
2016-01-28 17:48:00 +00:00
|
|
|
logical.UpdateOperation: b.pathRoleWrite,
|
2015-06-16 20:58:54 +00:00
|
|
|
logical.DeleteOperation: b.pathRoleDelete,
|
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathRoleHelpSyn,
|
|
|
|
HelpDescription: pathRoleHelpDesc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-29 18:21:36 +00:00
|
|
|
func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2015-08-13 00:36:27 +00:00
|
|
|
roleName := d.Get("role").(string)
|
2015-07-22 18:15:19 +00:00
|
|
|
if roleName == "" {
|
2016-09-19 18:40:43 +00:00
|
|
|
return logical.ErrorResponse("missing role name"), nil
|
2015-07-22 18:15:19 +00:00
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-08-19 02:00:27 +00:00
|
|
|
// Allowed users is an optional field, applicable for both OTP and Dynamic types.
|
2015-08-13 21:18:30 +00:00
|
|
|
allowedUsers := d.Get("allowed_users").(string)
|
|
|
|
|
2016-09-19 18:40:43 +00:00
|
|
|
// Validate the CIDR blocks
|
2015-08-13 15:46:55 +00:00
|
|
|
cidrList := d.Get("cidr_list").(string)
|
2016-09-22 15:37:55 +00:00
|
|
|
if cidrList != "" {
|
|
|
|
valid, err := cidrutil.ValidateCIDRListString(cidrList, ",")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to validate cidr_list: %v", err)
|
|
|
|
}
|
|
|
|
if !valid {
|
|
|
|
return logical.ErrorResponse("failed to validate cidr_list"), nil
|
|
|
|
}
|
2015-07-22 18:15:19 +00:00
|
|
|
}
|
2015-08-18 01:22:03 +00:00
|
|
|
|
2016-09-19 18:40:43 +00:00
|
|
|
// Validate the excluded CIDR blocks
|
2015-08-28 03:19:55 +00:00
|
|
|
excludeCidrList := d.Get("exclude_cidr_list").(string)
|
2016-09-22 15:37:55 +00:00
|
|
|
if excludeCidrList != "" {
|
|
|
|
valid, err := cidrutil.ValidateCIDRListString(excludeCidrList, ",")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to validate exclude_cidr_list entry: %v", err)
|
|
|
|
}
|
|
|
|
if !valid {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf("failed to validate exclude_cidr_list entry: %v", err)), nil
|
|
|
|
}
|
2015-07-22 18:15:19 +00:00
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-08-13 15:46:55 +00:00
|
|
|
port := d.Get("port").(int)
|
|
|
|
if port == 0 {
|
|
|
|
port = 22
|
2015-07-22 18:15:19 +00:00
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-07-29 18:21:36 +00:00
|
|
|
keyType := d.Get("key_type").(string)
|
|
|
|
if keyType == "" {
|
2016-09-19 18:40:43 +00:00
|
|
|
return logical.ErrorResponse("missing key type"), nil
|
2015-07-22 18:15:19 +00:00
|
|
|
}
|
2015-07-29 18:21:36 +00:00
|
|
|
keyType = strings.ToLower(keyType)
|
2015-07-22 18:15:19 +00:00
|
|
|
|
2015-08-13 17:36:31 +00:00
|
|
|
var roleEntry sshRole
|
2015-07-29 18:21:36 +00:00
|
|
|
if keyType == KeyTypeOTP {
|
2016-12-26 14:03:27 +00:00
|
|
|
defaultUser := d.Get("default_user").(string)
|
|
|
|
if defaultUser == "" {
|
|
|
|
return logical.ErrorResponse("missing default user"), nil
|
|
|
|
}
|
|
|
|
|
2015-08-18 01:22:03 +00:00
|
|
|
// Admin user is not used if OTP key type is used because there is
|
|
|
|
// no need to login to remote machine.
|
2015-07-29 18:21:36 +00:00
|
|
|
adminUser := d.Get("admin_user").(string)
|
|
|
|
if adminUser != "" {
|
2016-09-19 18:40:43 +00:00
|
|
|
return logical.ErrorResponse("admin user not required for OTP type"), nil
|
2015-07-29 18:21:36 +00:00
|
|
|
}
|
2015-07-22 18:15:19 +00:00
|
|
|
|
2015-08-18 01:22:03 +00:00
|
|
|
// Below are the only fields used from the role structure for OTP type.
|
2015-08-13 17:36:31 +00:00
|
|
|
roleEntry = sshRole{
|
2015-08-28 03:19:55 +00:00
|
|
|
DefaultUser: defaultUser,
|
|
|
|
CIDRList: cidrList,
|
|
|
|
ExcludeCIDRList: excludeCidrList,
|
|
|
|
KeyType: KeyTypeOTP,
|
|
|
|
Port: port,
|
|
|
|
AllowedUsers: allowedUsers,
|
2015-08-13 17:36:31 +00:00
|
|
|
}
|
2015-07-29 18:21:36 +00:00
|
|
|
} else if keyType == KeyTypeDynamic {
|
2016-12-26 14:03:27 +00:00
|
|
|
defaultUser := d.Get("default_user").(string)
|
|
|
|
if defaultUser == "" {
|
|
|
|
return logical.ErrorResponse("missing default user"), nil
|
|
|
|
}
|
2015-08-18 01:22:03 +00:00
|
|
|
// Key name is required by dynamic type and not by OTP type.
|
2015-07-29 18:21:36 +00:00
|
|
|
keyName := d.Get("key").(string)
|
|
|
|
if keyName == "" {
|
2016-09-19 18:40:43 +00:00
|
|
|
return logical.ErrorResponse("missing key name"), nil
|
2015-07-29 18:21:36 +00:00
|
|
|
}
|
|
|
|
keyEntry, err := req.Storage.Get(fmt.Sprintf("keys/%s", keyName))
|
|
|
|
if err != nil || keyEntry == nil {
|
2016-10-18 16:46:54 +00:00
|
|
|
return logical.ErrorResponse(fmt.Sprintf("invalid 'key': %q", keyName)), nil
|
2015-07-29 18:21:36 +00:00
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-08-06 18:48:19 +00:00
|
|
|
installScript := d.Get("install_script").(string)
|
2015-08-27 15:41:29 +00:00
|
|
|
keyOptionSpecs := d.Get("key_option_specs").(string)
|
2015-08-18 01:22:03 +00:00
|
|
|
|
|
|
|
// Setting the default script here. The script will install the
|
|
|
|
// generated public key in the authorized_keys file of linux host.
|
2015-08-06 18:48:19 +00:00
|
|
|
if installScript == "" {
|
2015-08-18 01:22:03 +00:00
|
|
|
installScript = DefaultPublicKeyInstallScript
|
2015-08-06 18:48:19 +00:00
|
|
|
}
|
|
|
|
|
2015-07-29 18:21:36 +00:00
|
|
|
adminUser := d.Get("admin_user").(string)
|
|
|
|
if adminUser == "" {
|
2016-09-19 18:40:43 +00:00
|
|
|
return logical.ErrorResponse("missing admin username"), nil
|
2015-07-29 18:21:36 +00:00
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-08-19 19:51:33 +00:00
|
|
|
// This defaults to 1024 and it can also be 2048.
|
2015-08-13 17:36:31 +00:00
|
|
|
keyBits := d.Get("key_bits").(int)
|
2015-08-19 19:51:33 +00:00
|
|
|
if keyBits != 0 && keyBits != 1024 && keyBits != 2048 {
|
2016-09-19 18:40:43 +00:00
|
|
|
return logical.ErrorResponse("invalid key_bits field"), nil
|
2015-06-30 20:30:13 +00:00
|
|
|
}
|
2015-08-18 01:22:03 +00:00
|
|
|
|
2015-08-19 19:51:33 +00:00
|
|
|
// If user has not set this field, default it to 1024
|
2015-08-13 17:36:31 +00:00
|
|
|
if keyBits == 0 {
|
2015-08-19 19:51:33 +00:00
|
|
|
keyBits = 1024
|
2015-07-29 18:21:36 +00:00
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-08-18 01:22:03 +00:00
|
|
|
// Store all the fields required by dynamic key type
|
2015-08-13 17:36:31 +00:00
|
|
|
roleEntry = sshRole{
|
2015-08-28 03:19:55 +00:00
|
|
|
KeyName: keyName,
|
|
|
|
AdminUser: adminUser,
|
|
|
|
DefaultUser: defaultUser,
|
|
|
|
CIDRList: cidrList,
|
|
|
|
ExcludeCIDRList: excludeCidrList,
|
|
|
|
Port: port,
|
|
|
|
KeyType: KeyTypeDynamic,
|
|
|
|
KeyBits: keyBits,
|
|
|
|
InstallScript: installScript,
|
|
|
|
AllowedUsers: allowedUsers,
|
|
|
|
KeyOptionSpecs: keyOptionSpecs,
|
2015-08-13 17:36:31 +00:00
|
|
|
}
|
2016-12-26 14:03:27 +00:00
|
|
|
} else if keyType == KeyTypeCA {
|
|
|
|
role, errorResponse := b.createCARole(allowedUsers, d.Get("default_user").(string), d)
|
|
|
|
if errorResponse != nil {
|
|
|
|
return errorResponse, nil
|
|
|
|
}
|
|
|
|
roleEntry = *role
|
2015-07-29 18:21:36 +00:00
|
|
|
} else {
|
2016-09-19 18:40:43 +00:00
|
|
|
return logical.ErrorResponse("invalid key type"), nil
|
2015-07-06 20:56:45 +00:00
|
|
|
}
|
2015-06-30 20:30:13 +00:00
|
|
|
|
2015-08-13 17:36:31 +00:00
|
|
|
entry, err := logical.StorageEntryJSON(fmt.Sprintf("roles/%s", roleName), roleEntry)
|
2015-06-24 22:13:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := req.Storage.Put(entry); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2016-12-26 14:03:27 +00:00
|
|
|
func (b *backend) createCARole(allowedUsers, defaultUser string, data *framework.FieldData) (*sshRole, *logical.Response) {
|
|
|
|
|
|
|
|
role := &sshRole{
|
2017-03-01 20:50:23 +00:00
|
|
|
MaxTTL: data.Get("max_ttl").(string),
|
|
|
|
TTL: data.Get("ttl").(string),
|
2016-12-26 14:03:27 +00:00
|
|
|
AllowedCriticalOptions: data.Get("allowed_critical_options").(string),
|
|
|
|
AllowedExtensions: data.Get("allowed_extensions").(string),
|
|
|
|
AllowUserCertificates: data.Get("allow_user_certificates").(bool),
|
|
|
|
AllowHostCertificates: data.Get("allow_host_certificates").(bool),
|
|
|
|
AllowedUsers: allowedUsers,
|
2017-02-28 08:24:31 +00:00
|
|
|
AllowedDomains: data.Get("allowed_domains").(string),
|
2016-12-26 14:03:27 +00:00
|
|
|
DefaultUser: defaultUser,
|
|
|
|
AllowBareDomains: data.Get("allow_bare_domains").(bool),
|
|
|
|
AllowSubdomains: data.Get("allow_subdomains").(bool),
|
|
|
|
KeyType: KeyTypeCA,
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultCriticalOptions := convertMapToStringValue(data.Get("default_critical_options").(map[string]interface{}))
|
|
|
|
defaultExtensions := convertMapToStringValue(data.Get("default_extensions").(map[string]interface{}))
|
|
|
|
|
|
|
|
var maxTTL time.Duration
|
|
|
|
maxSystemTTL := b.System().MaxLeaseTTL()
|
|
|
|
if len(role.MaxTTL) == 0 {
|
|
|
|
maxTTL = maxSystemTTL
|
|
|
|
} else {
|
|
|
|
var err error
|
2017-03-07 16:21:22 +00:00
|
|
|
maxTTL, err = parseutil.ParseDurationSecond(role.MaxTTL)
|
2016-12-26 14:03:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, logical.ErrorResponse(fmt.Sprintf(
|
2017-03-02 20:38:34 +00:00
|
|
|
"Invalid max ttl: %s", err))
|
2016-12-26 14:03:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if maxTTL > maxSystemTTL {
|
|
|
|
return nil, logical.ErrorResponse("Requested max TTL is higher than backend maximum")
|
|
|
|
}
|
|
|
|
|
|
|
|
ttl := b.System().DefaultLeaseTTL()
|
|
|
|
if len(role.TTL) != 0 {
|
|
|
|
var err error
|
2017-03-07 16:21:22 +00:00
|
|
|
ttl, err = parseutil.ParseDurationSecond(role.TTL)
|
2016-12-26 14:03:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"Invalid ttl: %s", err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ttl > maxTTL {
|
|
|
|
// If they are using the system default, cap it to the role max;
|
|
|
|
// if it was specified on the command line, make it an error
|
|
|
|
if len(role.TTL) == 0 {
|
|
|
|
ttl = maxTTL
|
|
|
|
} else {
|
|
|
|
return nil, logical.ErrorResponse(
|
|
|
|
`"ttl" value must be less than "max_ttl" and/or backend default max lease TTL value`,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Persist clamped TTLs
|
|
|
|
role.TTL = ttl.String()
|
|
|
|
role.MaxTTL = maxTTL.String()
|
|
|
|
role.DefaultCriticalOptions = defaultCriticalOptions
|
|
|
|
role.DefaultExtensions = defaultExtensions
|
|
|
|
|
|
|
|
return role, nil
|
|
|
|
}
|
|
|
|
|
2015-08-13 18:12:30 +00:00
|
|
|
func (b *backend) getRole(s logical.Storage, n string) (*sshRole, error) {
|
|
|
|
entry, err := s.Get("roles/" + n)
|
2015-06-19 00:48:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-08-13 18:12:30 +00:00
|
|
|
if entry == nil {
|
2015-06-19 00:48:41 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-08-13 18:12:30 +00:00
|
|
|
var result sshRole
|
|
|
|
if err := entry.DecodeJSON(&result); err != nil {
|
2015-07-02 21:23:09 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-07-27 17:02:31 +00:00
|
|
|
|
2015-08-13 18:12:30 +00:00
|
|
|
return &result, nil
|
|
|
|
}
|
|
|
|
|
2016-01-28 17:48:00 +00:00
|
|
|
func (b *backend) pathRoleList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
|
|
entries, err := req.Storage.List("roles/")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return logical.ListResponse(entries), nil
|
|
|
|
}
|
|
|
|
|
2015-08-13 18:12:30 +00:00
|
|
|
func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
|
|
role, err := b.getRole(req.Storage, d.Get("role").(string))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if role == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-08-18 01:22:03 +00:00
|
|
|
// Return information should be based on the key type of the role
|
2015-07-31 17:24:23 +00:00
|
|
|
if role.KeyType == KeyTypeOTP {
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
2015-08-28 03:19:55 +00:00
|
|
|
"default_user": role.DefaultUser,
|
|
|
|
"cidr_list": role.CIDRList,
|
|
|
|
"exclude_cidr_list": role.ExcludeCIDRList,
|
|
|
|
"key_type": role.KeyType,
|
|
|
|
"port": role.Port,
|
|
|
|
"allowed_users": role.AllowedUsers,
|
2015-07-31 17:24:23 +00:00
|
|
|
},
|
|
|
|
}, nil
|
2016-12-26 14:03:27 +00:00
|
|
|
} else if role.KeyType == KeyTypeCA {
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
2017-03-01 20:50:23 +00:00
|
|
|
"allowed_users": role.AllowedUsers,
|
|
|
|
"allowed_domains": role.AllowedDomains,
|
|
|
|
"default_user": role.DefaultUser,
|
|
|
|
"max_ttl": role.MaxTTL,
|
|
|
|
"ttl": role.TTL,
|
2016-12-26 14:03:27 +00:00
|
|
|
"allowed_critical_options": role.AllowedCriticalOptions,
|
|
|
|
"allowed_extensions": role.AllowedExtensions,
|
|
|
|
"allow_user_certificates": role.AllowUserCertificates,
|
|
|
|
"allow_host_certificates": role.AllowHostCertificates,
|
|
|
|
"allow_bare_domains": role.AllowBareDomains,
|
|
|
|
"allow_subdomains": role.AllowSubdomains,
|
2017-02-28 22:08:10 +00:00
|
|
|
"key_type": role.KeyType,
|
2017-03-01 20:50:23 +00:00
|
|
|
"default_critical_options": role.DefaultCriticalOptions,
|
|
|
|
"default_extensions": role.DefaultExtensions,
|
2016-12-26 14:03:27 +00:00
|
|
|
},
|
|
|
|
}, nil
|
2015-07-31 17:24:23 +00:00
|
|
|
} else {
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
2015-08-28 03:19:55 +00:00
|
|
|
"key": role.KeyName,
|
|
|
|
"admin_user": role.AdminUser,
|
|
|
|
"default_user": role.DefaultUser,
|
|
|
|
"cidr_list": role.CIDRList,
|
|
|
|
"exclude_cidr_list": role.ExcludeCIDRList,
|
|
|
|
"port": role.Port,
|
|
|
|
"key_type": role.KeyType,
|
|
|
|
"key_bits": role.KeyBits,
|
|
|
|
"allowed_users": role.AllowedUsers,
|
|
|
|
"key_option_specs": role.KeyOptionSpecs,
|
2015-08-18 01:22:03 +00:00
|
|
|
// Returning install script will make the output look messy.
|
|
|
|
// But this is one way for clients to see the script that is
|
|
|
|
// being used to install the key. If there is some problem,
|
|
|
|
// the script can be modified and configured by clients.
|
|
|
|
"install_script": role.InstallScript,
|
2015-07-31 17:24:23 +00:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
2015-06-16 20:58:54 +00:00
|
|
|
}
|
|
|
|
|
2015-06-19 00:48:41 +00:00
|
|
|
func (b *backend) pathRoleDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2015-08-13 00:36:27 +00:00
|
|
|
roleName := d.Get("role").(string)
|
2015-08-29 19:24:15 +00:00
|
|
|
|
|
|
|
// If the role was given privilege to accept any IP address, there will
|
|
|
|
// be an entry for this role in zero-address roles list. Before the role
|
|
|
|
// is removed, the entry in the list has to be removed.
|
|
|
|
err := b.removeZeroAddressRole(req.Storage, roleName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = req.Storage.Delete(fmt.Sprintf("roles/%s", roleName))
|
2015-06-19 00:48:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-06-16 20:58:54 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const pathRoleHelpSyn = `
|
2015-06-30 20:30:13 +00:00
|
|
|
Manage the 'roles' that can be created with this backend.
|
2015-06-16 20:58:54 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
const pathRoleHelpDesc = `
|
2015-08-18 01:22:03 +00:00
|
|
|
This path allows you to manage the roles that are used to generate credentials.
|
|
|
|
|
|
|
|
Role takes a 'key_type' parameter that decides what type of credential this role
|
|
|
|
can generate. If remote hosts have Vault SSH Agent installed, an 'otp' type can
|
|
|
|
be used, otherwise 'dynamic' type can be used.
|
|
|
|
|
|
|
|
If the backend is mounted at "ssh" and the role is created at "ssh/roles/web",
|
|
|
|
then a user could request for a credential at "ssh/creds/web" for an IP that
|
|
|
|
belongs to the role. The credential will be for the 'default_user' registered
|
|
|
|
with the role. There is also an optional parameter 'username' for 'creds/' endpoint.
|
2015-06-16 20:58:54 +00:00
|
|
|
`
|