feat: configure HTTP/HTTPS public entry and managed certificates
This commit is contained in:
1 parent
35596cda9d
commit
88ea6b7854
21 files changed
+1309
-149
No files matched your search
@@ -0,0 +1,119 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user