2020-07-08 02:09:29 +00:00
|
|
|
package provisioner
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2020-07-15 00:13:06 +00:00
|
|
|
"strings"
|
2020-07-08 02:09:29 +00:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2020-08-24 21:44:11 +00:00
|
|
|
"go.step.sm/crypto/jose"
|
2020-08-05 23:02:46 +00:00
|
|
|
"go.step.sm/crypto/x509util"
|
2020-07-08 02:09:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// CertificateOptions is an interface that returns a list of options passed when
|
|
|
|
// creating a new certificate.
|
|
|
|
type CertificateOptions interface {
|
2020-07-23 01:24:45 +00:00
|
|
|
Options(SignOptions) []x509util.Option
|
2020-07-08 02:09:29 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 01:24:45 +00:00
|
|
|
type certificateOptionsFunc func(SignOptions) []x509util.Option
|
2020-07-08 02:09:29 +00:00
|
|
|
|
2020-07-23 01:24:45 +00:00
|
|
|
func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option {
|
2020-07-08 02:09:29 +00:00
|
|
|
return fn(so)
|
|
|
|
}
|
|
|
|
|
2020-07-23 01:24:45 +00:00
|
|
|
// Options are a collection of custom options that can be added to
|
2020-07-15 00:12:07 +00:00
|
|
|
// each provisioner.
|
2020-07-23 01:24:45 +00:00
|
|
|
type Options struct {
|
2020-07-31 00:44:22 +00:00
|
|
|
X509 *X509Options `json:"x509,omitempty"`
|
2020-07-31 01:44:52 +00:00
|
|
|
SSH *SSHOptions `json:"ssh,omitempty"`
|
2020-07-31 00:44:22 +00:00
|
|
|
}
|
|
|
|
|
2020-07-31 01:44:52 +00:00
|
|
|
// GetX509Options returns the X.509 options.
|
2020-07-31 00:44:22 +00:00
|
|
|
func (o *Options) GetX509Options() *X509Options {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.X509
|
|
|
|
}
|
|
|
|
|
2020-07-31 01:44:52 +00:00
|
|
|
// GetSSHOptions returns the SSH options.
|
|
|
|
func (o *Options) GetSSHOptions() *SSHOptions {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.SSH
|
|
|
|
}
|
|
|
|
|
2020-07-31 00:44:22 +00:00
|
|
|
// X509Options contains specific options for X.509 certificates.
|
|
|
|
type X509Options struct {
|
2020-07-15 00:12:07 +00:00
|
|
|
// Template contains a X.509 certificate template. It can be a JSON template
|
|
|
|
// escaped in a string or it can be also encoded in base64.
|
2020-07-27 22:57:29 +00:00
|
|
|
Template string `json:"template,omitempty"`
|
2020-07-15 00:12:07 +00:00
|
|
|
|
|
|
|
// TemplateFile points to a file containing a X.509 certificate template.
|
2020-07-27 22:57:29 +00:00
|
|
|
TemplateFile string `json:"templateFile,omitempty"`
|
2020-07-15 00:12:07 +00:00
|
|
|
|
|
|
|
// TemplateData is a JSON object with variables that can be used in custom
|
|
|
|
// templates.
|
2020-07-27 22:57:29 +00:00
|
|
|
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
2022-01-03 11:25:24 +00:00
|
|
|
|
|
|
|
// AllowedNames contains the SANs the provisioner is authorized to sign
|
|
|
|
AllowedNames *AllowedX509NameOptions `json:"allow,omitempty"`
|
|
|
|
|
|
|
|
// DeniedNames contains the SANs the provisioner is not authorized to sign
|
|
|
|
DeniedNames *DeniedX509NameOptions `json:"deny,omitempty"`
|
2020-07-08 02:09:29 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:30:04 +00:00
|
|
|
// HasTemplate returns true if a template is defined in the provisioner options.
|
2020-07-31 00:44:22 +00:00
|
|
|
func (o *X509Options) HasTemplate() bool {
|
2020-07-15 01:30:04 +00:00
|
|
|
return o != nil && (o.Template != "" || o.TemplateFile != "")
|
|
|
|
}
|
|
|
|
|
2022-01-03 11:25:24 +00:00
|
|
|
// GetAllowedNameOptions returns the AllowedNameOptions, which models the
|
|
|
|
// SANs that a provisioner is authorized to sign x509 certificates for.
|
|
|
|
func (o *X509Options) GetAllowedNameOptions() *AllowedX509NameOptions {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.AllowedNames
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetDeniedNameOptions returns the DeniedNameOptions, which models the
|
|
|
|
// SANs that a provisioner is NOT authorized to sign x509 certificates for.
|
|
|
|
func (o *X509Options) GetDeniedNameOptions() *DeniedX509NameOptions {
|
|
|
|
if o == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return o.DeniedNames
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllowedX509NameOptions models the allowed names
|
|
|
|
type AllowedX509NameOptions struct {
|
|
|
|
DNSDomains []string `json:"dns,omitempty"`
|
|
|
|
IPRanges []string `json:"ip,omitempty"` // TODO(hs): support IPs as well as ranges
|
|
|
|
EmailAddresses []string `json:"email,omitempty"`
|
|
|
|
URIDomains []string `json:"uri,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeniedX509NameOptions models the denied names
|
|
|
|
type DeniedX509NameOptions struct {
|
|
|
|
DNSDomains []string `json:"dns,omitempty"`
|
|
|
|
IPRanges []string `json:"ip,omitempty"` // TODO(hs): support IPs as well as ranges
|
|
|
|
EmailAddresses []string `json:"email,omitempty"`
|
|
|
|
URIDomains []string `json:"uri,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasNames checks if the AllowedNameOptions has one or more
|
|
|
|
// names configured.
|
|
|
|
func (o *AllowedX509NameOptions) HasNames() bool {
|
|
|
|
return len(o.DNSDomains) > 0 ||
|
|
|
|
len(o.IPRanges) > 0 ||
|
|
|
|
len(o.EmailAddresses) > 0 ||
|
|
|
|
len(o.URIDomains) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasNames checks if the DeniedNameOptions has one or more
|
|
|
|
// names configured.
|
|
|
|
func (o *DeniedX509NameOptions) HasNames() bool {
|
|
|
|
return len(o.DNSDomains) > 0 ||
|
|
|
|
len(o.IPRanges) > 0 ||
|
|
|
|
len(o.EmailAddresses) > 0 ||
|
|
|
|
len(o.URIDomains) > 0
|
|
|
|
}
|
|
|
|
|
2020-07-13 19:47:26 +00:00
|
|
|
// TemplateOptions generates a CertificateOptions with the template and data
|
2020-07-08 02:09:29 +00:00
|
|
|
// defined in the ProvisionerOptions, the provisioner generated data, and the
|
2020-07-13 19:47:26 +00:00
|
|
|
// user data provided in the request. If no template has been provided,
|
|
|
|
// x509util.DefaultLeafTemplate will be used.
|
2020-07-23 01:24:45 +00:00
|
|
|
func TemplateOptions(o *Options, data x509util.TemplateData) (CertificateOptions, error) {
|
2020-07-13 19:47:26 +00:00
|
|
|
return CustomTemplateOptions(o, data, x509util.DefaultLeafTemplate)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CustomTemplateOptions generates a CertificateOptions with the template, data
|
|
|
|
// 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-23 01:24:45 +00:00
|
|
|
func CustomTemplateOptions(o *Options, data x509util.TemplateData, defaultTemplate string) (CertificateOptions, error) {
|
2020-07-31 00:44:22 +00:00
|
|
|
opts := o.GetX509Options()
|
|
|
|
if data == nil {
|
|
|
|
data = x509util.NewTemplateData()
|
|
|
|
}
|
2020-07-08 02:09:29 +00:00
|
|
|
|
2020-07-31 00:44:22 +00:00
|
|
|
if opts != nil {
|
2020-07-08 02: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 00:44:22 +00:00
|
|
|
if err := json.Unmarshal(opts.TemplateData, &data); err != nil {
|
2020-07-08 02:09:29 +00:00
|
|
|
return nil, errors.Wrap(err, "error unmarshaling template data")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-23 01:24:45 +00:00
|
|
|
return certificateOptionsFunc(func(so SignOptions) []x509util.Option {
|
2020-07-08 02:09:29 +00:00
|
|
|
// We're not provided user data without custom templates.
|
2020-07-31 00:44:22 +00:00
|
|
|
if !opts.HasTemplate() {
|
2020-07-08 02:09:29 +00:00
|
|
|
return []x509util.Option{
|
2020-07-13 19:47:26 +00:00
|
|
|
x509util.WithTemplate(defaultTemplate, data),
|
2020-07-08 02:09:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add user provided data.
|
2020-07-08 22:54:25 +00:00
|
|
|
if len(so.TemplateData) > 0 {
|
2020-07-08 02:09:29 +00:00
|
|
|
userObject := make(map[string]interface{})
|
2020-07-08 22:54:25 +00:00
|
|
|
if err := json.Unmarshal(so.TemplateData, &userObject); err != nil {
|
2020-07-15 00:12:07 +00:00
|
|
|
data.SetUserData(map[string]interface{}{})
|
2020-07-08 02:09:29 +00:00
|
|
|
} else {
|
2020-07-15 00:12:07 +00:00
|
|
|
data.SetUserData(userObject)
|
2020-07-08 02:09:29 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-15 00:12:07 +00:00
|
|
|
|
|
|
|
// Load a template from a file if Template is not defined.
|
2020-07-31 00:44:22 +00:00
|
|
|
if opts.Template == "" && opts.TemplateFile != "" {
|
2020-07-08 02:09:29 +00:00
|
|
|
return []x509util.Option{
|
2020-07-31 00:44:22 +00:00
|
|
|
x509util.WithTemplateFile(opts.TemplateFile, data),
|
2020-07-08 02:09:29 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-15 00:13:06 +00:00
|
|
|
|
|
|
|
// Load a template from the Template fields
|
|
|
|
// 1. As a JSON in a string.
|
2020-07-31 00:44:22 +00:00
|
|
|
template := strings.TrimSpace(opts.Template)
|
2020-07-15 00:13:06 +00:00
|
|
|
if strings.HasPrefix(template, "{") {
|
|
|
|
return []x509util.Option{
|
|
|
|
x509util.WithTemplate(template, data),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 2. As a base64 encoded JSON.
|
2020-07-08 02:09:29 +00:00
|
|
|
return []x509util.Option{
|
2020-07-15 00:13:06 +00:00
|
|
|
x509util.WithTemplateBase64(template, data),
|
2020-07-08 02:09:29 +00:00
|
|
|
}
|
|
|
|
}), nil
|
|
|
|
}
|
2020-07-21 18:41:36 +00:00
|
|
|
|
|
|
|
// unsafeParseSigned parses the given token and returns all the claims without
|
|
|
|
// verifying the signature of the token.
|
|
|
|
func unsafeParseSigned(s string) (map[string]interface{}, error) {
|
|
|
|
token, err := jose.ParseSigned(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
claims := make(map[string]interface{})
|
2021-10-08 18:59:57 +00:00
|
|
|
if err := token.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
2020-07-21 18:41:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return claims, nil
|
|
|
|
}
|