diff --git a/local/.gitignore b/local/.gitignore new file mode 100644 index 00000000..12b09c73 --- /dev/null +++ b/local/.gitignore @@ -0,0 +1,12 @@ +# Local dev harness — ignore generated output + un-ignore the seed SQL. +# +# A global ~/.gitignore *.sql rule (guards go:embed) would otherwise hide the +# init seed scripts, and assessment/hardening output must never be committed +# (it is development signal only, and can contain live DB output). + +# Generated assessment/hardening output — never commit (dev signal only). +reports/ + +# Force-track the seed SQL despite the global *.sql ignore. +!init/ +!init/*.sql diff --git a/local/Makefile b/local/Makefile new file mode 100644 index 00000000..80f362e0 --- /dev/null +++ b/local/Makefile @@ -0,0 +1,132 @@ +# Local Oracle 19c test harness. +# DEVELOPMENT SIGNAL ONLY — not compliance evidence. +# +# All targets are local Docker + go test; nothing here touches AWS or production. +# New here? Run `make -C local doctor` then `make -C local quickstart`. + +COMPOSE_ORACLE = docker compose -f docker-compose.oracle-free.yml +COMPOSE_MOTO = docker compose -f docker-compose.moto.yml + +ORACLE_CONN ?= APPUSER/devpw_ChangeMe1@//localhost:1521/FREEPDB1 +# Repo root (this Makefile lives in local/). +ROOT = .. +# The suite pulls in go-sqlite3 (a cgo package), so unit tests need cgo + a C +# compiler. Force it on here rather than relying on the toolchain default. +export CGO_ENABLED = 1 + +.PHONY: help +help: quickstart + +.PHONY: quickstart +quickstart: + @echo "Local Oracle 19c test harness — 3 layers (development signal only):" + @echo "" + @echo " 0. make doctor check your machine has the prerequisites" + @echo "" + @echo " 1. UNIT TESTS (fast; no Docker) — the everyday loop:" + @echo " make unit go test ./... with test secrets/catalog wired" + @echo "" + @echo " 2. BROKER FLOW vs a mock AWS RDS control plane (moto):" + @echo " make moto-up start moto on :5000" + @echo " make moto-smoke create an Oracle instance/param+option group via the AWS API" + @echo " make moto-down stop moto" + @echo "" + @echo " 3. REAL LOCAL ORACLE for SQL hardening/assessment:" + @echo " make oracle-up start gvenzl/oracle-free (native arm64), wait healthy" + @echo " make assess run assessment SQL -> reports/ (labeled dev signal)" + @echo " make harden apply allowed hardening (idempotent)" + @echo " make assess re-assess: state should have changed" + @echo "" + @echo " make down tear EVERYTHING down" + @echo "" + @echo "See README.md for the full from-a-clean-laptop guide." + +# --------------------------------------------------------------------------- +# 0. Prerequisite check +# --------------------------------------------------------------------------- +.PHONY: doctor +doctor: + @echo "== Local test harness prerequisites (macOS arm64 / Linux) ==" + @ok=1; \ + printf "%-22s" "docker:"; \ + if command -v docker >/dev/null 2>&1; then \ + if docker info >/dev/null 2>&1; then echo "OK (daemon running)"; \ + else echo "INSTALLED but daemon NOT running — start Docker Desktop/Colima"; ok=0; fi; \ + else echo "MISSING — install Docker Desktop or 'brew install colima docker && colima start'"; ok=0; fi; \ + printf "%-22s" "go:"; \ + if command -v go >/dev/null 2>&1; then go version | awk '{print "OK ("$$3")"}'; \ + else echo "MISSING — 'brew install go' (needed for layer 1 unit tests)"; ok=0; fi; \ + printf "%-22s" "C compiler (cgo):"; \ + if command -v cc >/dev/null 2>&1 || command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1; then echo "OK (cgo builds go-sqlite3)"; \ + else echo "MISSING — layer 1 needs cgo: 'xcode-select --install' (macOS) or 'apt-get install -y gcc' (Linux)"; ok=0; fi; \ + printf "%-22s" "aws (cli):"; \ + if command -v aws >/dev/null 2>&1; then echo "OK (layer 2 moto smoke)"; \ + else echo "MISSING for layer 2 — 'brew install awscli' (macOS) or 'apt-get install -y awscli' (Linux); only 'make moto-smoke' needs it"; fi; \ + printf "%-22s" "cinc-auditor:"; \ + echo "via Docker — 'docker run cincproject/auditor ...' (see README §3); no local install (avoids cinc-workstation/root)"; \ + printf "%-22s" "sqlplus:"; \ + if command -v sqlplus >/dev/null 2>&1; then echo "OK"; \ + else echo "not needed — 'make assess/harden' run sqlplus INSIDE the container"; fi; \ + printf "%-22s" "overlay repo:"; \ + if [ -d ../../cg-oracle-database-19c-stig-overlay/hardening/sql ]; then echo "OK (sibling clone found)"; \ + else echo "MISSING for layer 3 — clone cg-oracle-database-19c-stig-overlay as a sibling of aws-broker (or set SQL_DIR=)"; fi; \ + echo ""; \ + if [ "$$ok" = "1" ]; then echo "Ready for layers 1-2. Layer 3 also needs the overlay sibling clone (see above)."; \ + else echo "Install the MISSING items above, then re-run 'make doctor'."; exit 1; fi + +# --------------------------------------------------------------------------- +# 1. Unit tests (no Docker). Wires the test secrets/catalog the suite needs. +# --------------------------------------------------------------------------- +.PHONY: unit +unit: + @echo "Wiring test config (secrets-test.yml/catalog-test.yml -> secrets.yml/catalog.yml)..." + cp $(ROOT)/secrets-test.yml $(ROOT)/secrets.yml + cp $(ROOT)/catalog-test.yml $(ROOT)/catalog.yml + cp $(ROOT)/secrets-test.yml $(ROOT)/cmd/tasks/secrets.yml + cp $(ROOT)/catalog-test.yml $(ROOT)/cmd/tasks/catalog.yml + cd $(ROOT) && go test ./... + cd $(ROOT)/cmd/tasks && go test ./... + @echo "unit tests passed (secrets.yml/catalog.yml are gitignored, left in place)." + +.PHONY: unit-oracle +unit-oracle: + cp $(ROOT)/secrets-test.yml $(ROOT)/secrets.yml + cp $(ROOT)/catalog-test.yml $(ROOT)/catalog.yml + cd $(ROOT) && go test ./services/rds/... -run Oracle -v + +# --------------------------------------------------------------------------- +# 2. Broker control-flow vs moto (mock AWS RDS control plane) +# --------------------------------------------------------------------------- +.PHONY: moto-up +moto-up: + $(COMPOSE_MOTO) up -d + @echo "moto up on http://localhost:5000 (point the AWS SDK/CLI at it via --endpoint-url / BaseEndpoint)." + +.PHONY: moto-smoke +moto-smoke: + ./scripts/moto-smoke.sh + +.PHONY: moto-down +moto-down: + -$(COMPOSE_MOTO) down -v + +# --------------------------------------------------------------------------- +# 3. Real local Oracle (gvenzl/oracle-free, native arm64) for SQL hardening +# --------------------------------------------------------------------------- +.PHONY: oracle-up +oracle-up: + $(COMPOSE_ORACLE) up -d + ./scripts/wait-for-oracle.sh + +.PHONY: assess +assess: + ./scripts/run-assessment-local.sh "$(ORACLE_CONN)" + +.PHONY: harden +harden: + ./scripts/run-hardening-local.sh "$(ORACLE_CONN)" + +.PHONY: down +down: + -$(COMPOSE_ORACLE) down -v + -$(COMPOSE_MOTO) down -v diff --git a/local/README.md b/local/README.md new file mode 100644 index 00000000..eaca057c --- /dev/null +++ b/local/README.md @@ -0,0 +1,172 @@ +# Local Oracle test harness (23c engine; targets 19c on RDS) + +> **⚠️ DEVELOPMENT SIGNAL ONLY — NOT COMPLIANCE EVIDENCE.** +> Everything here is for fast iteration. Authoritative STIG evidence comes only +> from running `cg-oracle-database-19c-stig-overlay` against a real brokered +> GovCloud RDS instance (the gated dev proof). + +This lets you test the Oracle work **without any AWS access**, in three layers you +can use independently. Written for **macOS on Apple Silicon (arm64)**. + +> **Engine vs. target.** The local DB is `gvenzl/oracle-free` (Oracle **23c**); +> the target is **19c SE2** on RDS. No local 19c image exists for arm64 +> (see [§3](#3-real-local-oracle-for-sql-hardeningassessment)). + + +## TL;DR + +> Run these from the **repo root** (`aws-broker/`); `-C local` points make at +> `local/Makefile`. If you're already in `local/`, drop `-C local` (e.g. `make doctor`). + +```bash +make -C local doctor # 0. check prerequisites +make -C local unit # 1. Go unit tests (fast, no Docker) +make -C local moto-up moto-smoke moto-down # 2. broker flow vs mock AWS RDS +make -C local oracle-up assess harden assess # 3. real Oracle + SQL hardening +make -C local down # tear everything down +``` + +`make -C local quickstart` prints this menu any time. + +## 0. Prerequisites (macOS arm64 / Linux) + +Run `make -C local doctor` — it tells you exactly what's present/missing and the +command to install each. Summary: + +| Requirement | Needed for | How | +|------|-----------|---------| +| **Docker** (running) | layers 2 & 3 | Docker Desktop, or `brew install colima docker && colima start` | +| **go** | layer 1 (unit tests) | `brew install go` | +| **C compiler** (`cc`/`gcc`/`clang`) | layer 1 — cgo builds `go-sqlite3` | `xcode-select --install` (macOS) or `apt-get install -y gcc` (Linux) | +| `aws` CLI | layer 2 `moto-smoke` only | `brew install awscli` (macOS) or `apt-get install -y awscli` (Linux) | +| **`cg-oracle-database-19c-stig-overlay` cloned as a sibling** | layer 3 `assess`/`harden` | `git clone` it next to `aws-broker` (so `../../cg-oracle-database-19c-stig-overlay/hardening/sql` resolves), or pass `SQL_DIR=…` | +| `cinc-auditor` (via Docker) | running the STIG overlay locally (optional) | run it from the `cincproject/auditor` container — do **not** install cinc-workstation (needs root); see §3 | +| `sqlplus` | **not required** | `assess`/`harden` run `sqlplus` *inside* the container via `docker exec` | + +You do **not** need Oracle Instant Client or `sqlplus` on your Mac — layer 3 runs +`sqlplus` inside the `cg-oracle-free` container. Your clone layout for layer 3: + +``` +/ +├── aws-broker/ (this repo) +└── cg-oracle-database-19c-stig-overlay/ (sibling — provides hardening/sql/) +``` + +## 1. Unit tests (fast, no Docker) — the everyday loop + +```bash +make -C local unit # go test ./... + cmd/tasks, with test config wired +make -C local unit-oracle # just the Oracle-tagged tests, verbose +``` + +The broker's test suite needs `secrets.yml`/`catalog.yml` present; `make unit` +copies them from the committed `*-test.yml` files first (they're gitignored, so +this is safe and idempotent). This is the layer to run while editing Go code. + +## 2. Broker flow vs a mock AWS RDS control plane (moto) + +[moto](https://docs.getmoto.org/) mocks the AWS RDS **control plane** so the broker's +create / parameter-group / option-group calls can be exercised with **no real AWS**. + +```bash +make -C local moto-up # start moto on http://localhost:5000 +make -C local moto-smoke # create an Oracle instance + param + option group via the AWS API +make -C local moto-down +``` + +`moto-smoke` asserts the AWS API **accepts** the exact shape the broker builds +(`oracle-se2` 19c, encrypted, License Included, private; the `oracle-se2-19` +parameter group; the option group). To point the broker/tests themselves at moto, +override the AWS SDK endpoint (`--endpoint-url http://localhost:5000` for the CLI, +or `BaseEndpoint` in the Go client). + +> moto does **not** run an Oracle engine, apply parameters, or do a TLS handshake — +> it proves request shape, not RDS behavior. + +## 3. Real local Oracle for SQL hardening/assessment + +[`gvenzl/oracle-free`](https://github.com/gvenzl/oci-oracle-free) runs a **real +Oracle engine natively on arm64** — used to develop and idempotency-test the SQL +hardening/assessment scripts and to run the overlay's `oracledb_session` controls. +Requires the overlay repo cloned as a sibling (see Prerequisites); `sqlplus` runs +inside the container, so nothing extra on your Mac. + +```bash +make -C local oracle-up # start oracle-free, wait until healthy (first pull ~mins) +make -C local assess # run assessment SQL -> local/reports/ (labeled dev signal) +make -C local harden # apply allowed hardening (idempotent) +make -C local assess # re-assess: the DEFAULT profile limits + unified audit + # policies now report [PASS] in 90_validate +``` + +The container auto-seeds (from `init/`) a **non-`SYS`** privileged app user that +mirrors the RDS master-user privilege model, plus a deliberately-weak state. Note +`harden` only touches the DEFAULT profile + audit policies (10/20/30); the +PUBLIC-grant + network checks (40/50) are **detect-only** and never auto-remediate, +and the seeded `weak_profile`/`seed_weak` artifacts are on a non-DEFAULT profile +that hardening intentionally leaves alone. Reports land in `local/reports/` +(gitignored), each labeled *development signal only*. + +> **Why not a local Oracle 19c image?** The brokered product is Oracle 19c +> **Standard Edition 2 (SE2)** (License Included is SE2-only on RDS). On arm64, +> Oracle's `buildContainerImage.sh` supports **only** 19c Enterprise Edition and +> 26ai Free — there is no way to build a local SE2 image (`-s` errors out). An EE +> image would be the *wrong edition*: it exposes EE-only features (Oracle-native +> TDE, Fine-Grained Auditing) that SE2 lacks and the design compensates for, so it +> risks a misleading pass. Since edition-accurate STIG conformance is validated on +> a real brokered RDS SE2 instance regardless, this harness uses only the +> freely-pullable `gvenzl/oracle-free` engine and accepts that it is not 19c. + +**Optional — run the STIG overlay** against the local DB: point its +`oracledb_session` inputs at `localhost:1521/FREEPDB1` with the seeded app user. +Run cinc-auditor **via Docker** (image `cincproject/auditor`) rather than +installing cinc-workstation locally (the workstation install requires root and +pulls in a lot of complexity): + +```bash +docker run --rm -it --network host \ + -v "$PWD/../../cg-oracle-database-19c-stig-overlay:/share" \ + cincproject/auditor exec /share \ + --input-file /share/input.yml +``` + +The overlay's `oracledb_session` controls call `sqlplus`, so its README uses a +derived image (`cincproject/auditor` + Oracle Instant Client). See the overlay +repo's `README.md` for the authoritative image build and inputs. Local overlay +results are dev signal only. + +## What local CANNOT tell you (by design) + +- RDS parameter-group / option-group **effects** (moto only checks the API call is + made; it doesn't apply anything). +- **TLS/TCPS 2484**, KMS encryption, CloudWatch log exports, GovCloud + networking/partition, the RDS reboot/maintenance model. +- The exact RDS **privilege model** (`oracle-free` is 23c and grants more than RDS's + master user — develop as the seeded non-SYS user to surface RDS-only failures). + +All of the above is validated only on a live GovCloud RDS instance. + +## Layout + +``` +local/ + README.md (this file) + Makefile doctor / unit / moto-* / oracle-* / down + docker-compose.moto.yml motoserver/moto (free RDS control-plane mock) + docker-compose.oracle-free.yml gvenzl/oracle-free (native arm64) + scripts/ + wait-for-oracle.sh + moto-smoke.sh layer-2 broker-shape smoke + run-assessment-local.sh + run-hardening-local.sh + init/ + 00_create_test_users.sql non-SYS privileged app user (mirrors RDS) + 01_seed_insecure_state.sql deliberately-weak state for detection tests + reports/ (gitignored) assessment/hardening output +``` + +The **authoritative SQL hardening scripts** live in the overlay repo +(`hardening/sql/`, kept out of the broker so the broker never runs STIG +validation itself); +this harness runs them for a fast local loop and is never wired into the broker +runtime. diff --git a/local/docker-compose.moto.yml b/local/docker-compose.moto.yml new file mode 100644 index 00000000..75e86f65 --- /dev/null +++ b/local/docker-compose.moto.yml @@ -0,0 +1,14 @@ +# Free AWS RDS control-plane mock for broker flow tests. +# moto is control-plane ONLY (no Oracle engine). Point the broker/tests at +# http://localhost:5000 via the aws-sdk-go-v2 rds BaseEndpoint override. +# Known gap: moto does not implement create_db_subnet_group (stub that path). +services: + moto: + image: motoserver/moto:5.2.2 + container_name: cg-moto-rds + ports: + # Bind to loopback only — keep the unauthenticated moto control plane off + # the LAN. Do NOT change to "5000:5000" (0.0.0.0). + - "127.0.0.1:5000:5000" + environment: + MOTO_PORT: "5000" diff --git a/local/docker-compose.oracle-free.yml b/local/docker-compose.oracle-free.yml new file mode 100644 index 00000000..a1dd6c6e --- /dev/null +++ b/local/docker-compose.oracle-free.yml @@ -0,0 +1,24 @@ +# Local Oracle engine for fast SQL-hardening iteration. +# gvenzl/oracle-free is native arm64 (Apple Silicon) with a faststart tag. +# DEVELOPMENT SIGNAL ONLY: 23c Free, not RDS Oracle 19c EE. +services: + oracle: + image: gvenzl/oracle-free:23-slim-faststart + container_name: cg-oracle-free + ports: + # Bind to loopback only — a local dev DB with a known password must not be + # reachable from the LAN/VPN. Do NOT change to "1521:1521" (0.0.0.0). + - "127.0.0.1:1521:1521" + environment: + # Dev-only password; never used outside this local harness. + ORACLE_PASSWORD: "devpw_ChangeMe1" + # Create a dedicated app PDB user on first boot. + APP_USER: "APPUSER" + APP_USER_PASSWORD: "devpw_ChangeMe1" + volumes: + - ./init:/container-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD", "healthcheck.sh"] + interval: 10s + timeout: 5s + retries: 30 diff --git a/local/init/00_create_test_users.sql b/local/init/00_create_test_users.sql new file mode 100644 index 00000000..36537126 --- /dev/null +++ b/local/init/00_create_test_users.sql @@ -0,0 +1,23 @@ +-- 00_create_test_users.sql — local harness only. +-- DEVELOPMENT SIGNAL ONLY. +-- +-- Creates a NON-SYS privileged application user that mirrors the RDS master-user +-- privilege model (RDS does not grant SYS/SYSDBA). Hardening scripts are developed +-- and tested as this user so RDS-only permission failures surface locally. +-- +-- gvenzl/oracle-free already creates APPUSER via APP_USER env; this adds the +-- privileged-but-not-SYS role set an RDS master user typically has. + +ALTER SESSION SET CONTAINER = FREEPDB1; + +-- Grant the RDS-master-like privilege set (NOT SYSDBA). +GRANT CREATE SESSION TO APPUSER; +GRANT CREATE USER, ALTER USER, DROP USER TO APPUSER; +GRANT CREATE ROLE, GRANT ANY ROLE TO APPUSER; +GRANT CREATE PROFILE, ALTER PROFILE, DROP PROFILE TO APPUSER; +GRANT SELECT ON SYS.DBA_USERS TO APPUSER; +GRANT SELECT ON SYS.DBA_PROFILES TO APPUSER; +GRANT SELECT ON SYS.DBA_ROLE_PRIVS TO APPUSER; +GRANT SELECT ON SYS.DBA_SYS_PRIVS TO APPUSER; +GRANT SELECT ON SYS.DBA_TAB_PRIVS TO APPUSER; +GRANT AUDIT_ADMIN TO APPUSER; diff --git a/local/init/01_seed_insecure_state.sql b/local/init/01_seed_insecure_state.sql new file mode 100644 index 00000000..2c3c6c41 --- /dev/null +++ b/local/init/01_seed_insecure_state.sql @@ -0,0 +1,22 @@ +-- 01_seed_insecure_state.sql — local harness only. +-- DEVELOPMENT SIGNAL ONLY. +-- +-- Deliberately creates a WEAK state so the assessment scripts have something to +-- detect and the hardening scripts have something to remediate. Never run outside +-- the local throwaway container. + +ALTER SESSION SET CONTAINER = FREEPDB1; + +-- Weak profile: unlimited failed logins + no password expiry (STIG findings). +CREATE PROFILE weak_profile LIMIT + FAILED_LOGIN_ATTEMPTS UNLIMITED + PASSWORD_LIFE_TIME UNLIMITED; + +-- A user on the weak profile. +CREATE USER seed_weak IDENTIFIED BY "devpw_ChangeMe1" PROFILE weak_profile; +GRANT CREATE SESSION TO seed_weak; + +-- Over-privileged grant to PUBLIC (classic STIG finding; detect-first, never +-- auto-revoked by hardening without an explicit allowlist). +-- (Left as a comment: granting to PUBLIC on a real system is dangerous; the +-- assessment detects existing PUBLIC grants rather than us creating one here.) diff --git a/local/scripts/moto-smoke.sh b/local/scripts/moto-smoke.sh new file mode 100755 index 00000000..3e76b8d0 --- /dev/null +++ b/local/scripts/moto-smoke.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# moto-smoke.sh — exercise the broker's Oracle RDS control-plane calls against the +# moto mock (layer 2). Confirms the AWS API ACCEPTS the shape the broker builds: +# create-db-instance (oracle-se2 19c, encrypted, License Included, private) + +# create-db-parameter-group (oracle-se2-19) + create-option-group (oracle-se2 19). +# +# DEVELOPMENT SIGNAL ONLY: moto mocks the control plane — it does NOT run +# an Oracle engine, apply parameters, or do a real TLS handshake. This proves the +# request shape, not RDS behavior. Requires: `make moto-up` first, and the aws CLI. +set -euo pipefail + +# aws flags for the moto endpoint (array so it word-splits cleanly). +MOTO_ENDPOINT=(--endpoint-url http://localhost:5000 --region us-gov-west-1) +export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test +INSTANCE_ID="cg-oracle-smoke-$$" +PARAM_GROUP="cg-aws-broker-oracle-smoke-$$" +OPTION_GROUP="cg-aws-broker-oracle-smoke-og-$$" + +if ! command -v aws >/dev/null 2>&1; then + echo "aws CLI not found — 'brew install awscli' (layer-2 smoke only)." >&2 + exit 2 +fi +MOTO_TIMEOUT="${MOTO_TIMEOUT:-10}" +elapsed=0 +until curl -sf http://localhost:5000/moto-api/ >/dev/null 2>&1; do + if [ "$elapsed" -ge "$MOTO_TIMEOUT" ]; then + echo "moto is not up on :5000 after ${MOTO_TIMEOUT}s — run 'make moto-up' first." >&2 + exit 2 + fi + sleep 1 + elapsed=$((elapsed + 1)) +done + +cleanup() { + aws "${MOTO_ENDPOINT[@]}" rds delete-db-instance --db-instance-identifier "$INSTANCE_ID" --skip-final-snapshot >/dev/null 2>&1 || true + aws "${MOTO_ENDPOINT[@]}" rds delete-db-parameter-group --db-parameter-group-name "$PARAM_GROUP" >/dev/null 2>&1 || true + aws "${MOTO_ENDPOINT[@]}" rds delete-option-group --option-group-name "$OPTION_GROUP" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +echo "== create-db-instance (oracle-se2, License Included, encrypted, private) ==" +aws "${MOTO_ENDPOINT[@]}" rds create-db-instance \ + --db-instance-identifier "$INSTANCE_ID" \ + --db-instance-class db.t3.medium \ + --engine oracle-se2 \ + --engine-version 19.0.0.0.ru-2024-07.rur-2024-07.r1 \ + --license-model license-included \ + --allocated-storage 20 \ + --master-username APPUSER01 \ + --master-user-password "fakePw12345" \ + --db-name ORCL \ + --storage-encrypted \ + --no-publicly-accessible \ + --backup-retention-period 14 \ + --query 'DBInstance.[DBInstanceIdentifier,Engine,EngineVersion,StorageEncrypted,LicenseModel,PubliclyAccessible]' \ + --output text + +echo "== create-db-parameter-group (oracle-se2-19) ==" +aws "${MOTO_ENDPOINT[@]}" rds create-db-parameter-group \ + --db-parameter-group-name "$PARAM_GROUP" \ + --db-parameter-group-family oracle-se2-19 \ + --description "cg smoke" \ + --query 'DBParameterGroup.[DBParameterGroupName,DBParameterGroupFamily]' --output text + +echo "== create-option-group (oracle-se2 19) ==" +aws "${MOTO_ENDPOINT[@]}" rds create-option-group \ + --option-group-name "$OPTION_GROUP" \ + --engine-name oracle-se2 \ + --major-engine-version 19 \ + --option-group-description "cg smoke" \ + --query 'OptionGroup.[OptionGroupName,EngineName,MajorEngineVersion]' --output text + +echo "" +echo "moto smoke OK — the AWS API accepted the broker's Oracle create/param/option shapes." +echo "(development signal only: moto did not run Oracle, apply params, or do TLS.)" diff --git a/local/scripts/run-assessment-local.sh b/local/scripts/run-assessment-local.sh new file mode 100755 index 00000000..5b5b3ba0 --- /dev/null +++ b/local/scripts/run-assessment-local.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# run-assessment-local.sh — run the STIG assessment SQL against the local Oracle +# container and write a clearly-labeled report. Local harness only. +# +# ⚠️ DEVELOPMENT SIGNAL ONLY — NOT COMPLIANCE EVIDENCE. +# Authoritative evidence comes from cg-oracle-database-19c-stig-overlay run against +# a real brokered GovCloud RDS Oracle instance. +# +# Runs sqlplus INSIDE the oracle-free container (docker exec), so no Oracle Instant +# Client / sqlplus is needed on the host. +set -euo pipefail + +CONN="${1:?usage: run-assessment-local.sh }" +CONTAINER="${ORACLE_CONTAINER:-cg-oracle-free}" +SQL_DIR="${SQL_DIR:-../../cg-oracle-database-19c-stig-overlay/hardening/sql}" +OUT_DIR="reports" +STAMP="$(date -u +%Y%m%dT%H%M%SZ)" +OUT="${OUT_DIR}/assessment-${STAMP}.log" + +mkdir -p "${OUT_DIR}" +{ + echo "==============================================================" + echo " DEVELOPMENT SIGNAL ONLY — NOT COMPLIANCE EVIDENCE" + echo " local Oracle assessment @ ${STAMP}" + echo "==============================================================" +} | tee "${OUT}" + +if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then + echo "oracle container '${CONTAINER}' is not running — run 'make oracle-up' first." | tee -a "${OUT}" + exit 2 +fi +if [ ! -d "${SQL_DIR}" ]; then + echo "assessment SQL dir not found: ${SQL_DIR}" | tee -a "${OUT}" + echo "(clone cg-oracle-database-19c-stig-overlay as a SIBLING of aws-broker, or set SQL_DIR=)" | tee -a "${OUT}" + exit 2 +fi + +# Stage the SQL into the container in a fresh, world-readable dir (docker cp +# preserves host perms/owner, so reset them for the container's oracle user). +docker exec -u 0 "${CONTAINER}" rm -rf /tmp/hardening-sql >/dev/null 2>&1 || true +docker cp "${SQL_DIR}/." "${CONTAINER}:/tmp/hardening-sql" >/dev/null +docker exec -u 0 "${CONTAINER}" chmod -R a+rX /tmp/hardening-sql >/dev/null 2>&1 || true + +# Run only assessment-first scripts (00_*, 01_*, *_assess.sql, 90_validate.sql). +shopt -s nullglob +for f in "${SQL_DIR}"/00_*.sql "${SQL_DIR}"/01_*.sql "${SQL_DIR}"/*_assess.sql "${SQL_DIR}"/90_validate.sql; do + base="$(basename "${f}")" + echo ">>> ${base}" | tee -a "${OUT}" + docker exec -i "${CONTAINER}" bash -lc "cd /tmp/hardening-sql && sqlplus -S '${CONN}' @'${base}'" | tee -a "${OUT}" +done + +echo "assessment written to ${OUT} (development signal only)" diff --git a/local/scripts/run-hardening-local.sh b/local/scripts/run-hardening-local.sh new file mode 100755 index 00000000..493807ac --- /dev/null +++ b/local/scripts/run-hardening-local.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# run-hardening-local.sh — apply the ALLOWED hardening SQL against local Oracle. +# Local harness only. +# +# ⚠️ DEVELOPMENT SIGNAL ONLY. Hardening is assessment-first + idempotent; +# destructive / PUBLIC-grant changes are detect-first and require an explicit +# allowlist (they are NOT applied automatically). RDS-incompatible commands skip +# with a reason. Develop as a NON-SYS user (see init/00_create_test_users.sql). +# +# Runs sqlplus INSIDE the oracle-free container (docker exec), so no Oracle Instant +# Client / sqlplus is needed on the host. +set -euo pipefail + +CONN="${1:?usage: run-hardening-local.sh }" +CONTAINER="${ORACLE_CONTAINER:-cg-oracle-free}" +SQL_DIR="${SQL_DIR:-../../cg-oracle-database-19c-stig-overlay/hardening/sql}" +OUT_DIR="reports" +STAMP="$(date -u +%Y%m%dT%H%M%SZ)" +OUT="${OUT_DIR}/hardening-${STAMP}.log" + +mkdir -p "${OUT_DIR}" +{ + echo "==============================================================" + echo " DEVELOPMENT SIGNAL ONLY — NOT COMPLIANCE EVIDENCE" + echo " local Oracle hardening @ ${STAMP}" + echo "==============================================================" +} | tee "${OUT}" + +if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then + echo "oracle container '${CONTAINER}' is not running — run 'make oracle-up' first." | tee -a "${OUT}" + exit 2 +fi +if [ ! -d "${SQL_DIR}" ]; then + echo "hardening SQL dir not found: ${SQL_DIR}" | tee -a "${OUT}" + echo "(clone cg-oracle-database-19c-stig-overlay as a SIBLING of aws-broker, or set SQL_DIR=)" | tee -a "${OUT}" + exit 2 +fi + +# Stage the SQL into the container in a fresh, world-readable dir (docker cp +# preserves host perms/owner, so reset them for the container's oracle user). +docker exec -u 0 "${CONTAINER}" rm -rf /tmp/hardening-sql >/dev/null 2>&1 || true +docker cp "${SQL_DIR}/." "${CONTAINER}:/tmp/hardening-sql" >/dev/null +docker exec -u 0 "${CONTAINER}" chmod -R a+rX /tmp/hardening-sql >/dev/null 2>&1 || true + +# Apply remediation scripts (10_*..30_*), skipping assessment-only + rollback. +shopt -s nullglob +for f in "${SQL_DIR}"/10_*.sql "${SQL_DIR}"/20_*.sql "${SQL_DIR}"/30_*.sql; do + case "${f}" in + *_assess.sql) continue ;; + esac + base="$(basename "${f}")" + echo ">>> ${base}" | tee -a "${OUT}" + docker exec -i "${CONTAINER}" bash -lc "cd /tmp/hardening-sql && sqlplus -S '${CONN}' @'${base}'" | tee -a "${OUT}" +done + +echo "hardening applied; re-run 'make assess' to see the before/after (dev signal only)" diff --git a/local/scripts/wait-for-oracle.sh b/local/scripts/wait-for-oracle.sh new file mode 100755 index 00000000..f0775dfe --- /dev/null +++ b/local/scripts/wait-for-oracle.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# wait-for-oracle.sh — block until the local Oracle container is healthy. +set -euo pipefail + +CONTAINER="${1:-cg-oracle-free}" +TIMEOUT="${2:-300}" + +echo "Waiting up to ${TIMEOUT}s for ${CONTAINER} to become healthy..." +elapsed=0 +while true; do + status="$(docker inspect -f '{{.State.Health.Status}}' "${CONTAINER}" 2>/dev/null || echo "missing")" + case "${status}" in + healthy) echo "${CONTAINER} is healthy."; exit 0 ;; + missing) echo "container ${CONTAINER} not found"; exit 1 ;; + esac + if [ "${elapsed}" -ge "${TIMEOUT}" ]; then + echo "timed out waiting for ${CONTAINER} (last status: ${status})"; exit 1 + fi + sleep 5; elapsed=$((elapsed + 5)) +done