diff --git a/.demo/dependencies.txt b/.demo/dependencies.txt new file mode 100644 index 0000000..47ea8aa --- /dev/null +++ b/.demo/dependencies.txt @@ -0,0 +1,6 @@ +# ARCH-001 VIOLATION: Direct cloud SDK dependencies +com.amazonaws:aws-java-sdk-s3:1.12.400 +com.google.cloud:google-cloud-storage:2.20.0 + +# SEC-002 VIOLATION: JWT library for custom auth +io.jsonwebtoken:jjwt:0.9.1 diff --git a/.demo/infra/main.tf b/.demo/infra/main.tf new file mode 100644 index 0000000..3ebe457 --- /dev/null +++ b/.demo/infra/main.tf @@ -0,0 +1,28 @@ +# SEC-001 VIOLATION: IAM resources in a service repo +resource "aws_iam_role" "service_role" { + name = "user-service-role" + assume_role_policy = data.aws_iam_policy_document.assume.json +} + +resource "aws_iam_policy" "s3_access" { + name = "user-service-s3-access" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = ["s3:GetObject", "s3:PutObject"] + Resource = "arn:aws:s3:::reltio-prod-data/*" + }] + }) +} + +# OPS-002 VIOLATION: Infrastructure provisioning in service repo +resource "aws_sqs_queue" "user_events" { + name = "user-events-queue" +} + +resource "aws_dynamodb_table" "user_cache" { + name = "user-cache" + billing_mode = "PAY_PER_REQUEST" + hash_key = "userId" +} diff --git a/.demo/k8s/deployment.yaml b/.demo/k8s/deployment.yaml index 94ed340..56afdeb 100644 --- a/.demo/k8s/deployment.yaml +++ b/.demo/k8s/deployment.yaml @@ -11,4 +11,33 @@ spec: image: reltio/user-service:latest ports: - containerPort: 8080 - # OPS-003 VIOLATION: No resource limits defined +--- +# OPS-001 VIOLATION: Custom ingress instead of Foxtrot routing +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: user-service-ingress +spec: + rules: + - host: users.reltio.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: user-service + port: + number: 8080 +--- +# OPS-004 VIOLATION: Pinned infrastructure version +apiVersion: apps/v1 +kind: Deployment +metadata: + name: user-db +spec: + template: + spec: + containers: + - name: postgres + image: postgres:14.2 diff --git a/.demo/src/main/java/com/reltio/demo/UserController.java b/.demo/src/main/java/com/reltio/demo/UserController.java index 8b6f2b9..cc779ca 100644 --- a/.demo/src/main/java/com/reltio/demo/UserController.java +++ b/.demo/src/main/java/com/reltio/demo/UserController.java @@ -1,42 +1,48 @@ -// Sample service with intentional violations for demo purposes +// Demo service with intentional violations at the organizational/architectural level + package com.reltio.demo; +import com.amazonaws.services.s3.AmazonS3; // ARCH-001 VIOLATION: Direct AWS SDK import +import com.google.cloud.storage.Storage; // ARCH-001 VIOLATION: Direct GCP SDK import +import io.jsonwebtoken.Jwts; // SEC-002 VIOLATION: Custom JWT handling import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; -// SEC-003 VIOLATION: Missing @ReltioSecured annotation @RestController @RequestMapping("/api/users") public class UserController { - private final UserRepository repo; + private final AmazonS3 s3Client; + private final RestTemplate restTemplate; - public UserController(UserRepository repo) { - this.repo = repo; + // ARCH-003 VIOLATION: Hardcoded environment URL + private static final String ANALYTICS_URL = "https://prod.reltio.com/analytics/v1"; + + // ARCH-003 VIOLATION: Hardcoded tenant logic + public Object getTenantConfig(String tenantId) { + if (tenantId.equals("acme-corp")) { + return Map.of("maxEntities", 1000000); + } + return Map.of("maxEntities", 100000); } - // SEC-001 VIOLATION: Raw request parameter access - // SEC-002 VIOLATION: SQL string concatenation - @GetMapping("/search") - public List search(HttpServletRequest request) { - String name = request.getParameter("name"); - return repo.query("SELECT * FROM users WHERE name = '" + name + "'"); + // SEC-003 VIOLATION: Direct external HTTP call + public void notifyPartner(String event) { + restTemplate.postForObject("https://api.partner-system.com/webhook", event, String.class); } - // COMPLIANT: Validated input, parameterized query, auth annotation - @ReltioSecured(resource = "users", privilege = "READ") - @GetMapping("/{id}") - public User getById(@PathVariable @Valid Long id) { - return repo.findById(id); + // SEC-002 VIOLATION: Custom auth endpoint + @PostMapping("/login") + public String login(@RequestBody LoginRequest req) { + // Custom JWT generation instead of using platform auth + return Jwts.builder().setSubject(req.getUsername()).compact(); } - // SEC-004 VIOLATION: Hardcoded secret - private static final String API_SECRET = "sk-reltio-prod-a8f3b2c1d4e5f6789"; - - // OPS-002 VIOLATION: Raw stdout logging - @PostMapping - @ReltioSecured(resource = "users", privilege = "WRITE") - public User create(@RequestBody @Valid CreateUserRequest req) { - System.out.println("Creating user: " + req.getName()); - return repo.save(req.toUser()); + // ARCH-002 VIOLATION: Cross-service database query + public List getUserOrders(Long userId) { + // Directly querying the orders service's schema + return jdbcTemplate.query( + "SELECT * FROM orders_service.orders WHERE user_id = ?", + new Object[]{userId}, orderRowMapper); } } diff --git a/.demo/src/main/resources/db/migration/V2__update_users.sql b/.demo/src/main/resources/db/migration/V2__update_users.sql deleted file mode 100644 index f2ec55f..0000000 --- a/.demo/src/main/resources/db/migration/V2__update_users.sql +++ /dev/null @@ -1,7 +0,0 @@ --- OPS-004 VIOLATION: Destructive migration -ALTER TABLE users DROP COLUMN legacy_name; -ALTER TABLE users RENAME COLUMN full_name TO display_name; - --- COMPLIANT: Additive migration -ALTER TABLE users ADD COLUMN email VARCHAR(255); -CREATE INDEX idx_users_email ON users(email); diff --git a/.tests/check.sh b/.tests/check.sh index 30bc95e..70dd3c9 100755 --- a/.tests/check.sh +++ b/.tests/check.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Deterministic compliance checker -# Runs against a diff or directory and checks all requirement categories +# AI SDLC Standards — Deterministic Compliance Checker +# Checks organizational and architectural policy, NOT static code analysis. # Usage: ./check.sh [--diff ] set -euo pipefail @@ -18,106 +18,136 @@ fi RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' +CYAN='\033[0;36m' NC='\033[0m' pass() { echo -e " ${GREEN}✓${NC} $1"; } warn() { echo -e " ${YELLOW}⚠${NC} $1"; WARNINGS=$((WARNINGS + 1)); } fail() { echo -e " ${RED}✗${NC} $1"; VIOLATIONS=$((VIOLATIONS + 1)); } -get_changed_files() { - if $DIFF_MODE; then - git -C "$REPO" diff --name-only "$BASE_BRANCH" -- '*.java' '*.go' '*.js' '*.ts' '*.py' '*.kt' '*.sql' '*.yaml' '*.yml' '*.tf' '*.json' 2>/dev/null || true - else - find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.js' -o -name '*.ts' -o -name '*.py' -o -name '*.kt' \) ! -path '*/test/*' ! -path '*/node_modules/*' ! -path '*/.git/*' 2>/dev/null - fi -} - -FILES=$(get_changed_files) - echo "═══════════════════════════════════════" echo " AI SDLC Standards Compliance Check" echo "═══════════════════════════════════════" echo "" -# ── SEC-001: Input Validation ── -echo "▸ SEC-001: Input Validation" -RAW_ACCESS=$(echo "$FILES" | xargs grep -ln 'request\.getParameter\|req\.body\b\|request\.body\b' 2>/dev/null | grep -v test || true) -if [[ -n "$RAW_ACCESS" ]]; then - for f in $RAW_ACCESS; do fail "Raw request access: $f"; done +# ── SECURITY ── +echo -e "${CYAN}Security${NC}" + +# SEC-001: No IAM Resources in Service Repos +echo "▸ SEC-001: No IAM in Service Repos" +IAM_FILES=$(find "$REPO" -name '*.tf' -exec grep -l 'aws_iam_\|google_project_iam\|azurerm_role' {} \; 2>/dev/null || true) +if [[ -n "$IAM_FILES" ]]; then + for f in $IAM_FILES; do fail "IAM resources in service repo: $f"; done else - pass "No raw request body access found" + pass "No IAM resource definitions found" fi -# ── SEC-002: No Raw SQL ── -echo "▸ SEC-002: No Raw SQL" -SQL_CONCAT=$(echo "$FILES" | xargs grep -Pln '(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).*["\x27]\s*\+\s*|String\.format.*?(SELECT|INSERT|UPDATE|DELETE)' 2>/dev/null | grep -v test || true) -if [[ -n "$SQL_CONCAT" ]]; then - for f in $SQL_CONCAT; do fail "SQL concatenation: $f"; done -else - pass "No SQL string concatenation found" +# SEC-002: No Custom Auth / JWT Handling +echo "▸ SEC-002: No Custom Auth Mechanisms" +CUSTOM_AUTH=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -l 'io\.jsonwebtoken\|jwt\.decode\|jwt\.sign\|JWTVerifier\|PyJWT' {} \; 2>/dev/null || true) +LOGIN_ENDPOINTS=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -l '"/login"\|"/authenticate"\|"/signin"' {} \; 2>/dev/null || true) +if [[ -n "$CUSTOM_AUTH" ]]; then + for f in $CUSTOM_AUTH; do fail "Custom JWT/auth library: $f"; done +fi +if [[ -n "$LOGIN_ENDPOINTS" ]]; then + for f in $LOGIN_ENDPOINTS; do fail "Custom login endpoint: $f"; done +fi +if [[ -z "$CUSTOM_AUTH" && -z "$LOGIN_ENDPOINTS" ]]; then + pass "No custom auth mechanisms found" fi -# ── SEC-003: Auth Annotations ── -echo "▸ SEC-003: Authentication Annotations" -ENDPOINTS=$(echo "$FILES" | xargs grep -ln '@RequestMapping\|@GetMapping\|@PostMapping\|@PutMapping\|@DeleteMapping' 2>/dev/null || true) -for f in $ENDPOINTS; do - MISSING=$(grep -B5 '@\(Request\|Get\|Post\|Put\|Delete\)Mapping' "$f" | grep -c '@ReltioSecured\|@PublicEndpoint\|@PreAuthorize' || true) - TOTAL=$(grep -c '@\(Request\|Get\|Post\|Put\|Delete\)Mapping' "$f" || true) - if [[ "$MISSING" -lt "$TOTAL" ]]; then - warn "Endpoint without explicit auth: $f ($MISSING/$TOTAL annotated)" - fi -done -if [[ -z "$ENDPOINTS" ]]; then pass "No endpoint files in scope"; fi - -# ── SEC-004: Secrets in Code ── -echo "▸ SEC-004: Secrets in Code" -SECRETS=$(echo "$FILES" | xargs grep -Piln '(password|secret|api_key|apikey|token)\s*=\s*["\x27][^"\x27]{8,}' 2>/dev/null | grep -v test | grep -v '\.env\.example' || true) -if [[ -n "$SECRETS" ]]; then - for f in $SECRETS; do fail "Possible hardcoded secret: $f"; done +# SEC-003: No Direct External Network Calls +echo "▸ SEC-003: No Direct External Calls" +EXT_CALLS=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -Pn 'https?://(?!localhost|127\.0\.0\.1|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)(?!.*reltio\.(com|net|io))' {} \; 2>/dev/null | grep -v '^\s*//' | head -5 || true) +if [[ -n "$EXT_CALLS" ]]; then + echo "$EXT_CALLS" | while read -r line; do fail "Direct external call: $line"; done else - pass "No hardcoded secrets detected" + pass "No direct external network calls" fi -# ── OPS-002: Structured Logging ── -echo "▸ OPS-002: Structured Logging" -RAW_LOG=$(echo "$FILES" | xargs grep -ln 'System\.out\.print\|System\.err\.print' 2>/dev/null | grep -v test || true) -if [[ -n "$RAW_LOG" ]]; then - for f in $RAW_LOG; do warn "Raw stdout/stderr logging: $f"; done +echo "" + +# ── ARCHITECTURE ── +echo -e "${CYAN}Architecture${NC}" + +# ARCH-001: No Cross-Cloud Dependencies +echo "▸ ARCH-001: No Cross-Cloud SDK Imports" +CLOUD_IMPORTS=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' ! -path '*/platform/*' -exec grep -ln 'com\.amazonaws\.\|com\.google\.cloud\.\|com\.azure\.\|@aws-sdk\|@google-cloud\|@azure' {} \; 2>/dev/null || true) +if [[ -n "$CLOUD_IMPORTS" ]]; then + for f in $CLOUD_IMPORTS; do fail "Direct cloud SDK import: $f"; done else - pass "No raw stdout/stderr logging" + pass "No direct cloud SDK imports" fi -# ── OPS-003: Resource Limits ── -echo "▸ OPS-003: Resource Limits" -K8S_DEPLOYS=$(find "$REPO" -name '*.yaml' -o -name '*.yml' | xargs grep -l 'kind: Deployment' 2>/dev/null || true) -for f in $K8S_DEPLOYS; do - if ! grep -q 'resources:' "$f"; then - fail "K8s deployment missing resource limits: $f" - else - pass "Resource limits present: $f" - fi -done +# ARCH-002: No Cross-Service Database Access +echo "▸ ARCH-002: No Cross-Service DB Access" +CROSS_DB=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' -o -name '*.sql' \) ! -path '*/test/*' -exec grep -ln '[a-z_]*_service\.\|cross_schema\|foreign_schema' {} \; 2>/dev/null || true) +if [[ -n "$CROSS_DB" ]]; then + for f in $CROSS_DB; do warn "Possible cross-service DB access: $f"; done +else + pass "No cross-service database access detected" +fi -# ── OPS-004: Rollback Safety ── -echo "▸ OPS-004: Rollback Safety" -MIGRATIONS=$(find "$REPO" -path '*/migration*' -name '*.sql' 2>/dev/null || true) -for f in $MIGRATIONS; do - DANGEROUS=$(grep -Pic 'DROP\s+COLUMN|RENAME\s+COLUMN|ALTER\s+.*TYPE' "$f" || true) - if [[ "$DANGEROUS" -gt 0 ]]; then - fail "Destructive migration: $f (DROP/RENAME/ALTER TYPE)" - fi -done -if [[ -z "$MIGRATIONS" ]]; then pass "No migration files in scope"; fi +# ARCH-003: No Hardcoded Tenant/Environment Config +echo "▸ ARCH-003: No Hardcoded Tenant/Environment Config" +HARDCODED=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -Pln 'https?://(prod|staging|dev)\.\w+\.(com|net|io)|\.equals\("(acme|tenant|customer)-' {} \; 2>/dev/null || true) +if [[ -n "$HARDCODED" ]]; then + for f in $HARDCODED; do fail "Hardcoded tenant/environment config: $f"; done +else + pass "No hardcoded tenant/environment configuration" +fi -# ── COST-001: Resource Tagging ── +echo "" + +# ── DEVOPS ── +echo -e "${CYAN}DevOps${NC}" + +# OPS-001: Foxtrot-Compatible Helm Chart (no custom ingress) +echo "▸ OPS-001: No Custom Ingress (Foxtrot Routing)" +CUSTOM_INGRESS=$(find "$REPO" -name '*.yaml' -o -name '*.yml' | xargs grep -l 'kind: Ingress' 2>/dev/null || true) +if [[ -n "$CUSTOM_INGRESS" ]]; then + for f in $CUSTOM_INGRESS; do fail "Custom Ingress resource (Foxtrot manages routing): $f"; done +else + pass "No custom Ingress definitions" +fi + +# OPS-002: No Infrastructure Provisioning in Service Repos +echo "▸ OPS-002: No Infrastructure in Service Repos" +INFRA_FILES=$(find "$REPO" -name '*.tf' -o -name 'Pulumi.*' | grep -v '.terraform' 2>/dev/null || true) +CFN_FILES=$(find "$REPO" -name '*.template.yaml' -o -name '*.template.json' | xargs grep -l 'AWSTemplateFormatVersion\|AWS::' 2>/dev/null || true) +if [[ -n "$INFRA_FILES" ]]; then + for f in $INFRA_FILES; do fail "Infrastructure-as-code in service repo: $f"; done +fi +if [[ -n "$CFN_FILES" ]]; then + for f in $CFN_FILES; do fail "CloudFormation template in service repo: $f"; done +fi +if [[ -z "$INFRA_FILES" && -z "$CFN_FILES" ]]; then + pass "No infrastructure provisioning files" +fi + +# OPS-004: No Pinned Infrastructure Versions +echo "▸ OPS-004: No Pinned Infrastructure Versions" +PINNED=$(find "$REPO" -name '*.yaml' -o -name '*.yml' | xargs grep -Pn 'image:\s*(postgres|mysql|redis|mongo|elasticsearch|kafka|rabbitmq|zookeeper):\d' 2>/dev/null || true) +if [[ -n "$PINNED" ]]; then + echo "$PINNED" | while read -r line; do fail "Pinned infra version: $line"; done +else + pass "No pinned infrastructure image versions" +fi + +echo "" + +# ── COST ── +echo -e "${CYAN}Cost${NC}" + +# COST-001: Resource Tagging echo "▸ COST-001: Resource Tagging" -TF_FILES=$(find "$REPO" -name '*.tf' 2>/dev/null || true) -for f in $TF_FILES; do - if grep -q 'resource\s' "$f" && ! grep -q 'tags' "$f"; then +TF_RESOURCES=$(find "$REPO" -name '*.tf' -exec grep -l 'resource\s' {} \; 2>/dev/null || true) +for f in $TF_RESOURCES; do + if ! grep -q 'tags' "$f"; then warn "Terraform resource without tags: $f" fi done -if [[ -z "$TF_FILES" ]]; then pass "No Terraform files in scope"; fi +if [[ -z "$TF_RESOURCES" ]]; then pass "No Terraform resources in scope"; fi echo "" echo "═══════════════════════════════════════" diff --git a/architecture/requirements.md b/architecture/requirements.md index 9e42d48..129e360 100644 --- a/architecture/requirements.md +++ b/architecture/requirements.md @@ -1,39 +1,36 @@ # Architecture Requirements Phase: design -Enforcement: informational (graduating to blocking Q3 2026) +Enforcement: informational -## ARCH-001: API Contract First +## ARCH-001: No Cross-Cloud Dependencies -All new or modified REST APIs MUST have an updated OpenAPI/Swagger spec BEFORE implementation begins. +Services MUST NOT import cloud-provider-specific SDKs directly. All cloud interactions go through the platform abstraction layer. -**Rule:** If a PR adds or changes an endpoint, the corresponding `openapi.yaml` or Swagger annotation must be updated in the same PR. +**Rule:** No direct imports of `com.amazonaws.*`, `com.google.cloud.*`, or `com.azure.*` in service code. Use the platform's cloud-agnostic interfaces. If your service runs on AWS today, it must be deployable to GCP tomorrow without code changes. -**Test:** Diff check — if files in `src/**/controller/**` or `src/**/api/**` changed, verify `**/openapi*.yaml` or `**/swagger*.yaml` also changed. +**Test:** Scan imports for cloud-provider SDK packages outside the platform abstraction layer. -## ARCH-002: Service Boundary Respect +## ARCH-002: No Cross-Service Database Access -Changes to a service MUST NOT directly import or reference internal classes from another service's module. +Services MUST NOT directly query another service's database. All cross-service data access goes through APIs or events. -**Rule:** Cross-service communication happens through APIs, events, or SDK interfaces only. No direct classpath coupling between service modules. +**Rule:** Each service owns its schema. No shared database connections, no cross-schema joins, no direct table access outside your service boundary. If you need data from another service, call its API or consume its events. -**Test:** Import scan — flag imports crossing module boundaries (e.g., `import com.reltio.server.internal.*` from SDK module). +**Test:** Scan datasource configs and SQL for references to schemas/tables outside the service's declared ownership. -## ARCH-003: Breaking Change Protocol +## ARCH-003: No Hardcoded Tenant or Environment Configuration -Any breaking API change (removed field, changed type, renamed endpoint) MUST include: -1. A deprecation annotation on the old path -2. A migration note in the PR description -3. A minimum 30-day dual-support window +Services MUST NOT contain hardcoded tenant IDs, environment URLs, or deployment-specific configuration in source code. -**Rule:** Breaking changes without deprecation path are rejected. +**Rule:** All tenant and environment config comes from the platform config plane (environment variables, config service, or Helm values). No `if (tenant == "acme")` logic. No `https://prod.reltio.com` literals. -**Test:** Detect removed/renamed public API methods or fields in diff. Flag if no `@Deprecated` annotation present. +**Test:** Scan for URL literals matching known environment patterns, hardcoded tenant identifiers, environment-switching conditionals. -## ARCH-004: Multi-Repository Coordination +## ARCH-004: API Contract Versioning -For changes spanning multiple repositories (e.g., SDK + Server), the design spec MUST list all affected repos and the order of deployment. +All public API changes MUST maintain backward compatibility or follow the deprecation protocol (minimum 30-day dual-support window). -**Rule:** Multi-repo PRs must reference a shared Jira ticket and declare deployment sequence. +**Rule:** No removed fields, renamed endpoints, or changed response shapes without a versioned migration path. Breaking changes require a new API version. -**Test:** If PR description references multiple repos, verify Jira link present. +**Test:** Diff OpenAPI specs between branches. Flag removed paths, removed required fields, or changed response types. diff --git a/cost/requirements.md b/cost/requirements.md index 5ee5ed5..6154e92 100644 --- a/cost/requirements.md +++ b/cost/requirements.md @@ -3,30 +3,26 @@ Phase: deployment Enforcement: informational -## COST-001: Resource Tagging +## COST-001: Standard Resource Tagging -All cloud resources (AWS, GCP, Azure) MUST include the following tags: -- `team` — owning team name -- `service` — service identifier -- `environment` — dev/staging/prod -- `cost-center` — finance cost center code +All cloud resources MUST include the organization's standard tag set for cost attribution. -**Rule:** Infrastructure-as-code (Terraform, CloudFormation, Pulumi) must include these tags on every resource that supports tagging. +**Rule:** Every resource that supports tagging must include: `team`, `service`, `environment`, `cost-center`. These are used for chargeback and cost allocation dashboards. **Test:** Parse IaC files, verify tag block contains all four required keys. -## COST-002: No Open-Ended Auto-Scaling +## COST-002: No Unbounded Resource Allocation -Auto-scaling configurations MUST define a `maxReplicas` / `maxCapacity` ceiling. +Service configurations MUST define resource ceilings. No open-ended scaling or storage without limits. -**Rule:** Unbounded scaling is a cost incident waiting to happen. Every autoscaler must have an explicit maximum. +**Rule:** Auto-scaling must have explicit maximums. Storage must have lifecycle policies. Compute must have resource limits. "Unlimited" is not a valid configuration. -**Test:** Parse HPA/scaling configs, verify `maxReplicas` is set and is not unreasonably high (>50 requires justification). +**Test:** Check scaling configs for `maxReplicas`/`maxCapacity`. Check storage for lifecycle rules. -## COST-003: Storage Lifecycle +## COST-003: No Dedicated Infrastructure Per Tenant -All S3 buckets / GCS buckets / Blob containers MUST have a lifecycle policy defined. +Services MUST NOT provision tenant-specific infrastructure (dedicated databases, queues, or compute per customer). -**Rule:** No indefinite storage retention. Every bucket must transition to cheaper tiers or expire objects after a defined period. +**Rule:** Multi-tenancy is handled at the application layer, not the infrastructure layer. Tenant isolation through data partitioning, not resource duplication. Exceptions require FinOps approval. -**Test:** Check IaC for lifecycle configuration on storage resources. +**Test:** Scan IaC and Helm values for tenant-specific resource naming patterns or parameterized infrastructure per tenant. diff --git a/devops/requirements.md b/devops/requirements.md index 55a0f87..d4e8fe7 100644 --- a/devops/requirements.md +++ b/devops/requirements.md @@ -1,36 +1,44 @@ # DevOps Requirements Phase: deployment -Enforcement: informational (graduating to blocking Q3 2026) +Enforcement: informational -## OPS-001: Health Check Endpoint +## OPS-001: Foxtrot-Compatible Helm Chart -Every deployable service MUST expose a `/health` or `/actuator/health` endpoint that returns 200 when healthy. +Every deployable service MUST include a Helm chart that honors the Foxtrot deployment contract. -**Rule:** New services must include a health check. Existing services adding deployment config must verify health endpoint exists. +**Rule:** The Helm chart must: +- Use the standard Foxtrot base chart as a dependency (or implement its interface) +- Expose `values.yaml` with the required Foxtrot parameters (replicas, resources, env, configMap references) +- Support the standard lifecycle hooks (pre-deploy validation, health check, rollback trigger) +- Not define its own ingress/networking — Foxtrot manages routing -**Test:** Check that service has a health endpoint registered (grep for health route registration). +**Test:** Validate Helm chart structure: check for Foxtrot base chart dependency, required values keys, no ingress resource definitions. -## OPS-002: Structured Logging +## OPS-002: No Infrastructure Provisioning in Service Repos -All log statements MUST use structured logging (JSON format) with at minimum: timestamp, level, service name, correlation ID. +Service repositories MUST NOT provision infrastructure (databases, queues, storage, networking). Infrastructure is managed through the dedicated infrastructure repos. -**Rule:** No `System.out.println`, `console.log` for production logging. Use the logging framework with structured output. +**Rule:** No Terraform, CloudFormation, or Pulumi resource definitions in service repos. Services declare their infrastructure dependencies in a manifest; the platform provisions them. -**Test:** Grep for `System.out.print`, `System.err.print`, raw `console.log` in non-test source files. +**Test:** Scan for `*.tf`, `*.template.yaml` (CFN), `Pulumi.*` files in service repos. -## OPS-003: Resource Limits +## OPS-003: Standard Observability Contract -All Kubernetes deployment manifests MUST specify CPU and memory requests and limits. +Every service MUST expose metrics, health, and readiness endpoints in the standard format. -**Rule:** No unbounded resource consumption. Every container spec must have `resources.requests` and `resources.limits`. +**Rule:** +- `/health` or `/actuator/health` — returns 200 when healthy +- `/ready` or `/actuator/ready` — returns 200 when ready to accept traffic +- Prometheus metrics endpoint at `/metrics` or `/actuator/prometheus` +- Structured JSON logging with correlation ID propagation -**Test:** Parse YAML deployment files, verify `resources` block present with both `requests` and `limits`. +**Test:** Check for health/ready endpoint registration in code. Verify logging config outputs JSON format. -## OPS-004: Rollback Safety +## OPS-004: No Pinned Infrastructure Versions -Database migrations MUST be backward-compatible with the previous service version (N-1 compatibility). +Service Helm charts MUST NOT pin specific infrastructure versions (database versions, queue versions, runtime versions). -**Rule:** No column renames or drops without a multi-step migration. Additive changes only in a single release. +**Rule:** Infrastructure version management is handled by the platform team. Services declare compatibility ranges, not exact versions. No `image: postgres:14.2` in service charts. -**Test:** Scan migration SQL for `DROP COLUMN`, `RENAME COLUMN`, `ALTER TYPE` — flag for manual review. +**Test:** Scan Helm values and templates for hardcoded infrastructure image tags. diff --git a/security/requirements.md b/security/requirements.md index 6bddaae..0d6dc72 100644 --- a/security/requirements.md +++ b/security/requirements.md @@ -1,54 +1,28 @@ # Security Requirements Phase: implementation -Enforcement: informational (graduating to blocking Q3 2026) +Enforcement: informational -## SEC-001: Input Validation +## SEC-001: No IAM Resources in Service Repos -All external input (API request bodies, query parameters, headers, file uploads) MUST be validated through a schema validator before processing. +Service repositories MUST NOT contain IAM policies, roles, or identity resources. IAM is centrally managed by the security team through the infrastructure repo. -**Rule:** No raw request body access in business logic. All endpoints must define and validate against a schema (JSON Schema, protobuf, or framework-equivalent). +**Rule:** No Terraform/CloudFormation IAM resource definitions (`aws_iam_role`, `aws_iam_policy`, `google_project_iam_member`, etc.) in service-level repositories. If your service needs a new permission, request it through the IAM change process. -**Test:** Grep for direct `request.body` / `req.body` / `getParameter()` usage outside of controller/validation layer. +**Test:** Scan IaC files for IAM resource type declarations. -``` -# Bad -String name = request.getParameter("name"); -db.query("SELECT * FROM users WHERE name = '" + name + "'"); +## SEC-002: No Embedded Credentials or Auth Bypass -# Good -ValidatedInput input = validator.validate(request, CreateUserSchema.class); -userService.create(input); -``` +Services MUST NOT implement their own authentication mechanisms. All auth flows go through the centralized auth service. -## SEC-002: No Raw SQL +**Rule:** No custom JWT validation, no local user tables, no auth middleware that bypasses the platform auth layer. Services consume auth tokens validated by the platform. -All database queries MUST use parameterized queries or an ORM. No string concatenation in SQL statements. +**Test:** Scan for JWT libraries imported outside the auth module, custom `login`/`authenticate` endpoints, local user/password tables in migrations. -**Rule:** Zero tolerance for SQL string concatenation with user-controlled values. +## SEC-003: No Direct External Network Calls Without Proxy -**Test:** Regex scan for SQL keywords adjacent to string concatenation operators (`+`, `concat`, `format`, `f"`, template literals). +Services MUST NOT make direct outbound HTTP calls to external (non-Reltio) endpoints. All external traffic routes through the API gateway/proxy layer. -## SEC-003: Authentication Annotations +**Rule:** Outbound calls to third-party APIs must go through the approved proxy/gateway. No hardcoded external URLs in service code. -All new REST endpoints MUST have an explicit auth annotation. No endpoint may be implicitly public. - -**Rule:** Every `@RequestMapping`, `@GetMapping`, `@PostMapping` (or equivalent) must be accompanied by `@ReltioSecured` or `@PublicEndpoint`. Missing annotation = violation. - -**Test:** AST/regex check that every endpoint method has an auth annotation. - -## SEC-004: Secrets in Code - -No hardcoded secrets, tokens, passwords, or API keys in source code. - -**Rule:** All secrets must come from environment variables, vault, or config service. String literals matching secret patterns are violations. - -**Test:** Regex scan for patterns: API keys, JWT tokens, passwords in string literals, base64-encoded credentials. - -## SEC-005: Dependency Vulnerability - -No new dependencies with known critical/high CVEs. - -**Rule:** Any new dependency added to `pom.xml`, `package.json`, `go.mod`, or equivalent must pass a vulnerability scan. - -**Test:** Run `npm audit` / `mvn dependency-check:check` / `govulncheck` on changed dependency files. +**Test:** Scan for HTTP client instantiation with non-internal hostnames.