Add certificate revocation, deletion, and retrieval
Admins can now revoke or delete certificate records from the cert detail
page in the web UI. Revoked certificates display a [REVOKED] badge and
show revocation metadata (time and actor). Deletion redirects to the
issuer page.
The REST API gains three new authenticated endpoints that mirror the
gRPC surface:
GET /v1/ca/{mount}/cert/{serial} (auth required)
POST /v1/ca/{mount}/cert/{serial}/revoke (admin only)
DELETE /v1/ca/{mount}/cert/{serial} (admin only)
The CA engine stores revocation state (revoked, revoked_at, revoked_by)
directly in the existing CertRecord barrier entry. The proto CertRecord
message is extended with the same three fields (field numbers 10–12).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -420,6 +420,45 @@ func (cs *caServer) SignCSR(ctx context.Context, req *pb.SignCSRRequest) (*pb.Si
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cs *caServer) RevokeCert(ctx context.Context, req *pb.RevokeCertRequest) (*pb.RevokeCertResponse, error) {
|
||||
if req.Mount == "" || req.Serial == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "mount and serial are required")
|
||||
}
|
||||
resp, err := cs.caHandleRequest(ctx, req.Mount, "revoke-cert", &engine.Request{
|
||||
Operation: "revoke-cert",
|
||||
CallerInfo: cs.callerInfo(ctx),
|
||||
Data: map[string]interface{}{"serial": req.Serial},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serial, _ := resp.Data["serial"].(string)
|
||||
var revokedAt *timestamppb.Timestamp
|
||||
if s, ok := resp.Data["revoked_at"].(string); ok {
|
||||
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||
revokedAt = timestamppb.New(t)
|
||||
}
|
||||
}
|
||||
cs.s.logger.Info("audit: certificate revoked", "mount", req.Mount, "serial", serial, "username", callerUsername(ctx))
|
||||
return &pb.RevokeCertResponse{Serial: serial, RevokedAt: revokedAt}, nil
|
||||
}
|
||||
|
||||
func (cs *caServer) DeleteCert(ctx context.Context, req *pb.DeleteCertRequest) (*pb.DeleteCertResponse, error) {
|
||||
if req.Mount == "" || req.Serial == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "mount and serial are required")
|
||||
}
|
||||
_, err := cs.caHandleRequest(ctx, req.Mount, "delete-cert", &engine.Request{
|
||||
Operation: "delete-cert",
|
||||
CallerInfo: cs.callerInfo(ctx),
|
||||
Data: map[string]interface{}{"serial": req.Serial},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.s.logger.Info("audit: certificate deleted", "mount", req.Mount, "serial", req.Serial, "username", callerUsername(ctx))
|
||||
return &pb.DeleteCertResponse{}, nil
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
||||
@@ -429,8 +468,10 @@ func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
||||
profile, _ := d["profile"].(string)
|
||||
issuedBy, _ := d["issued_by"].(string)
|
||||
certPEM, _ := d["cert_pem"].(string)
|
||||
revoked, _ := d["revoked"].(bool)
|
||||
revokedBy, _ := d["revoked_by"].(string)
|
||||
sans := toStringSliceFromInterface(d["sans"])
|
||||
var issuedAt, expiresAt *timestamppb.Timestamp
|
||||
var issuedAt, expiresAt, revokedAt *timestamppb.Timestamp
|
||||
if s, ok := d["issued_at"].(string); ok {
|
||||
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||
issuedAt = timestamppb.New(t)
|
||||
@@ -441,6 +482,11 @@ func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
||||
expiresAt = timestamppb.New(t)
|
||||
}
|
||||
}
|
||||
if s, ok := d["revoked_at"].(string); ok {
|
||||
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||
revokedAt = timestamppb.New(t)
|
||||
}
|
||||
}
|
||||
return &pb.CertRecord{
|
||||
Serial: serial,
|
||||
Issuer: issuer,
|
||||
@@ -451,6 +497,9 @@ func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
||||
IssuedAt: issuedAt,
|
||||
ExpiresAt: expiresAt,
|
||||
CertPem: []byte(certPEM),
|
||||
Revoked: revoked,
|
||||
RevokedAt: revokedAt,
|
||||
RevokedBy: revokedBy,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user