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/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/x509util"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
|
@ -125,13 +126,14 @@ type awsInstanceIdentityDocument struct {
|
|||
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
|
||||
type AWS struct {
|
||||
*base
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Accounts []string `json:"accounts"`
|
||||
DisableCustomSANs bool `json:"disableCustomSANs"`
|
||||
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
|
||||
InstanceAge Duration `json:"instanceAge,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Accounts []string `json:"accounts"`
|
||||
DisableCustomSANs bool `json:"disableCustomSANs"`
|
||||
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
|
||||
InstanceAge Duration `json:"instanceAge,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
Options *ProvisionerOptions `json:"options,omitempty"`
|
||||
claimer *Claimer
|
||||
config *awsConfig
|
||||
audiences Audiences
|
||||
|
@ -276,14 +278,20 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
}
|
||||
|
||||
doc := payload.document
|
||||
|
||||
// Template options
|
||||
data := x509util.NewTemplateData()
|
||||
data.SetCommonName(payload.Claims.Subject)
|
||||
|
||||
// Enforce known CN and default DNS and IP if configured.
|
||||
// By default we'll accept the CN and SANs in the CSR.
|
||||
// There's no way to trust them other than TOFU.
|
||||
var so []SignOption
|
||||
if p.DisableCustomSANs {
|
||||
so = append(so, dnsNamesValidator([]string{
|
||||
fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region),
|
||||
}))
|
||||
dnsName := 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{
|
||||
net.ParseIP(doc.PrivateIP),
|
||||
}))
|
||||
|
@ -291,7 +299,13 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
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,
|
||||
templateOptions,
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
|
||||
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
|
||||
|
|
|
@ -21,14 +21,23 @@ func (fn certificateOptionsFunc) Options(so Options) []x509util.Option {
|
|||
|
||||
type ProvisionerOptions struct {
|
||||
Template string `json:"template"`
|
||||
TemplateFile string `json:"templateFile`
|
||||
TemplateFile string `json:"templateFile"`
|
||||
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
|
||||
// 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) {
|
||||
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 data == nil {
|
||||
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 certificateOptionsFunc(func(so Options) []x509util.Option {
|
||||
// We're not provided user data without custom templates.
|
||||
if o == nil || (o.Template == "" && o.TemplateFile == "") {
|
||||
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.
|
||||
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.
|
||||
func (m *MultiURL) UnmarshalJSON(data []byte) error {
|
||||
ms, err := unmarshalMultiString(data)
|
||||
|
|
|
@ -3,6 +3,7 @@ package x509util
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"text/template"
|
||||
|
||||
|
@ -42,6 +43,7 @@ func WithTemplate(text string, data TemplateData) Option {
|
|||
if err := tmpl.Execute(buf, data); err != nil {
|
||||
return errors.Wrapf(err, "error executing template")
|
||||
}
|
||||
fmt.Println(buf.String())
|
||||
o.CertBuffer = buf
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -41,6 +41,12 @@ func (t TemplateData) SetSubject(v Subject) {
|
|||
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) {
|
||||
t[SANsKey] = CreateSANs(sans)
|
||||
}
|
||||
|
@ -53,6 +59,9 @@ func (t TemplateData) SetCertificateRequest(cr *x509.CertificateRequest) {
|
|||
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 = `{
|
||||
"subject": {{ toJson .Subject }},
|
||||
"sans": {{ toJson .SANs }},
|
||||
|
@ -60,6 +69,30 @@ const DefaultLeafTemplate = `{
|
|||
"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 = `{
|
||||
"subject": {{ toJson .Subject }},
|
||||
"keyUsage": ["certSign", "crlSign"],
|
||||
|
@ -69,6 +102,8 @@ const DefaultIntermediateTemplate = `{
|
|||
}
|
||||
}`
|
||||
|
||||
// DefaultRootTemplate is a template that can be used to generate a root
|
||||
// certificate.
|
||||
const DefaultRootTemplate = `{
|
||||
"subject": {{ toJson .Subject }},
|
||||
"issuer": {{ toJson .Subject }},
|
||||
|
|
Loading…
Reference in a new issue