回应 Fasthei 复审(PR #52 CHANGES_REQUESTED): 1. 发现出的子资源不再继承账号 secret_ref:抽出 DB 无关的 applyDiscoveredResourceFields, 恒置 SecretRef=""(避免账号级凭据引用经 resourceToResponse / grant manifest 扩散到每个 VM/S3/DB)。加 TestApplyDiscoveredResourceFields_NoSecretInheritance。 3. GCP searchAllResources URL 修正:gcpSearchAllResourcesURL 不再 PathEscape 整个 scope (slash 是路径模板一部分),生成 /v1/projects/<id>:searchAllResources。加 TestGCPSearchAllResourcesURL。 2. AWS 覆盖范围据实声明:Resource Groups Tagging GetResources 仅覆盖 tagged/曾 tagged 资源, 不覆盖未打标签 EC2/RDS/S3;注释明确「tagged resources only」,#5 全量发现诉求不据此关闭。 controller 全套测试通过,go build/vet 干净。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
5.7 KiB
Go
151 lines
5.7 KiB
Go
package controller
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/heicode/manager/model"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// #5: 跨云类别分类 vm/database/blob 一致。
|
|
func TestClassifyCloudResourceType(t *testing.T) {
|
|
cases := map[string]string{
|
|
// vm
|
|
"Microsoft.Compute/virtualMachines": "vm",
|
|
"AWS::EC2::Instance": "vm",
|
|
"compute.googleapis.com/Instance": "vm",
|
|
// database
|
|
"Microsoft.Sql/servers/databases": "database",
|
|
"AWS::RDS::DBInstance": "database",
|
|
"sqladmin.googleapis.com/Instance": "database",
|
|
"AWS::DynamoDB::Table": "database",
|
|
// blob / object storage
|
|
"Microsoft.Storage/storageAccounts": "blob",
|
|
"AWS::S3::Bucket": "blob",
|
|
"storage.googleapis.com/Bucket": "blob",
|
|
// fallback
|
|
"Microsoft.Network/virtualNetworks": "cloud_resource",
|
|
"AWS::IAM::Role": "cloud_resource",
|
|
}
|
|
for native, want := range cases {
|
|
require.Equal(t, want, classifyCloudResourceType(native), "classify %s", native)
|
|
}
|
|
}
|
|
|
|
// #5: ARN 解析 → 归一化 + 正确分类。
|
|
func TestAWSARNToResource(t *testing.T) {
|
|
ec2, ok := awsARNToResource("arn:aws:ec2:us-east-1:123456789012:instance/i-0abc123")
|
|
require.True(t, ok)
|
|
require.Equal(t, "us-east-1", ec2.Location)
|
|
require.Equal(t, "i-0abc123", ec2.Name)
|
|
require.Equal(t, "vm", classifyCloudResourceType(ec2.NativeType))
|
|
|
|
s3, ok := awsARNToResource("arn:aws:s3:::my-bucket")
|
|
require.True(t, ok)
|
|
require.Equal(t, "my-bucket", s3.Name)
|
|
require.Equal(t, "blob", classifyCloudResourceType(s3.NativeType))
|
|
|
|
rds, ok := awsARNToResource("arn:aws:rds:eu-west-1:123:db:prod-pg")
|
|
require.True(t, ok)
|
|
require.Equal(t, "database", classifyCloudResourceType(rds.NativeType))
|
|
|
|
_, ok = awsARNToResource("not-an-arn")
|
|
require.False(t, ok)
|
|
}
|
|
|
|
// #5: SigV4 实现正确性 —— 对齐 AWS 官方 "get-vanilla" 测试向量。
|
|
func TestAWSSigV4_VanillaVector(t *testing.T) {
|
|
cred := awsDiscoveryCredentials{
|
|
AccessKeyID: "AKIDEXAMPLE",
|
|
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
|
Region: "us-east-1",
|
|
}
|
|
tm := time.Date(2015, 8, 30, 12, 36, 0, 0, time.UTC)
|
|
auth, amzDate, err := awsSigV4Authorization("GET", "https://example.amazonaws.com/", map[string]string{}, []byte(""), cred, "service", tm)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "20150830T123600Z", amzDate)
|
|
require.Contains(t, auth, "Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request")
|
|
require.Contains(t, auth, "SignedHeaders=host;x-amz-date")
|
|
// 官方向量期望签名
|
|
require.Contains(t, auth, "Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31")
|
|
}
|
|
|
|
// #5 复审 #3:GCP searchAllResources URL —— scope 的 slash 不得被 %2F 转义。
|
|
func TestGCPSearchAllResourcesURL(t *testing.T) {
|
|
u := gcpSearchAllResourcesURL("my-proj", "")
|
|
require.Equal(t, "https://cloudasset.googleapis.com/v1/projects/my-proj:searchAllResources?pageSize=500", u)
|
|
require.NotContains(t, u, "%2F", "scope slash 不应被转义")
|
|
|
|
u2 := gcpSearchAllResourcesURL("my-proj", "tok en/+")
|
|
require.Contains(t, u2, "&pageToken=tok+en%2F%2B")
|
|
}
|
|
|
|
// #5 复审 #1:发现出的资源**绝不继承**账号 secret_ref;元数据带 classified_type,不含账号凭据引用。
|
|
func TestApplyDiscoveredResourceFields_NoSecretInheritance(t *testing.T) {
|
|
account := model.ResourceBinding{
|
|
Id: 42, UserId: 7, ResourceType: "cloud_account", Provider: "aws",
|
|
SecretRef: "azkv://heicode-kv.vault.azure.net/secrets/aws-keys",
|
|
}
|
|
d := discoveredCloudResource{
|
|
ExternalId: "arn:aws:ec2:us-east-1:123:instance/i-1",
|
|
Name: "i-1", NativeType: "AWS::EC2::Instance", Location: "us-east-1",
|
|
Metadata: map[string]any{"region": "us-east-1"},
|
|
}
|
|
var r model.ResourceBinding
|
|
require.NoError(t, applyDiscoveredResourceFields(&r, account, "aws", d))
|
|
|
|
require.Equal(t, "", r.SecretRef, "发现出的子资源绝不继承账号 secret_ref")
|
|
require.Equal(t, "cloud_resource", r.ResourceType)
|
|
require.Equal(t, "aws", r.Provider)
|
|
require.Equal(t, 7, r.UserId)
|
|
require.NotContains(t, r.Metadata, "azkv://", "metadata 不得含账号凭据引用")
|
|
require.Contains(t, r.Metadata, "\"classified_type\":\"vm\"")
|
|
require.Contains(t, r.PermissionScope, "aws:read")
|
|
}
|
|
|
|
// #5: GCP SA JWT —— RS256 断言可被对应公钥验签,且 claims 正确。
|
|
func TestBuildGCPAssertion_RS256Roundtrip(t *testing.T) {
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
require.NoError(t, err)
|
|
der, err := x509.MarshalPKCS8PrivateKey(key)
|
|
require.NoError(t, err)
|
|
pemStr := string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der}))
|
|
|
|
cred := gcpDiscoveryCredentials{
|
|
ClientEmail: "svc@proj.iam.gserviceaccount.com",
|
|
PrivateKey: pemStr,
|
|
ProjectID: "proj",
|
|
}
|
|
tm := time.Date(2026, 6, 10, 0, 0, 0, 0, time.UTC)
|
|
jwt, err := buildGCPAssertion(cred, "https://www.googleapis.com/auth/cloud-platform", "https://oauth2.googleapis.com/token", tm)
|
|
require.NoError(t, err)
|
|
|
|
parts := strings.Split(jwt, ".")
|
|
require.Len(t, parts, 3)
|
|
|
|
// 验签:signingInput = header.payload
|
|
signingInput := parts[0] + "." + parts[1]
|
|
digest := sha256.Sum256([]byte(signingInput))
|
|
sig, err := base64.RawURLEncoding.DecodeString(parts[2])
|
|
require.NoError(t, err)
|
|
require.NoError(t, rsa.VerifyPKCS1v15(&key.PublicKey, crypto.SHA256, digest[:], sig), "RS256 签名应可被公钥验证")
|
|
|
|
// header alg + claims
|
|
hdr, err := base64.RawURLEncoding.DecodeString(parts[0])
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(hdr), "RS256")
|
|
claims, err := base64.RawURLEncoding.DecodeString(parts[1])
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(claims), "svc@proj.iam.gserviceaccount.com")
|
|
require.Contains(t, string(claims), "cloud-platform")
|
|
}
|