2022-03-15 14:51:45 +00:00
|
|
|
package authority
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-03-24 13:55:40 +00:00
|
|
|
"errors"
|
2022-03-21 14:53:59 +00:00
|
|
|
"fmt"
|
|
|
|
|
2022-03-15 14:51:45 +00:00
|
|
|
"go.step.sm/linkedca"
|
2022-03-21 14:53:59 +00:00
|
|
|
|
2022-04-21 23:20:38 +00:00
|
|
|
"github.com/smallstep/certificates/authority/admin"
|
2022-03-21 14:53:59 +00:00
|
|
|
authPolicy "github.com/smallstep/certificates/authority/policy"
|
|
|
|
policy "github.com/smallstep/certificates/policy"
|
2022-03-15 14:51:45 +00:00
|
|
|
)
|
|
|
|
|
2022-04-04 11:58:16 +00:00
|
|
|
type policyErrorType int
|
|
|
|
|
|
|
|
const (
|
2022-04-26 08:15:17 +00:00
|
|
|
AdminLockOut policyErrorType = iota + 1
|
2022-04-04 11:58:16 +00:00
|
|
|
StoreFailure
|
|
|
|
ReloadFailure
|
|
|
|
ConfigurationFailure
|
|
|
|
EvaluationFailure
|
|
|
|
InternalFailure
|
|
|
|
)
|
|
|
|
|
|
|
|
type PolicyError struct {
|
|
|
|
Typ policyErrorType
|
2022-04-15 08:43:10 +00:00
|
|
|
Err error
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PolicyError) Error() string {
|
2022-04-15 08:43:10 +00:00
|
|
|
return p.Err.Error()
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-15 14:51:45 +00:00
|
|
|
func (a *Authority) GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) {
|
|
|
|
a.adminMutex.Lock()
|
|
|
|
defer a.adminMutex.Unlock()
|
|
|
|
|
2022-03-24 12:10:49 +00:00
|
|
|
p, err := a.adminDB.GetAuthorityPolicy(ctx)
|
2022-03-15 14:51:45 +00:00
|
|
|
if err != nil {
|
2022-04-24 14:29:31 +00:00
|
|
|
return nil, &PolicyError{
|
|
|
|
Typ: InternalFailure,
|
|
|
|
Err: err,
|
|
|
|
}
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
2022-03-24 12:10:49 +00:00
|
|
|
return p, nil
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
2022-03-24 12:10:49 +00:00
|
|
|
func (a *Authority) CreateAuthorityPolicy(ctx context.Context, adm *linkedca.Admin, p *linkedca.Policy) (*linkedca.Policy, error) {
|
2022-03-15 14:51:45 +00:00
|
|
|
a.adminMutex.Lock()
|
|
|
|
defer a.adminMutex.Unlock()
|
|
|
|
|
2022-04-04 11:58:16 +00:00
|
|
|
if err := a.checkAuthorityPolicy(ctx, adm, p); err != nil {
|
2022-04-24 14:29:31 +00:00
|
|
|
return nil, err
|
2022-03-21 14:53:59 +00:00
|
|
|
}
|
|
|
|
|
2022-03-24 12:10:49 +00:00
|
|
|
if err := a.adminDB.CreateAuthorityPolicy(ctx, p); err != nil {
|
2022-04-04 11:58:16 +00:00
|
|
|
return nil, &PolicyError{
|
|
|
|
Typ: StoreFailure,
|
2022-04-15 08:43:10 +00:00
|
|
|
Err: err,
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.reloadPolicyEngines(ctx); err != nil {
|
2022-04-04 11:58:16 +00:00
|
|
|
return nil, &PolicyError{
|
|
|
|
Typ: ReloadFailure,
|
2022-04-15 08:43:10 +00:00
|
|
|
Err: fmt.Errorf("error reloading policy engines when creating authority policy: %w", err),
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
2022-04-18 19:14:30 +00:00
|
|
|
return p, nil
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
2022-03-24 12:10:49 +00:00
|
|
|
func (a *Authority) UpdateAuthorityPolicy(ctx context.Context, adm *linkedca.Admin, p *linkedca.Policy) (*linkedca.Policy, error) {
|
2022-03-15 14:51:45 +00:00
|
|
|
a.adminMutex.Lock()
|
|
|
|
defer a.adminMutex.Unlock()
|
|
|
|
|
2022-04-04 11:58:16 +00:00
|
|
|
if err := a.checkAuthorityPolicy(ctx, adm, p); err != nil {
|
2022-03-24 09:54:45 +00:00
|
|
|
return nil, err
|
2022-03-21 14:53:59 +00:00
|
|
|
}
|
|
|
|
|
2022-03-24 12:10:49 +00:00
|
|
|
if err := a.adminDB.UpdateAuthorityPolicy(ctx, p); err != nil {
|
2022-04-04 11:58:16 +00:00
|
|
|
return nil, &PolicyError{
|
|
|
|
Typ: StoreFailure,
|
2022-04-15 08:43:10 +00:00
|
|
|
Err: err,
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.reloadPolicyEngines(ctx); err != nil {
|
2022-04-04 11:58:16 +00:00
|
|
|
return nil, &PolicyError{
|
|
|
|
Typ: ReloadFailure,
|
2022-04-24 14:29:31 +00:00
|
|
|
Err: fmt.Errorf("error reloading policy engines when updating authority policy: %w", err),
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
2022-04-18 19:14:30 +00:00
|
|
|
return p, nil
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Authority) RemoveAuthorityPolicy(ctx context.Context) error {
|
|
|
|
a.adminMutex.Lock()
|
|
|
|
defer a.adminMutex.Unlock()
|
|
|
|
|
|
|
|
if err := a.adminDB.DeleteAuthorityPolicy(ctx); err != nil {
|
2022-04-04 11:58:16 +00:00
|
|
|
return &PolicyError{
|
|
|
|
Typ: StoreFailure,
|
2022-04-15 08:43:10 +00:00
|
|
|
Err: err,
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.reloadPolicyEngines(ctx); err != nil {
|
2022-04-04 11:58:16 +00:00
|
|
|
return &PolicyError{
|
|
|
|
Typ: ReloadFailure,
|
2022-04-18 19:14:30 +00:00
|
|
|
Err: fmt.Errorf("error reloading policy engines when deleting authority policy: %w", err),
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
2022-03-15 14:51:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-04 11:58:16 +00:00
|
|
|
func (a *Authority) checkAuthorityPolicy(ctx context.Context, currentAdmin *linkedca.Admin, p *linkedca.Policy) error {
|
|
|
|
// no policy and thus nothing to evaluate; return early
|
|
|
|
if p == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// get all current admins from the database
|
|
|
|
allAdmins, err := a.adminDB.GetAdmins(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return &PolicyError{
|
|
|
|
Typ: InternalFailure,
|
2022-04-15 08:43:10 +00:00
|
|
|
Err: fmt.Errorf("error retrieving admins: %w", err),
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.checkPolicy(ctx, currentAdmin, allAdmins, p)
|
|
|
|
}
|
|
|
|
|
2022-05-12 14:33:32 +00:00
|
|
|
func (a *Authority) checkProvisionerPolicy(ctx context.Context, provName string, p *linkedca.Policy) error {
|
2022-04-04 11:58:16 +00:00
|
|
|
// no policy and thus nothing to evaluate; return early
|
|
|
|
if p == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-24 14:29:31 +00:00
|
|
|
// get all admins for the provisioner; ignoring case in which they're not found
|
|
|
|
allProvisionerAdmins, _ := a.admins.LoadByProvisioner(provName)
|
2022-04-04 11:58:16 +00:00
|
|
|
|
2022-05-12 14:33:32 +00:00
|
|
|
// check the policy; pass in nil as the current admin, as all admins for the
|
|
|
|
// provisioner will be checked by looping through allProvisionerAdmins. Also,
|
|
|
|
// the current admin may be a super admin not belonging to the provisioner, so
|
|
|
|
// can't be blocked, but is not required to be in the policy, either.
|
|
|
|
return a.checkPolicy(ctx, nil, allProvisionerAdmins, p)
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 12:21:39 +00:00
|
|
|
// checkPolicy checks if a new or updated policy configuration results in the user
|
|
|
|
// locking themselves or other admins out of the CA.
|
2023-05-10 06:47:28 +00:00
|
|
|
func (a *Authority) checkPolicy(_ context.Context, currentAdmin *linkedca.Admin, otherAdmins []*linkedca.Admin, p *linkedca.Policy) error {
|
2022-03-21 14:53:59 +00:00
|
|
|
// convert the policy; return early if nil
|
2022-05-06 11:23:09 +00:00
|
|
|
policyOptions := authPolicy.LinkedToCertificates(p)
|
2022-03-21 14:53:59 +00:00
|
|
|
if policyOptions == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
engine, err := authPolicy.NewX509PolicyEngine(policyOptions.GetX509Options())
|
|
|
|
if err != nil {
|
2022-04-04 11:58:16 +00:00
|
|
|
return &PolicyError{
|
|
|
|
Typ: ConfigurationFailure,
|
2022-04-15 08:43:10 +00:00
|
|
|
Err: err,
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
2022-03-21 14:53:59 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 11:58:16 +00:00
|
|
|
// when an empty X.509 policy is provided, the resulting engine is nil
|
2022-03-30 12:21:39 +00:00
|
|
|
// and there's no policy to evaluate.
|
|
|
|
if engine == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:53:59 +00:00
|
|
|
// TODO(hs): Provide option to force the policy, even when the admin subject would be locked out?
|
|
|
|
|
2022-03-30 13:39:03 +00:00
|
|
|
// check if the admin user that instructed the authority policy to be
|
|
|
|
// created or updated, would still be allowed when the provided policy
|
2022-05-12 14:33:32 +00:00
|
|
|
// would be applied. This case is skipped when current admin is nil, which
|
|
|
|
// is the case when a provisioner policy is checked.
|
|
|
|
if currentAdmin != nil {
|
|
|
|
sans := []string{currentAdmin.GetSubject()}
|
|
|
|
if err := isAllowed(engine, sans); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-03-21 14:53:59 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 13:39:03 +00:00
|
|
|
// loop through admins to verify that none of them would be
|
|
|
|
// locked out when the new policy were to be applied. Returns
|
|
|
|
// an error with a message that includes the admin subject that
|
2022-04-04 11:58:16 +00:00
|
|
|
// would be locked out.
|
|
|
|
for _, adm := range otherAdmins {
|
2022-05-12 14:33:32 +00:00
|
|
|
sans := []string{adm.GetSubject()}
|
2022-03-30 13:39:03 +00:00
|
|
|
if err := isAllowed(engine, sans); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(hs): mask the error message for non-super admins?
|
2022-03-21 14:53:59 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-19 15:10:13 +00:00
|
|
|
// reloadPolicyEngines reloads x509 and SSH policy engines using
|
|
|
|
// configuration stored in the DB or from the configuration file.
|
|
|
|
func (a *Authority) reloadPolicyEngines(ctx context.Context) error {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
policyOptions *authPolicy.Options
|
|
|
|
)
|
2022-04-25 09:02:03 +00:00
|
|
|
|
2022-04-19 15:10:13 +00:00
|
|
|
if a.config.AuthorityConfig.EnableAdmin {
|
|
|
|
// temporarily disable policy loading when LinkedCA is in use
|
|
|
|
if _, ok := a.adminDB.(*linkedCaClient); ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
linkedPolicy, err := a.adminDB.GetAuthorityPolicy(ctx)
|
|
|
|
if err != nil {
|
2022-04-21 23:20:38 +00:00
|
|
|
var ae *admin.Error
|
|
|
|
if isAdminError := errors.As(err, &ae); (isAdminError && ae.Type != admin.ErrorNotFoundType.String()) || !isAdminError {
|
|
|
|
return fmt.Errorf("error getting policy to (re)load policy engines: %w", err)
|
|
|
|
}
|
2022-04-19 15:10:13 +00:00
|
|
|
}
|
2022-05-06 11:23:09 +00:00
|
|
|
policyOptions = authPolicy.LinkedToCertificates(linkedPolicy)
|
2022-04-19 15:10:13 +00:00
|
|
|
} else {
|
|
|
|
policyOptions = a.config.AuthorityConfig.Policy
|
|
|
|
}
|
|
|
|
|
2022-04-26 11:12:16 +00:00
|
|
|
engine, err := authPolicy.New(policyOptions)
|
|
|
|
if err != nil {
|
2022-04-19 15:10:13 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-26 11:12:16 +00:00
|
|
|
// only update the policy engine when no error was returned
|
|
|
|
a.policyEngine = engine
|
2022-04-19 15:10:13 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-03-21 14:53:59 +00:00
|
|
|
func isAllowed(engine authPolicy.X509Policy, sans []string) error {
|
2022-04-26 11:12:16 +00:00
|
|
|
if err := engine.AreSANsAllowed(sans); err != nil {
|
2022-03-21 14:53:59 +00:00
|
|
|
var policyErr *policy.NamePolicyError
|
2022-04-04 13:31:28 +00:00
|
|
|
isNamePolicyError := errors.As(err, &policyErr)
|
2022-04-25 23:47:07 +00:00
|
|
|
if isNamePolicyError && policyErr.Reason == policy.NotAllowed {
|
2022-04-04 11:58:16 +00:00
|
|
|
return &PolicyError{
|
|
|
|
Typ: AdminLockOut,
|
2023-02-21 18:25:06 +00:00
|
|
|
Err: fmt.Errorf("the provided policy would lock out %s from the CA. Please create an x509 policy to include %s as an allowed DNS name", sans, sans),
|
2022-04-04 11:58:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return &PolicyError{
|
|
|
|
Typ: EvaluationFailure,
|
2022-04-15 08:43:10 +00:00
|
|
|
Err: err,
|
2022-03-21 14:53:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|