admin: Use gin-gonic as router

This commit is contained in:
nemunaire 2022-05-16 11:38:46 +02:00
commit 8b3fbdb64a
32 changed files with 2748 additions and 1598 deletions

View file

@ -7,13 +7,13 @@ import (
"crypto/x509/pkix"
"encoding/base32"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"math"
"math/big"
"net/http"
"os"
"path"
"strconv"
@ -23,91 +23,178 @@ import (
"srs.epita.fr/fic-server/admin/pki"
"srs.epita.fr/fic-server/libfic"
"github.com/julienschmidt/httprouter"
"github.com/gin-gonic/gin"
)
var TeamsDir string
func init() {
router.GET("/api/htpasswd", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) {
return genHtpasswd(true)
}))
router.POST("/api/htpasswd", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) {
if htpasswd, err := genHtpasswd(true); err != nil {
return nil, err
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "ficpasswd"), []byte(htpasswd), 0644); err != nil {
return nil, err
} else {
return true, nil
}
}))
router.DELETE("/api/htpasswd", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) {
if err := os.Remove(path.Join(pki.PKIDir, "shared", "ficpasswd")); err != nil {
return nil, err
} else {
return true, nil
}
}))
router.GET("/api/htpasswd.apr1", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) {
return genHtpasswd(false)
}))
router.GET("/api/ca/", apiHandler(infoCA))
router.GET("/api/ca.pem", apiHandler(getCAPEM))
router.POST("/api/ca/new", apiHandler(
func(_ httprouter.Params, body []byte) (interface{}, error) {
var upki PKISettings
if err := json.Unmarshal(body, &upki); err != nil {
return nil, err
}
return true, pki.GenerateCA(upki.NotBefore, upki.NotAfter)
}))
func declareCertificateRoutes(router *gin.RouterGroup) {
router.GET("/htpasswd", func(c *gin.Context) {
ret, err := genHtpasswd(true)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, ret)
})
router.POST("/htpasswd", func(c *gin.Context) {
if htpasswd, err := genHtpasswd(true); err != nil {
log.Println("Unable to generate htpasswd:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "ficpasswd"), []byte(htpasswd), 0644); err != nil {
log.Println("Unable to write htpasswd:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.AbortWithStatus(http.StatusOK)
})
router.DELETE("/htpasswd", func(c *gin.Context) {
if err := os.Remove(path.Join(pki.PKIDir, "shared", "ficpasswd")); err != nil {
log.Println("Unable to remove htpasswd:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.AbortWithStatus(http.StatusOK)
})
router.GET("/htpasswd.apr1", func(c *gin.Context) {
ret, err := genHtpasswd(false)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, ret)
})
router.GET("/ca", infoCA)
router.GET("/ca.pem", getCAPEM)
router.POST("/ca/new", func(c *gin.Context) {
var upki PKISettings
err := c.ShouldBindJSON(&upki)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
router.GET("/api/teams/:tid/certificates", apiHandler(teamHandler(
func(team *fic.Team, _ []byte) (interface{}, error) {
if serials, err := pki.GetTeamSerials(TeamsDir, team.Id); err != nil {
return nil, err
} else {
var certs []CertExported
for _, serial := range serials {
if cert, err := fic.GetCertificate(serial); err == nil {
certs = append(certs, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, &team.Id, cert.Revoked})
} else {
log.Println("Unable to get back certificate, whereas an association exists on disk: ", err)
}
if err := pki.GenerateCA(upki.NotBefore, upki.NotAfter); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusCreated, true)
})
router.GET("/certs", getCertificates)
router.POST("/certs", generateClientCert)
router.DELETE("/certs", func(c *gin.Context) {
v, err := fic.ClearCertificates()
if err != nil {
log.Println("Unable to ClearCertificates:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, v)
})
apiCertificatesRoutes := router.Group("/certs/:certid")
apiCertificatesRoutes.Use(CertificateHandler)
apiCertificatesRoutes.HEAD("", getTeamP12File)
apiCertificatesRoutes.GET("", getTeamP12File)
apiCertificatesRoutes.PUT("", updateCertificateAssociation)
apiCertificatesRoutes.DELETE("", func(c *gin.Context) {
cert := c.MustGet("cert").(*fic.Certificate)
v, err := cert.Revoke()
if err != nil {
log.Println("Unable to Revoke:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, v)
})
}
func declareTeamCertificateRoutes(router *gin.RouterGroup) {
router.GET("/certificates", func(c *gin.Context) {
team := c.MustGet("team").(*fic.Team)
if serials, err := pki.GetTeamSerials(TeamsDir, team.Id); err != nil {
log.Println("Unable to GetTeamSerials:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
} else {
var certs []CertExported
for _, serial := range serials {
if cert, err := fic.GetCertificate(serial); err == nil {
certs = append(certs, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, &team.Id, cert.Revoked})
} else {
log.Println("Unable to get back certificate, whereas an association exists on disk: ", err)
}
return certs, nil
}
})))
c.JSON(http.StatusOK, certs)
}
})
router.GET("/api/teams/:tid/associations", apiHandler(teamHandler(
func(team *fic.Team, _ []byte) (interface{}, error) {
return pki.GetTeamAssociations(TeamsDir, team.Id)
})))
router.POST("/api/teams/:tid/associations/:assoc", apiHandler(teamAssocHandler(
func(team *fic.Team, assoc string, _ []byte) (interface{}, error) {
if err := os.Symlink(fmt.Sprintf("%d", team.Id), path.Join(TeamsDir, assoc)); err != nil {
return nil, err
}
return "\"" + assoc + "\"", nil
})))
router.DELETE("/api/teams/:tid/associations/:assoc", apiHandler(teamAssocHandler(
func(team *fic.Team, assoc string, _ []byte) (interface{}, error) {
return "null", pki.DeleteTeamAssociation(TeamsDir, assoc)
})))
router.GET("/associations", func(c *gin.Context) {
team := c.MustGet("team").(*fic.Team)
router.GET("/api/certs/", apiHandler(getCertificates))
router.POST("/api/certs/", apiHandler(generateClientCert))
router.DELETE("/api/certs/", apiHandler(func(_ httprouter.Params, _ []byte) (interface{}, error) { return fic.ClearCertificates() }))
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
if err != nil {
log.Println("Unable to GetTeamAssociations:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
router.HEAD("/api/certs/:certid", apiHandler(certificateHandler(getTeamP12File)))
router.GET("/api/certs/:certid", apiHandler(certificateHandler(getTeamP12File)))
router.PUT("/api/certs/:certid", apiHandler(certificateHandler(updateCertificateAssociation)))
router.DELETE("/api/certs/:certid", apiHandler(certificateHandler(
func(cert *fic.Certificate, _ []byte) (interface{}, error) { return cert.Revoke() })))
c.JSON(http.StatusOK, assocs)
})
apiTeamAssociationsRoutes := router.Group("/associations/:assoc")
apiTeamAssociationsRoutes.POST("", func(c *gin.Context) {
team := c.MustGet("team").(*fic.Team)
if err := os.Symlink(fmt.Sprintf("%d", team.Id), path.Join(TeamsDir, c.Params.ByName("assoc"))); err != nil {
log.Println("Unable to create association symlink:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to create association symlink: %s", err.Error())})
return
}
c.JSON(http.StatusOK, c.Params.ByName("assoc"))
})
apiTeamAssociationsRoutes.DELETE("", func(c *gin.Context) {
err := pki.DeleteTeamAssociation(TeamsDir, c.Params.ByName("assoc"))
if err != nil {
log.Printf("Unable to DeleteTeamAssociation(%s): %s", c.Params.ByName("assoc"), err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to delete association symlink: %s", err.Error())})
return
}
c.JSON(http.StatusOK, nil)
})
}
func CertificateHandler(c *gin.Context) {
var certid uint64
var err error
cid := strings.TrimSuffix(string(c.Params.ByName("certid")), ".p12")
if certid, err = strconv.ParseUint(cid, 10, 64); err != nil {
if certid, err = strconv.ParseUint(cid, 16, 64); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid certficate identifier"})
return
}
}
cert, err := fic.GetCertificate(certid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Certificate not found"})
return
}
c.Set("cert", cert)
c.Next()
}
func genHtpasswd(ssha bool) (ret string, err error) {
@ -187,13 +274,14 @@ type PKISettings struct {
PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"publicKeyAlgorithm"`
}
func infoCA(_ httprouter.Params, _ []byte) (interface{}, error) {
func infoCA(c *gin.Context) {
_, cacert, err := pki.LoadCA()
if err != nil {
return nil, err
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "CA not found"})
return
}
return PKISettings{
c.JSON(http.StatusOK, PKISettings{
Version: cacert.Version,
SerialNumber: cacert.SerialNumber,
Issuer: cacert.Issuer,
@ -202,47 +290,78 @@ func infoCA(_ httprouter.Params, _ []byte) (interface{}, error) {
NotAfter: cacert.NotAfter,
SignatureAlgorithm: cacert.SignatureAlgorithm,
PublicKeyAlgorithm: cacert.PublicKeyAlgorithm,
}, nil
})
}
func getCAPEM(_ httprouter.Params, _ []byte) (interface{}, error) {
func getCAPEM(c *gin.Context) {
if _, err := os.Stat(pki.CACertPath()); os.IsNotExist(err) {
return nil, errors.New("Unable to locate the CA root certificate. Have you generated it?")
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to locate the CA root certificate. Have you generated it?"})
return
} else if fd, err := os.Open(pki.CACertPath()); err != nil {
return nil, err
log.Println("Unable to open CA root certificate:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
} else {
defer fd.Close()
return ioutil.ReadAll(fd)
cnt, err := ioutil.ReadAll(fd)
if err != nil {
log.Println("Unable to read CA root certificate:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.String(http.StatusOK, string(cnt))
}
}
func getTeamP12File(cert *fic.Certificate, _ []byte) (interface{}, error) {
func getTeamP12File(c *gin.Context) {
cert := c.MustGet("cert").(*fic.Certificate)
// Create p12 if necessary
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
if err := pki.WriteP12(cert.Id, cert.Password); err != nil {
return nil, err
log.Println("Unable to WriteP12:", err.Error())
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
return nil, errors.New("Unable to locate the p12. Have you generated it?")
log.Println("Unable to compute ClientP12Path:", err.Error())
c.AbortWithError(http.StatusInternalServerError, errors.New("Unable to locate the p12. Have you generated it?"))
return
} else if fd, err := os.Open(pki.ClientP12Path(cert.Id)); err != nil {
return nil, err
log.Println("Unable to open ClientP12Path:", err.Error())
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Unable to open the p12: %w", err.Error()))
return
} else {
defer fd.Close()
return ioutil.ReadAll(fd)
data, err := ioutil.ReadAll(fd)
if err != nil {
log.Println("Unable to open ClientP12Path:", err.Error())
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Unable to open the p12: %w", err.Error()))
return
}
c.Data(http.StatusOK, "application/x-pkcs12", data)
}
}
func generateClientCert(_ httprouter.Params, _ []byte) (interface{}, error) {
func generateClientCert(c *gin.Context) {
// First, generate a new, unique, serial
var serial_gen [8]byte
if _, err := rand.Read(serial_gen[:]); err != nil {
return nil, err
log.Println("Unable to read enough entropy to generate client certificate:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to read enough entropy"})
return
}
for fic.ExistingCertSerial(serial_gen) {
if _, err := rand.Read(serial_gen[:]); err != nil {
return nil, err
log.Println("Unable to read enough entropy to generate client certificate:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to read enough entropy"})
return
}
}
@ -253,23 +372,35 @@ func generateClientCert(_ httprouter.Params, _ []byte) (interface{}, error) {
// Let's pick a random password
password, err := fic.GeneratePassword()
if err != nil {
return nil, err
log.Println("Unable to generate password:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate password: " + err.Error()})
return
}
// Ok, now load CA
capriv, cacert, err := pki.LoadCA()
if err != nil {
return nil, err
log.Println("Unable to load the CA:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to load the CA"})
return
}
// Generate our privkey
if err := pki.GenerateClient(serial, cacert.NotBefore, cacert.NotAfter, &cacert, &capriv); err != nil {
return nil, err
log.Println("Unable to generate private key:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate private key: " + err.Error()})
return
}
// Save in DB
cert, err := fic.RegisterCertificate(serial, password)
return CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, nil, cert.Revoked}, err
if err != nil {
log.Println("Unable to register certificate:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register certificate."})
return
}
c.JSON(http.StatusOK, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, nil, cert.Revoked})
}
type CertExported struct {
@ -280,35 +411,42 @@ type CertExported struct {
Revoked *time.Time `json:"revoked"`
}
func getCertificates(_ httprouter.Params, _ []byte) (interface{}, error) {
if certificates, err := fic.GetCertificates(); err != nil {
return nil, err
} else {
ret := make([]CertExported, 0)
for _, cert := range certificates {
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
var idTeam *int64 = nil
if lnk, err := os.Readlink(dstLinkPath); err == nil {
if tid, err := strconv.ParseInt(lnk, 10, 64); err == nil {
idTeam = &tid
}
}
ret = append(ret, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, "", idTeam, cert.Revoked})
}
return ret, nil
func getCertificates(c *gin.Context) {
certificates, err := fic.GetCertificates()
if err != nil {
log.Println("Unable to retrieve certificates list:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during certificates retrieval."})
return
}
ret := make([]CertExported, 0)
for _, cert := range certificates {
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
var idTeam *int64 = nil
if lnk, err := os.Readlink(dstLinkPath); err == nil {
if tid, err := strconv.ParseInt(lnk, 10, 64); err == nil {
idTeam = &tid
}
}
ret = append(ret, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, "", idTeam, cert.Revoked})
}
c.JSON(http.StatusOK, ret)
}
type CertUploaded struct {
Team *int64 `json:"id_team"`
}
func updateCertificateAssociation(cert *fic.Certificate, body []byte) (interface{}, error) {
func updateCertificateAssociation(c *gin.Context) {
cert := c.MustGet("cert").(*fic.Certificate)
var uc CertUploaded
if err := json.Unmarshal(body, &uc); err != nil {
return nil, err
err := c.ShouldBindJSON(&uc)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
@ -316,19 +454,26 @@ func updateCertificateAssociation(cert *fic.Certificate, body []byte) (interface
if uc.Team != nil {
srcLinkPath := fmt.Sprintf("%d", *uc.Team)
if err := os.Symlink(srcLinkPath, dstLinkPath); err != nil {
return nil, err
log.Println("Unable to create certificate symlink:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to create certificate symlink: %s", err.Error())})
return
}
// Mark team as active to ensure it'll be generated
if ut, err := fic.GetTeam(*uc.Team); err != nil {
return nil, err
log.Println("Unable to GetTeam:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team retrieval."})
return
} else if !ut.Active {
ut.Active = true
ut.Update()
_, err := ut.Update()
if err != nil {
log.Println("Unable to UpdateTeam after updateCertificateAssociation:", err.Error())
}
}
} else {
os.Remove(dstLinkPath)
}
return cert, nil
c.JSON(http.StatusOK, cert)
}