120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"vpn-platform/backend/internal/service"
|
|
"vpn-platform/backend/internal/tlsservice"
|
|
)
|
|
|
|
func pemPairForAPI(t *testing.T, host string) (certPEM, keyPEM string) {
|
|
t.Helper()
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serial, err := rand.Int(rand.Reader, big.NewInt(1<<62))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tmpl := &x509.Certificate{
|
|
SerialNumber: serial,
|
|
Subject: pkix.Name{CommonName: host},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(24 * time.Hour),
|
|
DNSNames: []string{host},
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
keyDER, err := x509.MarshalECPrivateKey(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})),
|
|
string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}))
|
|
}
|
|
|
|
func TestUploadTLSCertificateAPI(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
dir := t.TempDir()
|
|
t.Setenv("VPN_DATA_DIR", dir)
|
|
t.Setenv("VPN_TLS_DOMAIN", "")
|
|
t.Setenv("VPN_TLS_SCHEME", "")
|
|
t.Setenv("VPN_TLS_PORT", "")
|
|
t.Setenv("VPN_TLS_MODE", "")
|
|
t.Setenv("VPN_TLS_HTTP_ADDR", "127.0.0.1:0")
|
|
t.Setenv("VPN_TLS_HTTPS_ADDR", "127.0.0.1:0")
|
|
if err := tlsservice.ConfigureMode(dir, "nginx"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tlsservice.ConfigurePublicEntry(dir, "https", "vpn.example.com", 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tlsservice.Start(http.NotFoundHandler(), dir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = service.BaseDir() // ensure env seen
|
|
|
|
router := gin.New()
|
|
s := &Server{}
|
|
router.POST("/api/system/tls/certificate", func(c *gin.Context) {
|
|
c.Set("role", "admin")
|
|
c.Set("username", "admin")
|
|
c.Next()
|
|
}, s.uploadTLSCertificate)
|
|
|
|
certPEM, keyPEM := pemPairForAPI(t, "vpn.example.com")
|
|
body, _ := json.Marshal(map[string]string{
|
|
"certificate_pem": certPEM,
|
|
"private_key_pem": keyPEM,
|
|
"host": "vpn.example.com",
|
|
})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/system/tls/certificate", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
if resp.Code != http.StatusOK {
|
|
t.Fatalf("upload HTTP %d: %s", resp.Code, resp.Body.String())
|
|
}
|
|
var out map[string]any
|
|
if err := json.Unmarshal(resp.Body.Bytes(), &out); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status, _ := out["status"].(map[string]any)
|
|
if status["cert_source"] != "upload" || status["certificate_valid"] != true {
|
|
t.Fatalf("status=%v", status)
|
|
}
|
|
|
|
// mismatch
|
|
_, otherKey := pemPairForAPI(t, "vpn.example.com")
|
|
bad, _ := json.Marshal(map[string]string{
|
|
"certificate_pem": certPEM,
|
|
"private_key_pem": otherKey,
|
|
"host": "vpn.example.com",
|
|
})
|
|
req = httptest.NewRequest(http.MethodPost, "/api/system/tls/certificate", bytes.NewReader(bad))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
if resp.Code != http.StatusBadRequest {
|
|
t.Fatalf("mismatch want 400 got %d", resp.Code)
|
|
}
|
|
}
|