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:
@@ -14,3 +14,11 @@
|
|||||||
"NEW INSTRUCTION": "WHEN editing gRPC server configuration THEN remove v1 config and use only v2 fields"
|
"NEW INSTRUCTION": "WHEN editing gRPC server configuration THEN remove v1 config and use only v2 fields"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[2026-03-15 13:34] - Updated by Junie
|
||||||
|
{
|
||||||
|
"TYPE": "negative",
|
||||||
|
"CATEGORY": "tarball download",
|
||||||
|
"EXPECTATION": "The tarball download should succeed, or clearly show an error in the browser when it fails.",
|
||||||
|
"NEW INSTRUCTION": "WHEN implementing download endpoints THEN return non-200 on failure with an explanatory message"
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"lang":"en","usageCount":36}]
|
[{"lang":"en","usageCount":37}]
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.33.4
|
// protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/acme.proto
|
// source: proto/metacrypt/v1/acme.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v6.33.4
|
// - protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/acme.proto
|
// source: proto/metacrypt/v1/acme.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.33.4
|
// protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/auth.proto
|
// source: proto/metacrypt/v1/auth.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v6.33.4
|
// - protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/auth.proto
|
// source: proto/metacrypt/v1/auth.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.33.4
|
// protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/common.proto
|
// source: proto/metacrypt/v1/common.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.33.4
|
// protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/engine.proto
|
// source: proto/metacrypt/v1/engine.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v6.33.4
|
// - protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/engine.proto
|
// source: proto/metacrypt/v1/engine.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.33.4
|
// protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/pki.proto
|
// source: proto/metacrypt/v1/pki.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v6.33.4
|
// - protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/pki.proto
|
// source: proto/metacrypt/v1/pki.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.33.4
|
// protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/policy.proto
|
// source: proto/metacrypt/v1/policy.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v6.33.4
|
// - protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/policy.proto
|
// source: proto/metacrypt/v1/policy.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.11
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.33.4
|
// protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/system.proto
|
// source: proto/metacrypt/v1/system.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v6.33.4
|
// - protoc v3.20.3
|
||||||
// source: proto/metacrypt/v1/system.proto
|
// source: proto/metacrypt/v1/system.proto
|
||||||
|
|
||||||
package metacryptv1
|
package metacryptv1
|
||||||
|
|||||||
@@ -1484,6 +1484,198 @@ func (x *SignCSRResponse) GetChainPem() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RevokeCertRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"`
|
||||||
|
Serial string `protobuf:"bytes,2,opt,name=serial,proto3" json:"serial,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertRequest) Reset() {
|
||||||
|
*x = RevokeCertRequest{}
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[24]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RevokeCertRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RevokeCertRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[24]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RevokeCertRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RevokeCertRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{24}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertRequest) GetMount() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Mount
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertRequest) GetSerial() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Serial
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type RevokeCertResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Serial string `protobuf:"bytes,1,opt,name=serial,proto3" json:"serial,omitempty"`
|
||||||
|
RevokedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=revoked_at,json=revokedAt,proto3" json:"revoked_at,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertResponse) Reset() {
|
||||||
|
*x = RevokeCertResponse{}
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[25]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RevokeCertResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RevokeCertResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[25]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RevokeCertResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RevokeCertResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{25}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertResponse) GetSerial() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Serial
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RevokeCertResponse) GetRevokedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.RevokedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteCertRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"`
|
||||||
|
Serial string `protobuf:"bytes,2,opt,name=serial,proto3" json:"serial,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCertRequest) Reset() {
|
||||||
|
*x = DeleteCertRequest{}
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[26]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCertRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteCertRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteCertRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[26]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeleteCertRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteCertRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{26}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCertRequest) GetMount() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Mount
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCertRequest) GetSerial() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Serial
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteCertResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCertResponse) Reset() {
|
||||||
|
*x = DeleteCertResponse{}
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[27]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCertResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteCertResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteCertResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[27]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeleteCertResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteCertResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{27}
|
||||||
|
}
|
||||||
|
|
||||||
// CertRecord is the full certificate record including the PEM-encoded cert.
|
// CertRecord is the full certificate record including the PEM-encoded cert.
|
||||||
type CertRecord struct {
|
type CertRecord struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
@@ -1497,13 +1689,19 @@ type CertRecord struct {
|
|||||||
ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
|
ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
|
||||||
// cert_pem is the PEM-encoded certificate.
|
// cert_pem is the PEM-encoded certificate.
|
||||||
CertPem []byte `protobuf:"bytes,9,opt,name=cert_pem,json=certPem,proto3" json:"cert_pem,omitempty"`
|
CertPem []byte `protobuf:"bytes,9,opt,name=cert_pem,json=certPem,proto3" json:"cert_pem,omitempty"`
|
||||||
|
// revoked indicates whether the certificate has been revoked.
|
||||||
|
Revoked bool `protobuf:"varint,10,opt,name=revoked,proto3" json:"revoked,omitempty"`
|
||||||
|
// revoked_at is the time the certificate was revoked, if applicable.
|
||||||
|
RevokedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=revoked_at,json=revokedAt,proto3" json:"revoked_at,omitempty"`
|
||||||
|
// revoked_by is the username of the admin who revoked the certificate.
|
||||||
|
RevokedBy string `protobuf:"bytes,12,opt,name=revoked_by,json=revokedBy,proto3" json:"revoked_by,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CertRecord) Reset() {
|
func (x *CertRecord) Reset() {
|
||||||
*x = CertRecord{}
|
*x = CertRecord{}
|
||||||
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[24]
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[28]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1515,7 +1713,7 @@ func (x *CertRecord) String() string {
|
|||||||
func (*CertRecord) ProtoMessage() {}
|
func (*CertRecord) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CertRecord) ProtoReflect() protoreflect.Message {
|
func (x *CertRecord) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[24]
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[28]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1528,7 +1726,7 @@ func (x *CertRecord) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CertRecord.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CertRecord.ProtoReflect.Descriptor instead.
|
||||||
func (*CertRecord) Descriptor() ([]byte, []int) {
|
func (*CertRecord) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{24}
|
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{28}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CertRecord) GetSerial() string {
|
func (x *CertRecord) GetSerial() string {
|
||||||
@@ -1594,6 +1792,27 @@ func (x *CertRecord) GetCertPem() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *CertRecord) GetRevoked() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Revoked
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CertRecord) GetRevokedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.RevokedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CertRecord) GetRevokedBy() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RevokedBy
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// CertSummary is a lightweight certificate record without the PEM data,
|
// CertSummary is a lightweight certificate record without the PEM data,
|
||||||
// suitable for list responses.
|
// suitable for list responses.
|
||||||
type CertSummary struct {
|
type CertSummary struct {
|
||||||
@@ -1611,7 +1830,7 @@ type CertSummary struct {
|
|||||||
|
|
||||||
func (x *CertSummary) Reset() {
|
func (x *CertSummary) Reset() {
|
||||||
*x = CertSummary{}
|
*x = CertSummary{}
|
||||||
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[25]
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[29]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1623,7 +1842,7 @@ func (x *CertSummary) String() string {
|
|||||||
func (*CertSummary) ProtoMessage() {}
|
func (*CertSummary) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CertSummary) ProtoReflect() protoreflect.Message {
|
func (x *CertSummary) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[25]
|
mi := &file_proto_metacrypt_v2_ca_proto_msgTypes[29]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -1636,7 +1855,7 @@ func (x *CertSummary) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CertSummary.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CertSummary.ProtoReflect.Descriptor instead.
|
||||||
func (*CertSummary) Descriptor() ([]byte, []int) {
|
func (*CertSummary) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{25}
|
return file_proto_metacrypt_v2_ca_proto_rawDescGZIP(), []int{29}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CertSummary) GetSerial() string {
|
func (x *CertSummary) GetSerial() string {
|
||||||
@@ -1798,7 +2017,18 @@ const file_proto_metacrypt_v2_ca_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x19\n" +
|
"expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x19\n" +
|
||||||
"\bcert_pem\x18\x06 \x01(\fR\acertPem\x12\x1b\n" +
|
"\bcert_pem\x18\x06 \x01(\fR\acertPem\x12\x1b\n" +
|
||||||
"\tchain_pem\x18\a \x01(\fR\bchainPem\"\xb7\x02\n" +
|
"\tchain_pem\x18\a \x01(\fR\bchainPem\"A\n" +
|
||||||
|
"\x11RevokeCertRequest\x12\x14\n" +
|
||||||
|
"\x05mount\x18\x01 \x01(\tR\x05mount\x12\x16\n" +
|
||||||
|
"\x06serial\x18\x02 \x01(\tR\x06serial\"g\n" +
|
||||||
|
"\x12RevokeCertResponse\x12\x16\n" +
|
||||||
|
"\x06serial\x18\x01 \x01(\tR\x06serial\x129\n" +
|
||||||
|
"\n" +
|
||||||
|
"revoked_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\trevokedAt\"A\n" +
|
||||||
|
"\x11DeleteCertRequest\x12\x14\n" +
|
||||||
|
"\x05mount\x18\x01 \x01(\tR\x05mount\x12\x16\n" +
|
||||||
|
"\x06serial\x18\x02 \x01(\tR\x06serial\"\x14\n" +
|
||||||
|
"\x12DeleteCertResponse\"\xab\x03\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"CertRecord\x12\x16\n" +
|
"CertRecord\x12\x16\n" +
|
||||||
"\x06serial\x18\x01 \x01(\tR\x06serial\x12\x16\n" +
|
"\x06serial\x18\x01 \x01(\tR\x06serial\x12\x16\n" +
|
||||||
@@ -1811,7 +2041,13 @@ const file_proto_metacrypt_v2_ca_proto_rawDesc = "" +
|
|||||||
"\tissued_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x129\n" +
|
"\tissued_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x129\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"expires_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x19\n" +
|
"expires_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x19\n" +
|
||||||
"\bcert_pem\x18\t \x01(\fR\acertPem\"\x89\x02\n" +
|
"\bcert_pem\x18\t \x01(\fR\acertPem\x12\x18\n" +
|
||||||
|
"\arevoked\x18\n" +
|
||||||
|
" \x01(\bR\arevoked\x129\n" +
|
||||||
|
"\n" +
|
||||||
|
"revoked_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\trevokedAt\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"revoked_by\x18\f \x01(\tR\trevokedBy\"\x89\x02\n" +
|
||||||
"\vCertSummary\x12\x16\n" +
|
"\vCertSummary\x12\x16\n" +
|
||||||
"\x06serial\x18\x01 \x01(\tR\x06serial\x12\x16\n" +
|
"\x06serial\x18\x01 \x01(\tR\x06serial\x12\x16\n" +
|
||||||
"\x06issuer\x18\x02 \x01(\tR\x06issuer\x12\x1f\n" +
|
"\x06issuer\x18\x02 \x01(\tR\x06issuer\x12\x1f\n" +
|
||||||
@@ -1821,7 +2057,7 @@ const file_proto_metacrypt_v2_ca_proto_rawDesc = "" +
|
|||||||
"\tissued_by\x18\x05 \x01(\tR\bissuedBy\x127\n" +
|
"\tissued_by\x18\x05 \x01(\tR\bissuedBy\x127\n" +
|
||||||
"\tissued_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x129\n" +
|
"\tissued_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x129\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"expires_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt2\xcb\a\n" +
|
"expires_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt2\xed\b\n" +
|
||||||
"\tCAService\x12O\n" +
|
"\tCAService\x12O\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"ImportRoot\x12\x1f.metacrypt.v2.ImportRootRequest\x1a .metacrypt.v2.ImportRootResponse\x12F\n" +
|
"ImportRoot\x12\x1f.metacrypt.v2.ImportRootRequest\x1a .metacrypt.v2.ImportRootResponse\x12F\n" +
|
||||||
@@ -1835,7 +2071,11 @@ const file_proto_metacrypt_v2_ca_proto_rawDesc = "" +
|
|||||||
"\aGetCert\x12\x1c.metacrypt.v2.GetCertRequest\x1a\x1d.metacrypt.v2.GetCertResponse\x12L\n" +
|
"\aGetCert\x12\x1c.metacrypt.v2.GetCertRequest\x1a\x1d.metacrypt.v2.GetCertResponse\x12L\n" +
|
||||||
"\tListCerts\x12\x1e.metacrypt.v2.ListCertsRequest\x1a\x1f.metacrypt.v2.ListCertsResponse\x12L\n" +
|
"\tListCerts\x12\x1e.metacrypt.v2.ListCertsRequest\x1a\x1f.metacrypt.v2.ListCertsResponse\x12L\n" +
|
||||||
"\tRenewCert\x12\x1e.metacrypt.v2.RenewCertRequest\x1a\x1f.metacrypt.v2.RenewCertResponse\x12F\n" +
|
"\tRenewCert\x12\x1e.metacrypt.v2.RenewCertRequest\x1a\x1f.metacrypt.v2.RenewCertResponse\x12F\n" +
|
||||||
"\aSignCSR\x12\x1c.metacrypt.v2.SignCSRRequest\x1a\x1d.metacrypt.v2.SignCSRResponseB>Z<git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2;metacryptv2b\x06proto3"
|
"\aSignCSR\x12\x1c.metacrypt.v2.SignCSRRequest\x1a\x1d.metacrypt.v2.SignCSRResponse\x12O\n" +
|
||||||
|
"\n" +
|
||||||
|
"RevokeCert\x12\x1f.metacrypt.v2.RevokeCertRequest\x1a .metacrypt.v2.RevokeCertResponse\x12O\n" +
|
||||||
|
"\n" +
|
||||||
|
"DeleteCert\x12\x1f.metacrypt.v2.DeleteCertRequest\x1a .metacrypt.v2.DeleteCertResponseB>Z<git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2;metacryptv2b\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_proto_metacrypt_v2_ca_proto_rawDescOnce sync.Once
|
file_proto_metacrypt_v2_ca_proto_rawDescOnce sync.Once
|
||||||
@@ -1849,7 +2089,7 @@ func file_proto_metacrypt_v2_ca_proto_rawDescGZIP() []byte {
|
|||||||
return file_proto_metacrypt_v2_ca_proto_rawDescData
|
return file_proto_metacrypt_v2_ca_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proto_metacrypt_v2_ca_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
|
var file_proto_metacrypt_v2_ca_proto_msgTypes = make([]protoimpl.MessageInfo, 30)
|
||||||
var file_proto_metacrypt_v2_ca_proto_goTypes = []any{
|
var file_proto_metacrypt_v2_ca_proto_goTypes = []any{
|
||||||
(*ImportRootRequest)(nil), // 0: metacrypt.v2.ImportRootRequest
|
(*ImportRootRequest)(nil), // 0: metacrypt.v2.ImportRootRequest
|
||||||
(*ImportRootResponse)(nil), // 1: metacrypt.v2.ImportRootResponse
|
(*ImportRootResponse)(nil), // 1: metacrypt.v2.ImportRootResponse
|
||||||
@@ -1875,50 +2115,60 @@ var file_proto_metacrypt_v2_ca_proto_goTypes = []any{
|
|||||||
(*RenewCertResponse)(nil), // 21: metacrypt.v2.RenewCertResponse
|
(*RenewCertResponse)(nil), // 21: metacrypt.v2.RenewCertResponse
|
||||||
(*SignCSRRequest)(nil), // 22: metacrypt.v2.SignCSRRequest
|
(*SignCSRRequest)(nil), // 22: metacrypt.v2.SignCSRRequest
|
||||||
(*SignCSRResponse)(nil), // 23: metacrypt.v2.SignCSRResponse
|
(*SignCSRResponse)(nil), // 23: metacrypt.v2.SignCSRResponse
|
||||||
(*CertRecord)(nil), // 24: metacrypt.v2.CertRecord
|
(*RevokeCertRequest)(nil), // 24: metacrypt.v2.RevokeCertRequest
|
||||||
(*CertSummary)(nil), // 25: metacrypt.v2.CertSummary
|
(*RevokeCertResponse)(nil), // 25: metacrypt.v2.RevokeCertResponse
|
||||||
(*timestamppb.Timestamp)(nil), // 26: google.protobuf.Timestamp
|
(*DeleteCertRequest)(nil), // 26: metacrypt.v2.DeleteCertRequest
|
||||||
|
(*DeleteCertResponse)(nil), // 27: metacrypt.v2.DeleteCertResponse
|
||||||
|
(*CertRecord)(nil), // 28: metacrypt.v2.CertRecord
|
||||||
|
(*CertSummary)(nil), // 29: metacrypt.v2.CertSummary
|
||||||
|
(*timestamppb.Timestamp)(nil), // 30: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_proto_metacrypt_v2_ca_proto_depIdxs = []int32{
|
var file_proto_metacrypt_v2_ca_proto_depIdxs = []int32{
|
||||||
26, // 0: metacrypt.v2.ImportRootResponse.expires_at:type_name -> google.protobuf.Timestamp
|
30, // 0: metacrypt.v2.ImportRootResponse.expires_at:type_name -> google.protobuf.Timestamp
|
||||||
26, // 1: metacrypt.v2.IssueCertResponse.expires_at:type_name -> google.protobuf.Timestamp
|
30, // 1: metacrypt.v2.IssueCertResponse.expires_at:type_name -> google.protobuf.Timestamp
|
||||||
24, // 2: metacrypt.v2.GetCertResponse.cert:type_name -> metacrypt.v2.CertRecord
|
28, // 2: metacrypt.v2.GetCertResponse.cert:type_name -> metacrypt.v2.CertRecord
|
||||||
25, // 3: metacrypt.v2.ListCertsResponse.certs:type_name -> metacrypt.v2.CertSummary
|
29, // 3: metacrypt.v2.ListCertsResponse.certs:type_name -> metacrypt.v2.CertSummary
|
||||||
26, // 4: metacrypt.v2.RenewCertResponse.expires_at:type_name -> google.protobuf.Timestamp
|
30, // 4: metacrypt.v2.RenewCertResponse.expires_at:type_name -> google.protobuf.Timestamp
|
||||||
26, // 5: metacrypt.v2.SignCSRResponse.expires_at:type_name -> google.protobuf.Timestamp
|
30, // 5: metacrypt.v2.SignCSRResponse.expires_at:type_name -> google.protobuf.Timestamp
|
||||||
26, // 6: metacrypt.v2.CertRecord.issued_at:type_name -> google.protobuf.Timestamp
|
30, // 6: metacrypt.v2.RevokeCertResponse.revoked_at:type_name -> google.protobuf.Timestamp
|
||||||
26, // 7: metacrypt.v2.CertRecord.expires_at:type_name -> google.protobuf.Timestamp
|
30, // 7: metacrypt.v2.CertRecord.issued_at:type_name -> google.protobuf.Timestamp
|
||||||
26, // 8: metacrypt.v2.CertSummary.issued_at:type_name -> google.protobuf.Timestamp
|
30, // 8: metacrypt.v2.CertRecord.expires_at:type_name -> google.protobuf.Timestamp
|
||||||
26, // 9: metacrypt.v2.CertSummary.expires_at:type_name -> google.protobuf.Timestamp
|
30, // 9: metacrypt.v2.CertRecord.revoked_at:type_name -> google.protobuf.Timestamp
|
||||||
0, // 10: metacrypt.v2.CAService.ImportRoot:input_type -> metacrypt.v2.ImportRootRequest
|
30, // 10: metacrypt.v2.CertSummary.issued_at:type_name -> google.protobuf.Timestamp
|
||||||
2, // 11: metacrypt.v2.CAService.GetRoot:input_type -> metacrypt.v2.GetRootRequest
|
30, // 11: metacrypt.v2.CertSummary.expires_at:type_name -> google.protobuf.Timestamp
|
||||||
4, // 12: metacrypt.v2.CAService.CreateIssuer:input_type -> metacrypt.v2.CreateIssuerRequest
|
0, // 12: metacrypt.v2.CAService.ImportRoot:input_type -> metacrypt.v2.ImportRootRequest
|
||||||
6, // 13: metacrypt.v2.CAService.DeleteIssuer:input_type -> metacrypt.v2.DeleteIssuerRequest
|
2, // 13: metacrypt.v2.CAService.GetRoot:input_type -> metacrypt.v2.GetRootRequest
|
||||||
8, // 14: metacrypt.v2.CAService.ListIssuers:input_type -> metacrypt.v2.ListIssuersRequest
|
4, // 14: metacrypt.v2.CAService.CreateIssuer:input_type -> metacrypt.v2.CreateIssuerRequest
|
||||||
10, // 15: metacrypt.v2.CAService.GetIssuer:input_type -> metacrypt.v2.GetIssuerRequest
|
6, // 15: metacrypt.v2.CAService.DeleteIssuer:input_type -> metacrypt.v2.DeleteIssuerRequest
|
||||||
12, // 16: metacrypt.v2.CAService.GetChain:input_type -> metacrypt.v2.CAServiceGetChainRequest
|
8, // 16: metacrypt.v2.CAService.ListIssuers:input_type -> metacrypt.v2.ListIssuersRequest
|
||||||
14, // 17: metacrypt.v2.CAService.IssueCert:input_type -> metacrypt.v2.IssueCertRequest
|
10, // 17: metacrypt.v2.CAService.GetIssuer:input_type -> metacrypt.v2.GetIssuerRequest
|
||||||
16, // 18: metacrypt.v2.CAService.GetCert:input_type -> metacrypt.v2.GetCertRequest
|
12, // 18: metacrypt.v2.CAService.GetChain:input_type -> metacrypt.v2.CAServiceGetChainRequest
|
||||||
18, // 19: metacrypt.v2.CAService.ListCerts:input_type -> metacrypt.v2.ListCertsRequest
|
14, // 19: metacrypt.v2.CAService.IssueCert:input_type -> metacrypt.v2.IssueCertRequest
|
||||||
20, // 20: metacrypt.v2.CAService.RenewCert:input_type -> metacrypt.v2.RenewCertRequest
|
16, // 20: metacrypt.v2.CAService.GetCert:input_type -> metacrypt.v2.GetCertRequest
|
||||||
22, // 21: metacrypt.v2.CAService.SignCSR:input_type -> metacrypt.v2.SignCSRRequest
|
18, // 21: metacrypt.v2.CAService.ListCerts:input_type -> metacrypt.v2.ListCertsRequest
|
||||||
1, // 22: metacrypt.v2.CAService.ImportRoot:output_type -> metacrypt.v2.ImportRootResponse
|
20, // 22: metacrypt.v2.CAService.RenewCert:input_type -> metacrypt.v2.RenewCertRequest
|
||||||
3, // 23: metacrypt.v2.CAService.GetRoot:output_type -> metacrypt.v2.GetRootResponse
|
22, // 23: metacrypt.v2.CAService.SignCSR:input_type -> metacrypt.v2.SignCSRRequest
|
||||||
5, // 24: metacrypt.v2.CAService.CreateIssuer:output_type -> metacrypt.v2.CreateIssuerResponse
|
24, // 24: metacrypt.v2.CAService.RevokeCert:input_type -> metacrypt.v2.RevokeCertRequest
|
||||||
7, // 25: metacrypt.v2.CAService.DeleteIssuer:output_type -> metacrypt.v2.DeleteIssuerResponse
|
26, // 25: metacrypt.v2.CAService.DeleteCert:input_type -> metacrypt.v2.DeleteCertRequest
|
||||||
9, // 26: metacrypt.v2.CAService.ListIssuers:output_type -> metacrypt.v2.ListIssuersResponse
|
1, // 26: metacrypt.v2.CAService.ImportRoot:output_type -> metacrypt.v2.ImportRootResponse
|
||||||
11, // 27: metacrypt.v2.CAService.GetIssuer:output_type -> metacrypt.v2.GetIssuerResponse
|
3, // 27: metacrypt.v2.CAService.GetRoot:output_type -> metacrypt.v2.GetRootResponse
|
||||||
13, // 28: metacrypt.v2.CAService.GetChain:output_type -> metacrypt.v2.CAServiceGetChainResponse
|
5, // 28: metacrypt.v2.CAService.CreateIssuer:output_type -> metacrypt.v2.CreateIssuerResponse
|
||||||
15, // 29: metacrypt.v2.CAService.IssueCert:output_type -> metacrypt.v2.IssueCertResponse
|
7, // 29: metacrypt.v2.CAService.DeleteIssuer:output_type -> metacrypt.v2.DeleteIssuerResponse
|
||||||
17, // 30: metacrypt.v2.CAService.GetCert:output_type -> metacrypt.v2.GetCertResponse
|
9, // 30: metacrypt.v2.CAService.ListIssuers:output_type -> metacrypt.v2.ListIssuersResponse
|
||||||
19, // 31: metacrypt.v2.CAService.ListCerts:output_type -> metacrypt.v2.ListCertsResponse
|
11, // 31: metacrypt.v2.CAService.GetIssuer:output_type -> metacrypt.v2.GetIssuerResponse
|
||||||
21, // 32: metacrypt.v2.CAService.RenewCert:output_type -> metacrypt.v2.RenewCertResponse
|
13, // 32: metacrypt.v2.CAService.GetChain:output_type -> metacrypt.v2.CAServiceGetChainResponse
|
||||||
23, // 33: metacrypt.v2.CAService.SignCSR:output_type -> metacrypt.v2.SignCSRResponse
|
15, // 33: metacrypt.v2.CAService.IssueCert:output_type -> metacrypt.v2.IssueCertResponse
|
||||||
22, // [22:34] is the sub-list for method output_type
|
17, // 34: metacrypt.v2.CAService.GetCert:output_type -> metacrypt.v2.GetCertResponse
|
||||||
10, // [10:22] is the sub-list for method input_type
|
19, // 35: metacrypt.v2.CAService.ListCerts:output_type -> metacrypt.v2.ListCertsResponse
|
||||||
10, // [10:10] is the sub-list for extension type_name
|
21, // 36: metacrypt.v2.CAService.RenewCert:output_type -> metacrypt.v2.RenewCertResponse
|
||||||
10, // [10:10] is the sub-list for extension extendee
|
23, // 37: metacrypt.v2.CAService.SignCSR:output_type -> metacrypt.v2.SignCSRResponse
|
||||||
0, // [0:10] is the sub-list for field type_name
|
25, // 38: metacrypt.v2.CAService.RevokeCert:output_type -> metacrypt.v2.RevokeCertResponse
|
||||||
|
27, // 39: metacrypt.v2.CAService.DeleteCert:output_type -> metacrypt.v2.DeleteCertResponse
|
||||||
|
26, // [26:40] is the sub-list for method output_type
|
||||||
|
12, // [12:26] is the sub-list for method input_type
|
||||||
|
12, // [12:12] is the sub-list for extension type_name
|
||||||
|
12, // [12:12] is the sub-list for extension extendee
|
||||||
|
0, // [0:12] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proto_metacrypt_v2_ca_proto_init() }
|
func init() { file_proto_metacrypt_v2_ca_proto_init() }
|
||||||
@@ -1932,7 +2182,7 @@ func file_proto_metacrypt_v2_ca_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_metacrypt_v2_ca_proto_rawDesc), len(file_proto_metacrypt_v2_ca_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_metacrypt_v2_ca_proto_rawDesc), len(file_proto_metacrypt_v2_ca_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 26,
|
NumMessages: 30,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const (
|
|||||||
CAService_ListCerts_FullMethodName = "/metacrypt.v2.CAService/ListCerts"
|
CAService_ListCerts_FullMethodName = "/metacrypt.v2.CAService/ListCerts"
|
||||||
CAService_RenewCert_FullMethodName = "/metacrypt.v2.CAService/RenewCert"
|
CAService_RenewCert_FullMethodName = "/metacrypt.v2.CAService/RenewCert"
|
||||||
CAService_SignCSR_FullMethodName = "/metacrypt.v2.CAService/SignCSR"
|
CAService_SignCSR_FullMethodName = "/metacrypt.v2.CAService/SignCSR"
|
||||||
|
CAService_RevokeCert_FullMethodName = "/metacrypt.v2.CAService/RevokeCert"
|
||||||
|
CAService_DeleteCert_FullMethodName = "/metacrypt.v2.CAService/DeleteCert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CAServiceClient is the client API for CAService service.
|
// CAServiceClient is the client API for CAService service.
|
||||||
@@ -40,8 +42,8 @@ const (
|
|||||||
// CAService provides typed, authenticated access to CA engine operations.
|
// CAService provides typed, authenticated access to CA engine operations.
|
||||||
// All RPCs require the service to be unsealed. Write operations (CreateIssuer,
|
// All RPCs require the service to be unsealed. Write operations (CreateIssuer,
|
||||||
// DeleteIssuer, ImportRoot, IssueCert, RenewCert) require authentication.
|
// DeleteIssuer, ImportRoot, IssueCert, RenewCert) require authentication.
|
||||||
// Admin-only operations (CreateIssuer, DeleteIssuer, ImportRoot) additionally
|
// Admin-only operations (CreateIssuer, DeleteIssuer, ImportRoot, RevokeCert,
|
||||||
// require the caller to have admin privileges.
|
// DeleteCert) additionally require the caller to have admin privileges.
|
||||||
type CAServiceClient interface {
|
type CAServiceClient interface {
|
||||||
// ImportRoot imports an existing root CA certificate and private key.
|
// ImportRoot imports an existing root CA certificate and private key.
|
||||||
// Admin only. Only allowed when no valid root exists.
|
// Admin only. Only allowed when no valid root exists.
|
||||||
@@ -72,6 +74,11 @@ type CAServiceClient interface {
|
|||||||
// and SAN fields from the CSR are preserved exactly; profile defaults supply
|
// and SAN fields from the CSR are preserved exactly; profile defaults supply
|
||||||
// key usages and validity if not overridden. Auth required.
|
// key usages and validity if not overridden. Auth required.
|
||||||
SignCSR(ctx context.Context, in *SignCSRRequest, opts ...grpc.CallOption) (*SignCSRResponse, error)
|
SignCSR(ctx context.Context, in *SignCSRRequest, opts ...grpc.CallOption) (*SignCSRResponse, error)
|
||||||
|
// RevokeCert marks a certificate as revoked by serial number. Admin only.
|
||||||
|
RevokeCert(ctx context.Context, in *RevokeCertRequest, opts ...grpc.CallOption) (*RevokeCertResponse, error)
|
||||||
|
// DeleteCert permanently removes a certificate record by serial number.
|
||||||
|
// Admin only.
|
||||||
|
DeleteCert(ctx context.Context, in *DeleteCertRequest, opts ...grpc.CallOption) (*DeleteCertResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type cAServiceClient struct {
|
type cAServiceClient struct {
|
||||||
@@ -202,6 +209,26 @@ func (c *cAServiceClient) SignCSR(ctx context.Context, in *SignCSRRequest, opts
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cAServiceClient) RevokeCert(ctx context.Context, in *RevokeCertRequest, opts ...grpc.CallOption) (*RevokeCertResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(RevokeCertResponse)
|
||||||
|
err := c.cc.Invoke(ctx, CAService_RevokeCert_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cAServiceClient) DeleteCert(ctx context.Context, in *DeleteCertRequest, opts ...grpc.CallOption) (*DeleteCertResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(DeleteCertResponse)
|
||||||
|
err := c.cc.Invoke(ctx, CAService_DeleteCert_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CAServiceServer is the server API for CAService service.
|
// CAServiceServer is the server API for CAService service.
|
||||||
// All implementations must embed UnimplementedCAServiceServer
|
// All implementations must embed UnimplementedCAServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -209,8 +236,8 @@ func (c *cAServiceClient) SignCSR(ctx context.Context, in *SignCSRRequest, opts
|
|||||||
// CAService provides typed, authenticated access to CA engine operations.
|
// CAService provides typed, authenticated access to CA engine operations.
|
||||||
// All RPCs require the service to be unsealed. Write operations (CreateIssuer,
|
// All RPCs require the service to be unsealed. Write operations (CreateIssuer,
|
||||||
// DeleteIssuer, ImportRoot, IssueCert, RenewCert) require authentication.
|
// DeleteIssuer, ImportRoot, IssueCert, RenewCert) require authentication.
|
||||||
// Admin-only operations (CreateIssuer, DeleteIssuer, ImportRoot) additionally
|
// Admin-only operations (CreateIssuer, DeleteIssuer, ImportRoot, RevokeCert,
|
||||||
// require the caller to have admin privileges.
|
// DeleteCert) additionally require the caller to have admin privileges.
|
||||||
type CAServiceServer interface {
|
type CAServiceServer interface {
|
||||||
// ImportRoot imports an existing root CA certificate and private key.
|
// ImportRoot imports an existing root CA certificate and private key.
|
||||||
// Admin only. Only allowed when no valid root exists.
|
// Admin only. Only allowed when no valid root exists.
|
||||||
@@ -241,6 +268,11 @@ type CAServiceServer interface {
|
|||||||
// and SAN fields from the CSR are preserved exactly; profile defaults supply
|
// and SAN fields from the CSR are preserved exactly; profile defaults supply
|
||||||
// key usages and validity if not overridden. Auth required.
|
// key usages and validity if not overridden. Auth required.
|
||||||
SignCSR(context.Context, *SignCSRRequest) (*SignCSRResponse, error)
|
SignCSR(context.Context, *SignCSRRequest) (*SignCSRResponse, error)
|
||||||
|
// RevokeCert marks a certificate as revoked by serial number. Admin only.
|
||||||
|
RevokeCert(context.Context, *RevokeCertRequest) (*RevokeCertResponse, error)
|
||||||
|
// DeleteCert permanently removes a certificate record by serial number.
|
||||||
|
// Admin only.
|
||||||
|
DeleteCert(context.Context, *DeleteCertRequest) (*DeleteCertResponse, error)
|
||||||
mustEmbedUnimplementedCAServiceServer()
|
mustEmbedUnimplementedCAServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +319,12 @@ func (UnimplementedCAServiceServer) RenewCert(context.Context, *RenewCertRequest
|
|||||||
func (UnimplementedCAServiceServer) SignCSR(context.Context, *SignCSRRequest) (*SignCSRResponse, error) {
|
func (UnimplementedCAServiceServer) SignCSR(context.Context, *SignCSRRequest) (*SignCSRResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method SignCSR not implemented")
|
return nil, status.Error(codes.Unimplemented, "method SignCSR not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedCAServiceServer) RevokeCert(context.Context, *RevokeCertRequest) (*RevokeCertResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method RevokeCert not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCAServiceServer) DeleteCert(context.Context, *DeleteCertRequest) (*DeleteCertResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method DeleteCert not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedCAServiceServer) mustEmbedUnimplementedCAServiceServer() {}
|
func (UnimplementedCAServiceServer) mustEmbedUnimplementedCAServiceServer() {}
|
||||||
func (UnimplementedCAServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedCAServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@@ -524,6 +562,42 @@ func _CAService_SignCSR_Handler(srv interface{}, ctx context.Context, dec func(i
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _CAService_RevokeCert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RevokeCertRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CAServiceServer).RevokeCert(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CAService_RevokeCert_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CAServiceServer).RevokeCert(ctx, req.(*RevokeCertRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CAService_DeleteCert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DeleteCertRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CAServiceServer).DeleteCert(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CAService_DeleteCert_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CAServiceServer).DeleteCert(ctx, req.(*DeleteCertRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// CAService_ServiceDesc is the grpc.ServiceDesc for CAService service.
|
// CAService_ServiceDesc is the grpc.ServiceDesc for CAService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -579,6 +653,14 @@ var CAService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "SignCSR",
|
MethodName: "SignCSR",
|
||||||
Handler: _CAService_SignCSR_Handler,
|
Handler: _CAService_SignCSR_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RevokeCert",
|
||||||
|
Handler: _CAService_RevokeCert_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DeleteCert",
|
||||||
|
Handler: _CAService_DeleteCert_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "proto/metacrypt/v2/ca.proto",
|
Metadata: "proto/metacrypt/v2/ca.proto",
|
||||||
|
|||||||
@@ -304,6 +304,10 @@ func (e *CAEngine) HandleRequest(ctx context.Context, req *engine.Request) (*eng
|
|||||||
return e.handleSignCSR(ctx, req)
|
return e.handleSignCSR(ctx, req)
|
||||||
case "import-root":
|
case "import-root":
|
||||||
return e.handleImportRoot(ctx, req)
|
return e.handleImportRoot(ctx, req)
|
||||||
|
case "revoke-cert":
|
||||||
|
return e.handleRevokeCert(ctx, req)
|
||||||
|
case "delete-cert":
|
||||||
|
return e.handleDeleteCert(ctx, req)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("ca: unknown operation: %s", req.Operation)
|
return nil, fmt.Errorf("ca: unknown operation: %s", req.Operation)
|
||||||
}
|
}
|
||||||
@@ -831,8 +835,7 @@ func (e *CAEngine) handleGetCert(ctx context.Context, req *engine.Request) (*eng
|
|||||||
return nil, fmt.Errorf("ca: parse cert record: %w", err)
|
return nil, fmt.Errorf("ca: parse cert record: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &engine.Response{
|
data := map[string]interface{}{
|
||||||
Data: map[string]interface{}{
|
|
||||||
"serial": record.Serial,
|
"serial": record.Serial,
|
||||||
"issuer": record.Issuer,
|
"issuer": record.Issuer,
|
||||||
"cn": record.CN,
|
"cn": record.CN,
|
||||||
@@ -842,8 +845,13 @@ func (e *CAEngine) handleGetCert(ctx context.Context, req *engine.Request) (*eng
|
|||||||
"issued_by": record.IssuedBy,
|
"issued_by": record.IssuedBy,
|
||||||
"issued_at": record.IssuedAt.Format(time.RFC3339),
|
"issued_at": record.IssuedAt.Format(time.RFC3339),
|
||||||
"expires_at": record.ExpiresAt.Format(time.RFC3339),
|
"expires_at": record.ExpiresAt.Format(time.RFC3339),
|
||||||
},
|
"revoked": record.Revoked,
|
||||||
}, nil
|
}
|
||||||
|
if record.Revoked {
|
||||||
|
data["revoked_at"] = record.RevokedAt.Format(time.RFC3339)
|
||||||
|
data["revoked_by"] = record.RevokedBy
|
||||||
|
}
|
||||||
|
return &engine.Response{Data: data}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CAEngine) handleListCerts(ctx context.Context, req *engine.Request) (*engine.Response, error) {
|
func (e *CAEngine) handleListCerts(ctx context.Context, req *engine.Request) (*engine.Response, error) {
|
||||||
@@ -1119,6 +1127,99 @@ func (e *CAEngine) handleSignCSR(ctx context.Context, req *engine.Request) (*eng
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *CAEngine) handleRevokeCert(ctx context.Context, req *engine.Request) (*engine.Response, error) {
|
||||||
|
if req.CallerInfo == nil {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
if !req.CallerInfo.IsAdmin {
|
||||||
|
return nil, ErrForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
serial, _ := req.Data["serial"].(string)
|
||||||
|
if serial == "" {
|
||||||
|
serial = req.Path
|
||||||
|
}
|
||||||
|
if serial == "" {
|
||||||
|
return nil, fmt.Errorf("ca: serial is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
recordData, err := e.barrier.Get(ctx, e.mountPath+"certs/"+serial+".json")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, barrier.ErrNotFound) {
|
||||||
|
return nil, ErrCertNotFound
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("ca: load cert record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var record CertRecord
|
||||||
|
if err := json.Unmarshal(recordData, &record); err != nil {
|
||||||
|
return nil, fmt.Errorf("ca: parse cert record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.Revoked {
|
||||||
|
return nil, fmt.Errorf("ca: certificate is already revoked")
|
||||||
|
}
|
||||||
|
|
||||||
|
record.Revoked = true
|
||||||
|
record.RevokedAt = time.Now()
|
||||||
|
record.RevokedBy = req.CallerInfo.Username
|
||||||
|
|
||||||
|
updated, err := json.Marshal(&record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ca: marshal cert record: %w", err)
|
||||||
|
}
|
||||||
|
if err := e.barrier.Put(ctx, e.mountPath+"certs/"+serial+".json", updated); err != nil {
|
||||||
|
return nil, fmt.Errorf("ca: store cert record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &engine.Response{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"serial": serial,
|
||||||
|
"revoked_at": record.RevokedAt.Format(time.RFC3339),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CAEngine) handleDeleteCert(ctx context.Context, req *engine.Request) (*engine.Response, error) {
|
||||||
|
if req.CallerInfo == nil {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
if !req.CallerInfo.IsAdmin {
|
||||||
|
return nil, ErrForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
serial, _ := req.Data["serial"].(string)
|
||||||
|
if serial == "" {
|
||||||
|
serial = req.Path
|
||||||
|
}
|
||||||
|
if serial == "" {
|
||||||
|
return nil, fmt.Errorf("ca: serial is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
// Verify the record exists before deleting.
|
||||||
|
_, err := e.barrier.Get(ctx, e.mountPath+"certs/"+serial+".json")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, barrier.ErrNotFound) {
|
||||||
|
return nil, ErrCertNotFound
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("ca: load cert record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.barrier.Delete(ctx, e.mountPath+"certs/"+serial+".json"); err != nil {
|
||||||
|
return nil, fmt.Errorf("ca: delete cert record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &engine.Response{
|
||||||
|
Data: map[string]interface{}{"ok": true},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// --- Helpers ---
|
// --- Helpers ---
|
||||||
|
|
||||||
func defaultCAConfig() *CAConfig {
|
func defaultCAConfig() *CAConfig {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ca
|
package ca
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// CAConfig is the CA engine configuration stored in the barrier.
|
// CAConfig is the CA engine configuration stored in the barrier.
|
||||||
type CAConfig struct {
|
type CAConfig struct {
|
||||||
@@ -27,11 +29,14 @@ type IssuerConfig struct {
|
|||||||
type CertRecord struct {
|
type CertRecord struct {
|
||||||
IssuedAt time.Time `json:"issued_at"`
|
IssuedAt time.Time `json:"issued_at"`
|
||||||
ExpiresAt time.Time `json:"expires_at"`
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
RevokedAt time.Time `json:"revoked_at,omitempty"`
|
||||||
Serial string `json:"serial"`
|
Serial string `json:"serial"`
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
CN string `json:"cn"`
|
CN string `json:"cn"`
|
||||||
Profile string `json:"profile"`
|
Profile string `json:"profile"`
|
||||||
CertPEM string `json:"cert_pem"`
|
CertPEM string `json:"cert_pem"`
|
||||||
IssuedBy string `json:"issued_by"`
|
IssuedBy string `json:"issued_by"`
|
||||||
|
RevokedBy string `json:"revoked_by,omitempty"`
|
||||||
SANs []string `json:"sans,omitempty"`
|
SANs []string `json:"sans,omitempty"`
|
||||||
|
Revoked bool `json:"revoked,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -420,6 +420,45 @@ func (cs *caServer) SignCSR(ctx context.Context, req *pb.SignCSRRequest) (*pb.Si
|
|||||||
}, nil
|
}, 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 ---
|
// --- helpers ---
|
||||||
|
|
||||||
func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
||||||
@@ -429,8 +468,10 @@ func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
|||||||
profile, _ := d["profile"].(string)
|
profile, _ := d["profile"].(string)
|
||||||
issuedBy, _ := d["issued_by"].(string)
|
issuedBy, _ := d["issued_by"].(string)
|
||||||
certPEM, _ := d["cert_pem"].(string)
|
certPEM, _ := d["cert_pem"].(string)
|
||||||
|
revoked, _ := d["revoked"].(bool)
|
||||||
|
revokedBy, _ := d["revoked_by"].(string)
|
||||||
sans := toStringSliceFromInterface(d["sans"])
|
sans := toStringSliceFromInterface(d["sans"])
|
||||||
var issuedAt, expiresAt *timestamppb.Timestamp
|
var issuedAt, expiresAt, revokedAt *timestamppb.Timestamp
|
||||||
if s, ok := d["issued_at"].(string); ok {
|
if s, ok := d["issued_at"].(string); ok {
|
||||||
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||||
issuedAt = timestamppb.New(t)
|
issuedAt = timestamppb.New(t)
|
||||||
@@ -441,6 +482,11 @@ func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
|||||||
expiresAt = timestamppb.New(t)
|
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{
|
return &pb.CertRecord{
|
||||||
Serial: serial,
|
Serial: serial,
|
||||||
Issuer: issuer,
|
Issuer: issuer,
|
||||||
@@ -451,6 +497,9 @@ func certRecordFromData(d map[string]interface{}) *pb.CertRecord {
|
|||||||
IssuedAt: issuedAt,
|
IssuedAt: issuedAt,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
CertPem: []byte(certPEM),
|
CertPem: []byte(certPEM),
|
||||||
|
Revoked: revoked,
|
||||||
|
RevokedAt: revokedAt,
|
||||||
|
RevokedBy: revokedBy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ func (s *Server) registerRoutes(r chi.Router) {
|
|||||||
r.Post("/v1/engine/unmount", s.requireAdmin(s.handleEngineUnmount))
|
r.Post("/v1/engine/unmount", s.requireAdmin(s.handleEngineUnmount))
|
||||||
r.Post("/v1/engine/request", s.requireAuth(s.handleEngineRequest))
|
r.Post("/v1/engine/request", s.requireAuth(s.handleEngineRequest))
|
||||||
|
|
||||||
|
// CA certificate routes (auth required).
|
||||||
|
r.Get("/v1/ca/{mount}/cert/{serial}", s.requireAuth(s.handleGetCert))
|
||||||
|
r.Post("/v1/ca/{mount}/cert/{serial}/revoke", s.requireAdmin(s.handleRevokeCert))
|
||||||
|
r.Delete("/v1/ca/{mount}/cert/{serial}", s.requireAdmin(s.handleDeleteCert))
|
||||||
|
|
||||||
// Public PKI routes (no auth required, but must be unsealed).
|
// Public PKI routes (no auth required, but must be unsealed).
|
||||||
r.Get("/v1/pki/{mount}/ca", s.requireUnseal(s.handlePKIRoot))
|
r.Get("/v1/pki/{mount}/ca", s.requireUnseal(s.handlePKIRoot))
|
||||||
r.Get("/v1/pki/{mount}/ca/chain", s.requireUnseal(s.handlePKIChain))
|
r.Get("/v1/pki/{mount}/ca/chain", s.requireUnseal(s.handlePKIChain))
|
||||||
@@ -370,6 +375,91 @@ func (s *Server) handlePolicyRule(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- CA Certificate Handlers ---
|
||||||
|
|
||||||
|
func (s *Server) handleGetCert(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mountName := chi.URLParam(r, "mount")
|
||||||
|
serial := chi.URLParam(r, "serial")
|
||||||
|
|
||||||
|
info := TokenInfoFromContext(r.Context())
|
||||||
|
resp, err := s.engines.HandleRequest(r.Context(), mountName, &engine.Request{
|
||||||
|
Operation: "get-cert",
|
||||||
|
Data: map[string]interface{}{"serial": serial},
|
||||||
|
CallerInfo: &engine.CallerInfo{
|
||||||
|
Username: info.Username,
|
||||||
|
Roles: info.Roles,
|
||||||
|
IsAdmin: info.IsAdmin,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ca.ErrCertNotFound) {
|
||||||
|
http.Error(w, `{"error":"certificate not found"}`, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, `{"error":"`+err.Error()+`"}`, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleRevokeCert(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mountName := chi.URLParam(r, "mount")
|
||||||
|
serial := chi.URLParam(r, "serial")
|
||||||
|
|
||||||
|
info := TokenInfoFromContext(r.Context())
|
||||||
|
resp, err := s.engines.HandleRequest(r.Context(), mountName, &engine.Request{
|
||||||
|
Operation: "revoke-cert",
|
||||||
|
Data: map[string]interface{}{"serial": serial},
|
||||||
|
CallerInfo: &engine.CallerInfo{
|
||||||
|
Username: info.Username,
|
||||||
|
Roles: info.Roles,
|
||||||
|
IsAdmin: info.IsAdmin,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ca.ErrCertNotFound) {
|
||||||
|
http.Error(w, `{"error":"certificate not found"}`, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, ca.ErrForbidden) {
|
||||||
|
http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, `{"error":"`+err.Error()+`"}`, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleDeleteCert(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mountName := chi.URLParam(r, "mount")
|
||||||
|
serial := chi.URLParam(r, "serial")
|
||||||
|
|
||||||
|
info := TokenInfoFromContext(r.Context())
|
||||||
|
_, err := s.engines.HandleRequest(r.Context(), mountName, &engine.Request{
|
||||||
|
Operation: "delete-cert",
|
||||||
|
Data: map[string]interface{}{"serial": serial},
|
||||||
|
CallerInfo: &engine.CallerInfo{
|
||||||
|
Username: info.Username,
|
||||||
|
Roles: info.Roles,
|
||||||
|
IsAdmin: info.IsAdmin,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ca.ErrCertNotFound) {
|
||||||
|
http.Error(w, `{"error":"certificate not found"}`, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, ca.ErrForbidden) {
|
||||||
|
http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, `{"error":"`+err.Error()+`"}`, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusNoContent, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// --- Public PKI Handlers ---
|
// --- Public PKI Handlers ---
|
||||||
|
|
||||||
func (s *Server) handlePKIRoot(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handlePKIRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -98,6 +98,14 @@ func (m *mockVault) ListCerts(ctx context.Context, token, mount string) ([]CertS
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockVault) RevokeCert(ctx context.Context, token, mount, serial string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockVault) DeleteCert(ctx context.Context, token, mount, serial string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockVault) Close() error { return nil }
|
func (m *mockVault) Close() error { return nil }
|
||||||
|
|
||||||
// newTestWebServer builds a WebServer wired to the given mock, suitable for unit tests.
|
// newTestWebServer builds a WebServer wired to the given mock, suitable for unit tests.
|
||||||
|
|||||||
@@ -329,6 +329,9 @@ type CertDetail struct {
|
|||||||
IssuedAt string
|
IssuedAt string
|
||||||
ExpiresAt string
|
ExpiresAt string
|
||||||
CertPEM string
|
CertPEM string
|
||||||
|
Revoked bool
|
||||||
|
RevokedAt string
|
||||||
|
RevokedBy string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCert retrieves a full certificate record by serial number.
|
// GetCert retrieves a full certificate record by serial number.
|
||||||
@@ -349,6 +352,8 @@ func (c *VaultClient) GetCert(ctx context.Context, token, mount, serial string)
|
|||||||
Profile: rec.Profile,
|
Profile: rec.Profile,
|
||||||
IssuedBy: rec.IssuedBy,
|
IssuedBy: rec.IssuedBy,
|
||||||
CertPEM: string(rec.CertPem),
|
CertPEM: string(rec.CertPem),
|
||||||
|
Revoked: rec.Revoked,
|
||||||
|
RevokedBy: rec.RevokedBy,
|
||||||
}
|
}
|
||||||
if rec.IssuedAt != nil {
|
if rec.IssuedAt != nil {
|
||||||
cd.IssuedAt = rec.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z")
|
cd.IssuedAt = rec.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z")
|
||||||
@@ -356,9 +361,24 @@ func (c *VaultClient) GetCert(ctx context.Context, token, mount, serial string)
|
|||||||
if rec.ExpiresAt != nil {
|
if rec.ExpiresAt != nil {
|
||||||
cd.ExpiresAt = rec.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z")
|
cd.ExpiresAt = rec.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z")
|
||||||
}
|
}
|
||||||
|
if rec.RevokedAt != nil {
|
||||||
|
cd.RevokedAt = rec.RevokedAt.AsTime().Format("2006-01-02T15:04:05Z")
|
||||||
|
}
|
||||||
return cd, nil
|
return cd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RevokeCert marks a certificate as revoked.
|
||||||
|
func (c *VaultClient) RevokeCert(ctx context.Context, token, mount, serial string) error {
|
||||||
|
_, err := c.ca.RevokeCert(withToken(ctx, token), &pb.RevokeCertRequest{Mount: mount, Serial: serial})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCert permanently removes a certificate record.
|
||||||
|
func (c *VaultClient) DeleteCert(ctx context.Context, token, mount, serial string) error {
|
||||||
|
_, err := c.ca.DeleteCert(withToken(ctx, token), &pb.DeleteCertRequest{Mount: mount, Serial: serial})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// CertSummary holds lightweight certificate metadata for list views.
|
// CertSummary holds lightweight certificate metadata for list views.
|
||||||
type CertSummary struct {
|
type CertSummary struct {
|
||||||
Serial string
|
Serial string
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ func (ws *WebServer) registerRoutes(r chi.Router) {
|
|||||||
r.Get("/issuer/{issuer}", ws.requireAuth(ws.handleIssuerDetail))
|
r.Get("/issuer/{issuer}", ws.requireAuth(ws.handleIssuerDetail))
|
||||||
r.Get("/cert/{serial}", ws.requireAuth(ws.handleCertDetail))
|
r.Get("/cert/{serial}", ws.requireAuth(ws.handleCertDetail))
|
||||||
r.Get("/cert/{serial}/download", ws.requireAuth(ws.handleCertDownload))
|
r.Get("/cert/{serial}/download", ws.requireAuth(ws.handleCertDownload))
|
||||||
|
r.Post("/cert/{serial}/revoke", ws.requireAuth(ws.handleCertRevoke))
|
||||||
|
r.Post("/cert/{serial}/delete", ws.requireAuth(ws.handleCertDelete))
|
||||||
r.Get("/{issuer}", ws.requireAuth(ws.handlePKIIssuer))
|
r.Get("/{issuer}", ws.requireAuth(ws.handlePKIIssuer))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -531,6 +533,11 @@ func (ws *WebServer) handleIssueCert(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stream a tgz archive containing the private key (PKCS8) and certificate.
|
// Stream a tgz archive containing the private key (PKCS8) and certificate.
|
||||||
|
// Extend the write deadline before streaming so that slow gRPC backends
|
||||||
|
// don't consume the server WriteTimeout before we start writing.
|
||||||
|
rc := http.NewResponseController(w)
|
||||||
|
_ = rc.SetWriteDeadline(time.Now().Add(60 * time.Second))
|
||||||
|
|
||||||
filename := issuedCert.Serial + ".tgz"
|
filename := issuedCert.Serial + ".tgz"
|
||||||
w.Header().Set("Content-Type", "application/gzip")
|
w.Header().Set("Content-Type", "application/gzip")
|
||||||
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
|
||||||
@@ -621,6 +628,70 @@ func (ws *WebServer) handleCertDownload(w http.ResponseWriter, r *http.Request)
|
|||||||
_, _ = w.Write([]byte(cert.CertPEM))
|
_, _ = w.Write([]byte(cert.CertPEM))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) handleCertRevoke(w http.ResponseWriter, r *http.Request) {
|
||||||
|
info := tokenInfoFromContext(r.Context())
|
||||||
|
if !info.IsAdmin {
|
||||||
|
http.Error(w, "forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := extractCookie(r)
|
||||||
|
mountName, err := ws.findCAMount(r, token)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "no CA engine mounted", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serial := chi.URLParam(r, "serial")
|
||||||
|
if err := ws.vault.RevokeCert(r.Context(), token, mountName, serial); err != nil {
|
||||||
|
st, _ := status.FromError(err)
|
||||||
|
if st.Code() == codes.NotFound {
|
||||||
|
http.Error(w, "certificate not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, grpcMessage(err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/pki/cert/"+serial, http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) handleCertDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
info := tokenInfoFromContext(r.Context())
|
||||||
|
if !info.IsAdmin {
|
||||||
|
http.Error(w, "forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := extractCookie(r)
|
||||||
|
mountName, err := ws.findCAMount(r, token)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "no CA engine mounted", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serial := chi.URLParam(r, "serial")
|
||||||
|
|
||||||
|
// Fetch the cert to get the issuer for the redirect.
|
||||||
|
cert, certErr := ws.vault.GetCert(r.Context(), token, mountName, serial)
|
||||||
|
|
||||||
|
if err := ws.vault.DeleteCert(r.Context(), token, mountName, serial); err != nil {
|
||||||
|
st, _ := status.FromError(err)
|
||||||
|
if st.Code() == codes.NotFound {
|
||||||
|
http.Error(w, "certificate not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, grpcMessage(err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if certErr == nil && cert != nil {
|
||||||
|
http.Redirect(w, r, "/pki/issuer/"+cert.Issuer, http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, "/pki", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
func (ws *WebServer) handleSignCSR(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebServer) handleSignCSR(w http.ResponseWriter, r *http.Request) {
|
||||||
info := tokenInfoFromContext(r.Context())
|
info := tokenInfoFromContext(r.Context())
|
||||||
token := extractCookie(r)
|
token := extractCookie(r)
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ type vaultBackend interface {
|
|||||||
SignCSR(ctx context.Context, token string, req SignCSRRequest) (*SignedCert, error)
|
SignCSR(ctx context.Context, token string, req SignCSRRequest) (*SignedCert, error)
|
||||||
GetCert(ctx context.Context, token, mount, serial string) (*CertDetail, error)
|
GetCert(ctx context.Context, token, mount, serial string) (*CertDetail, error)
|
||||||
ListCerts(ctx context.Context, token, mount string) ([]CertSummary, error)
|
ListCerts(ctx context.Context, token, mount string) ([]CertSummary, error)
|
||||||
|
RevokeCert(ctx context.Context, token, mount, serial string) error
|
||||||
|
DeleteCert(ctx context.Context, token, mount, serial string) error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +100,12 @@ func (lw *loggingResponseWriter) WriteHeader(code int) {
|
|||||||
lw.ResponseWriter.WriteHeader(code)
|
lw.ResponseWriter.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying ResponseWriter so that http.ResponseController
|
||||||
|
// can reach it to set deadlines and perform other extended operations.
|
||||||
|
func (lw *loggingResponseWriter) Unwrap() http.ResponseWriter {
|
||||||
|
return lw.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
// Start starts the web server. It blocks until the server is closed.
|
// Start starts the web server. It blocks until the server is closed.
|
||||||
func (ws *WebServer) Start() error {
|
func (ws *WebServer) Start() error {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ option go_package = "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2;metacryptv
|
|||||||
// CAService provides typed, authenticated access to CA engine operations.
|
// CAService provides typed, authenticated access to CA engine operations.
|
||||||
// All RPCs require the service to be unsealed. Write operations (CreateIssuer,
|
// All RPCs require the service to be unsealed. Write operations (CreateIssuer,
|
||||||
// DeleteIssuer, ImportRoot, IssueCert, RenewCert) require authentication.
|
// DeleteIssuer, ImportRoot, IssueCert, RenewCert) require authentication.
|
||||||
// Admin-only operations (CreateIssuer, DeleteIssuer, ImportRoot) additionally
|
// Admin-only operations (CreateIssuer, DeleteIssuer, ImportRoot, RevokeCert,
|
||||||
// require the caller to have admin privileges.
|
// DeleteCert) additionally require the caller to have admin privileges.
|
||||||
service CAService {
|
service CAService {
|
||||||
// ImportRoot imports an existing root CA certificate and private key.
|
// ImportRoot imports an existing root CA certificate and private key.
|
||||||
// Admin only. Only allowed when no valid root exists.
|
// Admin only. Only allowed when no valid root exists.
|
||||||
@@ -52,6 +52,13 @@ service CAService {
|
|||||||
// and SAN fields from the CSR are preserved exactly; profile defaults supply
|
// and SAN fields from the CSR are preserved exactly; profile defaults supply
|
||||||
// key usages and validity if not overridden. Auth required.
|
// key usages and validity if not overridden. Auth required.
|
||||||
rpc SignCSR(SignCSRRequest) returns (SignCSRResponse);
|
rpc SignCSR(SignCSRRequest) returns (SignCSRResponse);
|
||||||
|
|
||||||
|
// RevokeCert marks a certificate as revoked by serial number. Admin only.
|
||||||
|
rpc RevokeCert(RevokeCertRequest) returns (RevokeCertResponse);
|
||||||
|
|
||||||
|
// DeleteCert permanently removes a certificate record by serial number.
|
||||||
|
// Admin only.
|
||||||
|
rpc DeleteCert(DeleteCertRequest) returns (DeleteCertResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ImportRoot ---
|
// --- ImportRoot ---
|
||||||
@@ -252,6 +259,27 @@ message SignCSRResponse {
|
|||||||
|
|
||||||
// --- Shared message types ---
|
// --- Shared message types ---
|
||||||
|
|
||||||
|
// --- RevokeCert ---
|
||||||
|
|
||||||
|
message RevokeCertRequest {
|
||||||
|
string mount = 1;
|
||||||
|
string serial = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RevokeCertResponse {
|
||||||
|
string serial = 1;
|
||||||
|
google.protobuf.Timestamp revoked_at = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DeleteCert ---
|
||||||
|
|
||||||
|
message DeleteCertRequest {
|
||||||
|
string mount = 1;
|
||||||
|
string serial = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteCertResponse {}
|
||||||
|
|
||||||
// CertRecord is the full certificate record including the PEM-encoded cert.
|
// CertRecord is the full certificate record including the PEM-encoded cert.
|
||||||
message CertRecord {
|
message CertRecord {
|
||||||
string serial = 1;
|
string serial = 1;
|
||||||
@@ -264,6 +292,12 @@ message CertRecord {
|
|||||||
google.protobuf.Timestamp expires_at = 8;
|
google.protobuf.Timestamp expires_at = 8;
|
||||||
// cert_pem is the PEM-encoded certificate.
|
// cert_pem is the PEM-encoded certificate.
|
||||||
bytes cert_pem = 9;
|
bytes cert_pem = 9;
|
||||||
|
// revoked indicates whether the certificate has been revoked.
|
||||||
|
bool revoked = 10;
|
||||||
|
// revoked_at is the time the certificate was revoked, if applicable.
|
||||||
|
google.protobuf.Timestamp revoked_at = 11;
|
||||||
|
// revoked_by is the username of the admin who revoked the certificate.
|
||||||
|
string revoked_by = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CertSummary is a lightweight certificate record without the PEM data,
|
// CertSummary is a lightweight certificate record without the PEM data,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{define "title"}} - Certificate: {{.Cert.Serial}}{{end}}
|
{{define "title"}} - Certificate: {{.Cert.Serial}}{{end}}
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>Certificate: {{.Cert.CommonName}}</h2>
|
<h2>Certificate: {{.Cert.CommonName}}{{if .Cert.Revoked}} <span style="color:#c0392b;">[REVOKED]</span>{{end}}</h2>
|
||||||
<div class="page-meta">
|
<div class="page-meta">
|
||||||
<a href="/pki/issuer/{{.Cert.Issuer}}">← Issuer: {{.Cert.Issuer}}</a>
|
<a href="/pki/issuer/{{.Cert.Issuer}}">← Issuer: {{.Cert.Issuer}}</a>
|
||||||
 · 
|
 · 
|
||||||
@@ -23,6 +23,10 @@
|
|||||||
<tr><th>Issued By</th><td>{{.Cert.IssuedBy}}</td></tr>
|
<tr><th>Issued By</th><td>{{.Cert.IssuedBy}}</td></tr>
|
||||||
<tr><th>Issued At</th><td>{{.Cert.IssuedAt}}</td></tr>
|
<tr><th>Issued At</th><td>{{.Cert.IssuedAt}}</td></tr>
|
||||||
<tr><th>Expires At</th><td>{{.Cert.ExpiresAt}}</td></tr>
|
<tr><th>Expires At</th><td>{{.Cert.ExpiresAt}}</td></tr>
|
||||||
|
{{if .Cert.Revoked}}
|
||||||
|
<tr><th>Revoked At</th><td>{{.Cert.RevokedAt}}</td></tr>
|
||||||
|
<tr><th>Revoked By</th><td>{{.Cert.RevokedBy}}</td></tr>
|
||||||
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,4 +37,22 @@
|
|||||||
<textarea rows="12" class="pem-input" readonly>{{.Cert.CertPEM}}</textarea>
|
<textarea rows="12" class="pem-input" readonly>{{.Cert.CertPEM}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{if .IsAdmin}}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">Admin Actions</div>
|
||||||
|
<div style="display:flex;gap:1rem;flex-wrap:wrap;">
|
||||||
|
{{if not .Cert.Revoked}}
|
||||||
|
<form method="POST" action="/pki/cert/{{.Cert.Serial}}/revoke"
|
||||||
|
onsubmit="return confirm('Revoke certificate {{.Cert.Serial}}? This cannot be undone.');">
|
||||||
|
<button type="submit" class="btn btn-danger">Revoke Certificate</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
<form method="POST" action="/pki/cert/{{.Cert.Serial}}/delete"
|
||||||
|
onsubmit="return confirm('Permanently delete certificate record {{.Cert.Serial}}? This cannot be undone.');">
|
||||||
|
<button type="submit" class="btn btn-danger">Delete Record</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user