forked from TrueCloudLab/certificates
Refactor configuration of allow/deny on authority level
This commit is contained in:
parent
af53a17bb4
commit
7c541888ad
21 changed files with 515 additions and 293 deletions
|
@ -16,6 +16,7 @@ import (
|
|||
adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql"
|
||||
"github.com/smallstep/certificates/authority/administrator"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/cas"
|
||||
casapi "github.com/smallstep/certificates/cas/apiv1"
|
||||
|
@ -75,6 +76,11 @@ type Authority struct {
|
|||
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)
|
||||
getIdentityFunc provisioner.GetIdentityFunc
|
||||
|
||||
// Policy engines
|
||||
x509Policy policy.X509Policy
|
||||
sshUserPolicy policy.UserPolicy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
|
||||
adminMutex sync.RWMutex
|
||||
}
|
||||
|
||||
|
@ -539,6 +545,21 @@ func (a *Authority) init() error {
|
|||
a.templates.Data["Step"] = tmplVars
|
||||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if a.x509Policy, err = policy.NewX509PolicyEngine(a.config.AuthorityConfig.Policy.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// // Initialize the SSH allow/deny policy engine for host certificates
|
||||
if a.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(a.config.AuthorityConfig.Policy.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// // Initialize the SSH allow/deny policy engine for user certificates
|
||||
if a.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(a.config.AuthorityConfig.Policy.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// JWT numeric dates are seconds.
|
||||
a.startTime = time.Now().Truncate(time.Second)
|
||||
// Set flag indicating that initialization has been completed, and should
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
cas "github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/db"
|
||||
|
@ -90,6 +91,7 @@ type AuthConfig struct {
|
|||
Admins []*linkedca.Admin `json:"-"`
|
||||
Template *ASN1DN `json:"template,omitempty"`
|
||||
Claims *provisioner.Claims `json:"claims,omitempty"`
|
||||
Policy *policy.Options `json:"policy,omitempty"`
|
||||
DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"`
|
||||
Backdate *provisioner.Duration `json:"backdate,omitempty"`
|
||||
EnableAdmin bool `json:"enableAdmin,omitempty"`
|
||||
|
|
170
authority/policy/options.go
Normal file
170
authority/policy/options.go
Normal file
|
@ -0,0 +1,170 @@
|
|||
package policy
|
||||
|
||||
type Options struct {
|
||||
X509 *X509PolicyOptions `json:"x509,omitempty"`
|
||||
SSH *SSHPolicyOptions `json:"ssh,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Options) GetX509Options() *X509PolicyOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.X509
|
||||
}
|
||||
|
||||
func (o *Options) GetSSHOptions() *SSHPolicyOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.SSH
|
||||
}
|
||||
|
||||
type X509PolicyOptionsInterface interface {
|
||||
GetAllowedNameOptions() *X509NameOptions
|
||||
GetDeniedNameOptions() *X509NameOptions
|
||||
}
|
||||
|
||||
type X509PolicyOptions struct {
|
||||
// AllowedNames ...
|
||||
AllowedNames *X509NameOptions `json:"allow,omitempty"`
|
||||
|
||||
// DeniedNames ...
|
||||
DeniedNames *X509NameOptions `json:"deny,omitempty"`
|
||||
}
|
||||
|
||||
// X509NameOptions models the X509 name policy configuration.
|
||||
type X509NameOptions struct {
|
||||
DNSDomains []string `json:"dns,omitempty"`
|
||||
IPRanges []string `json:"ip,omitempty"`
|
||||
EmailAddresses []string `json:"email,omitempty"`
|
||||
URIDomains []string `json:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// HasNames checks if the AllowedNameOptions has one or more
|
||||
// names configured.
|
||||
func (o *X509NameOptions) HasNames() bool {
|
||||
return len(o.DNSDomains) > 0 ||
|
||||
len(o.IPRanges) > 0 ||
|
||||
len(o.EmailAddresses) > 0 ||
|
||||
len(o.URIDomains) > 0
|
||||
}
|
||||
|
||||
type SSHPolicyOptionsInterface interface {
|
||||
GetAllowedUserNameOptions() *SSHNameOptions
|
||||
GetDeniedUserNameOptions() *SSHNameOptions
|
||||
GetAllowedHostNameOptions() *SSHNameOptions
|
||||
GetDeniedHostNameOptions() *SSHNameOptions
|
||||
}
|
||||
|
||||
type SSHPolicyOptions struct {
|
||||
// User contains SSH user certificate options.
|
||||
User *SSHUserCertificateOptions `json:"user,omitempty"`
|
||||
|
||||
// Host contains SSH host certificate options.
|
||||
Host *SSHHostCertificateOptions `json:"host,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllowedNameOptions returns AllowedNames, which models the
|
||||
// SANs that ...
|
||||
func (o *X509PolicyOptions) GetAllowedNameOptions() *X509NameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.AllowedNames
|
||||
}
|
||||
|
||||
// GetDeniedNameOptions returns the DeniedNames, which models the
|
||||
// SANs that ...
|
||||
func (o *X509PolicyOptions) GetDeniedNameOptions() *X509NameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.DeniedNames
|
||||
}
|
||||
|
||||
func (o *SSHPolicyOptions) GetAllowedUserNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.User == nil {
|
||||
return nil
|
||||
}
|
||||
return o.User.AllowedNames
|
||||
}
|
||||
|
||||
func (o *SSHPolicyOptions) GetDeniedUserNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.User == nil {
|
||||
return nil
|
||||
}
|
||||
return o.User.DeniedNames
|
||||
}
|
||||
|
||||
func (o *SSHPolicyOptions) GetAllowedHostNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.Host == nil {
|
||||
return nil
|
||||
}
|
||||
return o.Host.AllowedNames
|
||||
}
|
||||
|
||||
func (o *SSHPolicyOptions) GetDeniedHostNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.Host == nil {
|
||||
return nil
|
||||
}
|
||||
return o.Host.DeniedNames
|
||||
}
|
||||
|
||||
// SSHUserCertificateOptions is a collection of SSH user certificate options.
|
||||
type SSHUserCertificateOptions struct {
|
||||
// AllowedNames contains the names the provisioner is authorized to sign
|
||||
AllowedNames *SSHNameOptions `json:"allow,omitempty"`
|
||||
// DeniedNames contains the names the provisioner is not authorized to sign
|
||||
DeniedNames *SSHNameOptions `json:"deny,omitempty"`
|
||||
}
|
||||
|
||||
// SSHHostCertificateOptions is a collection of SSH host certificate options.
|
||||
// It's an alias of SSHUserCertificateOptions, as the options are the same
|
||||
// for both types of certificates.
|
||||
type SSHHostCertificateOptions SSHUserCertificateOptions
|
||||
|
||||
// SSHNameOptions models the SSH name policy configuration.
|
||||
type SSHNameOptions struct {
|
||||
DNSDomains []string `json:"dns,omitempty"`
|
||||
IPRanges []string `json:"ip,omitempty"`
|
||||
EmailAddresses []string `json:"email,omitempty"`
|
||||
Principals []string `json:"principal,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllowedNameOptions returns the AllowedSSHNameOptions, which models the
|
||||
// names that a provisioner is authorized to sign SSH certificates for.
|
||||
func (o *SSHUserCertificateOptions) GetAllowedNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.AllowedNames
|
||||
}
|
||||
|
||||
// GetDeniedNameOptions returns the DeniedSSHNameOptions, which models the
|
||||
// names that a provisioner is NOT authorized to sign SSH certificates for.
|
||||
func (o *SSHUserCertificateOptions) GetDeniedNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.DeniedNames
|
||||
}
|
||||
|
||||
// HasNames checks if the SSHNameOptions has one or more
|
||||
// names configured.
|
||||
func (o *SSHNameOptions) HasNames() bool {
|
||||
return len(o.DNSDomains) > 0 ||
|
||||
len(o.EmailAddresses) > 0 ||
|
||||
len(o.Principals) > 0
|
||||
}
|
134
authority/policy/policy.go
Normal file
134
authority/policy/policy.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/smallstep/certificates/policy"
|
||||
)
|
||||
|
||||
// X509Policy is an alias for policy.X509NamePolicyEngine
|
||||
type X509Policy policy.X509NamePolicyEngine
|
||||
|
||||
// UserPolicy is an alias for policy.SSHNamePolicyEngine
|
||||
type UserPolicy policy.SSHNamePolicyEngine
|
||||
|
||||
// HostPolicy is an alias for policy.SSHNamePolicyEngine
|
||||
type HostPolicy policy.SSHNamePolicyEngine
|
||||
|
||||
// NewX509PolicyEngine creates a new x509 name policy engine
|
||||
func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, error) {
|
||||
|
||||
// return early if no policy engine options to configure
|
||||
if policyOptions == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
options := []policy.NamePolicyOption{}
|
||||
|
||||
allowed := policyOptions.GetAllowedNameOptions()
|
||||
if allowed != nil && allowed.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithPermittedDNSDomains(allowed.DNSDomains),
|
||||
policy.WithPermittedIPsOrCIDRs(allowed.IPRanges),
|
||||
policy.WithPermittedEmailAddresses(allowed.EmailAddresses),
|
||||
policy.WithPermittedURIDomains(allowed.URIDomains),
|
||||
)
|
||||
}
|
||||
|
||||
denied := policyOptions.GetDeniedNameOptions()
|
||||
if denied != nil && denied.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithExcludedDNSDomains(denied.DNSDomains),
|
||||
policy.WithExcludedIPsOrCIDRs(denied.IPRanges),
|
||||
policy.WithExcludedEmailAddresses(denied.EmailAddresses),
|
||||
policy.WithExcludedURIDomains(denied.URIDomains),
|
||||
)
|
||||
}
|
||||
|
||||
// ensure no policy engine is returned when no name options were provided
|
||||
if len(options) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// enable x509 Subject Common Name validation by default
|
||||
options = append(options, policy.WithSubjectCommonNameVerification())
|
||||
|
||||
return policy.New(options...)
|
||||
}
|
||||
|
||||
type sshPolicyEngineType string
|
||||
|
||||
const (
|
||||
UserPolicyEngineType sshPolicyEngineType = "user"
|
||||
HostPolicyEngineType sshPolicyEngineType = "host"
|
||||
)
|
||||
|
||||
// newSSHUserPolicyEngine creates a new SSH user certificate policy engine
|
||||
func NewSSHUserPolicyEngine(policyOptions SSHPolicyOptionsInterface) (UserPolicy, error) {
|
||||
policyEngine, err := newSSHPolicyEngine(policyOptions, UserPolicyEngineType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policyEngine, nil
|
||||
}
|
||||
|
||||
// newSSHHostPolicyEngine create a new SSH host certificate policy engine
|
||||
func NewSSHHostPolicyEngine(policyOptions SSHPolicyOptionsInterface) (HostPolicy, error) {
|
||||
policyEngine, err := newSSHPolicyEngine(policyOptions, HostPolicyEngineType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policyEngine, nil
|
||||
}
|
||||
|
||||
// newSSHPolicyEngine creates a new SSH name policy engine
|
||||
func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEngineType) (policy.SSHNamePolicyEngine, error) {
|
||||
|
||||
// return early if no policy engine options to configure
|
||||
if policyOptions == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
allowed *SSHNameOptions
|
||||
denied *SSHNameOptions
|
||||
)
|
||||
|
||||
switch typ {
|
||||
case UserPolicyEngineType:
|
||||
allowed = policyOptions.GetAllowedUserNameOptions()
|
||||
denied = policyOptions.GetDeniedUserNameOptions()
|
||||
case HostPolicyEngineType:
|
||||
allowed = policyOptions.GetAllowedHostNameOptions()
|
||||
denied = policyOptions.GetDeniedHostNameOptions()
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown SSH policy engine type %s provided", typ)
|
||||
}
|
||||
|
||||
options := []policy.NamePolicyOption{}
|
||||
|
||||
if allowed != nil && allowed.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithPermittedDNSDomains(allowed.DNSDomains),
|
||||
policy.WithPermittedIPsOrCIDRs(allowed.IPRanges),
|
||||
policy.WithPermittedEmailAddresses(allowed.EmailAddresses),
|
||||
policy.WithPermittedPrincipals(allowed.Principals),
|
||||
)
|
||||
}
|
||||
|
||||
if denied != nil && denied.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithExcludedDNSDomains(denied.DNSDomains),
|
||||
policy.WithExcludedIPsOrCIDRs(denied.IPRanges),
|
||||
policy.WithExcludedEmailAddresses(denied.EmailAddresses),
|
||||
policy.WithExcludedPrincipals(denied.Principals),
|
||||
)
|
||||
}
|
||||
|
||||
// ensure no policy engine is returned when no name options were provided
|
||||
if len(options) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return policy.New(options...)
|
||||
}
|
|
@ -7,8 +7,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/policy"
|
||||
)
|
||||
|
||||
// ACME is the acme provisioner type, an entity that can authorize the ACME
|
||||
|
@ -27,7 +27,7 @@ type ACME struct {
|
|||
Claims *Claims `json:"claims,omitempty"`
|
||||
Options *Options `json:"options,omitempty"`
|
||||
claimer *Claimer
|
||||
x509Policy policy.X509NamePolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
|
@ -92,7 +92,7 @@ func (p *ACME) Init(config Config) (err error) {
|
|||
// Initialize the x509 allow/deny policy engine
|
||||
// TODO(hs): ensure no race conditions happen when reloading settings and requesting certs?
|
||||
// TODO(hs): implement memoization strategy, so that reloading is not required when no changes were made to allow/deny?
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
@ -267,8 +268,8 @@ type AWS struct {
|
|||
claimer *Claimer
|
||||
config *awsConfig
|
||||
audiences Audiences
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
|
@ -428,12 +429,12 @@ func (p *AWS) Init(config Config) (err error) {
|
|||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if p.sshHostPolicy, err = newSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
@ -100,8 +101,8 @@ type Azure struct {
|
|||
config *azureConfig
|
||||
oidcConfig openIDConfiguration
|
||||
keyStore *keyStore
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
|
@ -226,12 +227,12 @@ func (p *Azure) Init(config Config) (err error) {
|
|||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if p.sshHostPolicy, err = newSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
@ -92,8 +93,8 @@ type GCP struct {
|
|||
config *gcpConfig
|
||||
keyStore *keyStore
|
||||
audiences Audiences
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier. The name should uniquely
|
||||
|
@ -219,12 +220,12 @@ func (p *GCP) Init(config Config) error {
|
|||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if p.sshHostPolicy, err = newSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
@ -37,9 +38,9 @@ type JWK struct {
|
|||
Options *Options `json:"options,omitempty"`
|
||||
claimer *Claimer
|
||||
audiences Audiences
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
sshUserPolicy *userPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
sshUserPolicy policy.UserPolicy
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
|
@ -107,17 +108,17 @@ func (p *JWK) Init(config Config) (err error) {
|
|||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for user certificates
|
||||
if p.sshUserPolicy, err = newSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if p.sshHostPolicy, err = newSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
@ -52,9 +53,9 @@ type K8sSA struct {
|
|||
audiences Audiences
|
||||
//kauthn kauthn.AuthenticationV1Interface
|
||||
pubKeys []interface{}
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
sshUserPolicy *userPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
sshUserPolicy policy.UserPolicy
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
|
@ -148,17 +149,17 @@ func (p *K8sSA) Init(config Config) (err error) {
|
|||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for user certificates
|
||||
if p.sshUserPolicy, err = newSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if p.sshHostPolicy, err = newSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
nebula "github.com/slackhq/nebula/cert"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
@ -43,9 +44,9 @@ type Nebula struct {
|
|||
claimer *Claimer
|
||||
caPool *nebula.NebulaCAPool
|
||||
audiences Audiences
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
sshUserPolicy *userPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
sshUserPolicy policy.UserPolicy
|
||||
}
|
||||
|
||||
// Init verifies and initializes the Nebula provisioner.
|
||||
|
@ -72,17 +73,17 @@ func (p *Nebula) Init(config Config) error {
|
|||
p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for user certificates
|
||||
if p.sshUserPolicy, err = newSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if p.sshHostPolicy, err = newSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
@ -94,9 +95,9 @@ type OIDC struct {
|
|||
keyStore *keyStore
|
||||
claimer *Claimer
|
||||
getIdentityFunc GetIdentityFunc
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
sshUserPolicy *userPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
sshUserPolicy policy.UserPolicy
|
||||
}
|
||||
|
||||
func sanitizeEmail(email string) string {
|
||||
|
@ -212,17 +213,17 @@ func (o *OIDC) Init(config Config) (err error) {
|
|||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if o.x509Policy, err = newX509PolicyEngine(o.Options.GetX509Options()); err != nil {
|
||||
if o.x509Policy, err = policy.NewX509PolicyEngine(o.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for user certificates
|
||||
if o.sshUserPolicy, err = newSSHUserPolicyEngine(o.Options.GetSSHOptions()); err != nil {
|
||||
if o.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(o.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if o.sshHostPolicy, err = newSSHHostPolicyEngine(o.Options.GetSSHOptions()); err != nil {
|
||||
if o.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(o.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,11 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/x509util"
|
||||
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
)
|
||||
|
||||
// CertificateOptions is an interface that returns a list of options passed when
|
||||
|
@ -58,10 +61,10 @@ type X509Options struct {
|
|||
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
||||
|
||||
// AllowedNames contains the SANs the provisioner is authorized to sign
|
||||
AllowedNames *X509NameOptions `json:"allow,omitempty"`
|
||||
AllowedNames *policy.X509NameOptions
|
||||
|
||||
// DeniedNames contains the SANs the provisioner is not authorized to sign
|
||||
DeniedNames *X509NameOptions `json:"deny,omitempty"`
|
||||
DeniedNames *policy.X509NameOptions
|
||||
}
|
||||
|
||||
// HasTemplate returns true if a template is defined in the provisioner options.
|
||||
|
@ -69,41 +72,24 @@ func (o *X509Options) HasTemplate() bool {
|
|||
return o != nil && (o.Template != "" || o.TemplateFile != "")
|
||||
}
|
||||
|
||||
// GetAllowedNameOptions returns the AllowedNameOptions, which models the
|
||||
// GetAllowedNameOptions returns the AllowedNames, which models the
|
||||
// SANs that a provisioner is authorized to sign x509 certificates for.
|
||||
func (o *X509Options) GetAllowedNameOptions() *X509NameOptions {
|
||||
func (o *X509Options) GetAllowedNameOptions() *policy.X509NameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.AllowedNames
|
||||
}
|
||||
|
||||
// GetDeniedNameOptions returns the DeniedNameOptions, which models the
|
||||
// GetDeniedNameOptions returns the DeniedNames, which models the
|
||||
// SANs that a provisioner is NOT authorized to sign x509 certificates for.
|
||||
func (o *X509Options) GetDeniedNameOptions() *X509NameOptions {
|
||||
func (o *X509Options) GetDeniedNameOptions() *policy.X509NameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.DeniedNames
|
||||
}
|
||||
|
||||
// X509NameOptions models the X509 name policy configuration.
|
||||
type X509NameOptions struct {
|
||||
DNSDomains []string `json:"dns,omitempty"`
|
||||
IPRanges []string `json:"ip,omitempty"`
|
||||
EmailAddresses []string `json:"email,omitempty"`
|
||||
URIDomains []string `json:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// HasNames checks if the AllowedNameOptions has one or more
|
||||
// names configured.
|
||||
func (o *X509NameOptions) HasNames() bool {
|
||||
return len(o.DNSDomains) > 0 ||
|
||||
len(o.IPRanges) > 0 ||
|
||||
len(o.EmailAddresses) > 0 ||
|
||||
len(o.URIDomains) > 0
|
||||
}
|
||||
|
||||
// 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. If no template has been provided,
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
package provisioner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/smallstep/certificates/policy"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type sshPolicyEngineType string
|
||||
|
||||
const (
|
||||
userPolicyEngineType sshPolicyEngineType = "user"
|
||||
hostPolicyEngineType sshPolicyEngineType = "host"
|
||||
)
|
||||
|
||||
var certTypeToPolicyEngineType = map[uint32]sshPolicyEngineType{
|
||||
uint32(ssh.UserCert): userPolicyEngineType,
|
||||
uint32(ssh.HostCert): hostPolicyEngineType,
|
||||
}
|
||||
|
||||
type x509PolicyEngine interface {
|
||||
policy.X509NamePolicyEngine
|
||||
}
|
||||
|
||||
type userPolicyEngine struct {
|
||||
policy.SSHNamePolicyEngine
|
||||
}
|
||||
|
||||
type hostPolicyEngine struct {
|
||||
policy.SSHNamePolicyEngine
|
||||
}
|
||||
|
||||
// newX509PolicyEngine creates a new x509 name policy engine
|
||||
func newX509PolicyEngine(x509Opts *X509Options) (x509PolicyEngine, error) {
|
||||
|
||||
if x509Opts == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
options := []policy.NamePolicyOption{
|
||||
policy.WithSubjectCommonNameVerification(), // enable x509 Subject Common Name validation by default
|
||||
}
|
||||
|
||||
allowed := x509Opts.GetAllowedNameOptions()
|
||||
if allowed != nil && allowed.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithPermittedDNSDomains(allowed.DNSDomains),
|
||||
policy.WithPermittedIPsOrCIDRs(allowed.IPRanges),
|
||||
policy.WithPermittedEmailAddresses(allowed.EmailAddresses),
|
||||
policy.WithPermittedURIDomains(allowed.URIDomains),
|
||||
)
|
||||
}
|
||||
|
||||
denied := x509Opts.GetDeniedNameOptions()
|
||||
if denied != nil && denied.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithExcludedDNSDomains(denied.DNSDomains),
|
||||
policy.WithExcludedIPsOrCIDRs(denied.IPRanges),
|
||||
policy.WithExcludedEmailAddresses(denied.EmailAddresses),
|
||||
policy.WithExcludedURIDomains(denied.URIDomains),
|
||||
)
|
||||
}
|
||||
|
||||
return policy.New(options...)
|
||||
}
|
||||
|
||||
// newSSHUserPolicyEngine creates a new SSH user certificate policy engine
|
||||
func newSSHUserPolicyEngine(sshOpts *SSHOptions) (*userPolicyEngine, error) {
|
||||
policyEngine, err := newSSHPolicyEngine(sshOpts, userPolicyEngineType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ensure we're not wrapping a nil engine
|
||||
if policyEngine == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &userPolicyEngine{
|
||||
SSHNamePolicyEngine: policyEngine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newSSHHostPolicyEngine create a new SSH host certificate policy engine
|
||||
func newSSHHostPolicyEngine(sshOpts *SSHOptions) (*hostPolicyEngine, error) {
|
||||
policyEngine, err := newSSHPolicyEngine(sshOpts, hostPolicyEngineType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ensure we're not wrapping a nil engine
|
||||
if policyEngine == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &hostPolicyEngine{
|
||||
SSHNamePolicyEngine: policyEngine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newSSHPolicyEngine creates a new SSH name policy engine
|
||||
func newSSHPolicyEngine(sshOpts *SSHOptions, typ sshPolicyEngineType) (policy.SSHNamePolicyEngine, error) {
|
||||
|
||||
if sshOpts == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
allowed *SSHNameOptions
|
||||
denied *SSHNameOptions
|
||||
)
|
||||
|
||||
// TODO: embed the type in the policy engine itself for reference?
|
||||
switch typ {
|
||||
case userPolicyEngineType:
|
||||
if sshOpts.User != nil {
|
||||
allowed = sshOpts.User.GetAllowedNameOptions()
|
||||
denied = sshOpts.User.GetDeniedNameOptions()
|
||||
}
|
||||
case hostPolicyEngineType:
|
||||
if sshOpts.Host != nil {
|
||||
allowed = sshOpts.Host.AllowedNames
|
||||
denied = sshOpts.Host.DeniedNames
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown SSH policy engine type %s provided", typ)
|
||||
}
|
||||
|
||||
options := []policy.NamePolicyOption{}
|
||||
|
||||
if allowed != nil && allowed.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithPermittedDNSDomains(allowed.DNSDomains),
|
||||
policy.WithPermittedIPsOrCIDRs(allowed.IPRanges),
|
||||
policy.WithPermittedEmailAddresses(allowed.EmailAddresses),
|
||||
policy.WithPermittedPrincipals(allowed.Principals),
|
||||
)
|
||||
}
|
||||
|
||||
if denied != nil && denied.HasNames() {
|
||||
options = append(options,
|
||||
policy.WithExcludedDNSDomains(denied.DNSDomains),
|
||||
policy.WithExcludedIPsOrCIDRs(denied.IPRanges),
|
||||
policy.WithExcludedEmailAddresses(denied.EmailAddresses),
|
||||
policy.WithExcludedPrincipals(denied.Principals),
|
||||
)
|
||||
}
|
||||
|
||||
// Return nil, because there's no policy to execute. This is
|
||||
// important, because the logic that determines user vs. host certs
|
||||
// are allowed depends on this fact. The two policy engines are
|
||||
// not aware of eachother, so this check is performed in the
|
||||
// SSH name validator, instead.
|
||||
if len(options) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return policy.New(options...)
|
||||
}
|
|
@ -5,7 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/policy"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
)
|
||||
|
||||
// SCEP is the SCEP provisioner type, an entity that can authorize the
|
||||
|
@ -31,7 +31,7 @@ type SCEP struct {
|
|||
Options *Options `json:"options,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
claimer *Claimer
|
||||
x509Policy policy.X509NamePolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
secretChallengePassword string
|
||||
encryptionAlgorithm int
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ func (s *SCEP) Init(config Config) (err error) {
|
|||
// TODO: add other, SCEP specific, options?
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if s.x509Policy, err = newX509PolicyEngine(s.Options.GetX509Options()); err != nil {
|
||||
if s.x509Policy, err = policy.NewX509PolicyEngine(s.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
|
@ -407,18 +408,17 @@ func (v *validityValidator) Valid(cert *x509.Certificate, o SignOptions) error {
|
|||
// x509NamePolicyValidator validates that the certificate (to be signed)
|
||||
// contains only allowed SANs.
|
||||
type x509NamePolicyValidator struct {
|
||||
policyEngine x509PolicyEngine
|
||||
policyEngine policy.X509Policy
|
||||
}
|
||||
|
||||
// newX509NamePolicyValidator return a new SANs allow/deny validator.
|
||||
func newX509NamePolicyValidator(engine x509PolicyEngine) *x509NamePolicyValidator {
|
||||
func newX509NamePolicyValidator(engine policy.X509Policy) *x509NamePolicyValidator {
|
||||
return &x509NamePolicyValidator{
|
||||
policyEngine: engine,
|
||||
}
|
||||
}
|
||||
|
||||
// Valid validates validates that the certificate (to be signed)
|
||||
// contains only allowed SANs.
|
||||
// Valid validates that the certificate (to be signed) contains only allowed SANs.
|
||||
func (v *x509NamePolicyValidator) Valid(cert *x509.Certificate, _ SignOptions) error {
|
||||
if v.policyEngine == nil {
|
||||
return nil
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -448,20 +449,19 @@ func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SignSSHOpti
|
|||
// sshNamePolicyValidator validates that the certificate (to be signed)
|
||||
// contains only allowed principals.
|
||||
type sshNamePolicyValidator struct {
|
||||
hostPolicyEngine *hostPolicyEngine
|
||||
userPolicyEngine *userPolicyEngine
|
||||
hostPolicyEngine policy.HostPolicy
|
||||
userPolicyEngine policy.UserPolicy
|
||||
}
|
||||
|
||||
// newSSHNamePolicyValidator return a new SSH allow/deny validator.
|
||||
func newSSHNamePolicyValidator(host *hostPolicyEngine, user *userPolicyEngine) *sshNamePolicyValidator {
|
||||
func newSSHNamePolicyValidator(host policy.HostPolicy, user policy.UserPolicy) *sshNamePolicyValidator {
|
||||
return &sshNamePolicyValidator{
|
||||
hostPolicyEngine: host,
|
||||
userPolicyEngine: user,
|
||||
}
|
||||
}
|
||||
|
||||
// Valid validates validates that the certificate (to be signed)
|
||||
// contains only allowed principals.
|
||||
// Valid validates that the certificate (to be signed) contains only allowed principals.
|
||||
func (v *sshNamePolicyValidator) Valid(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
if v.hostPolicyEngine == nil && v.userPolicyEngine == nil {
|
||||
// no policy configured at all; allow anything
|
||||
|
@ -473,29 +473,25 @@ func (v *sshNamePolicyValidator) Valid(cert *ssh.Certificate, _ SignSSHOptions)
|
|||
// the same for host certs: if only a user policy engine is configured, host
|
||||
// certs are denied. When both policy engines are configured, the type of
|
||||
// cert determines which policy engine is used.
|
||||
policyType, ok := certTypeToPolicyEngineType[cert.CertType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected SSH cert type %d", cert.CertType)
|
||||
}
|
||||
switch policyType {
|
||||
case hostPolicyEngineType:
|
||||
switch cert.CertType {
|
||||
case ssh.HostCert:
|
||||
// when no host policy engine is configured, but a user policy engine is
|
||||
// configured, we don't allow the host certificate.
|
||||
// configured, the host certificate is denied.
|
||||
if v.hostPolicyEngine == nil && v.userPolicyEngine != nil {
|
||||
return errors.New("SSH host certificate not authorized") // TODO: include principals in message?
|
||||
return errors.New("SSH host certificate not authorized")
|
||||
}
|
||||
_, err := v.hostPolicyEngine.ArePrincipalsAllowed(cert)
|
||||
return err
|
||||
case userPolicyEngineType:
|
||||
case ssh.UserCert:
|
||||
// when no user policy engine is configured, but a host policy engine is
|
||||
// configured, we don't allow the user certificate.
|
||||
// configured, the user certificate is denied.
|
||||
if v.userPolicyEngine == nil && v.hostPolicyEngine != nil {
|
||||
return errors.New("SSH user certificate not authorized") // TODO: include principals in message?
|
||||
return errors.New("SSH user certificate not authorized")
|
||||
}
|
||||
_, err := v.userPolicyEngine.ArePrincipalsAllowed(cert)
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("unexpected policy engine type %q", policyType) // satisfy return; shouldn't happen
|
||||
return fmt.Errorf("unexpected SSH certificate type %d", cert.CertType) // satisfy return; shouldn't happen
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
)
|
||||
|
||||
// SSHCertificateOptions is an interface that returns a list of options passed when
|
||||
|
@ -35,32 +37,58 @@ type SSHOptions struct {
|
|||
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
||||
|
||||
// User contains SSH user certificate options.
|
||||
User *SSHUserCertificateOptions `json:"user,omitempty"`
|
||||
User *policy.SSHUserCertificateOptions
|
||||
|
||||
// Host contains SSH host certificate options.
|
||||
Host *SSHHostCertificateOptions `json:"host,omitempty"`
|
||||
Host *policy.SSHHostCertificateOptions
|
||||
}
|
||||
|
||||
// SSHUserCertificateOptions is a collection of SSH user certificate options.
|
||||
type SSHUserCertificateOptions struct {
|
||||
// AllowedNames contains the names the provisioner is authorized to sign
|
||||
AllowedNames *SSHNameOptions `json:"allow,omitempty"`
|
||||
|
||||
// DeniedNames contains the names the provisioner is not authorized to sign
|
||||
DeniedNames *SSHNameOptions `json:"deny,omitempty"`
|
||||
// GetAllowedUserNameOptions returns the SSHNameOptions that are
|
||||
// allowed when SSH User certificates are requested.
|
||||
func (o *SSHOptions) GetAllowedUserNameOptions() *policy.SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.User == nil {
|
||||
return nil
|
||||
}
|
||||
return o.User.AllowedNames
|
||||
}
|
||||
|
||||
// SSHHostCertificateOptions is a collection of SSH host certificate options.
|
||||
// It's an alias of SSHUserCertificateOptions, as the options are the same
|
||||
// for both types of certificates.
|
||||
type SSHHostCertificateOptions SSHUserCertificateOptions
|
||||
// GetDeniedUserNameOptions returns the SSHNameOptions that are
|
||||
// denied when SSH user certificates are requested.
|
||||
func (o *SSHOptions) GetDeniedUserNameOptions() *policy.SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.User == nil {
|
||||
return nil
|
||||
}
|
||||
return o.User.DeniedNames
|
||||
}
|
||||
|
||||
// SSHNameOptions models the SSH name policy configuration.
|
||||
type SSHNameOptions struct {
|
||||
DNSDomains []string `json:"dns,omitempty"`
|
||||
IPRanges []string `json:"ip,omitempty"`
|
||||
EmailAddresses []string `json:"email,omitempty"`
|
||||
Principals []string `json:"principal,omitempty"`
|
||||
// GetAllowedHostNameOptions returns the SSHNameOptions that are
|
||||
// allowed when SSH host certificates are requested.
|
||||
func (o *SSHOptions) GetAllowedHostNameOptions() *policy.SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.Host == nil {
|
||||
return nil
|
||||
}
|
||||
return o.Host.AllowedNames
|
||||
}
|
||||
|
||||
// GetDeniedHostNameOptions returns the SSHNameOptions that are
|
||||
// denied when SSH host certificates are requested.
|
||||
func (o *SSHOptions) GetDeniedHostNameOptions() *policy.SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
if o.Host == nil {
|
||||
return nil
|
||||
}
|
||||
return o.Host.DeniedNames
|
||||
}
|
||||
|
||||
// HasTemplate returns true if a template is defined in the provisioner options.
|
||||
|
@ -68,32 +96,6 @@ func (o *SSHOptions) HasTemplate() bool {
|
|||
return o != nil && (o.Template != "" || o.TemplateFile != "")
|
||||
}
|
||||
|
||||
// GetAllowedNameOptions returns the AllowedSSHNameOptions, which models the
|
||||
// names that a provisioner is authorized to sign SSH certificates for.
|
||||
func (o *SSHUserCertificateOptions) GetAllowedNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.AllowedNames
|
||||
}
|
||||
|
||||
// GetDeniedNameOptions returns the DeniedSSHNameOptions, which models the
|
||||
// names that a provisioner is NOT authorized to sign SSH certificates for.
|
||||
func (o *SSHUserCertificateOptions) GetDeniedNameOptions() *SSHNameOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.DeniedNames
|
||||
}
|
||||
|
||||
// HasNames checks if the SSHNameOptions has one or more
|
||||
// names configured.
|
||||
func (o *SSHNameOptions) HasNames() bool {
|
||||
return len(o.DNSDomains) > 0 ||
|
||||
len(o.EmailAddresses) > 0 ||
|
||||
len(o.Principals) > 0
|
||||
}
|
||||
|
||||
// TemplateSSHOptions generates a SSHCertificateOptions with the template and
|
||||
// data defined in the ProvisionerOptions, the provisioner generated data, and
|
||||
// the user data provided in the request. If no template has been provided,
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/policy"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
|
@ -35,9 +36,9 @@ type X5C struct {
|
|||
claimer *Claimer
|
||||
audiences Audiences
|
||||
rootPool *x509.CertPool
|
||||
x509Policy x509PolicyEngine
|
||||
sshHostPolicy *hostPolicyEngine
|
||||
sshUserPolicy *userPolicyEngine
|
||||
x509Policy policy.X509Policy
|
||||
sshHostPolicy policy.HostPolicy
|
||||
sshUserPolicy policy.UserPolicy
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
|
@ -129,17 +130,17 @@ func (p *X5C) Init(config Config) error {
|
|||
}
|
||||
|
||||
// Initialize the x509 allow/deny policy engine
|
||||
if p.x509Policy, err = newX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
if p.x509Policy, err = policy.NewX509PolicyEngine(p.Options.GetX509Options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for user certificates
|
||||
if p.sshUserPolicy, err = newSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the SSH allow/deny policy engine for host certificates
|
||||
if p.sshHostPolicy, err = newSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
if p.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(p.Options.GetSSHOptions()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
|
@ -241,6 +242,45 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
|
|||
return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType)
|
||||
}
|
||||
|
||||
switch certTpl.CertType {
|
||||
case ssh.UserCert:
|
||||
// when no user policy engine is configured, but a host policy engine is
|
||||
// configured, the user certificate is denied.
|
||||
if a.sshUserPolicy == nil && a.sshHostPolicy != nil {
|
||||
return nil, errs.ForbiddenErr(errors.New("authority not allowed to sign ssh user certificates"), "authority.SignSSH: error creating ssh user certificate")
|
||||
}
|
||||
if a.sshUserPolicy != nil {
|
||||
allowed, err := a.sshUserPolicy.ArePrincipalsAllowed(certTpl)
|
||||
if err != nil {
|
||||
return nil, errs.InternalServerErr(err,
|
||||
errs.WithMessage("authority.SignSSH: error creating ssh user certificate"),
|
||||
)
|
||||
}
|
||||
if !allowed {
|
||||
return nil, errs.ForbiddenErr(errors.New("authority not allowed to sign"), "authority.SignSSH: error creating ssh user certificate")
|
||||
}
|
||||
}
|
||||
case ssh.HostCert:
|
||||
// when no host policy engine is configured, but a user policy engine is
|
||||
// configured, the host certificate is denied.
|
||||
if a.sshHostPolicy == nil && a.sshUserPolicy != nil {
|
||||
return nil, errs.ForbiddenErr(errors.New("authority not allowed to sign ssh host certificates"), "authority.SignSSH: error creating ssh user certificate")
|
||||
}
|
||||
if a.sshHostPolicy != nil {
|
||||
allowed, err := a.sshHostPolicy.ArePrincipalsAllowed(certTpl)
|
||||
if err != nil {
|
||||
return nil, errs.InternalServerErr(err,
|
||||
errs.WithMessage("authority.SignSSH: error creating ssh host certificate"),
|
||||
)
|
||||
}
|
||||
if !allowed {
|
||||
return nil, errs.ForbiddenErr(errors.New("authority not allowed to sign"), "authority.SignSSH: error creating ssh host certificate")
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType)
|
||||
}
|
||||
|
||||
// Sign certificate.
|
||||
cert, err := sshutil.CreateCertificate(certTpl, signer)
|
||||
if err != nil {
|
||||
|
|
|
@ -191,6 +191,25 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
|
|||
}
|
||||
}
|
||||
|
||||
// If a policy is configured, perform allow/deny policy check on authority level
|
||||
if a.x509Policy != nil {
|
||||
allowed, err := a.x509Policy.AreCertificateNamesAllowed(leaf)
|
||||
if err != nil {
|
||||
return nil, errs.InternalServerErr(err,
|
||||
errs.WithKeyVal("csr", csr),
|
||||
errs.WithKeyVal("signOptions", signOpts),
|
||||
errs.WithMessage("error creating certificate"),
|
||||
)
|
||||
}
|
||||
if !allowed {
|
||||
// TODO: include SANs in error message?
|
||||
return nil, errs.ApplyOptions(
|
||||
errs.ForbiddenErr(errors.New("authority not allowed to sign"), "error creating certificate"),
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Sign certificate
|
||||
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate))
|
||||
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
|
||||
|
|
Loading…
Reference in a new issue