forked from TrueCloudLab/certificates
Add template support for AWS provisioner.
This commit is contained in:
parent
49b9aa6e3f
commit
13b704aeed
5 changed files with 83 additions and 15 deletions
|
@ -17,6 +17,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/certificates/errs"
|
"github.com/smallstep/certificates/errs"
|
||||||
|
"github.com/smallstep/certificates/x509util"
|
||||||
"github.com/smallstep/cli/jose"
|
"github.com/smallstep/cli/jose"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -125,13 +126,14 @@ type awsInstanceIdentityDocument struct {
|
||||||
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
|
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
|
||||||
type AWS struct {
|
type AWS struct {
|
||||||
*base
|
*base
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Accounts []string `json:"accounts"`
|
Accounts []string `json:"accounts"`
|
||||||
DisableCustomSANs bool `json:"disableCustomSANs"`
|
DisableCustomSANs bool `json:"disableCustomSANs"`
|
||||||
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
|
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
|
||||||
InstanceAge Duration `json:"instanceAge,omitempty"`
|
InstanceAge Duration `json:"instanceAge,omitempty"`
|
||||||
Claims *Claims `json:"claims,omitempty"`
|
Claims *Claims `json:"claims,omitempty"`
|
||||||
|
Options *ProvisionerOptions `json:"options,omitempty"`
|
||||||
claimer *Claimer
|
claimer *Claimer
|
||||||
config *awsConfig
|
config *awsConfig
|
||||||
audiences Audiences
|
audiences Audiences
|
||||||
|
@ -276,14 +278,20 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
||||||
}
|
}
|
||||||
|
|
||||||
doc := payload.document
|
doc := payload.document
|
||||||
|
|
||||||
|
// Template options
|
||||||
|
data := x509util.NewTemplateData()
|
||||||
|
data.SetCommonName(payload.Claims.Subject)
|
||||||
|
|
||||||
// Enforce known CN and default DNS and IP if configured.
|
// Enforce known CN and default DNS and IP if configured.
|
||||||
// By default we'll accept the CN and SANs in the CSR.
|
// By default we'll accept the CN and SANs in the CSR.
|
||||||
// There's no way to trust them other than TOFU.
|
// There's no way to trust them other than TOFU.
|
||||||
var so []SignOption
|
var so []SignOption
|
||||||
if p.DisableCustomSANs {
|
if p.DisableCustomSANs {
|
||||||
so = append(so, dnsNamesValidator([]string{
|
dnsName := fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region)
|
||||||
fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region),
|
data.SetSANs([]string{dnsName, doc.PrivateIP})
|
||||||
}))
|
|
||||||
|
so = append(so, dnsNamesValidator([]string{dnsName}))
|
||||||
so = append(so, ipAddressesValidator([]net.IP{
|
so = append(so, ipAddressesValidator([]net.IP{
|
||||||
net.ParseIP(doc.PrivateIP),
|
net.ParseIP(doc.PrivateIP),
|
||||||
}))
|
}))
|
||||||
|
@ -291,7 +299,13 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
||||||
so = append(so, urisValidator(nil))
|
so = append(so, urisValidator(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templateOptions, err := CustomTemplateOptions(p.Options, data, x509util.DefaultIIDLeafTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSign")
|
||||||
|
}
|
||||||
|
|
||||||
return append(so,
|
return append(so,
|
||||||
|
templateOptions,
|
||||||
// modifiers / withOptions
|
// modifiers / withOptions
|
||||||
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
|
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
|
||||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||||
|
|
|
@ -21,14 +21,23 @@ func (fn certificateOptionsFunc) Options(so Options) []x509util.Option {
|
||||||
|
|
||||||
type ProvisionerOptions struct {
|
type ProvisionerOptions struct {
|
||||||
Template string `json:"template"`
|
Template string `json:"template"`
|
||||||
TemplateFile string `json:"templateFile`
|
TemplateFile string `json:"templateFile"`
|
||||||
TemplateData json.RawMessage `json:"templateData"`
|
TemplateData json.RawMessage `json:"templateData"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateOptions generate a CertificateOptions with the template and data
|
// TemplateOptions generates a CertificateOptions with the template and data
|
||||||
// defined in the ProvisionerOptions, the provisioner generated data, and the
|
// defined in the ProvisionerOptions, the provisioner generated data, and the
|
||||||
// user data provided in the request.
|
// user data provided in the request. If no template has been provided,
|
||||||
|
// x509util.DefaultLeafTemplate will be used.
|
||||||
func TemplateOptions(o *ProvisionerOptions, data x509util.TemplateData) (CertificateOptions, error) {
|
func TemplateOptions(o *ProvisionerOptions, data x509util.TemplateData) (CertificateOptions, error) {
|
||||||
|
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.
|
||||||
|
func CustomTemplateOptions(o *ProvisionerOptions, data x509util.TemplateData, defaultTemplate string) (CertificateOptions, error) {
|
||||||
if o != nil {
|
if o != nil {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
data = x509util.NewTemplateData()
|
data = x509util.NewTemplateData()
|
||||||
|
@ -40,14 +49,13 @@ func TemplateOptions(o *ProvisionerOptions, data x509util.TemplateData) (Certifi
|
||||||
return nil, errors.Wrap(err, "error unmarshaling template data")
|
return nil, errors.Wrap(err, "error unmarshaling template data")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return certificateOptionsFunc(func(so Options) []x509util.Option {
|
return certificateOptionsFunc(func(so Options) []x509util.Option {
|
||||||
// We're not provided user data without custom templates.
|
// We're not provided user data without custom templates.
|
||||||
if o == nil || (o.Template == "" && o.TemplateFile == "") {
|
if o == nil || (o.Template == "" && o.TemplateFile == "") {
|
||||||
return []x509util.Option{
|
return []x509util.Option{
|
||||||
x509util.WithTemplate(x509util.DefaultLeafTemplate, data),
|
x509util.WithTemplate(defaultTemplate, data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,15 @@ func (m *MultiIPNet) UnmarshalJSON(data []byte) error {
|
||||||
// into a []*url.URL.
|
// into a []*url.URL.
|
||||||
type MultiURL []*url.URL
|
type MultiURL []*url.URL
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface for MultiURL.
|
||||||
|
func (m MultiURL) MarshalJSON() ([]byte, error) {
|
||||||
|
urls := make([]string, len(m))
|
||||||
|
for i, u := range m {
|
||||||
|
urls[i] = u.String()
|
||||||
|
}
|
||||||
|
return json.Marshal(urls)
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaler interface for MultiURL.
|
// UnmarshalJSON implements the json.Unmarshaler interface for MultiURL.
|
||||||
func (m *MultiURL) UnmarshalJSON(data []byte) error {
|
func (m *MultiURL) UnmarshalJSON(data []byte) error {
|
||||||
ms, err := unmarshalMultiString(data)
|
ms, err := unmarshalMultiString(data)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package x509util
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
@ -42,6 +43,7 @@ func WithTemplate(text string, data TemplateData) Option {
|
||||||
if err := tmpl.Execute(buf, data); err != nil {
|
if err := tmpl.Execute(buf, data); err != nil {
|
||||||
return errors.Wrapf(err, "error executing template")
|
return errors.Wrapf(err, "error executing template")
|
||||||
}
|
}
|
||||||
|
fmt.Println(buf.String())
|
||||||
o.CertBuffer = buf
|
o.CertBuffer = buf
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,12 @@ func (t TemplateData) SetSubject(v Subject) {
|
||||||
t[SubjectKey] = v
|
t[SubjectKey] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t TemplateData) SetCommonName(cn string) {
|
||||||
|
s, _ := t[SubjectKey].(Subject)
|
||||||
|
s.CommonName = cn
|
||||||
|
t[SubjectKey] = s
|
||||||
|
}
|
||||||
|
|
||||||
func (t TemplateData) SetSANs(sans []string) {
|
func (t TemplateData) SetSANs(sans []string) {
|
||||||
t[SANsKey] = CreateSANs(sans)
|
t[SANsKey] = CreateSANs(sans)
|
||||||
}
|
}
|
||||||
|
@ -53,6 +59,9 @@ func (t TemplateData) SetCertificateRequest(cr *x509.CertificateRequest) {
|
||||||
t[CertificateRequestKey] = newCertificateRequest(cr)
|
t[CertificateRequestKey] = newCertificateRequest(cr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultLeafTemplate is the default templated used to generate a leaf
|
||||||
|
// certificate. The keyUsage "keyEncipherment" is special and it will be only
|
||||||
|
// used for RSA keys.
|
||||||
const DefaultLeafTemplate = `{
|
const DefaultLeafTemplate = `{
|
||||||
"subject": {{ toJson .Subject }},
|
"subject": {{ toJson .Subject }},
|
||||||
"sans": {{ toJson .SANs }},
|
"sans": {{ toJson .SANs }},
|
||||||
|
@ -60,6 +69,30 @@ const DefaultLeafTemplate = `{
|
||||||
"extKeyUsage": ["serverAuth", "clientAuth"]
|
"extKeyUsage": ["serverAuth", "clientAuth"]
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
// DefaultIIDLeafTemplate is the template used by default on instance identity
|
||||||
|
// provisioners like AWS, GCP or Azure. By default, those provisioners allow the
|
||||||
|
// SANs provided in the certificate request, but the option `DisableCustomSANs`
|
||||||
|
// can be provided to force only the verified domains, if the option is true
|
||||||
|
// `.SANs` will be set with the verified domains.
|
||||||
|
//
|
||||||
|
// The keyUsage "keyEncipherment" is special and it will be only used for RSA
|
||||||
|
// keys.
|
||||||
|
const DefaultIIDLeafTemplate = `{
|
||||||
|
"subject": {{ toJson .Subject }},
|
||||||
|
{{- if .SANs }}
|
||||||
|
"sans": {{ toJson .SANs }},
|
||||||
|
{{- else }}
|
||||||
|
"dnsNames": {{ toJson .CR.DNSNames }},
|
||||||
|
"emailAddresses": {{ toJson .CR.EmailAddresses }},
|
||||||
|
"ipAddresses": {{ toJson .CR.IPAddresses }},
|
||||||
|
"uris": {{ toJson .CR.URIs }},
|
||||||
|
{{- end }}
|
||||||
|
"keyUsage": ["keyEncipherment", "digitalSignature"],
|
||||||
|
"extKeyUsage": ["serverAuth", "clientAuth"]
|
||||||
|
}`
|
||||||
|
|
||||||
|
// DefaultIntermediateTemplate is a template that can be used to generate an
|
||||||
|
// intermediate certificate.
|
||||||
const DefaultIntermediateTemplate = `{
|
const DefaultIntermediateTemplate = `{
|
||||||
"subject": {{ toJson .Subject }},
|
"subject": {{ toJson .Subject }},
|
||||||
"keyUsage": ["certSign", "crlSign"],
|
"keyUsage": ["certSign", "crlSign"],
|
||||||
|
@ -69,6 +102,8 @@ const DefaultIntermediateTemplate = `{
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
// DefaultRootTemplate is a template that can be used to generate a root
|
||||||
|
// certificate.
|
||||||
const DefaultRootTemplate = `{
|
const DefaultRootTemplate = `{
|
||||||
"subject": {{ toJson .Subject }},
|
"subject": {{ toJson .Subject }},
|
||||||
"issuer": {{ toJson .Subject }},
|
"issuer": {{ toJson .Subject }},
|
||||||
|
|
Loading…
Reference in a new issue