Add template support on k8ssa provisioner.

This commit is contained in:
Mariano Cano 2020-07-29 19:26:46 -07:00
parent 6c36ceb158
commit a78f7e8913
5 changed files with 62 additions and 24 deletions

View file

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/x509util" "github.com/smallstep/certificates/x509util"
"github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
@ -41,13 +42,14 @@ type k8sSAPayload struct {
// entity trusted to make signature requests. // entity trusted to make signature requests.
type K8sSA struct { type K8sSA struct {
*base *base
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
PubKeys []byte `json:"publicKeys,omitempty"` PubKeys []byte `json:"publicKeys,omitempty"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"` Options *Options `json:"options,omitempty"`
claimer *Claimer SSHOptions *SSHOptions `json:"sshOptions,omitempty"`
audiences Audiences claimer *Claimer
audiences Audiences
//kauthn kauthn.AuthenticationV1Interface //kauthn kauthn.AuthenticationV1Interface
pubKeys []interface{} pubKeys []interface{}
} }
@ -249,16 +251,27 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
if !p.claimer.IsSSHCAEnabled() { if !p.claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID()) return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID())
} }
if _, err := p.authorizeToken(token, p.audiences.SSHSign); err != nil { claims, err := p.authorizeToken(token, p.audiences.SSHSign)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign") return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign")
} }
// Default to a user certificate with no principals if not set // Certificate templates.
signOptions := []SignOption{sshCertDefaultsModifier{CertType: SSHUserCert}} // Set some default variables to be used in the templates.
data := sshutil.CreateTemplateData(sshutil.HostCert, claims.ServiceAccountName, []string{claims.ServiceAccountName})
if v, err := unsafeParseSigned(token); err == nil {
data.SetToken(v)
}
templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, sshutil.CertificateRequestTemplate)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign")
}
signOptions := []SignOption{templateOptions}
return append(signOptions, return append(signOptions,
// Set the default extensions. // Require type, key-id and principals in the SignSSHOptions.
&sshDefaultExtensionModifier{}, &sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true},
// Set the validity bounds if not set. // Set the validity bounds if not set.
&sshDefaultDuration{p.claimer}, &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key

View file

@ -299,6 +299,26 @@ func (v sshCertOptionsValidator) Valid(got SignSSHOptions) error {
return want.match(got) return want.match(got)
} }
// sshCertOptionsRequireValidator defines which elements in the SignSSHOptions are required.
type sshCertOptionsRequireValidator struct {
CertType bool
KeyID bool
Principals bool
}
func (v sshCertOptionsRequireValidator) Valid(got SignSSHOptions) error {
switch {
case v.CertType && got.CertType == "":
return errors.New("ssh certificate certType cannot be empty")
case v.KeyID && got.KeyID == "":
return errors.New("ssh certificate keyID cannot be empty")
case v.Principals && len(got.Principals) == 0:
return errors.New("ssh certificate principals cannot be empty")
default:
return nil
}
}
type sshCertValidityValidator struct { type sshCertValidityValidator struct {
*Claimer *Claimer
} }

View file

@ -206,8 +206,6 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname str
// SignSSH creates a signed SSH certificate with the given public key and options. // SignSSH creates a signed SSH certificate with the given public key and options.
func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
var ( var (
err error
certType sshutil.CertType
certOptions []sshutil.Option certOptions []sshutil.Option
mods []provisioner.SSHCertModifier mods []provisioner.SSHCertModifier
validators []provisioner.SSHCertValidator validators []provisioner.SSHCertValidator
@ -216,14 +214,6 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
// Set backdate with the configured value // Set backdate with the configured value
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
// Validate certificate type.
if opts.CertType != "" {
certType, err = sshutil.CertTypeFromString(opts.CertType)
if err != nil {
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH")
}
}
for _, op := range signOpts { for _, op := range signOpts {
switch o := op.(type) { switch o := op.(type) {
// add options to NewCertificate // add options to NewCertificate
@ -251,7 +241,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
// Simulated certificate request with request options. // Simulated certificate request with request options.
cr := sshutil.CertificateRequest{ cr := sshutil.CertificateRequest{
Type: certType, Type: opts.CertType,
KeyID: opts.KeyID, KeyID: opts.KeyID,
Principals: opts.Principals, Principals: opts.Principals,
Key: key, Key: key,

View file

@ -11,7 +11,7 @@ import "golang.org/x/crypto/ssh"
// passed with the API instead of the validated ones. // passed with the API instead of the validated ones.
type CertificateRequest struct { type CertificateRequest struct {
Key ssh.PublicKey Key ssh.PublicKey
Type CertType Type string
KeyID string KeyID string
Principals []string Principals []string
} }

View file

@ -143,3 +143,18 @@ const DefaultIIDCertificate = `{
{{- end }} {{- end }}
"extensions": {{ toJson .Extensions }} "extensions": {{ toJson .Extensions }}
}` }`
const CertificateRequestTemplate = `{
"type": "{{ .Insecure.CR.Type }}",
"keyId": "{{ .Insecure.CR.KeyID }}",
"principals": {{ toJson .Insecure.CR.Principals }}
{{- if eq .Insecure.CR.Type "user" }}
, "extensions": {
"permit-X11-forwarding": "",
"permit-agent-forwarding": "",
"permit-port-forwarding": "",
"permit-pty": "",
"permit-user-rc": ""
}
{{- end }}
}`