From 7104299119974b2105bb1c26a984f22feba1ae75 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 6 May 2022 13:12:13 +0200 Subject: [PATCH] Add full policy validation in API --- authority/admin/api/policy.go | 59 ++++++ authority/admin/api/policy_test.go | 283 +++++++++++++++++++++++++++++ authority/policy.go | 117 +----------- authority/policy/policy.go | 119 ++++++++++++ authority/policy/policy_test.go | 155 ++++++++++++++++ authority/policy_test.go | 136 -------------- 6 files changed, 618 insertions(+), 251 deletions(-) create mode 100644 authority/policy/policy_test.go diff --git a/authority/admin/api/policy.go b/authority/admin/api/policy.go index 275b947c..970b8785 100644 --- a/authority/admin/api/policy.go +++ b/authority/admin/api/policy.go @@ -11,6 +11,7 @@ import ( "github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/policy" ) type policyAdminResponderInterface interface { @@ -104,6 +105,11 @@ func (par *PolicyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r 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) var createdPolicy *linkedca.Policy @@ -149,6 +155,11 @@ func (par *PolicyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r 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) var updatedPolicy *linkedca.Policy @@ -239,6 +250,11 @@ func (par *PolicyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, newPolicy.Deduplicate() + if err := validatePolicy(newPolicy); err != nil { + render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) + return + } + prov.Policy = newPolicy if err := par.auth.UpdateProvisioner(ctx, prov); err != nil { @@ -278,6 +294,11 @@ func (par *PolicyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, newPolicy.Deduplicate() + if err := validatePolicy(newPolicy); err != nil { + render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) + return + } + prov.Policy = newPolicy if err := par.auth.UpdateProvisioner(ctx, prov); err != nil { if isBadRequest(err) { @@ -364,6 +385,11 @@ func (par *PolicyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, 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 acmeEAK := linkedEAKToCertificates(eak) @@ -400,6 +426,11 @@ func (par *PolicyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, 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 acmeEAK := linkedEAKToCertificates(eak) 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) 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 +} diff --git a/authority/admin/api/policy_test.go b/authority/admin/api/policy_test.go index 77879190..1e70db52 100644 --- a/authority/admin/api/policy_test.go +++ b/authority/admin/api/policy_test.go @@ -343,6 +343,32 @@ func TestPolicyAdminResponder_CreateAuthorityPolicy(t *testing.T) { 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 { adm := &linkedca.Admin{ Subject: "step", @@ -610,6 +636,39 @@ func TestPolicyAdminResponder_UpdateAuthorityPolicy(t *testing.T) { 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 { adm := &linkedca.Admin{ Subject: "step", @@ -1174,6 +1233,35 @@ func TestPolicyAdminResponder_CreateProvisionerPolicy(t *testing.T) { 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 { adm := &linkedca.Admin{ Subject: "step", @@ -1391,6 +1479,43 @@ func TestPolicyAdminResponder_UpdateProvisionerPolicy(t *testing.T) { 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 { adm := &linkedca.Admin{ Subject: "step", @@ -1916,6 +2041,34 @@ func TestPolicyAdminResponder_CreateACMEAccountPolicy(t *testing.T) { 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 { prov := &linkedca.Provisioner{ Id: "provID", @@ -2109,6 +2262,42 @@ func TestPolicyAdminResponder_UpdateACMEAccountPolicy(t *testing.T) { 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 { policy := &linkedca.Policy{ 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) + } + }) + } +} diff --git a/authority/policy.go b/authority/policy.go index 063a464c..4afe2535 100644 --- a/authority/policy.go +++ b/authority/policy.go @@ -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 { // convert the policy; return early if nil - policyOptions := policyToCertificates(p) + policyOptions := authPolicy.PolicyToCertificates(p) if policyOptions == 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) } } - policyOptions = policyToCertificates(linkedPolicy) + policyOptions = authPolicy.PolicyToCertificates(linkedPolicy) } else { policyOptions = a.config.AuthorityConfig.Policy } @@ -256,116 +256,3 @@ func isAllowed(engine authPolicy.X509Policy, sans []string) error { 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 -} diff --git a/authority/policy/policy.go b/authority/policy/policy.go index 52297d65..51ad0da4 100644 --- a/authority/policy/policy.go +++ b/authority/policy/policy.go @@ -3,6 +3,8 @@ package policy import ( "fmt" + "go.step.sm/linkedca" + "github.com/smallstep/certificates/policy" ) @@ -52,10 +54,14 @@ func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, return nil, nil } + // check if configuration specifies that wildcard names are allowed if policyOptions.AreWildcardNamesAllowed() { options = append(options, policy.WithAllowLiteralWildcardNames()) } + // enable subject common name verification by default + options = append(options, policy.WithSubjectCommonNameVerification()) + return policy.New(options...) } @@ -135,3 +141,116 @@ func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEn 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 +} diff --git a/authority/policy/policy_test.go b/authority/policy/policy_test.go new file mode 100644 index 00000000..a241d596 --- /dev/null +++ b/authority/policy/policy_test.go @@ -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)) + } + }) + } +} diff --git a/authority/policy_test.go b/authority/policy_test.go index e64752ec..efeb743b 100644 --- a/authority/policy_test.go +++ b/authority/policy_test.go @@ -6,7 +6,6 @@ import ( "reflect" "testing" - "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "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 { engine, err := policy.New(options) if err != nil {