Scert/backend/internal/cli/dns_test.go

165 lines
5.9 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 cli
import (
"bytes"
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/config"
"git.pcux.cn/xuans/Scert/backend/internal/dnsprovider"
"git.pcux.cn/xuans/Scert/backend/internal/secretstore"
)
func TestDNSCheckRequiresExplicitWriteConfirmation(t *testing.T) {
called := false
command := newDNSCheckCommand(func(context.Context, string, map[string]string) (dnsprovider.AuthorizationReport, error) {
called = true
return dnsprovider.AuthorizationReport{}, nil
})
command.SetArgs([]string{"--domain", "example.com"})
if err := command.Execute(); err == nil || !strings.Contains(err.Error(), "--confirm-dns-write") {
t.Fatalf("缺少写入确认时必须拒绝:%v", err)
}
if called {
t.Fatal("确认前不得调用云端 API")
}
}
func TestDNSCheckRejectsOutsideScopeBeforeVerifier(t *testing.T) {
prepareDNSCheckConfig(t)
command := newDNSCheckCommand(func(context.Context, string, map[string]string) (dnsprovider.AuthorizationReport, error) {
t.Fatal("CLI 越界不得进入云端验证")
return dnsprovider.AuthorizationReport{}, nil
})
command.SilenceUsage, command.SilenceErrors = true, true
command.SetArgs([]string{"--domain", "outside.net", "--confirm-dns-write"})
if err := command.Execute(); err == nil {
t.Fatal("确认写入不等于允许扩大范围")
}
}
func TestDNSInspectDoesNotRequireWriteOrPromoteReadableToVerified(t *testing.T) {
prepareDNSCheckConfig(t)
command := newDNSInspectCommand(func(context.Context, string, map[string]string) (dnsprovider.AuthorizationReport, error) {
return dnsprovider.AuthorizationReport{
Provider: "aliyun", Domains: []dnsprovider.AuthorizationDomain{{Domain: "example.com", Listed: true}},
}, nil
})
var output bytes.Buffer
command.SetOut(&output)
command.SetArgs([]string{"--domain", "example.com"})
if err := command.Execute(); err != nil {
t.Fatal(err)
}
var report dnsprovider.AuthorizationReport
if err := json.Unmarshal(output.Bytes(), &report); err != nil || report.Verified || report.Domains[0].Writable {
t.Fatalf("只读成功不能升级为写权限通过:%s", output.String())
}
if command.Flag("confirm-dns-write") != nil {
t.Fatal("只读命令不得提供写入开关")
}
}
func TestDNSCheckReportsLiveResultWithoutPersistingConfiguration(t *testing.T) {
for _, outcome := range []string{"allowed", "denied", "unknown", "unverified"} {
t.Run(outcome, func(t *testing.T) {
cfg := prepareDNSCheckConfig(t)
before, err := os.ReadFile(configPath)
if err != nil {
t.Fatal(err)
}
calls := 0
command := newDNSCheckCommand(func(ctx context.Context, name string, credentials map[string]string) (dnsprovider.AuthorizationReport, error) {
calls++
if name != "example.com" || credentials["access_key_id"] != "test-id" {
t.Fatal("检查目标或凭据来源不正确")
}
deadline, ok := ctx.Deadline()
if !ok {
t.Fatal("权限请求必须设置超时")
}
remaining := time.Until(deadline)
if remaining < 5*time.Second || remaining > 7*time.Second {
t.Fatalf("阿里云 CLI 权限验证时限错误:%s", remaining)
}
report := dnsprovider.AuthorizationReport{Provider: "aliyun", RequestedDomain: name, Verified: outcome == "allowed"}
switch outcome {
case "denied":
return report, errors.New("Forbidden.NoPermission")
case "unknown":
return report, context.DeadlineExceeded
default:
return report, nil
}
})
var output bytes.Buffer
command.SilenceErrors, command.SilenceUsage = true, true
command.SetOut(&output)
command.SetArgs([]string{"--domain", "EXAMPLE.COM.", "--confirm-dns-write"})
err = command.Execute()
if (err == nil) != (outcome == "allowed") || calls != 1 {
t.Fatalf("检查结果错误:outcome=%s calls=%d err=%v", outcome, calls, err)
}
var report dnsprovider.AuthorizationReport
if err := json.Unmarshal(output.Bytes(), &report); err != nil || report.Verified != (outcome == "allowed") {
t.Fatalf("必须输出可机器读取的真实报告:%s", output.String())
}
if strings.Contains(output.String(), "test-secret") || strings.Contains(output.String(), "test-id") {
t.Fatal("输出不得包含凭据")
}
after, err := os.ReadFile(configPath)
if err != nil || !bytes.Equal(before, after) {
t.Fatal("权限诊断不得回写配置或初始化状态")
}
if _, err := os.Stat(cfg.Paths.Data); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("权限诊断不得初始化业务数据库:%v", err)
}
})
}
}
func TestDNSCheckDoesNotCreateMissingMasterKey(t *testing.T) {
oldPath := configPath
t.Cleanup(func() { configPath = oldPath })
cfg := config.Default(t.TempDir())
cfg.DNS.Provider, cfg.DNS.CredentialRef = "aliyun", "secret://dns/check"
configPath = filepath.Join(cfg.Paths.Config, "scert.json")
if err := cfg.Save(configPath); err != nil {
t.Fatal(err)
}
command := newDNSCheckCommand(nil)
command.SetArgs([]string{"--domain", "example.com", "--confirm-dns-write"})
if err := command.Execute(); err == nil {
t.Fatal("主密钥缺失必须拒绝")
}
if _, err := os.Stat(filepath.Join(cfg.Paths.Config, "master.key")); !errors.Is(err, os.ErrNotExist) {
t.Fatal("诊断不得创建新主密钥")
}
}
func prepareDNSCheckConfig(t *testing.T) config.Config {
t.Helper()
oldPath := configPath
t.Cleanup(func() { configPath = oldPath })
cfg := config.Default(t.TempDir())
cfg.DNS.Provider, cfg.DNS.CredentialRef = "aliyun", "secret://dns/check"
configPath = filepath.Join(cfg.Paths.Config, "scert.json")
if err := cfg.Save(configPath); err != nil {
t.Fatal(err)
}
store, err := secretstore.Open(cfg.Paths.Config)
if err != nil {
t.Fatal(err)
}
if err := store.Put(t.Context(), cfg.DNS.CredentialRef, map[string]string{"access_key_id": "test-id", "access_key_secret": "test-secret", "managed_zones": "example.com", "allowed_domains": "example.com"}); err != nil {
t.Fatal(err)
}
return cfg
}