parent
7b8f0327bd
commit
eb210ccc70
11 changed files with 446 additions and 15 deletions
68
Gopkg.lock
generated
68
Gopkg.lock
generated
|
@ -9,6 +9,22 @@
|
|||
pruneopts = "UT"
|
||||
revision = "e2d15f34fcf99d5dbb871c820ec73f710fca9815"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3b10c6fd33854dc41de2cf78b7bae105da94c2789b6fa5b9ac9e593ea43484ac"
|
||||
name = "github.com/Masterminds/goutils"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "41ac8693c5c10a92ea1ff5ac3a7f95646f6123b0"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:181a6a5506bd83827d826df5b3272040e92c487516b2fc8edd066be941d68d9e"
|
||||
name = "github.com/Masterminds/sprig"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0e09f04f09aede2cc36cbecad7ec27b0055303e0"
|
||||
version = "v3.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:454adc7f974228ff789428b6dc098638c57a64aa0718f0bd61e53d3cd39d7a75"
|
||||
|
@ -64,6 +80,30 @@
|
|||
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:582b704bebaa06b48c29b0cec224a6058a09c86883aaddabde889cd1a5f73e1b"
|
||||
name = "github.com/google/uuid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0cd6bf5da1e1c83f8b45653022c74f71af0538a4"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f9a5e090336881be43cfc1cf468330c1bdd60abdc9dd194e0b1ab69f4b94dd7c"
|
||||
name = "github.com/huandu/xstrings"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f02667b379e2fb5916c3cda2cf31e0eb885d79f8"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:78d28d5b84a26159c67ea51996a230da4bc07cac648adaae1dfb5fc0ec8e40d3"
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "1afb36080aec31e0d1528973ebe6721b191b0369"
|
||||
version = "v0.3.8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e51f40f0c19b39c1825eadd07d5c0a98a2ad5942b166d9fc4f54750ce9a04810"
|
||||
|
@ -119,6 +159,22 @@
|
|||
revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c"
|
||||
version = "v0.0.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:09ca328575f38b80969ccf857f6d7302f2ce09d53778ea7aaba526cfd2cec739"
|
||||
name = "github.com/mitchellh/copystructure"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9a1b6f44e8da0e0e374624fb0a825a231b00c537"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2a7e6f8bebdca6bd8bc359c37f01ae1c4ea4f8481eaabf93b1ae4863f15b72c7"
|
||||
name = "github.com/mitchellh/reflectwalk"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3e2c75dfad4fbf904b58782a80fd595c760ad185"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ae08d850ba158ea3ba4a7bb90f8372608172d8920644e5a6693b940a1f4e5d01"
|
||||
|
@ -245,6 +301,14 @@
|
|||
pruneopts = "UT"
|
||||
revision = "f80b3f432de0662f07ebd58fe52b0a119fe5dcd9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8c9545af88b134710ab1cd196795e7f2388358d7"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6743b69de0d73e91004e4e201cf4965b59a0fa5caf6f0ffbe0cb9ee8807738a7"
|
||||
|
@ -263,7 +327,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:afc49fe39c8c591fc2c8ddc73adc4c69e67125dde6c58e24c91b3b0cf78602be"
|
||||
digest = "1:f492081c0e705c98ea169765a0fa60c9cfee2358a2434e96abd5bb6f59dbc190"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"cryptobyte",
|
||||
|
@ -276,6 +340,7 @@
|
|||
"ocsp",
|
||||
"pbkdf2",
|
||||
"poly1305",
|
||||
"scrypt",
|
||||
"ssh",
|
||||
"ssh/terminal",
|
||||
]
|
||||
|
@ -377,6 +442,7 @@
|
|||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/Masterminds/sprig",
|
||||
"github.com/go-chi/chi",
|
||||
"github.com/newrelic/go-agent",
|
||||
"github.com/pkg/errors",
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
ignored = ["github.com/Masterminds/semver/v3"]
|
||||
|
||||
[[override]]
|
||||
name = "gopkg.in/alecthomas/kingpin.v3-unstable"
|
||||
revision = "63abe20a23e29e80bbef8089bd3dee3ac25e5306"
|
||||
|
@ -54,3 +57,7 @@
|
|||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/Masterminds/sprig"
|
||||
version = "3.0.0"
|
||||
|
|
|
@ -252,6 +252,8 @@ func (h *caHandler) Route(r Router) {
|
|||
// SSH CA
|
||||
r.MethodFunc("POST", "/ssh/sign", h.SignSSH)
|
||||
r.MethodFunc("GET", "/ssh/keys", h.SSHKeys)
|
||||
r.MethodFunc("POST", "/ssh/config", h.SSHConfig)
|
||||
r.MethodFunc("POST", "/ssh/config/{type}", h.SSHConfig)
|
||||
|
||||
// For compatibility with old code:
|
||||
r.MethodFunc("POST", "/re-sign", h.Renew)
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -513,6 +514,7 @@ type mockAuthority struct {
|
|||
getRoots func() ([]*x509.Certificate, error)
|
||||
getFederation func() ([]*x509.Certificate, error)
|
||||
getSSHKeys func() (*authority.SSHKeys, error)
|
||||
getSSHConfig func(typ string) ([]templates.Output, error)
|
||||
}
|
||||
|
||||
// TODO: remove once Authorize is deprecated.
|
||||
|
@ -625,6 +627,13 @@ func (m *mockAuthority) GetSSHKeys() (*authority.SSHKeys, error) {
|
|||
return m.ret1.(*authority.SSHKeys), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) GetSSHConfig(typ string) ([]templates.Output, error) {
|
||||
if m.getSSHConfig != nil {
|
||||
return m.getSSHConfig(typ)
|
||||
}
|
||||
return m.ret1.([]templates.Output), m.err
|
||||
}
|
||||
|
||||
func Test_caHandler_Route(t *testing.T) {
|
||||
type fields struct {
|
||||
Authority Authority
|
||||
|
|
63
api/ssh.go
63
api/ssh.go
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -17,6 +18,7 @@ type SSHAuthority interface {
|
|||
SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
|
||||
SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
|
||||
GetSSHKeys() (*authority.SSHKeys, error)
|
||||
GetSSHConfig(typ string) ([]templates.Output, error)
|
||||
}
|
||||
|
||||
// SignSSHRequest is the request body of an SSH certificate request.
|
||||
|
@ -138,6 +140,34 @@ func (p *SSHPublicKey) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Template represents the output of a template.
|
||||
type Template = templates.Output
|
||||
|
||||
// SSHConfigRequest is the request body used to get the SSH configuration
|
||||
// templates.
|
||||
type SSHConfigRequest struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Validate checks the values of the SSHConfigurationRequest.
|
||||
func (r *SSHConfigRequest) Validate() error {
|
||||
switch r.Type {
|
||||
case "":
|
||||
r.Type = provisioner.SSHUserCert
|
||||
return nil
|
||||
case provisioner.SSHUserCert, provisioner.SSHHostCert:
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("unsupported type %s", r.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// SSHConfigResponse is the response that returns the rendered templates.
|
||||
type SSHConfigResponse struct {
|
||||
UserTemplates []Template `json:"userTemplates,omitempty"`
|
||||
HostTemplates []Template `json:"hostTemplates,omitempty"`
|
||||
}
|
||||
|
||||
// SignSSH is an HTTP handler that reads an SignSSHRequest with a one-time-token
|
||||
// (ott) from the body and creates a new SSH certificate with the information in
|
||||
// the request.
|
||||
|
@ -228,3 +258,36 @@ func (h *caHandler) SSHKeys(w http.ResponseWriter, r *http.Request) {
|
|||
UserKey: user,
|
||||
})
|
||||
}
|
||||
|
||||
// SSHConfig is an HTTP handler that returns rendered templates for ssh clients
|
||||
// and servers.
|
||||
func (h *caHandler) SSHConfig(w http.ResponseWriter, r *http.Request) {
|
||||
var body SSHConfigRequest
|
||||
if err := ReadJSON(r.Body, &body); err != nil {
|
||||
WriteError(w, BadRequest(errors.Wrap(err, "error reading request body")))
|
||||
return
|
||||
}
|
||||
if err := body.Validate(); err != nil {
|
||||
WriteError(w, BadRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
ts, err := h.Authority.GetSSHConfig(body.Type)
|
||||
if err != nil {
|
||||
WriteError(w, InternalServerError(err))
|
||||
return
|
||||
}
|
||||
|
||||
var config SSHConfigResponse
|
||||
switch body.Type {
|
||||
case provisioner.SSHUserCert:
|
||||
config.UserTemplates = ts
|
||||
case provisioner.SSHHostCert:
|
||||
config.UserTemplates = ts
|
||||
default:
|
||||
WriteError(w, InternalServerError(errors.New("it should hot get here")))
|
||||
return
|
||||
}
|
||||
|
||||
JSON(w, config)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/templates"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
|
@ -154,6 +156,23 @@ func (a *Authority) init() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Configure protected template variables:
|
||||
if t := a.config.Templates; t != nil {
|
||||
if t.Variables == nil {
|
||||
t.Variables = make(map[string]interface{})
|
||||
}
|
||||
var vars templates.Step
|
||||
if a.config.SSH != nil {
|
||||
if a.sshCAHostCertSignKey != nil {
|
||||
vars.SSH.HostKey = a.sshCAHostCertSignKey.PublicKey()
|
||||
}
|
||||
if a.sshCAUserCertSignKey != nil {
|
||||
vars.SSH.UserKey = a.sshCAUserCertSignKey.PublicKey()
|
||||
}
|
||||
}
|
||||
t.Variables["Step"] = vars
|
||||
}
|
||||
|
||||
// JWT numeric dates are seconds.
|
||||
a.startTime = time.Now().Truncate(time.Second)
|
||||
// Set flag indicating that initialization has been completed, and should
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/templates"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
|
@ -46,19 +48,20 @@ var (
|
|||
|
||||
// Config represents the CA configuration and it's mapped to a JSON object.
|
||||
type Config struct {
|
||||
Root multiString `json:"root"`
|
||||
FederatedRoots []string `json:"federatedRoots"`
|
||||
IntermediateCert string `json:"crt"`
|
||||
IntermediateKey string `json:"key"`
|
||||
Address string `json:"address"`
|
||||
DNSNames []string `json:"dnsNames"`
|
||||
SSH *SSHConfig `json:"ssh,omitempty"`
|
||||
Logger json.RawMessage `json:"logger,omitempty"`
|
||||
DB *db.Config `json:"db,omitempty"`
|
||||
Monitoring json.RawMessage `json:"monitoring,omitempty"`
|
||||
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
|
||||
TLS *tlsutil.TLSOptions `json:"tls,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Root multiString `json:"root"`
|
||||
FederatedRoots []string `json:"federatedRoots"`
|
||||
IntermediateCert string `json:"crt"`
|
||||
IntermediateKey string `json:"key"`
|
||||
Address string `json:"address"`
|
||||
DNSNames []string `json:"dnsNames"`
|
||||
SSH *SSHConfig `json:"ssh,omitempty"`
|
||||
Logger json.RawMessage `json:"logger,omitempty"`
|
||||
DB *db.Config `json:"db,omitempty"`
|
||||
Monitoring json.RawMessage `json:"monitoring,omitempty"`
|
||||
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
|
||||
TLS *tlsutil.TLSOptions `json:"tls,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Templates *templates.Templates `json:"templates,omitempty"`
|
||||
}
|
||||
|
||||
// AuthConfig represents the configuration options for the authority.
|
||||
|
@ -181,6 +184,11 @@ func (c *Config) Validate() error {
|
|||
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
|
||||
}
|
||||
|
||||
// Validate templates: nil is ok
|
||||
if err := c.Templates.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.AuthorityConfig.Validate(c.getAudiences())
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/smallstep/certificates/templates"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
|
@ -41,13 +43,51 @@ func (a *Authority) GetSSHKeys() (*SSHKeys, error) {
|
|||
}
|
||||
if keys.UserKey == nil && keys.HostKey == nil {
|
||||
return nil, &apiError{
|
||||
err: errors.New("sshConfig: ssh is not configured"),
|
||||
err: errors.New("getSSHKeys: ssh is not configured"),
|
||||
code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
return &keys, nil
|
||||
}
|
||||
|
||||
// GetSSHConfig returns rendered templates for clients (user) or servers (host).
|
||||
func (a *Authority) GetSSHConfig(typ string) ([]templates.Output, error) {
|
||||
if a.sshCAUserCertSignKey == nil && a.sshCAHostCertSignKey == nil {
|
||||
return nil, &apiError{
|
||||
err: errors.New("getSSHConfig: ssh is not configured"),
|
||||
code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
var ts []templates.Template
|
||||
switch typ {
|
||||
case provisioner.SSHUserCert:
|
||||
if a.config.Templates != nil && a.config.Templates.SSH != nil {
|
||||
ts = a.config.Templates.SSH.User
|
||||
}
|
||||
case provisioner.SSHHostCert:
|
||||
if a.config.Templates != nil && a.config.Templates.SSH != nil {
|
||||
ts = a.config.Templates.SSH.Host
|
||||
}
|
||||
default:
|
||||
return nil, &apiError{
|
||||
err: errors.Errorf("getSSHConfig: type %s is not valid", typ),
|
||||
code: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// Render templates.
|
||||
output := []templates.Output{}
|
||||
for _, t := range ts {
|
||||
o, err := t.Output(a.config.Templates.Variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = append(output, o)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// SignSSH creates a signed SSH certificate with the given public key and options.
|
||||
func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
|
||||
var mods []provisioner.SSHCertificateModifier
|
||||
|
|
22
ca/client.go
22
ca/client.go
|
@ -545,6 +545,28 @@ func (c *Client) SSHKeys() (*api.SSHKeysResponse, error) {
|
|||
return &keys, nil
|
||||
}
|
||||
|
||||
// SSHConfig performs the POST request to the CA to get the ssh configuration
|
||||
// templates.
|
||||
func (c *Client) SSHConfig(req *api.SSHConfigRequest) (*api.SSHConfigResponse, error) {
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/config"})
|
||||
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, readError(resp.Body)
|
||||
}
|
||||
var config api.SSHConfigResponse
|
||||
if err := readJSON(resp.Body, &config); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// RootFingerprint is a helper method that returns the current root fingerprint.
|
||||
// It does an health connection and gets the fingerprint from the TLS verified
|
||||
// chains.
|
||||
|
|
180
templates/templates.go
Normal file
180
templates/templates.go
Normal file
|
@ -0,0 +1,180 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TemplateType defines how a template will be written in disk.
|
||||
type TemplateType string
|
||||
|
||||
const (
|
||||
// Snippet will mark a template as a part of a file.
|
||||
Snippet TemplateType = "snippet"
|
||||
// File will mark a templates as a full file.
|
||||
File TemplateType = "file"
|
||||
)
|
||||
|
||||
// Output represents the text representation of a rendered template.
|
||||
type Output struct {
|
||||
Name string `json:"name"`
|
||||
Type TemplateType `json:"type"`
|
||||
Comment string `json:"comment"`
|
||||
Path string `json:"path"`
|
||||
Content []byte `json:"content"`
|
||||
}
|
||||
|
||||
// Templates is a collection of templates and variables.
|
||||
type Templates struct {
|
||||
SSH *SSHTemplates `json:"ssh,omitempty"`
|
||||
Variables map[string]interface{} `json:"variables,omitempty"`
|
||||
}
|
||||
|
||||
// Validate returns an error if a template is not valid.
|
||||
func (t *Templates) Validate() (err error) {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate members
|
||||
if err = t.SSH.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Do not allow "Step"
|
||||
if t.Variables != nil {
|
||||
if _, ok := t.Variables["Step"]; ok {
|
||||
return errors.New("templates variables cannot contain 'step' as a property")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAll preloads all templates in memory. It returns an error if an error is
|
||||
// found parsing at least one template.
|
||||
func LoadAll(t *Templates) (err error) {
|
||||
if t.SSH != nil {
|
||||
for _, tt := range t.SSH.User {
|
||||
if err = tt.Load(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, tt := range t.SSH.Host {
|
||||
if err = tt.Load(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SSHTemplates contains the templates defining ssh configuration files.
|
||||
type SSHTemplates struct {
|
||||
User []Template `json:"user"`
|
||||
Host []Template `json:"host"`
|
||||
}
|
||||
|
||||
// Validate returns an error if a template is not valid.
|
||||
func (t *SSHTemplates) Validate() (err error) {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
for _, tt := range t.User {
|
||||
if err = tt.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, tt := range t.Host {
|
||||
if err = tt.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Template represents on template file.
|
||||
type Template struct {
|
||||
*template.Template
|
||||
Name string `json:"name"`
|
||||
Type TemplateType `json:"type"`
|
||||
TemplatePath string `json:"template"`
|
||||
Path string `json:"path"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
// Validate returns an error if the template is not valid.
|
||||
func (t *Template) Validate() error {
|
||||
switch {
|
||||
case t == nil:
|
||||
return nil
|
||||
case t.Name == "":
|
||||
return errors.New("template name cannot be empty")
|
||||
case t.TemplatePath == "":
|
||||
return errors.New("template template cannot be empty")
|
||||
case t.Path == "":
|
||||
return errors.New("template path cannot be empty")
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if t.Type == "" {
|
||||
t.Type = Snippet
|
||||
}
|
||||
if t.Comment == "" {
|
||||
t.Comment = "#"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load loads the template in memory, returns an error if the parsing of the
|
||||
// template fails.
|
||||
func (t *Template) Load() error {
|
||||
if t.Template == nil {
|
||||
b, err := ioutil.ReadFile(t.TemplatePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading %s", t.TemplatePath)
|
||||
}
|
||||
tmpl, err := template.New(t.Name).Funcs(sprig.TxtFuncMap()).Parse(string(b))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing %s", t.TemplatePath)
|
||||
}
|
||||
t.Template = tmpl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Render executes the template with the given data and returns the rendered
|
||||
// version.
|
||||
func (t *Template) Render(data interface{}) ([]byte, error) {
|
||||
if err := t.Load(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Println(data)
|
||||
if err := t.Execute(buf, data); err != nil {
|
||||
return nil, errors.Wrapf(err, "error executing %s", t.TemplatePath)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Output renders the template and returns a template.Output struct or an error.
|
||||
func (t *Template) Output(data interface{}) (Output, error) {
|
||||
b, err := t.Render(data)
|
||||
if err != nil {
|
||||
return Output{}, err
|
||||
}
|
||||
|
||||
return Output{
|
||||
Name: t.Name,
|
||||
Type: t.Type,
|
||||
Comment: t.Comment,
|
||||
Path: t.Path,
|
||||
Content: b,
|
||||
}, nil
|
||||
}
|
15
templates/values.go
Normal file
15
templates/values.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Step represents the default variables available in the CA.
|
||||
type Step struct {
|
||||
SSH StepSSH
|
||||
}
|
||||
|
||||
type StepSSH struct {
|
||||
HostKey ssh.PublicKey
|
||||
UserKey ssh.PublicKey
|
||||
}
|
Loading…
Reference in a new issue