diff --git a/.agent/scripts/cron-dispatch.sh b/.agent/scripts/cron-dispatch.sh index 9677a4eb8d..5e673a0b76 100755 --- a/.agent/scripts/cron-dispatch.sh +++ b/.agent/scripts/cron-dispatch.sh @@ -5,17 +5,36 @@ # # Called by crontab entries managed by cron-helper.sh # Requires OpenCode server running (opencode serve) +# +# Security: +# - Uses HTTPS by default for remote hosts (non-localhost) +# - Supports basic auth via OPENCODE_SERVER_PASSWORD +# - SSL verification enabled by default (disable with OPENCODE_INSECURE=1) set -euo pipefail # Configuration readonly CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/aidevops" readonly CONFIG_FILE="$CONFIG_DIR/cron-jobs.json" -# WORKSPACE_DIR reserved for future use (e.g., temp files during execution) readonly OPENCODE_PORT="${OPENCODE_PORT:-4096}" readonly OPENCODE_HOST="${OPENCODE_HOST:-127.0.0.1}" +readonly OPENCODE_INSECURE="${OPENCODE_INSECURE:-}" readonly MAIL_HELPER="$HOME/.aidevops/agents/scripts/mail-helper.sh" +####################################### +# Determine protocol based on host +# Localhost uses HTTP, remote uses HTTPS +####################################### +get_protocol() { + local host="$1" + # Use HTTP only for localhost/127.0.0.1, HTTPS for everything else + if [[ "$host" == "localhost" || "$host" == "127.0.0.1" || "$host" == "::1" ]]; then + echo "http" + else + echo "https" + fi +} + # Timestamp for logging log_timestamp() { date -u +"%Y-%m-%dT%H:%M:%SZ" @@ -34,14 +53,27 @@ log_success() { } ####################################### -# Get auth header for OpenCode server +# Build curl arguments array for secure requests +# Populates CURL_ARGS array with auth and SSL options ####################################### -get_auth_header() { +build_curl_args() { + CURL_ARGS=(-sf) + + # Add authentication if configured if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then local user="${OPENCODE_SERVER_USERNAME:-admin}" - echo "-u ${user}:${OPENCODE_SERVER_PASSWORD}" - else - echo "" + CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}") + fi + + # Add SSL options for HTTPS + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + if [[ "$protocol" == "https" ]]; then + if [[ -n "$OPENCODE_INSECURE" ]]; then + # Allow insecure connections (self-signed certs) - use with caution + CURL_ARGS+=(-k) + log_info "WARNING: SSL verification disabled (OPENCODE_INSECURE=1)" + fi fi } @@ -49,12 +81,13 @@ get_auth_header() { # Check server health ####################################### check_server() { - local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health" - local auth_header - auth_header=$(get_auth_header) + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health" - # shellcheck disable=SC2086 - if curl -sf $auth_header "$url" &>/dev/null; then + build_curl_args + + if curl "${CURL_ARGS[@]}" "$url" &>/dev/null; then return 0 else return 1 @@ -93,12 +126,13 @@ update_job_status() { ####################################### create_session() { local title="$1" - local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session" - local auth_header - auth_header=$(get_auth_header) + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/session" + + build_curl_args - # shellcheck disable=SC2086 - curl -sf $auth_header -X POST "$url" \ + curl "${CURL_ARGS[@]}" -X POST "$url" \ -H "Content-Type: application/json" \ -d "{\"title\": \"$title\"}" | jq -r '.id' } @@ -110,10 +144,10 @@ send_prompt() { local session_id="$1" local task="$2" local model="$3" - local timeout="$4" - local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}/message" - local auth_header - auth_header=$(get_auth_header) + local cmd_timeout="$4" + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}/message" # Parse model into provider and model ID local provider_id model_id @@ -134,9 +168,10 @@ send_prompt() { parts: [{type: "text", text: $task}] }') + build_curl_args + # Send with timeout - # shellcheck disable=SC2086 - timeout "$timeout" curl -sf $auth_header -X POST "$url" \ + timeout "$cmd_timeout" curl "${CURL_ARGS[@]}" -X POST "$url" \ -H "Content-Type: application/json" \ -d "$body" } @@ -146,12 +181,13 @@ send_prompt() { ####################################### delete_session() { local session_id="$1" - local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}" - local auth_header - auth_header=$(get_auth_header) + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}" + + build_curl_args - # shellcheck disable=SC2086 - curl -sf $auth_header -X DELETE "$url" &>/dev/null || true + curl "${CURL_ARGS[@]}" -X DELETE "$url" &>/dev/null || true } ####################################### diff --git a/.agent/scripts/cron-helper.sh b/.agent/scripts/cron-helper.sh index 05040cb78a..41605f678d 100755 --- a/.agent/scripts/cron-helper.sh +++ b/.agent/scripts/cron-helper.sh @@ -14,6 +14,11 @@ # # Configuration: ~/.config/aidevops/cron-jobs.json # Logs: ~/.aidevops/.agent-workspace/cron/ +# +# Security: +# - Uses HTTPS by default for remote hosts (non-localhost) +# - Supports basic auth via OPENCODE_SERVER_PASSWORD +# - SSL verification enabled by default (disable with OPENCODE_INSECURE=1) set -euo pipefail @@ -25,6 +30,7 @@ readonly CRON_LOG_DIR="$WORKSPACE_DIR/cron" readonly SCRIPTS_DIR="$HOME/.aidevops/agents/scripts" readonly OPENCODE_PORT="${OPENCODE_PORT:-4096}" readonly OPENCODE_HOST="${OPENCODE_HOST:-127.0.0.1}" +readonly OPENCODE_INSECURE="${OPENCODE_INSECURE:-}" readonly DEFAULT_TIMEOUT=600 readonly DEFAULT_MODEL="anthropic/claude-sonnet-4-20250514" @@ -84,19 +90,53 @@ check_jq() { } ####################################### -# Check OpenCode server health +# Determine protocol based on host +# Localhost uses HTTP, remote uses HTTPS ####################################### -check_server() { - local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health" - local auth_header="" +get_protocol() { + local host="$1" + # Use HTTP only for localhost/127.0.0.1, HTTPS for everything else + if [[ "$host" == "localhost" || "$host" == "127.0.0.1" || "$host" == "::1" ]]; then + echo "http" + else + echo "https" + fi +} + +####################################### +# Build curl arguments array for secure requests +####################################### +build_curl_args() { + CURL_ARGS=(-sf) + # Add authentication if configured if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then local user="${OPENCODE_SERVER_USERNAME:-admin}" - auth_header="-u ${user}:${OPENCODE_SERVER_PASSWORD}" + CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}") fi - # shellcheck disable=SC2086 - if curl -sf $auth_header "$url" &>/dev/null; then + # Add SSL options for HTTPS + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + if [[ "$protocol" == "https" ]]; then + if [[ -n "$OPENCODE_INSECURE" ]]; then + # Allow insecure connections (self-signed certs) - use with caution + CURL_ARGS+=(-k) + fi + fi +} + +####################################### +# Check OpenCode server health +####################################### +check_server() { + local protocol + protocol=$(get_protocol "$OPENCODE_HOST") + local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health" + + build_curl_args + + if curl "${CURL_ARGS[@]}" "$url" &>/dev/null; then return 0 else return 1 diff --git a/.agent/tools/automation/cron-agent.md b/.agent/tools/automation/cron-agent.md index d6f580fcd2..d15ffa5da7 100644 --- a/.agent/tools/automation/cron-agent.md +++ b/.agent/tools/automation/cron-agent.md @@ -381,11 +381,39 @@ ls -la ~/.aidevops/.agent-workspace/cron/ ## Security Considerations -1. **Server authentication**: Always use `OPENCODE_SERVER_PASSWORD` for network-exposed servers -2. **Task validation**: Jobs only execute pre-defined tasks from `cron-jobs.json` -3. **Timeout limits**: All jobs have configurable timeouts to prevent runaway sessions -4. **Log rotation**: Old logs are automatically pruned (configurable retention) -5. **Credential isolation**: Tasks inherit environment from cron, not from config files +1. **HTTPS by default**: Remote hosts (non-localhost) automatically use HTTPS +2. **Server authentication**: Always use `OPENCODE_SERVER_PASSWORD` for network-exposed servers +3. **SSL verification**: Enabled by default; use `OPENCODE_INSECURE=1` only for self-signed certs +4. **Task validation**: Jobs only execute pre-defined tasks from `cron-jobs.json` +5. **Timeout limits**: All jobs have configurable timeouts to prevent runaway sessions +6. **Log rotation**: Old logs are automatically pruned (configurable retention) +7. **Credential isolation**: Tasks inherit environment from cron, not from config files + +### Remote Server Configuration + +For connecting to a remote OpenCode server: + +```bash +# Required: Set server host and authentication +export OPENCODE_HOST="opencode.example.com" +export OPENCODE_PORT="4096" +export OPENCODE_SERVER_PASSWORD="your-secure-password" + +# Optional: For self-signed certificates (not recommended for production) +export OPENCODE_INSECURE=1 + +# Test connection +cron-helper.sh status +``` + +### Protocol Selection + +| Host | Protocol | Notes | +|------|----------|-------| +| `localhost` | HTTP | Safe for local development | +| `127.0.0.1` | HTTP | Safe for local development | +| `::1` | HTTP | IPv6 localhost | +| Any other host | HTTPS | Encrypted connection required | ## Related Documentation