Scert/backend/internal/sshkey/service_test.go
xuans 7778aa5ced 停用 SSH 密钥兼容入口
停用历史 SSH 私钥生成、存储、读取和管理入口,强制服务器使用密码认证。
2026-10-02 04:03:23 +08:00

175 lines
6.3 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.

//go:build legacy_ssh_key_tests
package sshkey
import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"strings"
"testing"
"golang.org/x/crypto/ssh"
"git.pcux.cn/xuans/Scert/backend/internal/secretstore"
)
func TestServiceCreatesListsValidatesAndDeletesSSHKey(t *testing.T) {
store, err := secretstore.Open(t.TempDir())
if err != nil {
t.Fatalf("打开敏感信息存储失败:%v", err)
}
service := NewService(store)
privateKey := testPrivateKey(t)
created, err := service.Create(t.Context(), "生产部署私钥", privateKey, "")
if err != nil {
t.Fatalf("创建 SSH 私钥失败:%v", err)
}
if created.ID == "" || !strings.HasPrefix(created.Reference, ReferencePrefix) || created.Algorithm != "ssh-ed25519" || !strings.HasPrefix(created.Fingerprint, "SHA256:") {
t.Fatalf("SSH 私钥元数据无效:%+v", created)
}
items, err := service.List(t.Context())
if err != nil || len(items) != 1 || items[0].Name != "生产部署私钥" {
t.Fatalf("SSH 私钥列表无效:items=%+v err=%v", items, err)
}
validated, err := service.ValidateReference(t.Context(), created.Reference)
if err != nil || validated.Fingerprint != created.Fingerprint {
t.Fatalf("SSH 私钥引用校验失败:metadata=%+v err=%v", validated, err)
}
authorized, err := service.AuthorizedPublicKey(t.Context(), created.Reference)
if err != nil {
t.Fatalf("读取 SSH 公钥失败:%v", err)
}
publicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(authorized))
if err != nil || ssh.FingerprintSHA256(publicKey) != created.Fingerprint {
t.Fatalf("SSH 公钥指纹不匹配:fingerprint=%s err=%v", ssh.FingerprintSHA256(publicKey), err)
}
if err := service.Delete(t.Context(), created.ID); err != nil {
t.Fatalf("删除 SSH 私钥失败:%v", err)
}
if _, err := service.ValidateReference(t.Context(), created.Reference); err == nil {
t.Fatal("已删除的 SSH 私钥引用不应继续通过校验")
}
}
func TestServiceListIgnoresSSHPasswordSecrets(t *testing.T) {
store, err := secretstore.Open(t.TempDir())
if err != nil {
t.Fatalf("failed to open secret store: %v", err)
}
service := NewService(store)
if _, err := service.Create(t.Context(), "deployment key", testPrivateKey(t), ""); err != nil {
t.Fatalf("failed to create SSH key: %v", err)
}
if err := store.Put(t.Context(), ReferencePrefix+"password/test-password", map[string]string{
"kind": "ssh_password",
"password": "not-for-listing",
}); err != nil {
t.Fatalf("failed to create password secret: %v", err)
}
items, err := service.List(t.Context())
if err != nil {
t.Fatalf("failed to list SSH keys: %v", err)
}
if len(items) != 1 || items[0].Name != "deployment key" {
t.Fatalf("password secret leaked into SSH key list: %+v", items)
}
}
func TestServiceRejectsInvalidSSHKeyWithoutLeakingContent(t *testing.T) {
store, err := secretstore.Open(t.TempDir())
if err != nil {
t.Fatalf("打开敏感信息存储失败:%v", err)
}
privateKey := "very-sensitive-invalid-private-key"
_, err = NewService(store).Create(t.Context(), "错误私钥", privateKey, "")
if err == nil {
t.Fatal("无效 SSH 私钥应被拒绝")
}
if strings.Contains(err.Error(), privateKey) {
t.Fatalf("错误信息不应泄露私钥内容:%v", err)
}
}
func TestServiceSupportsEncryptedSSHKeyAndRejectsWrongPassphrase(t *testing.T) {
store, err := secretstore.Open(t.TempDir())
if err != nil {
t.Fatalf("failed to open secret store: %v", err)
}
privateKey := testEncryptedPrivateKey(t, "correct-passphrase")
service := NewService(store)
created, err := service.Create(t.Context(), "encrypted deployment key", privateKey, "correct-passphrase")
if err != nil {
t.Fatalf("failed to create encrypted SSH key: %v", err)
}
if created.Algorithm != "ssh-ed25519" || !strings.HasPrefix(created.Fingerprint, "SHA256:") {
t.Fatalf("invalid encrypted SSH key metadata: %+v", created)
}
_, err = service.Create(t.Context(), "wrong passphrase key", privateKey, "wrong-passphrase")
if err == nil || strings.Contains(err.Error(), "wrong-passphrase") || strings.Contains(err.Error(), "correct-passphrase") {
t.Fatalf("wrong passphrase was not rejected safely: %v", err)
}
}
func TestServiceRejectsOversizedPrivateKeyAndPassphrase(t *testing.T) {
store, err := secretstore.Open(t.TempDir())
if err != nil {
t.Fatalf("failed to open secret store: %v", err)
}
service := NewService(store)
if _, err := service.Create(t.Context(), "oversized key", strings.Repeat("a", MaxPrivateKeyBytes+1), ""); err == nil {
t.Fatal("oversized SSH private key should be rejected")
}
if _, err := service.Create(t.Context(), "oversized passphrase", testPrivateKey(t), strings.Repeat("p", maxPassphraseBytes+1)); err == nil {
t.Fatal("oversized SSH private key passphrase should be rejected")
}
}
func testPrivateKey(t *testing.T) string {
t.Helper()
_, key, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("生成测试私钥失败:%v", err)
}
encoded, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
t.Fatalf("编码测试私钥失败:%v", err)
}
return string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: encoded}))
}
func testEncryptedPrivateKey(t *testing.T, passphrase string) string {
t.Helper()
_, key, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("failed to generate test private key: %v", err)
}
block, err := ssh.MarshalPrivateKeyWithPassphrase(key, "scert-test", []byte(passphrase))
if err != nil {
t.Fatalf("failed to encrypt test private key: %v", err)
}
return string(pem.EncodeToMemory(block))
}
func TestServiceScopesSSHKeyToProject(t *testing.T) {
store, err := secretstore.Open(t.TempDir())
if err != nil {
t.Fatalf("打开敏感信息存储失败:%v", err)
}
service := NewService(store)
created, err := service.CreateForProject(t.Context(), "项目部署私钥", testPrivateKey(t), "", "project-a")
if err != nil {
t.Fatalf("创建项目 SSH 私钥失败:%v", err)
}
if created.ProjectID != "project-a" {
t.Fatalf("SSH 私钥项目归属错误:%+v", created)
}
if _, err := service.ValidateReferenceForProject(t.Context(), created.Reference, "project-a"); err != nil {
t.Fatalf("同项目 SSH 私钥校验失败:%v", err)
}
if _, err := service.ValidateReferenceForProject(t.Context(), created.Reference, "project-b"); err == nil {
t.Fatal("跨项目 SSH 私钥引用不应通过校验")
}
}