2020-07-25 00:09:29 +00:00
|
|
|
package provisioner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2020-08-10 18:26:51 +00:00
|
|
|
"go.step.sm/crypto/sshutil"
|
2022-03-08 12:26:07 +00:00
|
|
|
|
|
|
|
"github.com/smallstep/certificates/authority/policy"
|
2020-07-25 00:09:29 +00:00
|
|
|
)
|
|
|
|
|
2020-08-10 18:26:51 +00:00
|
|
|
// SSHCertificateOptions is an interface that returns a list of options passed when
|
2020-07-25 00:09:29 +00:00
|
|
|
// creating a new certificate.
|
|
|
|
type SSHCertificateOptions interface {
|
|
|
|
Options(SignSSHOptions) []sshutil.Option
|
|
|
|
}
|
|
|
|
|
|
|
|
type sshCertificateOptionsFunc func(SignSSHOptions) []sshutil.Option
|
|
|
|
|
|
|
|
func (fn sshCertificateOptionsFunc) Options(so SignSSHOptions) []sshutil.Option {
|
|
|
|
return fn(so)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SSHOptions are a collection of custom options that can be added to each
|
|
|
|
// provisioner.
|
|
|
|
type SSHOptions struct {
|
|
|
|
// Template contains an SSH certificate template. It can be a JSON template
|
|
|
|
// escaped in a string or it can be also encoded in base64.
|
|
|
|
Template string `json:"template,omitempty"`
|
|
|
|
|
|
|
|
// TemplateFile points to a file containing a SSH certificate template.
|
|
|
|
TemplateFile string `json:"templateFile,omitempty"`
|
|
|
|
|
|
|
|
// TemplateData is a JSON object with variables that can be used in custom
|
|
|
|
// templates.
|
|
|
|
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
2022-01-03 11:25:24 +00:00
|
|
|
|
2022-02-01 13:58:13 +00:00
|
|
|
// User contains SSH user certificate options.
|
2022-03-08 12:26:07 +00:00
|
|
|
User *policy.SSHUserCertificateOptions
|
2022-02-01 13:58:13 +00:00
|
|
|
|
|
|
|
// Host contains SSH host certificate options.
|
2022-03-08 12:26:07 +00:00
|
|
|
Host *policy.SSHHostCertificateOptions
|
2022-01-03 11:25:24 +00:00
|
|
|
}
|
|
|
|
|
2022-03-08 12:26:07 +00:00
|
|
|
// GetAllowedUserNameOptions returns the SSHNameOptions that are
|
|
|
|
// allowed when SSH User certificates are requested.
|
|
|
|
func (o *SSHOptions) GetAllowedUserNameOptions() *policy.SSHNameOptions {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if o.User == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.User.AllowedNames
|
2020-07-25 00:09:29 +00:00
|
|
|
}
|
|
|
|
|
2022-03-08 12:26:07 +00:00
|
|
|
// GetDeniedUserNameOptions returns the SSHNameOptions that are
|
|
|
|
// denied when SSH user certificates are requested.
|
|
|
|
func (o *SSHOptions) GetDeniedUserNameOptions() *policy.SSHNameOptions {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if o.User == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.User.DeniedNames
|
2020-07-25 00:09:29 +00:00
|
|
|
}
|
|
|
|
|
2022-03-08 12:26:07 +00:00
|
|
|
// GetAllowedHostNameOptions returns the SSHNameOptions that are
|
|
|
|
// allowed when SSH host certificates are requested.
|
|
|
|
func (o *SSHOptions) GetAllowedHostNameOptions() *policy.SSHNameOptions {
|
2022-01-03 11:25:24 +00:00
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-03-08 12:26:07 +00:00
|
|
|
if o.Host == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.Host.AllowedNames
|
2022-01-03 11:25:24 +00:00
|
|
|
}
|
|
|
|
|
2022-03-08 12:26:07 +00:00
|
|
|
// GetDeniedHostNameOptions returns the SSHNameOptions that are
|
|
|
|
// denied when SSH host certificates are requested.
|
|
|
|
func (o *SSHOptions) GetDeniedHostNameOptions() *policy.SSHNameOptions {
|
2022-01-03 11:25:24 +00:00
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-03-08 12:26:07 +00:00
|
|
|
if o.Host == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.Host.DeniedNames
|
2022-01-03 11:25:24 +00:00
|
|
|
}
|
|
|
|
|
2022-03-08 12:26:07 +00:00
|
|
|
// HasTemplate returns true if a template is defined in the provisioner options.
|
|
|
|
func (o *SSHOptions) HasTemplate() bool {
|
|
|
|
return o != nil && (o.Template != "" || o.TemplateFile != "")
|
2022-01-03 11:25:24 +00:00
|
|
|
}
|
|
|
|
|
2020-09-08 20:59:22 +00:00
|
|
|
// TemplateSSHOptions generates a SSHCertificateOptions with the template and
|
2020-08-28 21:22:13 +00:00
|
|
|
// data defined in the ProvisionerOptions, the provisioner generated data, and
|
|
|
|
// the user data provided in the request. If no template has been provided,
|
2020-07-25 00:09:29 +00:00
|
|
|
// x509util.DefaultLeafTemplate will be used.
|
2020-07-31 01:44:52 +00:00
|
|
|
func TemplateSSHOptions(o *Options, data sshutil.TemplateData) (SSHCertificateOptions, error) {
|
2020-08-10 18:26:51 +00:00
|
|
|
return CustomSSHTemplateOptions(o, data, sshutil.DefaultTemplate)
|
2020-07-25 00:09:29 +00:00
|
|
|
}
|
|
|
|
|
2020-09-08 20:59:22 +00:00
|
|
|
// CustomSSHTemplateOptions generates a CertificateOptions with the template, data
|
2020-07-25 00:09:29 +00:00
|
|
|
// defined in the ProvisionerOptions, the provisioner generated data and the
|
|
|
|
// user data provided in the request. If no template has been provided in the
|
|
|
|
// ProvisionerOptions, the given template will be used.
|
2020-07-31 01:44:52 +00:00
|
|
|
func CustomSSHTemplateOptions(o *Options, data sshutil.TemplateData, defaultTemplate string) (SSHCertificateOptions, error) {
|
|
|
|
opts := o.GetSSHOptions()
|
|
|
|
if data == nil {
|
|
|
|
data = sshutil.NewTemplateData()
|
|
|
|
}
|
2020-07-25 00:09:29 +00:00
|
|
|
|
2020-07-31 01:44:52 +00:00
|
|
|
if opts != nil {
|
2020-07-25 00:09:29 +00:00
|
|
|
// Add template data if any.
|
2020-09-08 20:59:22 +00:00
|
|
|
if len(opts.TemplateData) > 0 && string(opts.TemplateData) != "null" {
|
2020-07-31 01:44:52 +00:00
|
|
|
if err := json.Unmarshal(opts.TemplateData, &data); err != nil {
|
2020-07-25 00:09:29 +00:00
|
|
|
return nil, errors.Wrap(err, "error unmarshaling template data")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return sshCertificateOptionsFunc(func(so SignSSHOptions) []sshutil.Option {
|
|
|
|
// We're not provided user data without custom templates.
|
2020-07-31 01:44:52 +00:00
|
|
|
if !opts.HasTemplate() {
|
2020-07-25 00:09:29 +00:00
|
|
|
return []sshutil.Option{
|
|
|
|
sshutil.WithTemplate(defaultTemplate, data),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add user provided data.
|
|
|
|
if len(so.TemplateData) > 0 {
|
|
|
|
userObject := make(map[string]interface{})
|
|
|
|
if err := json.Unmarshal(so.TemplateData, &userObject); err != nil {
|
|
|
|
data.SetUserData(map[string]interface{}{})
|
|
|
|
} else {
|
|
|
|
data.SetUserData(userObject)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load a template from a file if Template is not defined.
|
2020-07-31 01:44:52 +00:00
|
|
|
if opts.Template == "" && opts.TemplateFile != "" {
|
2020-07-25 00:09:29 +00:00
|
|
|
return []sshutil.Option{
|
2020-07-31 01:44:52 +00:00
|
|
|
sshutil.WithTemplateFile(opts.TemplateFile, data),
|
2020-07-25 00:09:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load a template from the Template fields
|
|
|
|
// 1. As a JSON in a string.
|
2020-07-31 01:44:52 +00:00
|
|
|
template := strings.TrimSpace(opts.Template)
|
2020-07-25 00:09:29 +00:00
|
|
|
if strings.HasPrefix(template, "{") {
|
|
|
|
return []sshutil.Option{
|
|
|
|
sshutil.WithTemplate(template, data),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 2. As a base64 encoded JSON.
|
|
|
|
return []sshutil.Option{
|
|
|
|
sshutil.WithTemplateBase64(template, data),
|
|
|
|
}
|
|
|
|
}), nil
|
|
|
|
}
|