diff --git a/builtin/logical/pki/backend.go b/builtin/logical/pki/backend.go index 819ff3e55a5f..4521585d77ce 100644 --- a/builtin/logical/pki/backend.go +++ b/builtin/logical/pki/backend.go @@ -74,6 +74,7 @@ func Backend(conf *logical.BackendConfig) *backend { pathFetchValid(&b), pathFetchListCerts(&b), pathRevoke(&b), + pathRevokeByRole(&b), pathTidy(&b), }, diff --git a/builtin/logical/pki/crl_util.go b/builtin/logical/pki/crl_util.go index 9e046ffcfc63..53918c993f28 100644 --- a/builtin/logical/pki/crl_util.go +++ b/builtin/logical/pki/crl_util.go @@ -23,7 +23,7 @@ type revocationInfo struct { } // Revokes a cert, and tries to be smart about error recovery -func revokeCert(ctx context.Context, b *backend, req *logical.Request, serial string, fromLease bool) (*logical.Response, error) { +func revokeCert(ctx context.Context, b *backend, req *logical.Request, serial string, roleName string, fromLease bool) (*logical.Response, error) { // As this backend is self-contained and this function does not hook into // third parties to manage users or resources, if the mount is tainted, // revocation doesn't matter anyways -- the CRL that would be written will @@ -45,7 +45,7 @@ func revokeCert(ctx context.Context, b *backend, req *logical.Request, serial st } colonSerial := strings.Replace(strings.ToLower(serial), "-", ":", -1) if colonSerial == certutil.GetHexFormatted(signingBundle.Certificate.SerialNumber.Bytes(), ":") { - return logical.ErrorResponse("adding CA to CRL is not allowed"), nil + return logical.ErrorResponse("adding Root CA to CRL is not allowed"), nil } alreadyRevoked := false @@ -97,6 +97,12 @@ func revokeCert(ctx context.Context, b *backend, req *logical.Request, serial st if cert == nil { return nil, fmt.Errorf("got a nil certificate") } + // Check if we can do anything based on the roleName + if roleName != "" { // if no roleName was supplied, the user had general revoke permission + if check, err := checkCNrole(ctx, b, req, roleName, cert.Issuer.CommonName); check { + return nil, err + } + } // Add a little wiggle room because leases are stored with a second // granularity @@ -146,6 +152,32 @@ func revokeCert(ctx context.Context, b *backend, req *logical.Request, serial st return resp, nil } +func checkCNrole(ctx context.Context, b *backend, req *logical.Request, roleName string, cn string) (bool, error) { + // Get the role + role, err := b.getRole(ctx, req.Storage, roleName) + if err != nil { + return false, err + } + if role == nil { + return false, errutil.UserError{Err: fmt.Sprintf("unknown role: %s", roleName)} + } + + // Create an inputBundle from the role. This is so we can use the preexisting helper + // to validate the CN for revocation. + roleInputBundle := &inputBundle{role: role} + + // Check the CN. This ensures that the CN is checked even if it's + // excluded from SANs. + if cn != "" { + badName := validateNames(b, roleInputBundle, []string{cn}) + if len(badName) != 0 { + return false, errutil.UserError{Err: fmt.Sprintf( + "common name %s not allowed by this role", badName)} + } + } + return true, nil +} + // Builds a CRL by going through the list of revoked certificates and building // a new CRL with the stored revocation times and serial numbers. func buildCRL(ctx context.Context, b *backend, req *logical.Request, forceNew bool) error { diff --git a/builtin/logical/pki/path_revoke.go b/builtin/logical/pki/path_revoke.go index 1906e24bb254..4f4af02ba2da 100644 --- a/builtin/logical/pki/path_revoke.go +++ b/builtin/logical/pki/path_revoke.go @@ -32,6 +32,30 @@ hyphen-separated octal`, } } +func pathRevokeByRole(b *backend) *framework.Path { + return &framework.Path{ + Pattern: `revoke/` + framework.GenericNameRegex("name"), + Fields: map[string]*framework.FieldSchema{ + "serial_number": &framework.FieldSchema{ + Type: framework.TypeString, + Description: `Certificate serial number, in colon- or +hyphen-separated octal`, + }, + "name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "Name of the role", + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.UpdateOperation: b.pathRevokeWrite, + }, + + HelpSynopsis: pathRevokeHelpSyn, + HelpDescription: pathRevokeHelpDesc, + } +} + func pathRotateCRL(b *backend) *framework.Path { return &framework.Path{ Pattern: `crl/rotate`, @@ -47,6 +71,12 @@ func pathRotateCRL(b *backend) *framework.Path { func (b *backend) pathRevokeWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { serial := data.Get("serial_number").(string) + roleNameRaw, ok := data.GetOk("name") + if !ok { + roleNameRaw = "" + } + roleName := roleNameRaw.(string) + if len(serial) == 0 { return logical.ErrorResponse("The serial number must be provided"), nil } @@ -62,7 +92,7 @@ func (b *backend) pathRevokeWrite(ctx context.Context, req *logical.Request, dat b.revokeStorageLock.Lock() defer b.revokeStorageLock.Unlock() - return revokeCert(ctx, b, req, serial, false) + return revokeCert(ctx, b, req, serial, roleName, false) } func (b *backend) pathRotateCRLRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { diff --git a/builtin/logical/pki/secret_certs.go b/builtin/logical/pki/secret_certs.go index 3244004e399c..8f8e30b3d95f 100644 --- a/builtin/logical/pki/secret_certs.go +++ b/builtin/logical/pki/secret_certs.go @@ -48,5 +48,6 @@ func (b *backend) secretCredsRevoke(ctx context.Context, req *logical.Request, d b.revokeStorageLock.Lock() defer b.revokeStorageLock.Unlock() - return revokeCert(ctx, b, req, serialInt.(string), true) + // TODO: Come back to this + return revokeCert(ctx, b, req, serialInt.(string), "", true) }