Add full policy validation in API

This commit is contained in:
Herman Slatman 2022-05-06 13:12:13 +02:00
parent ed231d29e2
commit 7104299119
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
6 changed files with 618 additions and 251 deletions

View file

@ -11,6 +11,7 @@ import (
"github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/authority/policy"
) )
type policyAdminResponderInterface interface { type policyAdminResponderInterface interface {
@ -104,6 +105,11 @@ func (par *PolicyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return
}
adm := linkedca.MustAdminFromContext(ctx) adm := linkedca.MustAdminFromContext(ctx)
var createdPolicy *linkedca.Policy var createdPolicy *linkedca.Policy
@ -149,6 +155,11 @@ func (par *PolicyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return
}
adm := linkedca.MustAdminFromContext(ctx) adm := linkedca.MustAdminFromContext(ctx)
var updatedPolicy *linkedca.Policy var updatedPolicy *linkedca.Policy
@ -239,6 +250,11 @@ func (par *PolicyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return
}
prov.Policy = newPolicy prov.Policy = newPolicy
if err := par.auth.UpdateProvisioner(ctx, prov); err != nil { if err := par.auth.UpdateProvisioner(ctx, prov); err != nil {
@ -278,6 +294,11 @@ func (par *PolicyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return
}
prov.Policy = newPolicy prov.Policy = newPolicy
if err := par.auth.UpdateProvisioner(ctx, prov); err != nil { if err := par.auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) { if isBadRequest(err) {
@ -364,6 +385,11 @@ func (par *PolicyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return
}
eak.Policy = newPolicy eak.Policy = newPolicy
acmeEAK := linkedEAKToCertificates(eak) acmeEAK := linkedEAKToCertificates(eak)
@ -400,6 +426,11 @@ func (par *PolicyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
newPolicy.Deduplicate() newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return
}
eak.Policy = newPolicy eak.Policy = newPolicy
acmeEAK := linkedEAKToCertificates(eak) acmeEAK := linkedEAKToCertificates(eak)
if err := par.acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil { if err := par.acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
@ -455,3 +486,31 @@ func isBadRequest(err error) bool {
isPolicyError := errors.As(err, &pe) isPolicyError := errors.As(err, &pe)
return isPolicyError && (pe.Typ == authority.AdminLockOut || pe.Typ == authority.EvaluationFailure || pe.Typ == authority.ConfigurationFailure) return isPolicyError && (pe.Typ == authority.AdminLockOut || pe.Typ == authority.EvaluationFailure || pe.Typ == authority.ConfigurationFailure)
} }
func validatePolicy(p *linkedca.Policy) error {
// convert the policy; return early if nil
options := policy.PolicyToCertificates(p)
if options == nil {
return nil
}
var err error
// Initialize a temporary x509 allow/deny policy engine
if _, err = policy.NewX509PolicyEngine(options.GetX509Options()); err != nil {
return err
}
// Initialize a temporary SSH allow/deny policy engine for host certificates
if _, err = policy.NewSSHHostPolicyEngine(options.GetSSHOptions()); err != nil {
return err
}
// Initialize a temporary SSH allow/deny policy engine for user certificates
if _, err = policy.NewSSHUserPolicyEngine(options.GetSSHOptions()); err != nil {
return err
}
return nil
}

View file

@ -343,6 +343,32 @@ func TestPolicyAdminResponder_CreateAuthorityPolicy(t *testing.T) {
statusCode: 400, statusCode: 400,
} }
}, },
"fail/validatePolicy": func(t *testing.T) test {
ctx := context.Background()
adminErr := admin.NewError(admin.ErrorBadRequestType, "error validating authority policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)")
adminErr.Message = "error validating authority policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)"
body := []byte(`
{
"x509": {
"allow": {
"uris": [
"https://example.com"
]
}
}
}`)
return test{
ctx: ctx,
auth: &mockAdminAuthority{
MockGetAuthorityPolicy: func(ctx context.Context) (*linkedca.Policy, error) {
return nil, admin.NewError(admin.ErrorNotFoundType, "not found")
},
},
body: body,
err: adminErr,
statusCode: 400,
}
},
"fail/CreateAuthorityPolicy-policy-admin-lockout-error": func(t *testing.T) test { "fail/CreateAuthorityPolicy-policy-admin-lockout-error": func(t *testing.T) test {
adm := &linkedca.Admin{ adm := &linkedca.Admin{
Subject: "step", Subject: "step",
@ -610,6 +636,39 @@ func TestPolicyAdminResponder_UpdateAuthorityPolicy(t *testing.T) {
statusCode: 400, statusCode: 400,
} }
}, },
"fail/validatePolicy": func(t *testing.T) test {
policy := &linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"*.local"},
},
},
}
ctx := context.Background()
adminErr := admin.NewError(admin.ErrorBadRequestType, "error validating authority policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)")
adminErr.Message = "error validating authority policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)"
body := []byte(`
{
"x509": {
"allow": {
"uris": [
"https://example.com"
]
}
}
}`)
return test{
ctx: ctx,
auth: &mockAdminAuthority{
MockGetAuthorityPolicy: func(ctx context.Context) (*linkedca.Policy, error) {
return policy, nil
},
},
body: body,
err: adminErr,
statusCode: 400,
}
},
"fail/UpdateAuthorityPolicy-policy-admin-lockout-error": func(t *testing.T) test { "fail/UpdateAuthorityPolicy-policy-admin-lockout-error": func(t *testing.T) test {
adm := &linkedca.Admin{ adm := &linkedca.Admin{
Subject: "step", Subject: "step",
@ -1174,6 +1233,35 @@ func TestPolicyAdminResponder_CreateProvisionerPolicy(t *testing.T) {
statusCode: 400, statusCode: 400,
} }
}, },
"fail/validatePolicy": func(t *testing.T) test {
prov := &linkedca.Provisioner{
Name: "provName",
}
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
adminErr := admin.NewError(admin.ErrorBadRequestType, "error validating provisioner policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)")
adminErr.Message = "error validating provisioner policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)"
body := []byte(`
{
"x509": {
"allow": {
"uris": [
"https://example.com"
]
}
}
}`)
return test{
ctx: ctx,
auth: &mockAdminAuthority{
MockGetAuthorityPolicy: func(ctx context.Context) (*linkedca.Policy, error) {
return nil, admin.NewError(admin.ErrorNotFoundType, "not found")
},
},
body: body,
err: adminErr,
statusCode: 400,
}
},
"fail/auth.UpdateProvisioner-policy-admin-lockout-error": func(t *testing.T) test { "fail/auth.UpdateProvisioner-policy-admin-lockout-error": func(t *testing.T) test {
adm := &linkedca.Admin{ adm := &linkedca.Admin{
Subject: "step", Subject: "step",
@ -1391,6 +1479,43 @@ func TestPolicyAdminResponder_UpdateProvisionerPolicy(t *testing.T) {
statusCode: 400, statusCode: 400,
} }
}, },
"fail/validatePolicy": func(t *testing.T) test {
policy := &linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"*.local"},
},
},
}
prov := &linkedca.Provisioner{
Name: "provName",
Policy: policy,
}
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
adminErr := admin.NewError(admin.ErrorBadRequestType, "error validating provisioner policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)")
adminErr.Message = "error validating provisioner policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)"
body := []byte(`
{
"x509": {
"allow": {
"uris": [
"https://example.com"
]
}
}
}`)
return test{
ctx: ctx,
auth: &mockAdminAuthority{
MockGetAuthorityPolicy: func(ctx context.Context) (*linkedca.Policy, error) {
return nil, admin.NewError(admin.ErrorNotFoundType, "not found")
},
},
body: body,
err: adminErr,
statusCode: 400,
}
},
"fail/auth.UpdateProvisioner-policy-admin-lockout-error": func(t *testing.T) test { "fail/auth.UpdateProvisioner-policy-admin-lockout-error": func(t *testing.T) test {
adm := &linkedca.Admin{ adm := &linkedca.Admin{
Subject: "step", Subject: "step",
@ -1916,6 +2041,34 @@ func TestPolicyAdminResponder_CreateACMEAccountPolicy(t *testing.T) {
statusCode: 400, statusCode: 400,
} }
}, },
"fail/validatePolicy": func(t *testing.T) test {
prov := &linkedca.Provisioner{
Name: "provName",
}
eak := &linkedca.EABKey{
Id: "eakID",
}
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
ctx = linkedca.NewContextWithExternalAccountKey(ctx, eak)
adminErr := admin.NewError(admin.ErrorBadRequestType, "error validating ACME EAK policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)")
adminErr.Message = "error validating ACME EAK policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)"
body := []byte(`
{
"x509": {
"allow": {
"uris": [
"https://example.com"
]
}
}
}`)
return test{
ctx: ctx,
body: body,
err: adminErr,
statusCode: 400,
}
},
"fail/acmeDB.UpdateExternalAccountKey-error": func(t *testing.T) test { "fail/acmeDB.UpdateExternalAccountKey-error": func(t *testing.T) test {
prov := &linkedca.Provisioner{ prov := &linkedca.Provisioner{
Id: "provID", Id: "provID",
@ -2109,6 +2262,42 @@ func TestPolicyAdminResponder_UpdateACMEAccountPolicy(t *testing.T) {
statusCode: 400, statusCode: 400,
} }
}, },
"fail/validatePolicy": func(t *testing.T) test {
policy := &linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"*.local"},
},
},
}
prov := &linkedca.Provisioner{
Name: "provName",
}
eak := &linkedca.EABKey{
Id: "eakID",
Policy: policy,
}
ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
ctx = linkedca.NewContextWithExternalAccountKey(ctx, eak)
adminErr := admin.NewError(admin.ErrorBadRequestType, "error validating ACME EAK policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)")
adminErr.Message = "error validating ACME EAK policy: cannot parse permitted URI domain constraint \"https://example.com\": URI domain constraint \"https://example.com\" contains scheme (not supported yet)"
body := []byte(`
{
"x509": {
"allow": {
"uris": [
"https://example.com"
]
}
}
}`)
return test{
ctx: ctx,
body: body,
err: adminErr,
statusCode: 400,
}
},
"fail/acmeDB.UpdateExternalAccountKey-error": func(t *testing.T) test { "fail/acmeDB.UpdateExternalAccountKey-error": func(t *testing.T) test {
policy := &linkedca.Policy{ policy := &linkedca.Policy{
X509: &linkedca.X509Policy{ X509: &linkedca.X509Policy{
@ -2426,3 +2615,97 @@ func Test_isBadRequest(t *testing.T) {
}) })
} }
} }
func Test_validatePolicy(t *testing.T) {
type args struct {
p *linkedca.Policy
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "nil",
args: args{
p: nil,
},
wantErr: false,
},
{
name: "x509",
args: args{
p: &linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"**.local"},
},
},
},
},
wantErr: true,
},
{
name: "ssh user",
args: args{
p: &linkedca.Policy{
Ssh: &linkedca.SSHPolicy{
User: &linkedca.SSHUserPolicy{
Allow: &linkedca.SSHUserNames{
Emails: []string{"@@example.com"},
},
},
},
},
},
wantErr: true,
},
{
name: "ssh host",
args: args{
p: &linkedca.Policy{
Ssh: &linkedca.SSHPolicy{
Host: &linkedca.SSHHostPolicy{
Allow: &linkedca.SSHHostNames{
Dns: []string{"**.local"},
},
},
},
},
},
wantErr: true,
},
{
name: "ok",
args: args{
p: &linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"*.local"},
},
},
Ssh: &linkedca.SSHPolicy{
User: &linkedca.SSHUserPolicy{
Allow: &linkedca.SSHUserNames{
Emails: []string{"@example.com"},
},
},
Host: &linkedca.SSHHostPolicy{
Allow: &linkedca.SSHHostNames{
Dns: []string{"*.local"},
},
},
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validatePolicy(tt.args.p); (err != nil) != tt.wantErr {
t.Errorf("validatePolicy() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -155,7 +155,7 @@ func (a *Authority) checkProvisionerPolicy(ctx context.Context, currentAdmin *li
func (a *Authority) checkPolicy(ctx context.Context, currentAdmin *linkedca.Admin, otherAdmins []*linkedca.Admin, p *linkedca.Policy) error { func (a *Authority) checkPolicy(ctx context.Context, currentAdmin *linkedca.Admin, otherAdmins []*linkedca.Admin, p *linkedca.Policy) error {
// convert the policy; return early if nil // convert the policy; return early if nil
policyOptions := policyToCertificates(p) policyOptions := authPolicy.PolicyToCertificates(p)
if policyOptions == nil { if policyOptions == nil {
return nil return nil
} }
@ -222,7 +222,7 @@ func (a *Authority) reloadPolicyEngines(ctx context.Context) error {
return fmt.Errorf("error getting policy to (re)load policy engines: %w", err) return fmt.Errorf("error getting policy to (re)load policy engines: %w", err)
} }
} }
policyOptions = policyToCertificates(linkedPolicy) policyOptions = authPolicy.PolicyToCertificates(linkedPolicy)
} else { } else {
policyOptions = a.config.AuthorityConfig.Policy policyOptions = a.config.AuthorityConfig.Policy
} }
@ -256,116 +256,3 @@ func isAllowed(engine authPolicy.X509Policy, sans []string) error {
return nil return nil
} }
func policyToCertificates(p *linkedca.Policy) *authPolicy.Options {
// return early
if p == nil {
return nil
}
// return early if x509 nor SSH is set
if p.GetX509() == nil && p.GetSsh() == nil {
return nil
}
opts := &authPolicy.Options{}
// fill x509 policy configuration
if x509 := p.GetX509(); x509 != nil {
opts.X509 = &authPolicy.X509PolicyOptions{}
if allow := x509.GetAllow(); allow != nil {
opts.X509.AllowedNames = &authPolicy.X509NameOptions{}
if allow.Dns != nil {
opts.X509.AllowedNames.DNSDomains = allow.Dns
}
if allow.Ips != nil {
opts.X509.AllowedNames.IPRanges = allow.Ips
}
if allow.Emails != nil {
opts.X509.AllowedNames.EmailAddresses = allow.Emails
}
if allow.Uris != nil {
opts.X509.AllowedNames.URIDomains = allow.Uris
}
if allow.CommonNames != nil {
opts.X509.AllowedNames.CommonNames = allow.CommonNames
}
}
if deny := x509.GetDeny(); deny != nil {
opts.X509.DeniedNames = &authPolicy.X509NameOptions{}
if deny.Dns != nil {
opts.X509.DeniedNames.DNSDomains = deny.Dns
}
if deny.Ips != nil {
opts.X509.DeniedNames.IPRanges = deny.Ips
}
if deny.Emails != nil {
opts.X509.DeniedNames.EmailAddresses = deny.Emails
}
if deny.Uris != nil {
opts.X509.DeniedNames.URIDomains = deny.Uris
}
if deny.CommonNames != nil {
opts.X509.DeniedNames.CommonNames = deny.CommonNames
}
}
opts.X509.AllowWildcardNames = x509.GetAllowWildcardNames()
}
// fill ssh policy configuration
if ssh := p.GetSsh(); ssh != nil {
opts.SSH = &authPolicy.SSHPolicyOptions{}
if host := ssh.GetHost(); host != nil {
opts.SSH.Host = &authPolicy.SSHHostCertificateOptions{}
if allow := host.GetAllow(); allow != nil {
opts.SSH.Host.AllowedNames = &authPolicy.SSHNameOptions{}
if allow.Dns != nil {
opts.SSH.Host.AllowedNames.DNSDomains = allow.Dns
}
if allow.Ips != nil {
opts.SSH.Host.AllowedNames.IPRanges = allow.Ips
}
if allow.Principals != nil {
opts.SSH.Host.AllowedNames.Principals = allow.Principals
}
}
if deny := host.GetDeny(); deny != nil {
opts.SSH.Host.DeniedNames = &authPolicy.SSHNameOptions{}
if deny.Dns != nil {
opts.SSH.Host.DeniedNames.DNSDomains = deny.Dns
}
if deny.Ips != nil {
opts.SSH.Host.DeniedNames.IPRanges = deny.Ips
}
if deny.Principals != nil {
opts.SSH.Host.DeniedNames.Principals = deny.Principals
}
}
}
if user := ssh.GetUser(); user != nil {
opts.SSH.User = &authPolicy.SSHUserCertificateOptions{}
if allow := user.GetAllow(); allow != nil {
opts.SSH.User.AllowedNames = &authPolicy.SSHNameOptions{}
if allow.Emails != nil {
opts.SSH.User.AllowedNames.EmailAddresses = allow.Emails
}
if allow.Principals != nil {
opts.SSH.User.AllowedNames.Principals = allow.Principals
}
}
if deny := user.GetDeny(); deny != nil {
opts.SSH.User.DeniedNames = &authPolicy.SSHNameOptions{}
if deny.Emails != nil {
opts.SSH.User.DeniedNames.EmailAddresses = deny.Emails
}
if deny.Principals != nil {
opts.SSH.User.DeniedNames.Principals = deny.Principals
}
}
}
}
return opts
}

