- 添加 testutil 容器管理工具(lolly/nginx) - 添加测试配置文件(basic/proxy/ssl) - 添加测试用 SSL 证书 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
133 lines
3.6 KiB
Go
133 lines
3.6 KiB
Go
//go:build e2e
|
|
|
|
// Package testutil 提供 E2E 测试的工具函数。
|
|
package testutil
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// GenerateSelfSignedCert 生成自签名证书和私钥。
|
|
//
|
|
// 返回证书路径、私钥路径、清理函数和错误。
|
|
// 清理函数会删除生成的文件。
|
|
func GenerateSelfSignedCert(tmpDir string) (certPath, keyPath string, cleanup func(), err error) {
|
|
// 生成 ECDSA 私钥
|
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return "", "", nil, fmt.Errorf("failed to generate private key: %w", err)
|
|
}
|
|
|
|
// 生成序列号
|
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
if err != nil {
|
|
return "", "", nil, fmt.Errorf("failed to generate serial number: %w", err)
|
|
}
|
|
|
|
// 创建证书模板
|
|
template := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
Organization: []string{"Lolly E2E Test"},
|
|
CommonName: "localhost",
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(24 * time.Hour),
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
DNSNames: []string{"localhost"},
|
|
IPAddresses: nil,
|
|
}
|
|
|
|
// 生成证书
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
|
if err != nil {
|
|
return "", "", nil, fmt.Errorf("failed to create certificate: %w", err)
|
|
}
|
|
|
|
// 确保目录存在
|
|
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
|
|
return "", "", nil, fmt.Errorf("failed to create temp dir: %w", err)
|
|
}
|
|
|
|
// 写入证书文件
|
|
certPath = filepath.Join(tmpDir, "cert.pem")
|
|
certFile, err := os.Create(certPath)
|
|
if err != nil {
|
|
return "", "", nil, fmt.Errorf("failed to create cert file: %w", err)
|
|
}
|
|
if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
|
certFile.Close()
|
|
return "", "", nil, fmt.Errorf("failed to write cert: %w", err)
|
|
}
|
|
certFile.Close()
|
|
|
|
// 写入私钥文件
|
|
keyPath = filepath.Join(tmpDir, "key.pem")
|
|
keyFile, err := os.Create(keyPath)
|
|
if err != nil {
|
|
return "", "", nil, fmt.Errorf("failed to create key file: %w", err)
|
|
}
|
|
privBytes, err := x509.MarshalECPrivateKey(privateKey)
|
|
if err != nil {
|
|
keyFile.Close()
|
|
return "", "", nil, fmt.Errorf("failed to marshal private key: %w", err)
|
|
}
|
|
if err := pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}); err != nil {
|
|
keyFile.Close()
|
|
return "", "", nil, fmt.Errorf("failed to write key: %w", err)
|
|
}
|
|
keyFile.Close()
|
|
|
|
// 清理函数
|
|
cleanup = func() {
|
|
os.Remove(certPath)
|
|
os.Remove(keyPath)
|
|
}
|
|
|
|
return certPath, keyPath, cleanup, nil
|
|
}
|
|
|
|
// GenerateCertPool 从证书文件创建 x509.CertPool。
|
|
func GenerateCertPool(certPath string) (*x509.CertPool, error) {
|
|
certPEM, err := os.ReadFile(certPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read cert file: %w", err)
|
|
}
|
|
|
|
certPool := x509.NewCertPool()
|
|
if !certPool.AppendCertsFromPEM(certPEM) {
|
|
return nil, fmt.Errorf("failed to append cert to pool")
|
|
}
|
|
|
|
return certPool, nil
|
|
}
|
|
|
|
// GenerateTLSConfig 生成客户端 TLS 配置,信任指定的证书。
|
|
func GenerateTLSConfig(certPath string) (*TLSConfig, error) {
|
|
certPool, err := GenerateCertPool(certPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TLSConfig{
|
|
RootCAs: certPool,
|
|
}, nil
|
|
}
|
|
|
|
// TLSConfig 简化的 TLS 配置。
|
|
type TLSConfig struct {
|
|
RootCAs *x509.CertPool
|
|
}
|