Scert/backend/internal/nginxactivation/nginx_linux_test.go

217 lines
6.8 KiB
Go
Raw Permalink 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 nginxactivation
import (
"context"
"encoding/pem"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"syscall"
"testing"
"time"
)
// 实机验收显式指定 Nginx 路径;仅启动独立进程,不使用系统站点、PID 或服务单元。
func TestRotationWithRealNginx(t *testing.T) {
f := newRotationFixture(t)
useRealNginx(t, f)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
id, err := f.rotation.Stage(ctx, f.candidate.chain, f.candidate.key)
if err != nil {
t.Fatal(err)
}
f.upstream.active.Store(&f.candidate.pair)
if err := f.rotation.Commit(ctx, id); err != nil {
t.Fatal(err)
}
f.assertStatus(t, "committed")
next := f.authority.issue(t, testHostname, time.Now().Add(-time.Hour), time.Now().Add(72*time.Hour))
if _, err := f.rotation.Stage(ctx, next.chain, next.key); err != nil {
t.Fatal(err)
}
// 模拟调用方退出后由新组件恢复,真实入口必须重新提供上一张已提交证书。
restarted := f.rotation
if err := restarted.Recover(ctx); err != nil {
t.Fatal(err)
}
f.assertStatus(t, "rolled_back")
if _, err := restarted.Proxy.Check(ctx, f.candidate.pair.Certificate[0]); err != nil {
t.Fatal(err)
}
}
func useRealNginx(t *testing.T, f *rotationFixture) {
t.Helper()
binary := os.Getenv("SCERT_TEST_NGINX_BINARY")
if binary == "" {
t.Skip("未指定隔离验收使用的 Nginx 可执行文件")
}
if !filepath.IsAbs(binary) {
t.Fatal("测试 Nginx 路径必须是绝对路径")
}
prefix := t.TempDir()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
address := listener.Addr().String()
if err := listener.Close(); err != nil {
t.Fatal(err)
}
rootPath := filepath.Join(prefix, "ca.pem")
writeTestFile(t, rootPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: f.authority.certificate.Raw}))
configPath := filepath.Join(prefix, "nginx.conf")
configuration := fmt.Sprintf(`worker_processes 1;
pid %s/nginx.pid;
error_log stderr warn;
events { worker_connections 64; }
http {
access_log off;
client_body_temp_path %s/client;
proxy_temp_path %s/proxy;
fastcgi_temp_path %s/fastcgi;
uwsgi_temp_path %s/uwsgi;
scgi_temp_path %s/scgi;
server {
listen %s ssl;
server_name %s;
include %s/active.conf;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass https://%s;
proxy_set_header Host %s;
proxy_ssl_server_name on;
proxy_ssl_name %s;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate %s;
}
}
}
`, prefix, prefix, prefix, prefix, prefix, prefix, address, testHostname,
f.rotation.Directory, f.upstream.Address, net.JoinHostPort(testHostname, endpointPort(t, f.upstream.Address)), testHostname, rootPath)
writeTestFile(t, configPath, []byte(configuration))
reloader := CommandReloader{Binary: binary, Config: configPath, Prefix: prefix + "/"}
if err := reloader.Check(context.Background()); err != nil {
t.Fatal(err)
}
logFile, err := os.OpenFile(filepath.Join(prefix, "process.log"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err != nil {
t.Fatal(err)
}
command := exec.Command(binary, "-p", prefix+"/", "-c", configPath, "-g", "daemon off;")
command.Stdout, command.Stderr = logFile, logFile
command.Env = []string{"PATH=/usr/sbin:/usr/bin:/sbin:/bin", "LANG=C"}
if err := command.Start(); err != nil {
_ = logFile.Close()
t.Fatal(err)
}
finished := make(chan error, 1)
go func() { finished <- command.Wait() }()
t.Cleanup(func() {
_ = command.Process.Signal(syscall.SIGQUIT)
select {
case err := <-finished:
if err != nil {
t.Errorf("隔离 Nginx 退出失败:%v", err)
}
case <-time.After(5 * time.Second):
_ = command.Process.Kill()
<-finished
t.Error("隔离 Nginx 未在时限内退出")
}
_ = logFile.Close()
if t.Failed() {
content, _ := os.ReadFile(filepath.Join(prefix, "process.log"))
t.Logf("隔离 Nginx 日志:\n%s", content)
}
})
f.rotation.Proxy = Endpoint{Address: address, Hostname: testHostname, Roots: f.authority.roots}
f.rotation.Reloader = reloader
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := f.rotation.Proxy.Wait(ctx, f.old.pair.Certificate[0]); err != nil {
t.Fatal(err)
}
}
func TestCoordinatorWithRealNginxAndIPC(t *testing.T) {
for _, failure := range []string{"none", "stage_response", "commit_response"} {
t.Run(failure, func(t *testing.T) {
f := newCoordinatorFixture(t)
useRealNginx(t, f.rotationFixture)
f.previous.ProxyAddress = f.rotation.Proxy.Address
f.candidate.ProxyAddress = f.rotation.Proxy.Address
f.session.current = f.previous
client, cancel, done := startLocalService(t, f.rotation, uint32(os.Geteuid()))
defer stopLocalService(t, cancel, done)
proxy := coordinatedTestProxy{ProxyClient: client}
if failure == "stage_response" {
proxy.stage = func(ctx context.Context, id string, chain, key []byte) (string, error) {
if _, err := client.StageRequest(ctx, id, chain, key); err != nil {
return "", err
}
return "", fmt.Errorf("注入暂存应答丢失")
}
}
if failure == "commit_response" {
proxy.commit = func(ctx context.Context, id string) error {
if err := client.Commit(ctx, id); err != nil {
return err
}
return fmt.Errorf("注入提交应答丢失")
}
}
f.coordinator.Proxy = proxy
ctx, stop := context.WithTimeout(t.Context(), 20*time.Second)
defer stop()
err := f.coordinator.Activate(ctx, f.candidate.CertificateFiles)
want := "committed"
leaf := f.rotationFixture.candidate.pair.Certificate[0]
if failure == "stage_response" {
want, leaf = "rolled_back", f.old.pair.Certificate[0]
if err == nil {
t.Fatal("暂存应答丢失应回退并报告失败")
}
} else if err != nil {
t.Fatal(err)
}
if record := f.record(t); record.Phase != want || record.RequestID != record.ProxyID {
t.Fatal("真实 Nginx 协调终态错误", record)
}
if _, err := f.rotation.Proxy.Check(ctx, leaf); err != nil {
t.Fatal(err)
}
if _, err := f.rotation.Upstream.Check(ctx, leaf); err != nil {
t.Fatal(err)
}
if err := f.coordinator.Ready(ctx); err != nil {
t.Fatal(err)
}
})
}
}
func endpointPort(t *testing.T, address string) string {
t.Helper()
_, port, err := net.SplitHostPort(address)
if err != nil {
t.Fatal(err)
}
return port
}
func TestCommandReloaderRejectsRelativePolicy(t *testing.T) {
for _, policy := range []CommandReloader{
{Binary: "nginx", Config: "/etc/nginx/nginx.conf", Prefix: "/etc/nginx"},
{Binary: "/usr/sbin/nginx", Config: "nginx.conf", Prefix: "/etc/nginx"},
{Binary: "/usr/sbin/nginx", Config: "/etc/nginx/nginx.conf", Prefix: "."},
} {
if err := policy.Check(context.Background()); err == nil {
t.Fatal("不应执行相对路径策略")
}
}
}