View file

@ -3,6 +3,8 @@ package policy
import ( import (
"fmt" "fmt"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/policy" "github.com/smallstep/certificates/policy"
) )
@ -52,10 +54,14 @@ func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy,
return nil, nil return nil, nil
} }
// check if configuration specifies that wildcard names are allowed
if policyOptions.AreWildcardNamesAllowed() { if policyOptions.AreWildcardNamesAllowed() {
options = append(options, policy.WithAllowLiteralWildcardNames()) options = append(options, policy.WithAllowLiteralWildcardNames())
} }
// enable subject common name verification by default
options = append(options, policy.WithSubjectCommonNameVerification())
return policy.New(options...) return policy.New(options...)
} }
@ -135,3 +141,116 @@ func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEn
return policy.New(options...) return policy.New(options...)
} }
func PolicyToCertificates(p *linkedca.Policy) *Options {
// return early
if p == nil {
return nil
}
// return early if x509 nor SSH is set
if p.GetX509() == nil && p.GetSsh() == nil {
return nil
}
opts := &Options{}
// fill x509 policy configuration
if x509 := p.GetX509(); x509 != nil {
opts.X509 = &X509PolicyOptions{}
if allow := x509.GetAllow(); allow != nil {
opts.X509.AllowedNames = &X509NameOptions{}
if allow.Dns != nil {
opts.X509.AllowedNames.DNSDomains = allow.Dns
}
if allow.Ips != nil {
opts.X509.AllowedNames.IPRanges = allow.Ips
}
if allow.Emails != nil {
opts.X509.AllowedNames.EmailAddresses = allow.Emails
}
if allow.Uris != nil {
opts.X509.AllowedNames.URIDomains = allow.Uris
}
if allow.CommonNames != nil {
opts.X509.AllowedNames.CommonNames = allow.CommonNames
}
}
if deny := x509.GetDeny(); deny != nil {
opts.X509.DeniedNames = &X509NameOptions{}
if deny.Dns != nil {
opts.X509.DeniedNames.DNSDomains = deny.Dns
}
if deny.Ips != nil {
opts.X509.DeniedNames.IPRanges = deny.Ips
}
if deny.Emails != nil {
opts.X509.DeniedNames.EmailAddresses = deny.Emails
}
if deny.Uris != nil {
opts.X509.DeniedNames.URIDomains = deny.Uris
}
if deny.CommonNames != nil {
opts.X509.DeniedNames.CommonNames = deny.CommonNames
}
}
opts.X509.AllowWildcardNames = x509.GetAllowWildcardNames()
}
// fill ssh policy configuration
if ssh := p.GetSsh(); ssh != nil {
opts.SSH = &SSHPolicyOptions{}
if host := ssh.GetHost(); host != nil {
opts.SSH.Host = &SSHHostCertificateOptions{}
if allow := host.GetAllow(); allow != nil {
opts.SSH.Host.AllowedNames = &SSHNameOptions{}
if allow.Dns != nil {
opts.SSH.Host.AllowedNames.DNSDomains = allow.Dns
}
if allow.Ips != nil {
opts.SSH.Host.AllowedNames.IPRanges = allow.Ips
}
if allow.Principals != nil {
opts.SSH.Host.AllowedNames.Principals = allow.Principals
}
}
if deny := host.GetDeny(); deny != nil {
opts.SSH.Host.DeniedNames = &SSHNameOptions{}
if deny.Dns != nil {
opts.SSH.Host.DeniedNames.DNSDomains = deny.Dns
}
if deny.Ips != nil {
opts.SSH.Host.DeniedNames.IPRanges = deny.Ips
}
if deny.Principals != nil {
opts.SSH.Host.DeniedNames.Principals = deny.Principals
}
}
}
if user := ssh.GetUser(); user != nil {
opts.SSH.User = &SSHUserCertificateOptions{}
if allow := user.GetAllow(); allow != nil {
opts.SSH.User.AllowedNames = &SSHNameOptions{}
if allow.Emails != nil {
opts.SSH.User.AllowedNames.EmailAddresses = allow.Emails
}
if allow.Principals != nil {
opts.SSH.User.AllowedNames.Principals = allow.Principals
}
}
if deny := user.GetDeny(); deny != nil {
opts.SSH.User.DeniedNames = &SSHNameOptions{}
if deny.Emails != nil {
opts.SSH.User.DeniedNames.EmailAddresses = deny.Emails
}
if deny.Principals != nil {
opts.SSH.User.DeniedNames.Principals = deny.Principals
}
}
}
}
return opts
}

