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
|
||||
|
||||
### 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
|
||||
|
||||
- Docker and Docker Compose
|
||||
- OCI API Key pair (private `.pem` key + fingerprint)
|
||||
- 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
|
||||
@@ -877,18 +864,33 @@ pip install -r requirements.txt
|
||||
DATA_DIR=./data uvicorn app:app --reload --port 8000
|
||||
```
|
||||
|
||||
### Run with Docker
|
||||
### Run with Docker Compose
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
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
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up -d --build
|
||||
docker compose up -d --build backend && docker compose restart frontend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
### 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
|
||||
|
||||
- Docker installed
|
||||
- Access to the OCI Container Registry (OCIR)
|
||||
- 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)
|
||||
- Python 3 with `cryptography` package (auto-installed by setup.sh if missing)
|
||||
|
||||
---
|
||||
|
||||
### Option 1 — Docker (any machine)
|
||||
|
||||
Run on any machine with Docker installed (Linux, macOS, Windows).
|
||||
### Alternative: Manual Docker Run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
@@ -214,7 +235,7 @@ docker run -d \
|
||||
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