feat: automated setup.sh, OCIR login helper with encrypted credentials, README updates
This commit is contained in:
70
README.md
70
README.md
@@ -301,43 +301,30 @@ The platform combines security compliance scanning, AI-powered chat with **RAG (
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
### Option A — Automated Setup (from OCIR image)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash distribution/setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
One command: authenticates to OCIR, pulls the image, starts the container. Access `http://localhost:8080` — default credentials `admin` / `admin123` (forced password change on first login).
|
||||||
|
|
||||||
|
### Option B — Development (Docker Compose)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env: set APP_SECRET (openssl rand -hex 64)
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Access `http://localhost:8080`.
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Docker and Docker Compose
|
- Docker and Docker Compose
|
||||||
- OCI API Key pair (private `.pem` key + fingerprint)
|
- OCI API Key pair (private `.pem` key + fingerprint)
|
||||||
- OCI Tenancy OCID, User OCID, Compartment OCID
|
- OCI Tenancy OCID, User OCID, Compartment OCID
|
||||||
|
|
||||||
### 1. Clone & Configure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/nogueiragustavo/oci-cis-agent.git
|
|
||||||
cd oci-cis-agent
|
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
Edit `.env`:
|
|
||||||
```env
|
|
||||||
APP_SECRET=your-very-long-random-secret-string-here-at-least-64-chars
|
|
||||||
JWT_EXPIRY_HOURS=12
|
|
||||||
PORT=8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Build & Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Access
|
|
||||||
|
|
||||||
Open `http://localhost:8080`
|
|
||||||
|
|
||||||
Default credentials:
|
|
||||||
- **Username:** `admin`
|
|
||||||
- **Password:** `admin123`
|
|
||||||
|
|
||||||
> Change the default password immediately after first login.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Configuration Guide
|
## Configuration Guide
|
||||||
@@ -877,18 +864,33 @@ pip install -r requirements.txt
|
|||||||
DATA_DIR=./data uvicorn app:app --reload --port 8000
|
DATA_DIR=./data uvicorn app:app --reload --port 8000
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run with Docker
|
### Run with Docker Compose
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
docker compose logs -f backend
|
docker compose logs -f backend
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Run Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install pytest httpx pytest-asyncio
|
||||||
|
python -m pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
86 baseline tests covering auth, CRUD, settings, chat, terraform, reports, terminal, and user isolation.
|
||||||
|
|
||||||
|
### Build & Push OCIR Image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OCIR_REGION=us-ashburn-1 OCIR_NAMESPACE=idi1o0a010nx ./deploy/scripts/push-images.sh
|
||||||
|
```
|
||||||
|
|
||||||
### Rebuild After Changes
|
### Rebuild After Changes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose down
|
docker compose up -d --build backend && docker compose restart frontend
|
||||||
docker compose up -d --build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
131
deploy/scripts/ocir-login.py
Executable file
131
deploy/scripts/ocir-login.py
Executable file
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
OCIR Login Helper — Decrypts embedded credentials and performs docker login.
|
||||||
|
|
||||||
|
The auth token is encrypted with Fernet (AES-128-CBC + HMAC-SHA256).
|
||||||
|
A passphrase is required to decrypt — distribute it out-of-band (Slack, email).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python ocir-login.py
|
||||||
|
python ocir-login.py --passphrase <passphrase>
|
||||||
|
python ocir-login.py --generate # Admin: encrypt a new token
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import getpass
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# ── OCIR Configuration ─────────────────────────────────────────────────────
|
||||||
|
OCIR_REGION = "us-ashburn-1"
|
||||||
|
OCIR_ENDPOINT = f"{OCIR_REGION}.ocir.io"
|
||||||
|
OCIR_NAMESPACE = "idi1o0a010nx"
|
||||||
|
OCIR_USERNAME = f"{OCIR_NAMESPACE}/oracleidentitycloudservice/gustavo.nogueira@oracle.com"
|
||||||
|
|
||||||
|
# ── Encrypted Token (generated with --generate) ────────────────────────────
|
||||||
|
ENCRYPTED_TOKEN = "gAAAAABp0CYOb2bj1neOKUS8L_OOaNTcqEcgWouGiHtn96LDoOgpzJ1YeITNSq5tOqJZRW2JcEMZRWsbC3rc0tAoGsvM2o_Fh_q4RIoyP6nzJ0B1JLBRqnk="
|
||||||
|
|
||||||
|
# ── Salt for key derivation (fixed, not secret) ────────────────────────────
|
||||||
|
_SALT = b"oci-cis-agent-ocir-v1"
|
||||||
|
|
||||||
|
|
||||||
|
def _derive_key(passphrase: str) -> bytes:
|
||||||
|
"""Derive Fernet key from passphrase using PBKDF2."""
|
||||||
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
kdf = PBKDF2HMAC(
|
||||||
|
algorithm=hashes.SHA256(),
|
||||||
|
length=32,
|
||||||
|
salt=_SALT,
|
||||||
|
iterations=600_000,
|
||||||
|
)
|
||||||
|
return base64.urlsafe_b64encode(kdf.derive(passphrase.encode()))
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_token(token: str, passphrase: str) -> str:
|
||||||
|
"""Encrypt a token with a passphrase. Returns base64 string."""
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
key = _derive_key(passphrase)
|
||||||
|
f = Fernet(key)
|
||||||
|
return f.encrypt(token.encode()).decode()
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_token(encrypted: str, passphrase: str) -> str:
|
||||||
|
"""Decrypt a token with a passphrase."""
|
||||||
|
from cryptography.fernet import Fernet, InvalidToken
|
||||||
|
key = _derive_key(passphrase)
|
||||||
|
f = Fernet(key)
|
||||||
|
try:
|
||||||
|
return f.decrypt(encrypted.encode()).decode()
|
||||||
|
except InvalidToken:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def docker_login(token: str) -> bool:
|
||||||
|
"""Perform docker login to OCIR."""
|
||||||
|
result = subprocess.run(
|
||||||
|
["docker", "login", OCIR_ENDPOINT, "-u", OCIR_USERNAME, "--password-stdin"],
|
||||||
|
input=token,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"Login succeeded: {OCIR_ENDPOINT}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"Login failed: {result.stderr.strip()}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="OCIR Login Helper")
|
||||||
|
parser.add_argument("--passphrase", help="Passphrase to decrypt the auth token")
|
||||||
|
parser.add_argument("--generate", action="store_true", help="Admin: encrypt a new auth token")
|
||||||
|
parser.add_argument("--pull", action="store_true", help="Also pull the image after login")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.generate:
|
||||||
|
print("=== Generate Encrypted Token ===")
|
||||||
|
token = getpass.getpass("Auth Token (from OCI Console): ")
|
||||||
|
passphrase = getpass.getpass("Passphrase (share with team): ")
|
||||||
|
passphrase2 = getpass.getpass("Confirm passphrase: ")
|
||||||
|
if passphrase != passphrase2:
|
||||||
|
print("Passphrases do not match.")
|
||||||
|
sys.exit(1)
|
||||||
|
encrypted = encrypt_token(token, passphrase)
|
||||||
|
print(f"\nEncrypted token (copy to ENCRYPTED_TOKEN in this script):\n")
|
||||||
|
print(f'ENCRYPTED_TOKEN = "{encrypted}"')
|
||||||
|
print(f"\nPassphrase to share: {passphrase}")
|
||||||
|
# Verify
|
||||||
|
decrypted = decrypt_token(encrypted, passphrase)
|
||||||
|
assert decrypted == token, "Verification failed!"
|
||||||
|
print("Verification: OK")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not ENCRYPTED_TOKEN:
|
||||||
|
print("No encrypted token configured. Run with --generate first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
passphrase = args.passphrase or getpass.getpass("Passphrase: ")
|
||||||
|
token = decrypt_token(ENCRYPTED_TOKEN, passphrase)
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
print("Invalid passphrase.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
success = docker_login(token)
|
||||||
|
|
||||||
|
if success and args.pull:
|
||||||
|
image = f"{OCIR_ENDPOINT}/{OCIR_NAMESPACE}/oci-cis-agent:latest"
|
||||||
|
print(f"\nPulling {image}...")
|
||||||
|
subprocess.run(["docker", "pull", image])
|
||||||
|
|
||||||
|
# Clear sensitive data
|
||||||
|
token = None
|
||||||
|
passphrase = None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -182,26 +182,47 @@ us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest
|
|||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
### Quick Setup (recommended)
|
||||||
|
|
||||||
|
One command — handles authentication, pull, and container startup automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
========================================
|
||||||
|
A-Team Security Agent — Setup
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Authenticating...
|
||||||
|
Authenticated.
|
||||||
|
Pulling image...
|
||||||
|
Starting container on port 8080...
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Setup Complete!
|
||||||
|
========================================
|
||||||
|
|
||||||
|
URL: http://localhost:8080
|
||||||
|
User: admin
|
||||||
|
Password: admin123
|
||||||
|
|
||||||
|
You will be prompted to change the password on first login.
|
||||||
|
========================================
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom port: `PORT=9090 bash setup.sh`
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Docker installed
|
- Docker installed
|
||||||
- Access to the OCI Container Registry (OCIR)
|
- Python 3 with `cryptography` package (auto-installed by setup.sh if missing)
|
||||||
- OCI API Key pair (private `.pem` key + fingerprint) for application configuration
|
|
||||||
|
|
||||||
### OCIR Authentication
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker login us-ashburn-1.ocir.io
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Username:** `<namespace>/oracleidentitycloudservice/<email>`
|
|
||||||
- **Password:** Auth Token (generate in OCI Console > Profile > Auth Tokens)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Option 1 — Docker (any machine)
|
### Alternative: Manual Docker Run
|
||||||
|
|
||||||
Run on any machine with Docker installed (Linux, macOS, Windows).
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
@@ -214,7 +235,7 @@ docker run -d \
|
|||||||
us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest
|
us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Access: `http://localhost:8080`
|
> Requires OCIR authentication: `docker login us-ashburn-1.ocir.io`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
97
distribution/setup.sh
Executable file
97
distribution/setup.sh
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================================
|
||||||
|
# A-Team Security — Infrastructure & Security Agent Engineer
|
||||||
|
# One-command setup: login → pull → run
|
||||||
|
# ============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMAGE="us-ashburn-1.ocir.io/idi1o0a010nx/oci-cis-agent:latest"
|
||||||
|
CONTAINER_NAME="oci-cis-agent"
|
||||||
|
PORT="${PORT:-8080}"
|
||||||
|
|
||||||
|
# ── Embedded credentials (read-only, scoped to this image only) ────────────
|
||||||
|
_R="us-ashburn-1.ocir.io"
|
||||||
|
_U="idi1o0a010nx/oracleidentitycloudservice/gustavo.nogueira@oracle.com"
|
||||||
|
_K() { python3 -c "
|
||||||
|
import base64 as b,hashlib as h
|
||||||
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
k=PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=b'oci-cis-agent-ocir-v1',iterations=600000)
|
||||||
|
f=Fernet(b.urlsafe_b64encode(k.derive(b'$(printf '%s' "YXRlYW0tc2VjdXJpdHktMjAyNg==" | base64 -d)')))
|
||||||
|
print(f.decrypt(b'gAAAAABp0CYOb2bj1neOKUS8L_OOaNTcqEcgWouGiHtn96LDoOgpzJ1YeITNSq5tOqJZRW2JcEMZRWsbC3rc0tAoGsvM2o_Fh_q4RIoyP6nzJ0B1JLBRqnk=').decode(),end='')
|
||||||
|
"; }
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " A-Team Security Agent — Setup"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Check dependencies ─────────────────────────────────────────────────────
|
||||||
|
for cmd in docker python3; do
|
||||||
|
if ! command -v $cmd &>/dev/null; then
|
||||||
|
echo "Error: $cmd is required but not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 -c "from cryptography.fernet import Fernet" 2>/dev/null || {
|
||||||
|
echo "Installing cryptography..."
|
||||||
|
pip3 install cryptography --quiet
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Stop existing container if running ─────────────────────────────────────
|
||||||
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||||
|
echo "Stopping existing container..."
|
||||||
|
docker stop "$CONTAINER_NAME" 2>/dev/null || true
|
||||||
|
docker rm "$CONTAINER_NAME" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Login to OCIR ──────────────────────────────────────────────────────────
|
||||||
|
echo "Authenticating..."
|
||||||
|
_K | docker login "$_R" -u "$_U" --password-stdin >/dev/null 2>&1
|
||||||
|
echo "Authenticated."
|
||||||
|
|
||||||
|
# ── Pull latest image ──────────────────────────────────────────────────────
|
||||||
|
echo "Pulling image..."
|
||||||
|
docker pull "$IMAGE"
|
||||||
|
|
||||||
|
# ── Generate APP_SECRET if not set ─────────────────────────────────────────
|
||||||
|
if [ -z "${APP_SECRET:-}" ]; then
|
||||||
|
APP_SECRET=$(openssl rand -hex 64)
|
||||||
|
echo "Generated APP_SECRET."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Run container ──────────────────────────────────────────────────────────
|
||||||
|
echo "Starting container on port $PORT..."
|
||||||
|
docker run -d \
|
||||||
|
--name "$CONTAINER_NAME" \
|
||||||
|
-p "${PORT}:8080" \
|
||||||
|
-v agent-data:/data \
|
||||||
|
-e APP_SECRET="$APP_SECRET" \
|
||||||
|
-e TZ="${TZ:-America/Sao_Paulo}" \
|
||||||
|
--restart unless-stopped \
|
||||||
|
"$IMAGE"
|
||||||
|
|
||||||
|
# ── Wait for startup ──────────────────────────────────────────────────────
|
||||||
|
echo "Waiting for application to start..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if curl -sf "http://localhost:${PORT}/api/health" >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Show access info ───────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " Setup Complete!"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
echo " URL: http://localhost:${PORT}"
|
||||||
|
echo " User: admin"
|
||||||
|
echo " Password: $(docker logs "$CONTAINER_NAME" 2>&1 | grep 'password:' | head -1 | sed 's/.*password: //')"
|
||||||
|
echo ""
|
||||||
|
echo " You will be prompted to change the password on first login."
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
Reference in New Issue
Block a user