View file

@ -0,0 +1,155 @@
package policy
import (
"testing"
"github.com/google/go-cmp/cmp"
"go.step.sm/linkedca"
)
func TestPolicyToCertificates(t *testing.T) {
type args struct {
policy *linkedca.Policy
}
tests := []struct {
name string
args args
want *Options
}{
{
name: "nil",
args: args{
policy: nil,
},
want: nil,
},
{
name: "no-policy",
args: args{
&linkedca.Policy{},
},
want: nil,
},
{
name: "partial-policy",
args: args{
&linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"*.local"},
},
AllowWildcardNames: false,
},
},
},
want: &Options{
X509: &X509PolicyOptions{
AllowedNames: &X509NameOptions{
DNSDomains: []string{"*.local"},
},
AllowWildcardNames: false,
},
},
},
{
name: "full-policy",
args: args{
&linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"step"},
Ips: []string{"127.0.0.1/24"},
Emails: []string{"*.example.com"},
Uris: []string{"https://*.local"},
CommonNames: []string{"some name"},
},
Deny: &linkedca.X509Names{
Dns: []string{"bad"},
Ips: []string{"127.0.0.30"},
Emails: []string{"badhost.example.com"},
Uris: []string{"https://badhost.local"},
CommonNames: []string{"another name"},
},
AllowWildcardNames: true,
},
Ssh: &linkedca.SSHPolicy{
Host: &linkedca.SSHHostPolicy{
Allow: &linkedca.SSHHostNames{
Dns: []string{"*.localhost"},
Ips: []string{"127.0.0.1/24"},
Principals: []string{"user"},
},
Deny: &linkedca.SSHHostNames{
Dns: []string{"badhost.localhost"},
Ips: []string{"127.0.0.40"},
Principals: []string{"root"},
},
},
User: &linkedca.SSHUserPolicy{
Allow: &linkedca.SSHUserNames{
Emails: []string{"@work"},
Principals: []string{"user"},
},
Deny: &linkedca.SSHUserNames{
Emails: []string{"root@work"},
Principals: []string{"root"},
},
},
},
},
},
want: &Options{
X509: &X509PolicyOptions{
AllowedNames: &X509NameOptions{
DNSDomains: []string{"step"},
IPRanges: []string{"127.0.0.1/24"},
EmailAddresses: []string{"*.example.com"},
URIDomains: []string{"https://*.local"},
CommonNames: []string{"some name"},
},
DeniedNames: &X509NameOptions{
DNSDomains: []string{"bad"},
IPRanges: []string{"127.0.0.30"},
EmailAddresses: []string{"badhost.example.com"},
URIDomains: []string{"https://badhost.local"},
CommonNames: []string{"another name"},
},
AllowWildcardNames: true,
},
SSH: &SSHPolicyOptions{
Host: &SSHHostCertificateOptions{
AllowedNames: &SSHNameOptions{
DNSDomains: []string{"*.localhost"},
IPRanges: []string{"127.0.0.1/24"},
Principals: []string{"user"},
},
DeniedNames: &SSHNameOptions{
DNSDomains: []string{"badhost.localhost"},
IPRanges: []string{"127.0.0.40"},
Principals: []string{"root"},
},
},
User: &SSHUserCertificateOptions{
AllowedNames: &SSHNameOptions{
EmailAddresses: []string{"@work"},
Principals: []string{"user"},
},
DeniedNames: &SSHNameOptions{
EmailAddresses: []string{"root@work"},
Principals: []string{"root"},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PolicyToCertificates(tt.args.policy)
if !cmp.Equal(tt.want, got) {
t.Errorf("policyToCertificates() diff=\n%s", cmp.Diff(tt.want, got))
}
})
}
}

