Add certificate issuance, CSR signing, and cert listing to web UI
- Add SignCSR RPC to v2 CA proto and regenerate; implement handleSignCSR
in CA engine and caServer gRPC layer; add SignCSR client method and
POST /pki/sign-csr web route with result display in pki.html
- Fix issuer detail cert listing: template was using map-style index on
CertSummary structs; switch to struct field access and populate
IssuedBy/IssuedAt fields from proto response
- Add certificate detail view (cert_detail.html) with GET /cert/{serial}
and GET /cert/{serial}/download routes
- Update Makefile proto target to generate both v1 and v2 protos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -371,6 +371,55 @@ func (cs *caServer) RenewCert(ctx context.Context, req *pb.RenewCertRequest) (*p
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cs *caServer) SignCSR(ctx context.Context, req *pb.SignCSRRequest) (*pb.SignCSRResponse, error) {
|
||||
if req.Mount == "" || req.Issuer == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "mount and issuer are required")
|
||||
}
|
||||
if len(req.CsrPem) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "csr_pem is required")
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"issuer": req.Issuer,
|
||||
"csr_pem": string(req.CsrPem),
|
||||
}
|
||||
if req.Profile != "" {
|
||||
data["profile"] = req.Profile
|
||||
}
|
||||
if req.Ttl != "" {
|
||||
data["ttl"] = req.Ttl
|
||||
}
|
||||
resp, err := cs.caHandleRequest(ctx, req.Mount, "sign-csr", &engine.Request{
|
||||
Operation: "sign-csr",
|
||||
CallerInfo: cs.callerInfo(ctx),
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serial, _ := resp.Data["serial"].(string)
|
||||
cn, _ := resp.Data["cn"].(string)
|
||||
issuedBy, _ := resp.Data["issued_by"].(string)
|
||||
certPEM, _ := resp.Data["cert_pem"].(string)
|
||||
chainPEM, _ := resp.Data["chain_pem"].(string)
|
||||
sans := toStringSliceFromInterface(resp.Data["sans"])
|
||||
var expiresAt *timestamppb.Timestamp
|
||||
if s, ok := resp.Data["expires_at"].(string); ok {
|
||||
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||
expiresAt = timestamppb.New(t)
|
||||
}
|
||||
}
|
||||
cs.s.logger.Info("audit: CSR signed", "mount", req.Mount, "issuer", req.Issuer, "cn", cn, "serial", serial, "username", callerUsername(ctx))
|
||||
return &pb.SignCSRResponse{
|
||||
Serial: serial,
|
||||
CommonName: cn,
|
||||
Sans: sans,
|
||||
IssuedBy: issuedBy,
|
||||
ExpiresAt: expiresAt,
|
||||
CertPem: []byte(certPEM),
|
||||
ChainPem: []byte(chainPEM),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
||||
|
||||
Reference in New Issue
Block a user