Add provisioner controller
The provisioner controller has the implementation of the identity function as well as the renew methods with renew after expiry support.
This commit is contained in:
parent
2e715cd505
commit
fd6a2eeb9c
4 changed files with 246 additions and 107 deletions
|
@ -26,23 +26,26 @@ var (
|
||||||
DefaultBackdate = time.Minute
|
DefaultBackdate = time.Minute
|
||||||
// DefaultDisableRenewal disables renewals per provisioner.
|
// DefaultDisableRenewal disables renewals per provisioner.
|
||||||
DefaultDisableRenewal = false
|
DefaultDisableRenewal = false
|
||||||
|
// DefaultEnableRenewAfterExpiry enables renewals even when the certificate is expired.
|
||||||
|
DefaultEnableRenewAfterExpiry = false
|
||||||
// DefaultEnableSSHCA enable SSH CA features per provisioner or globally
|
// DefaultEnableSSHCA enable SSH CA features per provisioner or globally
|
||||||
// for all provisioners.
|
// for all provisioners.
|
||||||
DefaultEnableSSHCA = false
|
DefaultEnableSSHCA = false
|
||||||
// GlobalProvisionerClaims default claims for the Authority. Can be overridden
|
// GlobalProvisionerClaims default claims for the Authority. Can be overridden
|
||||||
// by provisioner specific claims.
|
// by provisioner specific claims.
|
||||||
GlobalProvisionerClaims = provisioner.Claims{
|
GlobalProvisionerClaims = provisioner.Claims{
|
||||||
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs
|
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs
|
||||||
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||||
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||||
DisableRenewal: &DefaultDisableRenewal,
|
MinUserSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // User SSH certs
|
||||||
MinUserSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // User SSH certs
|
MaxUserSSHDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||||
MaxUserSSHDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
DefaultUserSSHDur: &provisioner.Duration{Duration: 16 * time.Hour},
|
||||||
DefaultUserSSHDur: &provisioner.Duration{Duration: 16 * time.Hour},
|
MinHostSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // Host SSH certs
|
||||||
MinHostSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // Host SSH certs
|
MaxHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
||||||
MaxHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
DefaultHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
||||||
DefaultHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
EnableSSHCA: &DefaultEnableSSHCA,
|
||||||
EnableSSHCA: &DefaultEnableSSHCA,
|
DisableRenewal: &DefaultDisableRenewal,
|
||||||
|
EnableRenewAfterExpiry: &DefaultEnableRenewAfterExpiry,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
// Claims so that individual provisioners can override global claims.
|
// Claims so that individual provisioners can override global claims.
|
||||||
type Claims struct {
|
type Claims struct {
|
||||||
// TLS CA properties
|
// TLS CA properties
|
||||||
MinTLSDur *Duration `json:"minTLSCertDuration,omitempty"`
|
MinTLSDur *Duration `json:"minTLSCertDuration,omitempty"`
|
||||||
MaxTLSDur *Duration `json:"maxTLSCertDuration,omitempty"`
|
MaxTLSDur *Duration `json:"maxTLSCertDuration,omitempty"`
|
||||||
DefaultTLSDur *Duration `json:"defaultTLSCertDuration,omitempty"`
|
DefaultTLSDur *Duration `json:"defaultTLSCertDuration,omitempty"`
|
||||||
DisableRenewal *bool `json:"disableRenewal,omitempty"`
|
|
||||||
// SSH CA properties
|
// SSH CA properties
|
||||||
MinUserSSHDur *Duration `json:"minUserSSHCertDuration,omitempty"`
|
MinUserSSHDur *Duration `json:"minUserSSHCertDuration,omitempty"`
|
||||||
MaxUserSSHDur *Duration `json:"maxUserSSHCertDuration,omitempty"`
|
MaxUserSSHDur *Duration `json:"maxUserSSHCertDuration,omitempty"`
|
||||||
|
@ -22,6 +22,10 @@ type Claims struct {
|
||||||
MaxHostSSHDur *Duration `json:"maxHostSSHCertDuration,omitempty"`
|
MaxHostSSHDur *Duration `json:"maxHostSSHCertDuration,omitempty"`
|
||||||
DefaultHostSSHDur *Duration `json:"defaultHostSSHCertDuration,omitempty"`
|
DefaultHostSSHDur *Duration `json:"defaultHostSSHCertDuration,omitempty"`
|
||||||
EnableSSHCA *bool `json:"enableSSHCA,omitempty"`
|
EnableSSHCA *bool `json:"enableSSHCA,omitempty"`
|
||||||
|
|
||||||
|
// Renewal properties
|
||||||
|
DisableRenewal *bool `json:"disableRenewal,omitempty"`
|
||||||
|
EnableRenewAfterExpiry *bool `json:"enableRenewAfterExpiry,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Claimer is the type that controls claims. It provides an interface around the
|
// Claimer is the type that controls claims. It provides an interface around the
|
||||||
|
@ -40,19 +44,22 @@ func NewClaimer(claims *Claims, global Claims) (*Claimer, error) {
|
||||||
// Claims returns the merge of the inner and global claims.
|
// Claims returns the merge of the inner and global claims.
|
||||||
func (c *Claimer) Claims() Claims {
|
func (c *Claimer) Claims() Claims {
|
||||||
disableRenewal := c.IsDisableRenewal()
|
disableRenewal := c.IsDisableRenewal()
|
||||||
|
enablerenewAfterExpiry := c.IsRenewAfterExpiry()
|
||||||
enableSSHCA := c.IsSSHCAEnabled()
|
enableSSHCA := c.IsSSHCAEnabled()
|
||||||
|
|
||||||
return Claims{
|
return Claims{
|
||||||
MinTLSDur: &Duration{c.MinTLSCertDuration()},
|
MinTLSDur: &Duration{c.MinTLSCertDuration()},
|
||||||
MaxTLSDur: &Duration{c.MaxTLSCertDuration()},
|
MaxTLSDur: &Duration{c.MaxTLSCertDuration()},
|
||||||
DefaultTLSDur: &Duration{c.DefaultTLSCertDuration()},
|
DefaultTLSDur: &Duration{c.DefaultTLSCertDuration()},
|
||||||
DisableRenewal: &disableRenewal,
|
MinUserSSHDur: &Duration{c.MinUserSSHCertDuration()},
|
||||||
MinUserSSHDur: &Duration{c.MinUserSSHCertDuration()},
|
MaxUserSSHDur: &Duration{c.MaxUserSSHCertDuration()},
|
||||||
MaxUserSSHDur: &Duration{c.MaxUserSSHCertDuration()},
|
DefaultUserSSHDur: &Duration{c.DefaultUserSSHCertDuration()},
|
||||||
DefaultUserSSHDur: &Duration{c.DefaultUserSSHCertDuration()},
|
MinHostSSHDur: &Duration{c.MinHostSSHCertDuration()},
|
||||||
MinHostSSHDur: &Duration{c.MinHostSSHCertDuration()},
|
MaxHostSSHDur: &Duration{c.MaxHostSSHCertDuration()},
|
||||||
MaxHostSSHDur: &Duration{c.MaxHostSSHCertDuration()},
|
DefaultHostSSHDur: &Duration{c.DefaultHostSSHCertDuration()},
|
||||||
DefaultHostSSHDur: &Duration{c.DefaultHostSSHCertDuration()},
|
EnableSSHCA: &enableSSHCA,
|
||||||
EnableSSHCA: &enableSSHCA,
|
DisableRenewal: &disableRenewal,
|
||||||
|
EnableRenewAfterExpiry: &enablerenewAfterExpiry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +109,16 @@ func (c *Claimer) IsDisableRenewal() bool {
|
||||||
return *c.claims.DisableRenewal
|
return *c.claims.DisableRenewal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRenewAfterExpiry returns if the renewal flow is authorized even if the
|
||||||
|
// certificate is expired. If the property is not set within the provisioner
|
||||||
|
// then the global value from the authority configuration will be used.
|
||||||
|
func (c *Claimer) IsRenewAfterExpiry() bool {
|
||||||
|
if c.claims == nil || c.claims.EnableRenewAfterExpiry == nil {
|
||||||
|
return *c.global.EnableRenewAfterExpiry
|
||||||
|
}
|
||||||
|
return *c.claims.EnableRenewAfterExpiry
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultSSHCertDuration returns the default SSH certificate duration for the
|
// DefaultSSHCertDuration returns the default SSH certificate duration for the
|
||||||
// given certificate type.
|
// given certificate type.
|
||||||
func (c *Claimer) DefaultSSHCertDuration(certType uint32) (time.Duration, error) {
|
func (c *Claimer) DefaultSSHCertDuration(certType uint32) (time.Duration, error) {
|
||||||
|
|
194
authority/provisioner/controller.go
Normal file
194
authority/provisioner/controller.go
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
package provisioner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/errs"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Controller wraps a provisioner with other attributes useful in callback
|
||||||
|
// functions.
|
||||||
|
type Controller struct {
|
||||||
|
Interface
|
||||||
|
Audiences *Audiences
|
||||||
|
Claimer *Claimer
|
||||||
|
IdentityFunc GetIdentityFunc
|
||||||
|
AuthorizeRenewFunc AuthorizeRenewFunc
|
||||||
|
AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewController initializes a new provisioner controller.
|
||||||
|
func NewController(p Interface, claims *Claims, config Config) (*Controller, error) {
|
||||||
|
claimer, err := NewClaimer(claims, config.Claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Controller{
|
||||||
|
Interface: p,
|
||||||
|
Audiences: &config.Audiences,
|
||||||
|
Claimer: claimer,
|
||||||
|
IdentityFunc: config.GetIdentityFunc,
|
||||||
|
AuthorizeRenewFunc: config.AuthorizeRenewFunc,
|
||||||
|
AuthorizeSSHRenewFunc: config.AuthorizeSSHRenewFunc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdentity returns the identity for a given email.
|
||||||
|
func (c *Controller) GetIdentity(ctx context.Context, email string) (*Identity, error) {
|
||||||
|
if c.IdentityFunc != nil {
|
||||||
|
return c.IdentityFunc(ctx, c.Interface, email)
|
||||||
|
}
|
||||||
|
return DefaultIdentityFunc(ctx, c.Interface, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeRenew returns nil if the given cert can be renewed, returns an error
|
||||||
|
// otherwise.
|
||||||
|
func (c *Controller) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||||
|
if c.AuthorizeRenewFunc != nil {
|
||||||
|
return c.AuthorizeRenewFunc(ctx, c, cert)
|
||||||
|
}
|
||||||
|
return DefaultAuthorizeRenew(ctx, c, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorizeSSHRenew returns nil if the given cert can be renewed, returns an
|
||||||
|
// error otherwise.
|
||||||
|
func (c *Controller) AuthorizeSSHRenew(ctx context.Context, cert *ssh.Certificate) error {
|
||||||
|
if c.AuthorizeSSHRenewFunc != nil {
|
||||||
|
return c.AuthorizeSSHRenewFunc(ctx, c, cert)
|
||||||
|
}
|
||||||
|
return DefaultAuthorizeSSHRenew(ctx, c, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identity is the type representing an externally supplied identity that is used
|
||||||
|
// by provisioners to populate certificate fields.
|
||||||
|
type Identity struct {
|
||||||
|
Usernames []string `json:"usernames"`
|
||||||
|
Permissions `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIdentityFunc is a function that returns an identity.
|
||||||
|
type GetIdentityFunc func(ctx context.Context, p Interface, email string) (*Identity, error)
|
||||||
|
|
||||||
|
// AuthorizeRenewFunc is a function that returns nil if the renewal of a
|
||||||
|
// certificate is enabled.
|
||||||
|
type AuthorizeRenewFunc func(ctx context.Context, p *Controller, cert *x509.Certificate) error
|
||||||
|
|
||||||
|
// AuthorizeSSHRenewFunc is a function that returns nil if the renewal of the
|
||||||
|
// given SSH certificate is enabled.
|
||||||
|
type AuthorizeSSHRenewFunc func(ctx context.Context, p *Controller, cert *ssh.Certificate) error
|
||||||
|
|
||||||
|
// DefaultIdentityFunc return a default identity depending on the provisioner
|
||||||
|
// type. For OIDC email is always present and the usernames might
|
||||||
|
// contain empty strings.
|
||||||
|
func DefaultIdentityFunc(ctx context.Context, p Interface, email string) (*Identity, error) {
|
||||||
|
switch k := p.(type) {
|
||||||
|
case *OIDC:
|
||||||
|
// OIDC principals would be:
|
||||||
|
// ~~1. Preferred usernames.~~ Note: Under discussion, currently disabled
|
||||||
|
// 2. Sanitized local.
|
||||||
|
// 3. Raw local (if different).
|
||||||
|
// 4. Email address.
|
||||||
|
name := SanitizeSSHUserPrincipal(email)
|
||||||
|
if !sshUserRegex.MatchString(name) {
|
||||||
|
return nil, errors.Errorf("invalid principal '%s' from email '%s'", name, email)
|
||||||
|
}
|
||||||
|
usernames := []string{name}
|
||||||
|
if i := strings.LastIndex(email, "@"); i >= 0 {
|
||||||
|
usernames = append(usernames, email[:i])
|
||||||
|
}
|
||||||
|
usernames = append(usernames, email)
|
||||||
|
return &Identity{
|
||||||
|
Usernames: SanitizeStringSlices(usernames),
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("provisioner type '%T' not supported by identity function", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultAuthorizeRenew is the default implementation of AuthorizeRenew. It
|
||||||
|
// will return an error if the provisioner has the renewal disabled, if the
|
||||||
|
// certificate is not yet valid or if the certificate is expired and renew after
|
||||||
|
// expiry is disabled.
|
||||||
|
func DefaultAuthorizeRenew(ctx context.Context, p *Controller, cert *x509.Certificate) error {
|
||||||
|
if p.Claimer.IsDisableRenewal() {
|
||||||
|
return errs.Unauthorized("renew is disabled for provisioner '%s'", p.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Truncate(time.Second)
|
||||||
|
if now.Before(cert.NotBefore) {
|
||||||
|
return errs.Unauthorized("certificate is not yet valid")
|
||||||
|
}
|
||||||
|
if now.After(cert.NotAfter) && !p.Claimer.IsRenewAfterExpiry() {
|
||||||
|
return errs.Unauthorized("certificate has expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultAuthorizeSSHRenew is the default implementation of AuthorizeSSHRenew. It
|
||||||
|
// will return an error if the provisioner has the renewal disabled, if the
|
||||||
|
// certificate is not yet valid or if the certificate is expired and renew after
|
||||||
|
// expiry is disabled.
|
||||||
|
func DefaultAuthorizeSSHRenew(ctx context.Context, p *Controller, cert *ssh.Certificate) error {
|
||||||
|
if p.Claimer.IsDisableRenewal() {
|
||||||
|
return errs.Unauthorized("renew is disabled for provisioner '%s'", p.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
unixNow := time.Now().Unix()
|
||||||
|
if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
|
||||||
|
return errs.Unauthorized("certificate is not yet valid")
|
||||||
|
}
|
||||||
|
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(ssh.CertTimeInfinity) && (unixNow >= before || before < 0) && !p.Claimer.IsRenewAfterExpiry() {
|
||||||
|
return errs.Unauthorized("certificate has expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sshUserRegex = regexp.MustCompile("^[a-z][-a-z0-9_]*$")
|
||||||
|
|
||||||
|
// SanitizeStringSlices removes duplicated an empty strings.
|
||||||
|
func SanitizeStringSlices(original []string) []string {
|
||||||
|
output := []string{}
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for _, entry := range original {
|
||||||
|
if entry == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, value := seen[entry]; !value {
|
||||||
|
seen[entry] = struct{}{}
|
||||||
|
output = append(output, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeSSHUserPrincipal grabs an email or a string with the format
|
||||||
|
// local@domain and returns a sanitized version of the local, valid to be used
|
||||||
|
// as a user name. If the email starts with a letter between a and z, the
|
||||||
|
// resulting string will match the regular expression `^[a-z][-a-z0-9_]*$`.
|
||||||
|
func SanitizeSSHUserPrincipal(email string) string {
|
||||||
|
if i := strings.LastIndex(email, "@"); i >= 0 {
|
||||||
|
email = email[:i]
|
||||||
|
}
|
||||||
|
return strings.Map(func(r rune) rune {
|
||||||
|
switch {
|
||||||
|
case r >= 'a' && r <= 'z':
|
||||||
|
return r
|
||||||
|
case r >= '0' && r <= '9':
|
||||||
|
return r
|
||||||
|
case r == '-':
|
||||||
|
return '-'
|
||||||
|
case r == '.': // drop dots
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return '_'
|
||||||
|
}
|
||||||
|
}, strings.ToLower(email))
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
stderrors "errors"
|
stderrors "errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -210,6 +209,12 @@ type Config struct {
|
||||||
// GetIdentityFunc is a function that returns an identity that will be
|
// GetIdentityFunc is a function that returns an identity that will be
|
||||||
// used by the provisioner to populate certificate attributes.
|
// used by the provisioner to populate certificate attributes.
|
||||||
GetIdentityFunc GetIdentityFunc
|
GetIdentityFunc GetIdentityFunc
|
||||||
|
// AuthorizeRenewFunc is a function that returns nil if a given X.509
|
||||||
|
// certificate can be renewed.
|
||||||
|
AuthorizeRenewFunc AuthorizeRenewFunc
|
||||||
|
// AuthorizeSSHRenewFunc is a function that returns nil if a given SSH
|
||||||
|
// certificate can be renewed.
|
||||||
|
AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type provisioner struct {
|
type provisioner struct {
|
||||||
|
@ -278,32 +283,6 @@ func (l *List) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var sshUserRegex = regexp.MustCompile("^[a-z][-a-z0-9_]*$")
|
|
||||||
|
|
||||||
// SanitizeSSHUserPrincipal grabs an email or a string with the format
|
|
||||||
// local@domain and returns a sanitized version of the local, valid to be used
|
|
||||||
// as a user name. If the email starts with a letter between a and z, the
|
|
||||||
// resulting string will match the regular expression `^[a-z][-a-z0-9_]*$`.
|
|
||||||
func SanitizeSSHUserPrincipal(email string) string {
|
|
||||||
if i := strings.LastIndex(email, "@"); i >= 0 {
|
|
||||||
email = email[:i]
|
|
||||||
}
|
|
||||||
return strings.Map(func(r rune) rune {
|
|
||||||
switch {
|
|
||||||
case r >= 'a' && r <= 'z':
|
|
||||||
return r
|
|
||||||
case r >= '0' && r <= '9':
|
|
||||||
return r
|
|
||||||
case r == '-':
|
|
||||||
return '-'
|
|
||||||
case r == '.': // drop dots
|
|
||||||
return -1
|
|
||||||
default:
|
|
||||||
return '_'
|
|
||||||
}
|
|
||||||
}, strings.ToLower(email))
|
|
||||||
}
|
|
||||||
|
|
||||||
type base struct{}
|
type base struct{}
|
||||||
|
|
||||||
// AuthorizeSign returns an unimplemented error. Provisioners should overwrite
|
// AuthorizeSign returns an unimplemented error. Provisioners should overwrite
|
||||||
|
@ -348,66 +327,12 @@ func (b *base) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certif
|
||||||
return nil, nil, errs.Unauthorized("provisioner.AuthorizeSSHRekey not implemented")
|
return nil, nil, errs.Unauthorized("provisioner.AuthorizeSSHRekey not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identity is the type representing an externally supplied identity that is used
|
|
||||||
// by provisioners to populate certificate fields.
|
|
||||||
type Identity struct {
|
|
||||||
Usernames []string `json:"usernames"`
|
|
||||||
Permissions `json:"permissions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permissions defines extra extensions and critical options to grant to an SSH certificate.
|
// Permissions defines extra extensions and critical options to grant to an SSH certificate.
|
||||||
type Permissions struct {
|
type Permissions struct {
|
||||||
Extensions map[string]string `json:"extensions"`
|
Extensions map[string]string `json:"extensions"`
|
||||||
CriticalOptions map[string]string `json:"criticalOptions"`
|
CriticalOptions map[string]string `json:"criticalOptions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIdentityFunc is a function that returns an identity.
|
|
||||||
type GetIdentityFunc func(ctx context.Context, p Interface, email string) (*Identity, error)
|
|
||||||
|
|
||||||
// DefaultIdentityFunc return a default identity depending on the provisioner
|
|
||||||
// type. For OIDC email is always present and the usernames might
|
|
||||||
// contain empty strings.
|
|
||||||
func DefaultIdentityFunc(ctx context.Context, p Interface, email string) (*Identity, error) {
|
|
||||||
switch k := p.(type) {
|
|
||||||
case *OIDC:
|
|
||||||
// OIDC principals would be:
|
|
||||||
// ~~1. Preferred usernames.~~ Note: Under discussion, currently disabled
|
|
||||||
// 2. Sanitized local.
|
|
||||||
// 3. Raw local (if different).
|
|
||||||
// 4. Email address.
|
|
||||||
name := SanitizeSSHUserPrincipal(email)
|
|
||||||
if !sshUserRegex.MatchString(name) {
|
|
||||||
return nil, errors.Errorf("invalid principal '%s' from email '%s'", name, email)
|
|
||||||
}
|
|
||||||
usernames := []string{name}
|
|
||||||
if i := strings.LastIndex(email, "@"); i >= 0 {
|
|
||||||
usernames = append(usernames, email[:i])
|
|
||||||
}
|
|
||||||
usernames = append(usernames, email)
|
|
||||||
return &Identity{
|
|
||||||
Usernames: SanitizeStringSlices(usernames),
|
|
||||||
}, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("provisioner type '%T' not supported by identity function", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SanitizeStringSlices removes duplicated an empty strings.
|
|
||||||
func SanitizeStringSlices(original []string) []string {
|
|
||||||
output := []string{}
|
|
||||||
seen := make(map[string]struct{})
|
|
||||||
for _, entry := range original {
|
|
||||||
if entry == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, value := seen[entry]; !value {
|
|
||||||
seen[entry] = struct{}{}
|
|
||||||
output = append(output, entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockProvisioner for testing
|
// MockProvisioner for testing
|
||||||
type MockProvisioner struct {
|
type MockProvisioner struct {
|
||||||
Mret1, Mret2, Mret3 interface{}
|
Mret1, Mret2, Mret3 interface{}
|
||||||
|
|
Loading…
Reference in a new issue