mirror of
https://github.com/smallstep/certificates.git
synced 2024-10-31 03:20:16 +00:00
Add full policy validation in API
This commit is contained in:
parent
ed231d29e2
commit
7104299119
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
155
authority/policy/policy_test.go
Normal file
155
authority/policy/policy_test.go
Normal 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user