142 lines
4.6 KiB
Go
142 lines
4.6 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|