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
165 lines
6.4 KiB
Go
165 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// RunServicesSuite tests whether internal infrastructure services are
|
|
// improperly exposed to the host network.
|
|
func RunServicesSuite(cfg *Config) SuiteResult {
|
|
suite := SuiteResult{Name: "Service Exposure"}
|
|
timeout := 3 * time.Second
|
|
|
|
// ── Valkey (port 6379) ──────────────────────────────────────────────
|
|
suite.Results = append(suite.Results, testValkeyExposure(cfg.ValkeyAddr, timeout))
|
|
|
|
// ── PostgreSQL (port 5432) ──────────────────────────────────────────
|
|
suite.Results = append(suite.Results, testPostgresExposure(cfg.PostgresAddr, timeout))
|
|
|
|
// ── RabbitMQ AMQP (port 5672) ───────────────────────────────────────
|
|
suite.Results = append(suite.Results, testRabbitMQAMQP(cfg.RabbitMQAddr, timeout))
|
|
|
|
// ── RabbitMQ Management UI (port 15672) ─────────────────────────────
|
|
suite.Results = append(suite.Results, testRabbitMQManagement(cfg.RabbitMQMgmt, timeout)...)
|
|
|
|
// ── Engine HTTP port (5174) ─────────────────────────────────────────
|
|
suite.Results = append(suite.Results, testEngineHTTP(cfg.EngineHTTPAddr, timeout))
|
|
|
|
return suite
|
|
}
|
|
|
|
// ────────────────── Valkey ──────────────────
|
|
|
|
func testValkeyExposure(addr string, timeout time.Duration) TestResult {
|
|
name := "Valkey (Redis) exposure on " + addr
|
|
|
|
// Try to connect and send PING.
|
|
resp, err := tcpSendRecv(addr, "PING\r\n", timeout)
|
|
if err != nil {
|
|
return TestResult{Name: name, Result: Pass,
|
|
Detail: "not reachable from host (good)"}
|
|
}
|
|
|
|
if strings.Contains(resp, "+PONG") {
|
|
return TestResult{Name: name, Result: Warn, Severity: SevHigh,
|
|
Detail: "Valkey accepts unauthenticated PING from host — no AUTH configured"}
|
|
}
|
|
|
|
if strings.Contains(resp, "-NOAUTH") || strings.Contains(resp, "-ERR") {
|
|
return TestResult{Name: name, Result: Info, Severity: SevLow,
|
|
Detail: "Valkey reachable but requires authentication (acceptable)"}
|
|
}
|
|
|
|
return TestResult{Name: name, Result: Warn, Severity: SevMedium,
|
|
Detail: fmt.Sprintf("Valkey reachable, response: %s", truncate(resp, 60))}
|
|
}
|
|
|
|
// ────────────────── PostgreSQL ──────────────────
|
|
|
|
func testPostgresExposure(addr string, timeout time.Duration) TestResult {
|
|
name := "PostgreSQL exposure on " + addr
|
|
|
|
if !tcpProbe(addr, timeout) {
|
|
return TestResult{Name: name, Result: Pass,
|
|
Detail: "not reachable from host (good)"}
|
|
}
|
|
|
|
// PostgreSQL sends a greeting when a connection is opened. Try to read it.
|
|
// If we can connect, the port is exposed.
|
|
return TestResult{Name: name, Result: Warn, Severity: SevMedium,
|
|
Detail: "PostgreSQL port is reachable from host — consider restricting to internal network only"}
|
|
}
|
|
|
|
// ────────────────── RabbitMQ AMQP ──────────────────
|
|
|
|
func testRabbitMQAMQP(addr string, timeout time.Duration) TestResult {
|
|
name := "RabbitMQ AMQP exposure on " + addr
|
|
|
|
resp, err := tcpSendRecv(addr, "", timeout)
|
|
if err != nil {
|
|
return TestResult{Name: name, Result: Pass,
|
|
Detail: "not reachable from host (good)"}
|
|
}
|
|
|
|
if strings.Contains(resp, "AMQP") {
|
|
return TestResult{Name: name, Result: Warn, Severity: SevMedium,
|
|
Detail: "RabbitMQ AMQP port is reachable and responded with AMQP handshake"}
|
|
}
|
|
|
|
return TestResult{Name: name, Result: Warn, Severity: SevMedium,
|
|
Detail: fmt.Sprintf("RabbitMQ port reachable, response: %s", truncate(resp, 60))}
|
|
}
|
|
|
|
// ────────────────── RabbitMQ Management ──────────────────
|
|
|
|
func testRabbitMQManagement(addr string, timeout time.Duration) []TestResult {
|
|
var results []TestResult
|
|
|
|
mgmtURL := fmt.Sprintf("http://%s", addr)
|
|
name := "RabbitMQ Management UI exposure on " + addr
|
|
|
|
code, _, _, err := httpGet(mgmtURL+"/api/overview", nil)
|
|
if err != nil {
|
|
results = append(results, TestResult{Name: name, Result: Pass,
|
|
Detail: "management UI not reachable from host (good)"})
|
|
return results
|
|
}
|
|
|
|
if code == 401 {
|
|
results = append(results, TestResult{Name: name, Result: Info, Severity: SevLow,
|
|
Detail: "management UI reachable but requires authentication"})
|
|
} else {
|
|
results = append(results, TestResult{Name: name, Result: Warn, Severity: SevMedium,
|
|
Detail: fmt.Sprintf("management UI responded with %d — may be accessible", code)})
|
|
}
|
|
|
|
// Test default credentials.
|
|
_ = timeout // already tested reachability above
|
|
nameCreds := "RabbitMQ default credentials (guest:guest)"
|
|
headers := map[string]string{
|
|
"Authorization": "Basic Z3Vlc3Q6Z3Vlc3Q=", // base64("guest:guest")
|
|
}
|
|
code, _, _, err = httpGet(mgmtURL+"/api/overview", headers)
|
|
if err != nil {
|
|
results = append(results, TestResult{Name: nameCreds, Result: Pass,
|
|
Detail: "request failed"})
|
|
return results
|
|
}
|
|
|
|
if code == 200 {
|
|
results = append(results, TestResult{Name: nameCreds, Result: Warn, Severity: SevHigh,
|
|
Detail: "default credentials accepted — change RabbitMQ admin password"})
|
|
} else if code == 401 {
|
|
results = append(results, TestResult{Name: nameCreds, Result: Pass,
|
|
Detail: "default credentials rejected (good)"})
|
|
} else {
|
|
results = append(results, TestResult{Name: nameCreds, Result: Info, Severity: SevLow,
|
|
Detail: fmt.Sprintf("unexpected status %d", code)})
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// ────────────────── Engine HTTP ──────────────────
|
|
|
|
func testEngineHTTP(addr string, timeout time.Duration) TestResult {
|
|
name := "Engine HTTP port exposure on " + addr
|
|
|
|
url := fmt.Sprintf("http://%s/health", addr)
|
|
code, _, _, err := httpGet(url, nil)
|
|
if err != nil {
|
|
return TestResult{Name: name, Result: Info, Severity: SevInfo,
|
|
Detail: "engine HTTP port not reachable from host"}
|
|
}
|
|
|
|
if code == 200 {
|
|
return TestResult{Name: name, Result: Info, Severity: SevLow,
|
|
Detail: "engine health endpoint reachable (expected for monitoring)"}
|
|
}
|
|
|
|
return TestResult{Name: name, Result: Info, Severity: SevLow,
|
|
Detail: fmt.Sprintf("engine HTTP reachable, status %d", code)}
|
|
}
|