test(identity): 禁止 Gateway 依赖认证内核语义
将 OIDC 与安全事件集成测试中的 Realm 风格 Issuer 收敛为稳定 Issuer,并增加扫描所有 Gateway 已跟踪源码类别的回归门禁,避免 Keycloak 或 Realm URL 成为消费方契约。\n\n验证:go vet ./...;go test -race ./... -count=1;go test ./... -count=1;govulncheck ./...。
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGatewaySourceDoesNotDependOnAuthenticationCoreVocabulary(t *testing.T) {
|
||||||
|
_, currentFile, _, ok := runtime.Caller(0)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("resolve current test file")
|
||||||
|
}
|
||||||
|
repositoryRoot := filepath.Clean(filepath.Join(filepath.Dir(currentFile), "..", "..", "..", ".."))
|
||||||
|
appsRoot := filepath.Join(repositoryRoot, "apps")
|
||||||
|
forbiddenMarkers := []string{
|
||||||
|
strings.ToLower("key" + "cloak"),
|
||||||
|
"/rea" + "lms/",
|
||||||
|
}
|
||||||
|
sourceExtensions := []string{".go", ".js", ".json", ".ts", ".tsx", ".yaml", ".yml"}
|
||||||
|
skippedDirectories := []string{"coverage", "dist", "node_modules", "test-results"}
|
||||||
|
var violations []string
|
||||||
|
|
||||||
|
err := filepath.WalkDir(appsRoot, func(filePath string, entry os.DirEntry, walkErr error) error {
|
||||||
|
if walkErr != nil {
|
||||||
|
return walkErr
|
||||||
|
}
|
||||||
|
if entry.IsDir() {
|
||||||
|
if filePath != appsRoot && slices.Contains(skippedDirectories, entry.Name()) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !slices.Contains(sourceExtensions, strings.ToLower(filepath.Ext(entry.Name()))) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
content, readErr := os.ReadFile(filePath)
|
||||||
|
if readErr != nil {
|
||||||
|
return readErr
|
||||||
|
}
|
||||||
|
lowerContent := strings.ToLower(string(content))
|
||||||
|
for _, marker := range forbiddenMarkers {
|
||||||
|
if strings.Contains(lowerContent, marker) {
|
||||||
|
relativePath, relativeErr := filepath.Rel(repositoryRoot, filePath)
|
||||||
|
if relativeErr != nil {
|
||||||
|
return relativeErr
|
||||||
|
}
|
||||||
|
violations = append(violations, filepath.ToSlash(relativePath))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("scan Gateway sources: %v", err)
|
||||||
|
}
|
||||||
|
if len(violations) > 0 {
|
||||||
|
t.Fatalf("Gateway source contains authentication-core-specific vocabulary: %s", strings.Join(violations, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ func TestResolveOrProvisionOIDCUserLifecycleAndConcurrency(t *testing.T) {
|
|||||||
suffix := time.Now().UTC().Format("20060102150405.000000000")
|
suffix := time.Now().UTC().Format("20060102150405.000000000")
|
||||||
subject := "platform-jit-" + suffix
|
subject := "platform-jit-" + suffix
|
||||||
input := ResolveOrProvisionOIDCUserInput{
|
input := ResolveOrProvisionOIDCUserInput{
|
||||||
Issuer: "https://auth.test.example/realms/easyai",
|
Issuer: "https://auth.test.example/issuer/shared",
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
Username: "jit-user-" + suffix,
|
Username: "jit-user-" + suffix,
|
||||||
Roles: []string{"basic"},
|
Roles: []string{"basic"},
|
||||||
@@ -176,7 +176,7 @@ func TestResolveOrProvisionOIDCUserRejectsMissingMappingWithoutWrites(t *testing
|
|||||||
|
|
||||||
subject := "platform-jit-missing-" + time.Now().UTC().Format("20060102150405.000000000")
|
subject := "platform-jit-missing-" + time.Now().UTC().Format("20060102150405.000000000")
|
||||||
input := ResolveOrProvisionOIDCUserInput{
|
input := ResolveOrProvisionOIDCUserInput{
|
||||||
Issuer: "https://auth.test.example/realms/easyai",
|
Issuer: "https://auth.test.example/issuer/shared",
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
Username: "missing-user",
|
Username: "missing-user",
|
||||||
Roles: []string{"basic"},
|
Roles: []string{"basic"},
|
||||||
@@ -239,7 +239,7 @@ RETURNING id::text`, tenantKey, groupID).Scan(&tenantID); err != nil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOIDCUserKeyIsStableAndDoesNotExposeClaims(t *testing.T) {
|
func TestOIDCUserKeyIsStableAndDoesNotExposeClaims(t *testing.T) {
|
||||||
issuer := "https://auth.test.example/realms/easyai"
|
issuer := "https://auth.test.example/issuer/shared"
|
||||||
subject := "platform-sensitive-subject"
|
subject := "platform-sensitive-subject"
|
||||||
first := deriveOIDCUserKey(issuer, subject)
|
first := deriveOIDCUserKey(issuer, subject)
|
||||||
second := deriveOIDCUserKey(issuer+"/", subject)
|
second := deriveOIDCUserKey(issuer+"/", subject)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func TestSecurityEventWatermarkVerificationAndFallbackLifecycle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
issuer := "https://auth.test.example/ssf"
|
issuer := "https://auth.test.example/ssf"
|
||||||
subjectIssuer := "https://auth.test/realms/tenant"
|
subjectIssuer := "https://auth.test/issuer/shared"
|
||||||
audience := "urn:easyai:ssf:receiver:" + uuid.NewString()
|
audience := "urn:easyai:ssf:receiver:" + uuid.NewString()
|
||||||
streamID, tenantID, subject := uuid.NewString(), uuid.NewString(), uuid.NewString()
|
streamID, tenantID, subject := uuid.NewString(), uuid.NewString(), uuid.NewString()
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
|||||||
Reference in New Issue
Block a user