153 lines
4.7 KiB
Go
153 lines
4.7 KiB
Go
package nginxactivation
|
||
|
||
import (
|
||
"context"
|
||
"encoding/pem"
|
||
"fmt"
|
||
"net"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"syscall"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// 实机验收显式指定 Nginx 路径;仅启动独立进程,不使用系统站点、PID 或服务单元。
|
||
func TestRotationWithRealNginx(t *testing.T) {
|
||
binary := os.Getenv("SCERT_TEST_NGINX_BINARY")
|
||
if binary == "" {
|
||
t.Skip("未指定隔离验收使用的 Nginx 可执行文件")
|
||
}
|
||
if !filepath.IsAbs(binary) {
|
||
t.Fatal("测试 Nginx 路径必须是绝对路径")
|
||
}
|
||
f := newRotationFixture(t)
|
||
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)
|
||
}
|
||
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 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("不应执行相对路径策略")
|
||
}
|
||
}
|
||
}
|