Add support for user data in templates.

This commit is contained in:
Mariano Cano 2019-10-04 17:08:42 -07:00
parent eb210ccc70
commit 50c8b10a4f
5 changed files with 36 additions and 22 deletions

View file

@ -514,7 +514,7 @@ type mockAuthority struct {
getRoots func() ([]*x509.Certificate, error) getRoots func() ([]*x509.Certificate, error)
getFederation func() ([]*x509.Certificate, error) getFederation func() ([]*x509.Certificate, error)
getSSHKeys func() (*authority.SSHKeys, error) getSSHKeys func() (*authority.SSHKeys, error)
getSSHConfig func(typ string) ([]templates.Output, error) getSSHConfig func(typ string, data map[string]string) ([]templates.Output, error)
} }
// TODO: remove once Authorize is deprecated. // TODO: remove once Authorize is deprecated.
@ -627,9 +627,9 @@ func (m *mockAuthority) GetSSHKeys() (*authority.SSHKeys, error) {
return m.ret1.(*authority.SSHKeys), m.err return m.ret1.(*authority.SSHKeys), m.err
} }
func (m *mockAuthority) GetSSHConfig(typ string) ([]templates.Output, error) { func (m *mockAuthority) GetSSHConfig(typ string, data map[string]string) ([]templates.Output, error) {
if m.getSSHConfig != nil { if m.getSSHConfig != nil {
return m.getSSHConfig(typ) return m.getSSHConfig(typ, data)
} }
return m.ret1.([]templates.Output), m.err return m.ret1.([]templates.Output), m.err
} }

View file

@ -18,7 +18,7 @@ type SSHAuthority interface {
SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
GetSSHKeys() (*authority.SSHKeys, error) GetSSHKeys() (*authority.SSHKeys, error)
GetSSHConfig(typ string) ([]templates.Output, error) GetSSHConfig(typ string, data map[string]string) ([]templates.Output, error)
} }
// SignSSHRequest is the request body of an SSH certificate request. // SignSSHRequest is the request body of an SSH certificate request.
@ -146,7 +146,8 @@ type Template = templates.Output
// SSHConfigRequest is the request body used to get the SSH configuration // SSHConfigRequest is the request body used to get the SSH configuration
// templates. // templates.
type SSHConfigRequest struct { type SSHConfigRequest struct {
Type string `json:"type"` Type string `json:"type"`
Data map[string]string `json:"data"`
} }
// Validate checks the values of the SSHConfigurationRequest. // Validate checks the values of the SSHConfigurationRequest.
@ -272,7 +273,7 @@ func (h *caHandler) SSHConfig(w http.ResponseWriter, r *http.Request) {
return return
} }
ts, err := h.Authority.GetSSHConfig(body.Type) ts, err := h.Authority.GetSSHConfig(body.Type, body.Data)
if err != nil { if err != nil {
WriteError(w, InternalServerError(err)) WriteError(w, InternalServerError(err))
return return

View file

@ -158,8 +158,8 @@ func (a *Authority) init() error {
// Configure protected template variables: // Configure protected template variables:
if t := a.config.Templates; t != nil { if t := a.config.Templates; t != nil {
if t.Variables == nil { if t.Data == nil {
t.Variables = make(map[string]interface{}) t.Data = make(map[string]interface{})
} }
var vars templates.Step var vars templates.Step
if a.config.SSH != nil { if a.config.SSH != nil {
@ -170,7 +170,7 @@ func (a *Authority) init() error {
vars.SSH.UserKey = a.sshCAUserCertSignKey.PublicKey() vars.SSH.UserKey = a.sshCAUserCertSignKey.PublicKey()
} }
} }
t.Variables["Step"] = vars t.Data["Step"] = vars
} }
// JWT numeric dates are seconds. // JWT numeric dates are seconds.

View file

@ -6,10 +6,9 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/smallstep/certificates/templates"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/randutil" "github.com/smallstep/cli/crypto/randutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -51,7 +50,7 @@ func (a *Authority) GetSSHKeys() (*SSHKeys, error) {
} }
// GetSSHConfig returns rendered templates for clients (user) or servers (host). // GetSSHConfig returns rendered templates for clients (user) or servers (host).
func (a *Authority) GetSSHConfig(typ string) ([]templates.Output, error) { func (a *Authority) GetSSHConfig(typ string, data map[string]string) ([]templates.Output, error) {
if a.sshCAUserCertSignKey == nil && a.sshCAHostCertSignKey == nil { if a.sshCAUserCertSignKey == nil && a.sshCAHostCertSignKey == nil {
return nil, &apiError{ return nil, &apiError{
err: errors.New("getSSHConfig: ssh is not configured"), err: errors.New("getSSHConfig: ssh is not configured"),
@ -76,10 +75,23 @@ func (a *Authority) GetSSHConfig(typ string) ([]templates.Output, error) {
} }
} }
// Render templates. // Merge user and default data
var mergedData map[string]interface{}
if len(data) == 0 {
mergedData = a.config.Templates.Data
} else {
mergedData = make(map[string]interface{}, len(a.config.Templates.Data)+1)
mergedData["User"] = data
for k, v := range a.config.Templates.Data {
mergedData[k] = v
}
}
// Render templates
output := []templates.Output{} output := []templates.Output{}
for _, t := range ts { for _, t := range ts {
o, err := t.Output(a.config.Templates.Variables) o, err := t.Output(mergedData)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -2,7 +2,6 @@ package templates
import ( import (
"bytes" "bytes"
"fmt"
"io/ioutil" "io/ioutil"
"text/template" "text/template"
@ -31,8 +30,8 @@ type Output struct {
// Templates is a collection of templates and variables. // Templates is a collection of templates and variables.
type Templates struct { type Templates struct {
SSH *SSHTemplates `json:"ssh,omitempty"` SSH *SSHTemplates `json:"ssh,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty"` Data map[string]interface{} `json:"data,omitempty"`
} }
// Validate returns an error if a template is not valid. // Validate returns an error if a template is not valid.
@ -46,10 +45,13 @@ func (t *Templates) Validate() (err error) {
return return
} }
// Do not allow "Step" // Do not allow "Step" and "User"
if t.Variables != nil { if t.Data != nil {
if _, ok := t.Variables["Step"]; ok { if _, ok := t.Data["Step"]; ok {
return errors.New("templates variables cannot contain 'step' as a property") return errors.New("templates variables cannot contain 'Step' as a property")
}
if _, ok := t.Data["User"]; ok {
return errors.New("templates variables cannot contain 'User' as a property")
} }
} }
return nil return nil
@ -156,7 +158,6 @@ func (t *Template) Render(data interface{}) ([]byte, error) {
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
fmt.Println(data)
if err := t.Execute(buf, data); err != nil { if err := t.Execute(buf, data); err != nil {
return nil, errors.Wrapf(err, "error executing %s", t.TemplatePath) return nil, errors.Wrapf(err, "error executing %s", t.TemplatePath)
} }