161ef94b4f
Check engine pin consistency / Dockerfile / CI pin consistency (push) Successful in 8s
Sirius CI/CD Pipeline / Detect Changes (push) Successful in 23s
Validate Docker Configuration / Validate Docker Compose Configuration (push) Successful in 47s
Sirius CI/CD Pipeline / Build API (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Build UI (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Engine Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Merge API Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Merge UI Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Build Engine (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Build Infra (${{ matrix.service }}, ${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-postgres) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-rabbitmq) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-valkey) (push) Has been cancelled
Sirius CI/CD Pipeline / Integration Test (push) Has been cancelled
Sirius CI/CD Pipeline / Public Stack Contract (push) Has been cancelled
Sirius CI/CD Pipeline / Dispatch Demo Deployment (sirius-demo branch) (push) Has been cancelled
Sirius CI/CD Pipeline / Dispatch Demo Canary (main branch) (push) Has been cancelled
Sirius CI/CD Pipeline / Guard Registry Namespace (push) Has been cancelled
173 lines
5.1 KiB
Go
173 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// RunAuthSurfaceSuite validates authentication and selector-hardening behavior
|
|
// across direct tRPC backends (Valkey/RabbitMQ) and sensitive Go API endpoints.
|
|
func RunAuthSurfaceSuite(cfg *Config) SuiteResult {
|
|
suite := SuiteResult{Name: "Auth Surface Coverage"}
|
|
|
|
baseUI := cfg.UIURL
|
|
baseAPI := cfg.APIURL
|
|
|
|
// 1) tRPC direct-backend procedures must reject unauthenticated callers.
|
|
trpcMutations := []struct {
|
|
name string
|
|
path string
|
|
body string
|
|
}{
|
|
{
|
|
name: "tRPC store.setValue without session",
|
|
path: "/api/trpc/store.setValue",
|
|
body: `{"json":{"key":"currentScan","value":"{}"}}`,
|
|
},
|
|
{
|
|
name: "tRPC queue.sendMsg without session",
|
|
path: "/api/trpc/queue.sendMsg",
|
|
body: `{"json":{"queue":"agent_commands","message":"{\"action\":\"list_agents\"}"}}`,
|
|
},
|
|
{
|
|
name: "tRPC terminal.executeCommand without session",
|
|
path: "/api/trpc/terminal.executeCommand",
|
|
body: `{"json":{"command":"whoami","target":{"type":"engine"}}}`,
|
|
},
|
|
{
|
|
name: "tRPC agentScan.dispatchAgentScan without session",
|
|
path: "/api/trpc/agentScan.dispatchAgentScan",
|
|
body: `{"json":{"scanId":"scan-test","mode":"safe","concurrency":1,"timeout":30}}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range trpcMutations {
|
|
code, body, _, err := httpPost(baseUI+tc.path, nil, tc.body)
|
|
if err != nil {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Fail, Severity: SevHigh,
|
|
Detail: fmt.Sprintf("request error: %v", err),
|
|
})
|
|
continue
|
|
}
|
|
if isUnauthorizedLike(code, body) {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Pass, Detail: fmt.Sprintf("%d unauthorized as expected", code),
|
|
})
|
|
} else {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Fail, Severity: SevHigh,
|
|
Detail: fmt.Sprintf("expected unauthorized, got %d", code),
|
|
})
|
|
}
|
|
}
|
|
|
|
// 2) Sensitive API endpoints must reject missing API key.
|
|
apiProtected := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
body string
|
|
}{
|
|
{"API admin command without key", "POST", "/api/v1/admin/command", `{"action":"restart","container_name":"sirius-api"}`},
|
|
{"API logs clear without key", "DELETE", "/api/v1/logs/clear", ""},
|
|
{"API source-aware host ingest without key", "POST", "/host/with-source", `{"host":{"ip":"10.10.10.10"},"source":{"name":"security-test","version":"1.0.0","config":"test"}}`},
|
|
{"API key list without key", "GET", "/api/v1/keys", ""},
|
|
}
|
|
|
|
for _, tc := range apiProtected {
|
|
code, body, _, err := httpDo(tc.method, baseAPI+tc.path, nil, tc.body)
|
|
if err != nil {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Fail, Severity: SevHigh,
|
|
Detail: fmt.Sprintf("request error: %v", err),
|
|
})
|
|
continue
|
|
}
|
|
if code == 401 || isUnauthorizedLike(code, body) {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Pass, Detail: fmt.Sprintf("%d unauthorized as expected", code),
|
|
})
|
|
} else {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Fail, Severity: SevHigh,
|
|
Detail: fmt.Sprintf("expected 401/unauthorized, got %d", code),
|
|
})
|
|
}
|
|
}
|
|
|
|
// 3) Selector-hardening checks (requires a valid key).
|
|
if cfg.APIKey == "" {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: "Selector-hardening checks",
|
|
Result: Warn,
|
|
Severity: SevLow,
|
|
Detail: "Skipped: SIRIUS_API_KEY not set for positive-key validation",
|
|
})
|
|
return suite
|
|
}
|
|
|
|
authHeaders := apiHeaders(cfg.APIKey)
|
|
selectorCases := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
body string
|
|
expectStatus int
|
|
}{
|
|
{
|
|
name: "Template invalid ID format returns 400",
|
|
method: "GET",
|
|
path: "/templates/invalid!template",
|
|
body: "",
|
|
expectStatus: 400,
|
|
},
|
|
{
|
|
name: "API key revoke invalid ID format returns 400",
|
|
method: "DELETE",
|
|
path: "/api/v1/keys/not-a-valid-hash",
|
|
body: "",
|
|
expectStatus: 400,
|
|
},
|
|
{
|
|
name: "Agent template test requires connected agent",
|
|
method: "POST",
|
|
path: "/api/agent-templates/nonexistent/test",
|
|
body: `{"agentId":"definitely-not-connected-agent"}`,
|
|
expectStatus: 400,
|
|
},
|
|
}
|
|
|
|
for _, tc := range selectorCases {
|
|
code, _, _, err := httpDo(tc.method, baseAPI+tc.path, authHeaders, tc.body)
|
|
if err != nil {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Fail, Severity: SevMedium,
|
|
Detail: fmt.Sprintf("request error: %v", err),
|
|
})
|
|
continue
|
|
}
|
|
if code == tc.expectStatus {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Pass, Detail: fmt.Sprintf("got expected status %d", code),
|
|
})
|
|
} else {
|
|
suite.Results = append(suite.Results, TestResult{
|
|
Name: tc.name, Result: Fail, Severity: SevMedium,
|
|
Detail: fmt.Sprintf("expected %d, got %d", tc.expectStatus, code),
|
|
})
|
|
}
|
|
}
|
|
|
|
return suite
|
|
}
|
|
|
|
func isUnauthorizedLike(code int, body string) bool {
|
|
if code == 401 || code == 403 {
|
|
return true
|
|
}
|
|
lower := strings.ToLower(body)
|
|
return strings.Contains(lower, "unauthorized") || strings.Contains(lower, "unauthenticated")
|
|
}
|
|
|