View file

@ -6,7 +6,6 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.step.sm/linkedca" "go.step.sm/linkedca"
@ -195,141 +194,6 @@ func TestAuthority_checkPolicy(t *testing.T) {
} }
} }
func Test_policyToCertificates(t *testing.T) {
tests := []struct {
name string
policy *linkedca.Policy
want *policy.Options
}{
{
name: "nil",
policy: nil,
want: nil,
},
{
name: "no-policy",
policy: &linkedca.Policy{},
want: nil,
},
{
name: "partial-policy",
policy: &linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"*.local"},
},
AllowWildcardNames: false,
},
},
want: &policy.Options{
X509: &policy.X509PolicyOptions{
AllowedNames: &policy.X509NameOptions{
DNSDomains: []string{"*.local"},
},
AllowWildcardNames: false,
},
},
},
{
name: "full-policy",
policy: &linkedca.Policy{
X509: &linkedca.X509Policy{
Allow: &linkedca.X509Names{
Dns: []string{"step"},
Ips: []string{"127.0.0.1/24"},
Emails: []string{"*.example.com"},
Uris: []string{"https://*.local"},
CommonNames: []string{"some name"},
},
Deny: &linkedca.X509Names{
Dns: []string{"bad"},
Ips: []string{"127.0.0.30"},
Emails: []string{"badhost.example.com"},
Uris: []string{"https://badhost.local"},
CommonNames: []string{"another name"},
},
AllowWildcardNames: true,
},
Ssh: &linkedca.SSHPolicy{
Host: &linkedca.SSHHostPolicy{
Allow: &linkedca.SSHHostNames{
Dns: []string{"*.localhost"},
Ips: []string{"127.0.0.1/24"},
Principals: []string{"user"},
},
Deny: &linkedca.SSHHostNames{
Dns: []string{"badhost.localhost"},
Ips: []string{"127.0.0.40"},
Principals: []string{"root"},
},
},
User: &linkedca.SSHUserPolicy{
Allow: &linkedca.SSHUserNames{
Emails: []string{"@work"},
Principals: []string{"user"},
},
Deny: &linkedca.SSHUserNames{
Emails: []string{"root@work"},
Principals: []string{"root"},
},
},
},
},
want: &policy.Options{
X509: &policy.X509PolicyOptions{
AllowedNames: &policy.X509NameOptions{
DNSDomains: []string{"step"},
IPRanges: []string{"127.0.0.1/24"},
EmailAddresses: []string{"*.example.com"},
URIDomains: []string{"https://*.local"},
CommonNames: []string{"some name"},
},
DeniedNames: &policy.X509NameOptions{
DNSDomains: []string{"bad"},
IPRanges: []string{"127.0.0.30"},
EmailAddresses: []string{"badhost.example.com"},
URIDomains: []string{"https://badhost.local"},
CommonNames: []string{"another name"},
},
AllowWildcardNames: true,
},
SSH: &policy.SSHPolicyOptions{
Host: &policy.SSHHostCertificateOptions{
AllowedNames: &policy.SSHNameOptions{
DNSDomains: []string{"*.localhost"},
IPRanges: []string{"127.0.0.1/24"},
Principals: []string{"user"},
},
DeniedNames: &policy.SSHNameOptions{
DNSDomains: []string{"badhost.localhost"},
IPRanges: []string{"127.0.0.40"},
Principals: []string{"root"},
},
},
User: &policy.SSHUserCertificateOptions{
AllowedNames: &policy.SSHNameOptions{
EmailAddresses: []string{"@work"},
Principals: []string{"user"},
},
DeniedNames: &policy.SSHNameOptions{
EmailAddresses: []string{"root@work"},
Principals: []string{"root"},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := policyToCertificates(tt.policy)
if !cmp.Equal(tt.want, got) {
t.Errorf("policyToCertificates() diff=\n%s", cmp.Diff(tt.want, got))
}
})
}
}
func mustPolicyEngine(t *testing.T, options *policy.Options) *policy.Engine { func mustPolicyEngine(t *testing.T, options *policy.Options) *policy.Engine {
engine, err := policy.New(options) engine, err := policy.New(options)
if err != nil { if err != nil {