feat: PKI API Go avec persistance MongoDB et abstraction storage
- API REST complète pour gestion Infrastructure à Clé Publique - Authentification JWT sur tous les endpoints (sauf login) - Hiérarchie de certificats (Root CA, Sub-CA, End Certificates) - Abstraction de stockage avec MemoryStore et MongoStore - Configuration centralisée via variables d'environnement - Support déploiement Docker Compose avec MongoDB - Tests unitaires pour sérialisation des clés RSA - Documentation complète avec exemples API
This commit is contained in:
57
internal/api/auth.go
Normal file
57
internal/api/auth.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stef/pkiapi/internal/auth"
|
||||
)
|
||||
|
||||
// LoginRequest représente la requête de connexion
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// LoginResponse représente la réponse de connexion
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// Login génère un token JWT (simple pour démo)
|
||||
func Login(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Validation simple (à remplacer par une vraie authentification)
|
||||
if req.Username == "" || req.Password == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "identifiants invalides"})
|
||||
return
|
||||
}
|
||||
|
||||
// Créer JWT manager
|
||||
secretKey := os.Getenv("JWT_SECRET_KEY")
|
||||
if secretKey == "" {
|
||||
secretKey = "your-secret-key-change-in-prod"
|
||||
}
|
||||
|
||||
jwtManager := auth.NewJWTManager(secretKey)
|
||||
|
||||
// Générer token avec expiration de 24h
|
||||
token, err := jwtManager.GenerateToken(req.Username, "user", 24*time.Hour)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur génération token"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, LoginResponse{
|
||||
Token: token,
|
||||
ExpiresIn: 86400, // 24 heures en secondes
|
||||
})
|
||||
}
|
||||
286
internal/api/ca.go
Normal file
286
internal/api/ca.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stef/pkiapi/internal/pki"
|
||||
"github.com/stef/pkiapi/internal/storage"
|
||||
)
|
||||
|
||||
// CreateCARequest représente la requête de création d'une CA
|
||||
type CreateCARequest struct {
|
||||
Subject string `json:"subject" binding:"required"`
|
||||
ValidityDays int `json:"validity_days" binding:"required,min=1,max=36500"`
|
||||
}
|
||||
|
||||
// CAResponse représente la réponse avec les données de la CA
|
||||
type CAResponse struct {
|
||||
ID string `json:"id"`
|
||||
Subject string `json:"subject"`
|
||||
NotBefore string `json:"not_before"`
|
||||
NotAfter string `json:"not_after"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Certificate string `json:"certificate"` // Base64 encoded
|
||||
IsCA bool `json:"is_ca"`
|
||||
}
|
||||
|
||||
// caStore est un store global pour les CAs
|
||||
var caStore storage.CertificateStore
|
||||
|
||||
// InitCAStore initialise le store pour les CAs
|
||||
func InitCAStore(store storage.CertificateStore) {
|
||||
caStore = store
|
||||
}
|
||||
|
||||
// ListCAResponse représente une CA dans une liste
|
||||
type ListCAResponse struct {
|
||||
ID string `json:"id"`
|
||||
Subject string `json:"subject"`
|
||||
Issuer string `json:"issuer"`
|
||||
NotBefore string `json:"not_before"`
|
||||
NotAfter string `json:"not_after"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
}
|
||||
|
||||
// ListCAs retourne toutes les CAs
|
||||
func ListCAs(c *gin.Context) {
|
||||
cas := caStore.ListCertificates()
|
||||
|
||||
var responses []ListCAResponse
|
||||
for _, ca := range cas {
|
||||
if ca.IsCA {
|
||||
response := ListCAResponse{
|
||||
ID: ca.ID,
|
||||
Subject: ca.Subject,
|
||||
Issuer: ca.Issuer,
|
||||
NotBefore: ca.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: ca.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
if ca.Cert != nil {
|
||||
response.SerialNumber = ca.Cert.SerialNumber.String()
|
||||
}
|
||||
responses = append(responses, response)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"cas": responses})
|
||||
}
|
||||
|
||||
// CreateCA crée une nouvelle autorité de certification
|
||||
func CreateCA(c *gin.Context) {
|
||||
var req CreateCARequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Générer la CA
|
||||
ca, err := pki.GenerateCA(req.Subject, req.ValidityDays)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur génération CA"})
|
||||
return
|
||||
}
|
||||
|
||||
// Générer un ID unique
|
||||
caID := uuid.New().String()
|
||||
ca.ID = caID
|
||||
|
||||
// Sauvegarder la CA
|
||||
if err := caStore.SaveCertificate(caID, ca); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur sauvegarde CA"})
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer l'utilisateur depuis le contexte JWT
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
// Retourner la réponse
|
||||
response := CAResponse{
|
||||
ID: caID,
|
||||
Subject: ca.Subject,
|
||||
NotBefore: ca.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: ca.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
if ca.Cert != nil {
|
||||
response.SerialNumber = ca.Cert.SerialNumber.String()
|
||||
response.Certificate = base64.StdEncoding.EncodeToString(ca.Cert.Raw)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"ca": response,
|
||||
"created_by": userID,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCA retourne une CA par ID
|
||||
func GetCA(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
ca, err := caStore.GetCertificate(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "CA non trouvée"})
|
||||
return
|
||||
}
|
||||
|
||||
if !ca.IsCA {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "ce certificat n'est pas une CA"})
|
||||
return
|
||||
}
|
||||
|
||||
response := CAResponse{
|
||||
ID: ca.ID,
|
||||
Subject: ca.Subject,
|
||||
NotBefore: ca.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: ca.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
if ca.Cert != nil {
|
||||
response.SerialNumber = ca.Cert.SerialNumber.String()
|
||||
response.Certificate = base64.StdEncoding.EncodeToString(ca.Cert.Raw)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ca": response})
|
||||
}
|
||||
|
||||
// SignCertificateRequest représente une requête de signature de certificat par une CA
|
||||
type SignCertificateRequest struct {
|
||||
CAId string `json:"ca_id" binding:"required"`
|
||||
Subject string `json:"subject" binding:"required"`
|
||||
ValidityDays int `json:"validity_days" binding:"required,min=1,max=3650"`
|
||||
}
|
||||
|
||||
// SignCertificateWithCA signe un certificat avec une CA
|
||||
func SignCertificateWithCA(c *gin.Context) {
|
||||
var req SignCertificateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer la CA
|
||||
ca, err := caStore.GetCertificate(req.CAId)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "CA non trouvée"})
|
||||
return
|
||||
}
|
||||
|
||||
if !ca.IsCA {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "ce certificat n'est pas une CA"})
|
||||
return
|
||||
}
|
||||
|
||||
// Signer le certificat avec la CA
|
||||
cert, err := pki.SignCertificate(ca, req.Subject, req.ValidityDays)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur signature certificat: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Générer un ID unique
|
||||
certID := uuid.New().String()
|
||||
cert.ID = certID
|
||||
|
||||
// Sauvegarder le certificat
|
||||
if err := certificateStore.SaveCertificate(certID, cert); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur sauvegarde certificat"})
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer l'utilisateur depuis le contexte JWT
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
// Retourner la réponse
|
||||
response := CertificateResponse{
|
||||
ID: certID,
|
||||
Subject: cert.Subject,
|
||||
Issuer: cert.Issuer,
|
||||
NotBefore: cert.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: cert.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
Revoked: false,
|
||||
}
|
||||
|
||||
if cert.Cert != nil {
|
||||
response.SerialNumber = cert.Cert.SerialNumber.String()
|
||||
response.Certificate = base64.StdEncoding.EncodeToString(cert.Cert.Raw)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"certificate": response,
|
||||
"signed_by": req.CAId,
|
||||
"created_by": userID,
|
||||
})
|
||||
}
|
||||
|
||||
// SignSubCARequest représente une requête de création de sub-CA
|
||||
type SignSubCARequest struct {
|
||||
ParentCAId string `json:"parent_ca_id" binding:"required"`
|
||||
Subject string `json:"subject" binding:"required"`
|
||||
ValidityDays int `json:"validity_days" binding:"required,min=1,max=36500"`
|
||||
}
|
||||
|
||||
// SignSubCA crée une CA intermédiaire signée par une CA parent
|
||||
func SignSubCA(c *gin.Context) {
|
||||
var req SignSubCARequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer la CA parent
|
||||
parentCA, err := caStore.GetCertificate(req.ParentCAId)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "CA parent non trouvée"})
|
||||
return
|
||||
}
|
||||
|
||||
if !parentCA.IsCA {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "ce certificat n'est pas une CA"})
|
||||
return
|
||||
}
|
||||
|
||||
// Signer la sub-CA avec la CA parent
|
||||
subCA, err := pki.SignSubCA(parentCA, req.Subject, req.ValidityDays)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur signature sub-CA: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Générer un ID unique
|
||||
subCAId := uuid.New().String()
|
||||
subCA.ID = subCAId
|
||||
|
||||
// Sauvegarder la sub-CA
|
||||
if err := caStore.SaveCertificate(subCAId, subCA); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur sauvegarde sub-CA"})
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer l'utilisateur depuis le contexte JWT
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
// Retourner la réponse
|
||||
response := CAResponse{
|
||||
ID: subCAId,
|
||||
Subject: subCA.Subject,
|
||||
NotBefore: subCA.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: subCA.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
if subCA.Cert != nil {
|
||||
response.SerialNumber = subCA.Cert.SerialNumber.String()
|
||||
response.Certificate = base64.StdEncoding.EncodeToString(subCA.Cert.Raw)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"ca": response,
|
||||
"signed_by": req.ParentCAId,
|
||||
"created_by": userID,
|
||||
})
|
||||
}
|
||||
184
internal/api/certificates.go
Normal file
184
internal/api/certificates.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stef/pkiapi/internal/pki"
|
||||
"github.com/stef/pkiapi/internal/storage"
|
||||
)
|
||||
|
||||
// CreateCertificateRequest représente la requête de création de certificat
|
||||
type CreateCertificateRequest struct {
|
||||
Subject string `json:"subject" binding:"required"`
|
||||
ValidityDays int `json:"validity_days" binding:"required,min=1,max=3650"`
|
||||
}
|
||||
|
||||
// CertificateResponse représente la réponse avec les données du certificat
|
||||
type CertificateResponse struct {
|
||||
ID string `json:"id"`
|
||||
Subject string `json:"subject"`
|
||||
Issuer string `json:"issuer"`
|
||||
NotBefore string `json:"not_before"`
|
||||
NotAfter string `json:"not_after"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Certificate string `json:"certificate"` // Base64 encoded
|
||||
Revoked bool `json:"revoked"`
|
||||
}
|
||||
|
||||
// certificateStore est un store global pour les certificats
|
||||
var certificateStore storage.CertificateStore
|
||||
|
||||
// InitCertificateStore initialise le store pour les certificats
|
||||
func InitCertificateStore(store storage.CertificateStore) {
|
||||
certificateStore = store
|
||||
}
|
||||
|
||||
// CreateCertificate crée un nouveau certificat auto-signé
|
||||
func CreateCertificate(c *gin.Context) {
|
||||
var req CreateCertificateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Générer le certificat
|
||||
cert, err := pki.GenerateCertificate(req.Subject, req.ValidityDays)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur génération certificat"})
|
||||
return
|
||||
}
|
||||
|
||||
// Générer un ID unique
|
||||
certID := uuid.New().String()
|
||||
cert.ID = certID
|
||||
|
||||
// Sauvegarder le certificat
|
||||
if err := certificateStore.SaveCertificate(certID, cert); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "erreur sauvegarde certificat"})
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer l'utilisateur depuis le contexte JWT
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
// Retourner la réponse
|
||||
response := CertificateResponse{
|
||||
ID: certID,
|
||||
Subject: cert.Subject,
|
||||
Issuer: cert.Issuer,
|
||||
NotBefore: cert.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: cert.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
Revoked: false,
|
||||
}
|
||||
|
||||
if cert.Cert != nil {
|
||||
response.SerialNumber = cert.Cert.SerialNumber.String()
|
||||
response.Certificate = base64.StdEncoding.EncodeToString(cert.Cert.Raw)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"certificate": response,
|
||||
"created_by": userID,
|
||||
})
|
||||
}
|
||||
|
||||
// ListCertificates retourne tous les certificats
|
||||
func ListCertificates(c *gin.Context) {
|
||||
certs := certificateStore.ListCertificates()
|
||||
|
||||
var responses []CertificateResponse
|
||||
for _, cert := range certs {
|
||||
response := CertificateResponse{
|
||||
ID: cert.ID,
|
||||
Subject: cert.Subject,
|
||||
Issuer: cert.Issuer,
|
||||
NotBefore: cert.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: cert.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
Revoked: cert.Revoked,
|
||||
}
|
||||
if cert.Cert != nil {
|
||||
response.SerialNumber = cert.Cert.SerialNumber.String()
|
||||
}
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"certificates": responses})
|
||||
}
|
||||
|
||||
// GetCertificate retourne un certificat par ID
|
||||
func GetCertificate(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
cert, err := certificateStore.GetCertificate(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"})
|
||||
return
|
||||
}
|
||||
|
||||
response := CertificateResponse{
|
||||
ID: cert.ID,
|
||||
Subject: cert.Subject,
|
||||
Issuer: cert.Issuer,
|
||||
NotBefore: cert.NotBefore.Format("2006-01-02T15:04:05Z"),
|
||||
NotAfter: cert.NotAfter.Format("2006-01-02T15:04:05Z"),
|
||||
Revoked: cert.Revoked,
|
||||
}
|
||||
|
||||
if cert.Cert != nil {
|
||||
response.SerialNumber = cert.Cert.SerialNumber.String()
|
||||
response.Certificate = base64.StdEncoding.EncodeToString(cert.Cert.Raw)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"certificate": response})
|
||||
}
|
||||
|
||||
// RevokeCertificate révoque un certificat
|
||||
func RevokeCertificate(c *gin.Context) {
|
||||
var req struct {
|
||||
CertificateID string `json:"certificate_id" binding:"required"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
cert, err := certificateStore.GetCertificate(req.CertificateID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "certificat non trouvé"})
|
||||
return
|
||||
}
|
||||
|
||||
cert.Revoked = true
|
||||
certificateStore.SaveCertificate(req.CertificateID, cert)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "certificat révoqué",
|
||||
"id": req.CertificateID,
|
||||
"reason": req.Reason,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCRL retourne la liste de révocation
|
||||
func GetCRL(c *gin.Context) {
|
||||
certs := certificateStore.ListCertificates()
|
||||
|
||||
var revokedCerts []gin.H
|
||||
for _, cert := range certs {
|
||||
if cert.Revoked && cert.Cert != nil {
|
||||
revokedCerts = append(revokedCerts, gin.H{
|
||||
"serial_number": cert.Cert.SerialNumber.String(),
|
||||
"subject": cert.Subject,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"crl": revokedCerts,
|
||||
"version": 1,
|
||||
})
|
||||
}
|
||||
53
internal/api/router.go
Normal file
53
internal/api/router.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stef/pkiapi/internal/auth"
|
||||
"github.com/stef/pkiapi/internal/storage"
|
||||
)
|
||||
|
||||
// RegisterRoutesWithStore enregistre les routes avec un store personnalisé
|
||||
func RegisterRoutesWithStore(router *gin.Engine, caStore storage.CertificateStore, certStore storage.CertificateStore) {
|
||||
// Initialiser les stores
|
||||
InitCAStore(caStore)
|
||||
InitCertificateStore(certStore)
|
||||
|
||||
// Initialiser le JWT manager
|
||||
secretKey := os.Getenv("JWT_SECRET_KEY")
|
||||
if secretKey == "" {
|
||||
secretKey = "your-secret-key-change-in-prod"
|
||||
}
|
||||
jwtManager := auth.NewJWTManager(secretKey)
|
||||
|
||||
// Endpoints publics
|
||||
router.POST("/api/v1/login", Login)
|
||||
|
||||
// Group pour l'API v1 avec authentification
|
||||
v1 := router.Group("/api/v1")
|
||||
v1.Use(auth.AuthMiddleware(jwtManager))
|
||||
{
|
||||
// Endpoints CA
|
||||
v1.GET("/ca", ListCAs)
|
||||
v1.POST("/ca", CreateCA)
|
||||
v1.GET("/ca/:id", GetCA)
|
||||
v1.POST("/ca/sign", SignSubCA)
|
||||
|
||||
// Endpoints Certificats
|
||||
v1.GET("/certificates", ListCertificates)
|
||||
v1.POST("/certificates", CreateCertificate)
|
||||
v1.POST("/certificates/sign", SignCertificateWithCA)
|
||||
v1.GET("/certificates/:id", GetCertificate)
|
||||
v1.POST("/revoke", RevokeCertificate)
|
||||
|
||||
// Endpoints CRL
|
||||
v1.GET("/crl", GetCRL)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes enregistre les routes avec un store mémoire (compatibilité)
|
||||
func RegisterRoutes(router *gin.Engine) {
|
||||
store := storage.NewMemoryStore()
|
||||
RegisterRoutesWithStore(router, store, store)
|
||||
}
|
||||
Reference in New Issue
Block a user