diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index c5ffff2f..802ec37b 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -19,9 +19,18 @@ func (fn certificateOptionsFunc) Options(so Options) []x509util.Option { return fn(so) } +// ProvisionerOptions are a collection of custom options that can be added to +// each provisioner. type ProvisionerOptions struct { - Template string `json:"template"` - TemplateFile string `json:"templateFile"` + // 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. + Template string `json:"template"` + + // TemplateFile points to a file containing a X.509 certificate template. + TemplateFile string `json:"templateFile"` + + // TemplateData is a JSON object with variables that can be used in custom + // templates. TemplateData json.RawMessage `json:"templateData"` } @@ -63,11 +72,13 @@ func CustomTemplateOptions(o *ProvisionerOptions, data x509util.TemplateData, de if len(so.TemplateData) > 0 { userObject := make(map[string]interface{}) if err := json.Unmarshal(so.TemplateData, &userObject); err != nil { - data[x509util.UserKey] = map[string]interface{}{} + data.SetUserData(map[string]interface{}{}) } else { - data[x509util.UserKey] = userObject + data.SetUserData(userObject) } } + + // Load a template from a file if Template is not defined. if o.Template == "" && o.TemplateFile != "" { return []x509util.Option{ x509util.WithTemplateFile(o.TemplateFile, data), diff --git a/x509util/options.go b/x509util/options.go index 9a061bed..6b2b8697 100644 --- a/x509util/options.go +++ b/x509util/options.go @@ -3,6 +3,7 @@ package x509util import ( "bytes" "crypto/x509" + "encoding/base64" "io/ioutil" "text/template" @@ -47,6 +48,19 @@ func WithTemplate(text string, data TemplateData) Option { } } +// WithTemplateBase64 is an options that executes the given template base64 +// string with the given data. +func WithTemplateBase64(s string, data TemplateData) Option { + return func(cr *x509.CertificateRequest, o *Options) error { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return errors.Wrap(err, "error decoding template") + } + fn := WithTemplate(string(b), data) + return fn(cr, o) + } +} + // WithTemplateFile is an options that reads the template file and executes it // with the given data. func WithTemplateFile(path string, data TemplateData) Option { @@ -56,18 +70,7 @@ func WithTemplateFile(path string, data TemplateData) Option { if err != nil { return errors.Wrapf(err, "error reading %s", path) } - - tmpl, err := template.New(path).Funcs(sprig.TxtFuncMap()).Parse(string(b)) - if err != nil { - return errors.Wrapf(err, "error parsing %s", path) - } - - buf := new(bytes.Buffer) - data.SetCertificateRequest(cr) - if err := tmpl.Execute(buf, data); err != nil { - return errors.Wrapf(err, "error executing %s", path) - } - o.CertBuffer = buf - return nil + fn := WithTemplate(string(b), data) + return fn(cr, o) } } diff --git a/x509util/templates.go b/x509util/templates.go index 8ef8d5d3..855370a1 100644 --- a/x509util/templates.go +++ b/x509util/templates.go @@ -3,10 +3,11 @@ package x509util import "crypto/x509" const ( - UserKey = "User" SubjectKey = "Subject" SANsKey = "SANs" TokenKey = "Token" + InsecureKey = "Insecure" + UserKey = "User" CertificateRequestKey = "CR" ) @@ -33,12 +34,16 @@ func (t TemplateData) Set(key string, v interface{}) { t[key] = v } -func (t TemplateData) SetUserData(v Subject) { - t[UserKey] = v +func (t TemplateData) SetInsecure(key string, v interface{}) { + if m, ok := t[InsecureKey].(TemplateData); ok { + m[key] = v + } else { + t[InsecureKey] = TemplateData{key: v} + } } func (t TemplateData) SetSubject(v Subject) { - t[SubjectKey] = v + t.Set(SubjectKey, v) } func (t TemplateData) SetCommonName(cn string) { @@ -48,15 +53,19 @@ func (t TemplateData) SetCommonName(cn string) { } func (t TemplateData) SetSANs(sans []string) { - t[SANsKey] = CreateSANs(sans) + t.Set(SANsKey, CreateSANs(sans)) } func (t TemplateData) SetToken(v interface{}) { - t[TokenKey] = v + t.Set(TokenKey, v) +} + +func (t TemplateData) SetUserData(v interface{}) { + t.SetInsecure(UserKey, v) } func (t TemplateData) SetCertificateRequest(cr *x509.CertificateRequest) { - t[CertificateRequestKey] = newCertificateRequest(cr) + t.SetInsecure(CertificateRequestKey, newCertificateRequest(cr)) } // DefaultLeafTemplate is the default templated used to generate a leaf @@ -79,14 +88,14 @@ const DefaultLeafTemplate = `{ // keys. const DefaultIIDLeafTemplate = `{ {{- if .SANs }} - "subject": {"commonName": "{{ .CR.Subject.CommonName }}"}, + "subject": {"commonName": "{{ .Insecure.CR.Subject.CommonName }}"}, "sans": {{ toJson .SANs }}, {{- else }} - "subject": {{ toJson .CR.Subject }}, - "dnsNames": {{ toJson .CR.DNSNames }}, - "emailAddresses": {{ toJson .CR.EmailAddresses }}, - "ipAddresses": {{ toJson .CR.IPAddresses }}, - "uris": {{ toJson .CR.URIs }}, + "subject": {{ toJson .Insecure.CR.Subject }}, + "dnsNames": {{ toJson .Insecure.CR.DNSNames }}, + "emailAddresses": {{ toJson .Insecure.CR.EmailAddresses }}, + "ipAddresses": {{ toJson .Insecure.CR.IPAddresses }}, + "uris": {{ toJson .Insecure.CR.URIs }}, {{- end }} "keyUsage": ["keyEncipherment", "digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] @@ -117,4 +126,4 @@ const DefaultRootTemplate = `{ // CertificateRequestTemplate is a template that will sign the given certificate // request. -const CertificateRequestTemplate = `{{ toJson .CR }}` +const CertificateRequestTemplate = `{{ toJson .Insecure.CR }}`