Scert/backend/internal/bootstrap/management_certificate_test.go
2026-09-16 18:39:07 +08:00

142 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package bootstrap
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"errors"
"net"
"os"
"path/filepath"
"runtime"
"testing"
"time"
)
func TestGenerateManagementCertificateUsesOnlyRequestedIPSAN(t *testing.T) {
root := filepath.Join(t.TempDir(), "certificates")
saved, err := GenerateManagementCertificate(context.Background(), root, "203.0.113.25")
if err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(saved.CertificatePath)
if err != nil {
t.Fatal(err)
}
block, _ := pem.Decode(data)
if block == nil {
t.Fatal("管理证书不是 PEM 格式")
}
leaf, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatal(err)
}
if len(leaf.DNSNames) != 0 || len(leaf.IPAddresses) != 1 || !leaf.IPAddresses[0].Equal(net.ParseIP("203.0.113.25")) {
t.Fatalf("管理证书 SAN 超出目标 IP:DNS=%v IP=%v", leaf.DNSNames, leaf.IPAddresses)
}
publicKey, ok := leaf.PublicKey.(*ecdsa.PublicKey)
if !ok || publicKey.Curve != elliptic.P256() {
t.Fatalf("管理证书未使用 ECDSA P-256:%T", leaf.PublicKey)
}
if duration := leaf.NotAfter.Sub(leaf.NotBefore); duration < managementCertificateLifetime || duration > managementCertificateLifetime+10*time.Minute {
t.Fatalf("管理证书有效期不符合预期:%s", duration)
}
if saved.TargetIP != "203.0.113.25" || saved.CertificatePath == saved.PrivateKeyPath {
t.Fatalf("管理证书结果不完整:%+v", saved)
}
if runtime.GOOS != "windows" {
info, err := os.Stat(saved.PrivateKeyPath)
if err != nil {
t.Fatal(err)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("私钥权限不是 0600:%04o", info.Mode().Perm())
}
}
}
func TestGenerateManagementCertificateRejectsNonPublicAddresses(t *testing.T) {
for _, value := range []string{"", "not-an-ip", "127.0.0.1", "::1", "10.0.0.8", "192.168.1.9", "169.254.1.2", "224.0.0.1"} {
t.Run(value, func(t *testing.T) {
_, err := GenerateManagementCertificate(context.Background(), filepath.Join(t.TempDir(), "certificates"), value)
if err == nil {
t.Fatalf("非公网 IP 未被拒绝:%q", value)
}
})
}
}
func TestGenerateManagementCertificateRejectsSymlinkTarget(t *testing.T) {
root := filepath.Join(t.TempDir(), "certificates")
targetDirectory := filepath.Join(root, "management-ip", "203.0.113.31")
if err := os.MkdirAll(targetDirectory, 0o700); err != nil {
t.Fatal(err)
}
external := filepath.Join(t.TempDir(), "external.pem")
if err := os.WriteFile(external, []byte("do-not-touch"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink(external, filepath.Join(targetDirectory, "key.pem")); err != nil {
if runtime.GOOS == "windows" {
t.Skipf("当前 Windows 环境无法创建符号链接:%v", err)
}
t.Fatal(err)
}
if _, err := GenerateManagementCertificate(context.Background(), root, "203.0.113.31"); err == nil {
t.Fatal("符号链接私钥目标未被拒绝")
}
data, err := os.ReadFile(external)
if err != nil || string(data) != "do-not-touch" {
t.Fatalf("符号链接外部文件被修改:%q err=%v", data, err)
}
}
func TestRollbackManagementCertificateRestoresExactPreviousFiles(t *testing.T) {
root := filepath.Join(t.TempDir(), "certificates")
directory := filepath.Join(root, "management-ip", "203.0.113.42")
if err := os.MkdirAll(directory, 0o700); err != nil {
t.Fatal(err)
}
previous := map[string]string{
"cert.pem": "previous-certificate",
"fullchain.pem": "previous-fullchain",
"key.pem": "previous-private-key",
}
for name, value := range previous {
if err := os.WriteFile(filepath.Join(directory, name), []byte(value), 0o600); err != nil {
t.Fatal(err)
}
}
saved, err := GenerateManagementCertificate(context.Background(), root, "203.0.113.42")
if err != nil {
t.Fatal(err)
}
if err := RollbackManagementCertificate(context.Background(), saved); err != nil {
t.Fatal(err)
}
for name, value := range previous {
data, err := os.ReadFile(filepath.Join(directory, name))
if err != nil || string(data) != value {
t.Fatalf("回滚未恢复 %s:%q err=%v", name, data, err)
}
}
}
func TestRollbackManagementCertificateRemovesNewFilesOnly(t *testing.T) {
root := filepath.Join(t.TempDir(), "certificates")
saved, err := GenerateManagementCertificate(context.Background(), root, "203.0.113.50")
if err != nil {
t.Fatal(err)
}
if err := RollbackManagementCertificate(context.Background(), saved); err != nil {
t.Fatal(err)
}
for _, path := range managementCertificatePaths(saved) {
if _, err := os.Lstat(path); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("新管理证书文件未被回滚:%s err=%v", path, err)
}
}
}