mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-09-27 12:04:54 +02:00
- Implement cloud provider infrastructure (DigitalOcean, AWS, GCP, Linode, Azure) with Pulumi integration for distributed scanning - Add nmap and tmux utility functions for port scanning results processing and long-running background session management - Introduce webhook-triggered run execution with unique UUID and authentication key support for external integrations
1285 lines
42 KiB
Go
1285 lines
42 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// exampleConfigYAML contains the default configuration template
|
|
// Source of truth: public/presets/osm-settings.example.yaml
|
|
var exampleConfigYAML = []byte(`# Osmedeus Configuration File
|
|
# This file contains all available configuration options for osmedeus.
|
|
# Copy this file to ~/osmedeus-base/osm-settings.yaml and customize as needed.
|
|
|
|
# =============================================================================
|
|
# Base Folder
|
|
# =============================================================================
|
|
# Root directory for all osmedeus data (workflows, binaries, data, etc.)
|
|
# Environment variables like $HOME are automatically expanded
|
|
base_folder: $HOME/osmedeus-base
|
|
|
|
# =============================================================================
|
|
# Environment Paths
|
|
# =============================================================================
|
|
# Directory paths for various osmedeus components
|
|
# Use {{base_folder}} to reference the base_folder value above
|
|
environments:
|
|
# Path to binary executables (tools like nmap, ffuf, etc.)
|
|
external_binaries_path: "{{base_folder}}/external-binaries"
|
|
|
|
# Data directory for storing assets, wordlists, etc.
|
|
external_data: "{{base_folder}}/external-data"
|
|
|
|
# External configuration files (nuclei templates, etc.)
|
|
external_configs: "{{base_folder}}/external-configs"
|
|
|
|
# Output directory for scan workspaces
|
|
# Each target gets its own subdirectory here
|
|
workspaces: "$HOME/workspaces-osmedeus"
|
|
|
|
# Directory containing workflow YAML files
|
|
# Subdirectories: flows/, modules/
|
|
workflows: "{{base_folder}}/workflows"
|
|
|
|
# Directory for workspace snapshots (zip archives)
|
|
# Used by the snapshot-download API endpoint
|
|
snapshot: "{{base_folder}}/snapshot"
|
|
|
|
# Directory for markdown report templates
|
|
# Used by render_markdown_report() function
|
|
markdown_report_templates: "{{base_folder}}/markdown-report-templates"
|
|
|
|
# Directory for external agent configurations
|
|
# Used for LLM Agent commands, skills, and related configurations
|
|
external_agent_configs: "{{base_folder}}/external-agent-configs"
|
|
|
|
# Directory for external utility scripts
|
|
# Used for storing custom scripts and utilities
|
|
external_scripts: "{{base_folder}}/external-scripts"
|
|
|
|
# =============================================================================
|
|
# Database Configuration
|
|
# =============================================================================
|
|
# Osmedeus supports SQLite (default) and PostgreSQL
|
|
database:
|
|
# Database engine: "sqlite" or "postgresql"
|
|
db_engine: sqlite
|
|
|
|
# SQLite: Path to the database file
|
|
# Ignored when using PostgreSQL
|
|
db_path: "{{base_folder}}/database-osm.sqlite"
|
|
|
|
# PostgreSQL connection settings
|
|
# Only used when db_engine is "postgresql"
|
|
host: localhost
|
|
port: 5432
|
|
username: osmedeus
|
|
password: osmedeus
|
|
db_name: osmedeus
|
|
|
|
# Connection timeout in seconds
|
|
connection_timeout: 60
|
|
|
|
# PostgreSQL SSL mode: disable, require, verify-ca, verify-full
|
|
ssl_mode: disable
|
|
|
|
# =============================================================================
|
|
# Server Configuration
|
|
# =============================================================================
|
|
# REST API server settings for the web interface
|
|
server:
|
|
# Host to bind the server to
|
|
# Use "0.0.0.0" to listen on all interfaces
|
|
# Use "127.0.0.1" to listen only on localhost
|
|
host: "0.0.0.0"
|
|
|
|
# Port number for the API server
|
|
port: 8002
|
|
|
|
# Path to serve static UI files
|
|
# Default: {{base_folder}}/ui/ - if this directory exists, it will be served at /ui
|
|
# Set to empty string to disable UI serving
|
|
ui_path: "{{base_folder}}/ui/"
|
|
|
|
# Random prefix for workspace static files (auto-generated 16 chars if empty)
|
|
# Used as URL path segment for direct access to workspaces folder
|
|
workspace_prefix_key: ""
|
|
|
|
# Authentication credentials (map of username:password)
|
|
# Supports multiple users (password auto-generated if empty)
|
|
simple_user_map_key:
|
|
osmedeus: ""
|
|
|
|
# JWT (JSON Web Token) settings
|
|
jwt:
|
|
# Secret key for signing JWT tokens (auto-generated if empty)
|
|
secret_signing_key: ""
|
|
|
|
# Token expiration time in minutes (default: 1440 = 1 day)
|
|
expiration_minutes: 1440
|
|
|
|
# License type shown in HTTP Server header and /server-info endpoint
|
|
license: "open-source"
|
|
|
|
# Enable Prometheus metrics endpoint at /metrics (default: true)
|
|
# Set to false to disable metrics collection and endpoint
|
|
enable_metrics: true
|
|
|
|
# CORS allowed origins (default: "reflect-origin" echoes back the request origin)
|
|
# Use "reflect-origin" to dynamically reflect the requesting origin (required for credentials)
|
|
# Use comma-separated list for specific origins: "https://example.com,https://app.example.com"
|
|
cors_allowed_origins: "reflect-origin"
|
|
|
|
# API Key Authentication (alternative to JWT login flow)
|
|
# When enabled, all API requests must include header: x-osm-api-key: <your-key>
|
|
# This takes priority over JWT authentication when enabled
|
|
enabled_auth_api: true
|
|
auth_api_key: ""
|
|
|
|
# Enable webhook trigger endpoints for runs
|
|
# When enabled, runs registered with --as-webhook can be triggered via
|
|
# GET/POST /osm/api/webhook-runs/<uuid>/trigger without authentication
|
|
enable_trigger_via_webhook: false
|
|
|
|
# =============================================================================
|
|
# Scan Tactic Configuration
|
|
# =============================================================================
|
|
# Thread counts for different scan intensity levels
|
|
# Higher values = faster but more aggressive scans
|
|
# Lower values = slower but gentler on target systems
|
|
scan_tactic:
|
|
# Aggressive/fast mode - maximum parallelism
|
|
# Used with: osmedeus scan -t target --tactic aggressive
|
|
aggressive: 40
|
|
|
|
# Default/normal mode - balanced approach
|
|
# Used when no tactic is specified
|
|
default: 10
|
|
|
|
# Gentle/thorough mode - minimal parallelism
|
|
# Used with: osmedeus scan -t target --tactic gently
|
|
gently: 5
|
|
|
|
# =============================================================================
|
|
# Redis Configuration (Optional)
|
|
# =============================================================================
|
|
# Redis is required for distributed scanning mode
|
|
# Leave host empty to disable Redis
|
|
redis:
|
|
# Redis server hostname
|
|
# Leave empty to disable distributed mode
|
|
host: ""
|
|
|
|
# Redis server port
|
|
port: 6379
|
|
|
|
# Redis authentication (if required)
|
|
username: ""
|
|
password: ""
|
|
|
|
# Redis database number (0-15)
|
|
db: 0
|
|
|
|
# Connection timeout in seconds
|
|
connection_timeout: 60
|
|
|
|
# =============================================================================
|
|
# Global Variables
|
|
# =============================================================================
|
|
# User-defined variables available in workflows via {{VARIABLE_NAME}}
|
|
# Variables can optionally be exported to environment variables
|
|
# Use _API_KEY suffix for secrets to indicate sensitive values
|
|
#
|
|
# Format:
|
|
# VARIABLE_NAME:
|
|
# value: "the-value"
|
|
# as_env: true # Optional: export as env var (default: true)
|
|
#
|
|
# Example usage in workflows:
|
|
# - bash: "echo {{GITHUB_API_KEY}}"
|
|
# - bash: "shodan search $SHODAN_API_KEY" # Uses env var
|
|
global_vars:
|
|
# GitHub personal access token for API access
|
|
GITHUB_API_KEY:
|
|
value: ""
|
|
as_env: true # Exports as GITHUB_API_KEY
|
|
|
|
# Shodan API key for passive reconnaissance
|
|
SHODAN_API_KEY:
|
|
value: ""
|
|
as_env: true # Exports as SHODAN_API_KEY
|
|
|
|
# Censys API key for certificate/host search
|
|
CENSYS_API_KEY:
|
|
value: ""
|
|
as_env: true # Exports as CENSYS_API_KEY
|
|
|
|
# PassiveTotal API key for passive DNS/WHOIS
|
|
PASSIVETOTAL_API_KEY:
|
|
value: ""
|
|
as_env: true # Exports as PASSIVETOTAL_API_KEY
|
|
|
|
# Add more API keys as needed (use _API_KEY suffix for secrets)
|
|
|
|
# =============================================================================
|
|
# Notification Configuration
|
|
# =============================================================================
|
|
# Send notifications when scans complete or find interesting results
|
|
notification:
|
|
# Notification provider: "telegram" (future: slack, discord, webhook)
|
|
provider: telegram
|
|
|
|
# Master switch to enable/disable all notifications
|
|
enabled: false
|
|
|
|
# Telegram bot settings
|
|
# Create a bot via @BotFather and get the token
|
|
# Get your chat ID by messaging @userinfobot
|
|
telegram:
|
|
# Bot token from @BotFather
|
|
bot_token: ""
|
|
|
|
# Chat ID to send messages to (can be user or group)
|
|
chat_id: 0
|
|
|
|
# Enable Telegram notifications
|
|
enabled: false
|
|
|
|
# =============================================================================
|
|
# Cloud Storage Configuration (Optional)
|
|
# =============================================================================
|
|
# S3-compatible storage for backing up scan results
|
|
# Supports AWS S3, MinIO, Cloudflare R2, Google Cloud Storage, DigitalOcean Spaces, Oracle OCI
|
|
storage:
|
|
# Storage provider: "s3", "minio", "r2", "gcs", "spaces", "oci"
|
|
provider: s3
|
|
|
|
# Storage endpoint URL (auto-resolved for most providers)
|
|
# Leave empty to auto-resolve based on provider and region/account_id
|
|
endpoint: ""
|
|
|
|
# Access credentials
|
|
access_key_id: ""
|
|
secret_access_key: ""
|
|
|
|
# Bucket name for storing results
|
|
bucket: ""
|
|
|
|
# Cloud region (e.g., us-east-1, eu-west-1)
|
|
region: us-east-1
|
|
|
|
# Account ID or Namespace (R2: account ID, OCI: namespace)
|
|
account_id: ""
|
|
|
|
# Use SSL/TLS for connections
|
|
use_ssl: true
|
|
|
|
# Force path-style URLs
|
|
path_style: false
|
|
|
|
# Default presigned URL expiry (e.g., "1h", "30m")
|
|
presign_expiry: "1h"
|
|
|
|
# Enable cloud storage uploads
|
|
enabled: false
|
|
|
|
# =============================================================================
|
|
# Cloud Infrastructure Configuration (Optional)
|
|
# =============================================================================
|
|
# Ephemeral cloud infrastructure for distributed workflow execution
|
|
# Uses Pulumi (Go SDK) with local state - no Pulumi Cloud account needed
|
|
cloud:
|
|
# Path to cloud infrastructure state and metadata
|
|
cloud_path: "{{base_folder}}/cloud"
|
|
|
|
# Path to cloud provider configuration file (YAML)
|
|
# Contains credentials, provider settings, cost limits, etc.
|
|
cloud_settings: "{{base_folder}}/cloud/cloud-settings.yaml"
|
|
|
|
# Enable cloud execution features
|
|
enabled: false
|
|
|
|
# =============================================================================
|
|
# LLM Configuration (Optional)
|
|
# =============================================================================
|
|
# Large Language Model settings for AI-powered features
|
|
# Supports providers like Ollama, OpenAI, Anthropic, etc.
|
|
# Multiple providers can be configured for automatic rotation on error/rate limit
|
|
llm_config:
|
|
# List of LLM providers (rotates to next on error/rate limit)
|
|
llm_providers:
|
|
# Primary provider (used first)
|
|
- provider: ollama
|
|
base_url: "http://localhost:11434/v1/chat/completions"
|
|
auth_token: ""
|
|
model: "gpt-oss:120b-cloud"
|
|
# Backup provider example (uncomment to enable rotation)
|
|
# - provider: openai
|
|
# base_url: "https://api.openai.com/v1/chat/completions"
|
|
# auth_token: "sk-your-api-key"
|
|
# model: "gpt-4"
|
|
|
|
# Enable LLM tool call features
|
|
enabled_tool_call: false
|
|
|
|
# Maximum number of tokens to generate
|
|
max_tokens: 1000
|
|
|
|
# Temperature for sampling
|
|
temperature: 0.7
|
|
|
|
# Top-k sampling
|
|
top_k: 50
|
|
|
|
# Top-p sampling
|
|
top_p: 0.9
|
|
|
|
# Number of completions to generate
|
|
n: 1
|
|
|
|
# Maximum number of retries for failed requests
|
|
max_retries: 3
|
|
|
|
# Timeout for API requests
|
|
timeout: 120s
|
|
|
|
# Enable streaming responses
|
|
stream: false
|
|
|
|
# Enable structured JSON output format
|
|
structured_json_format: false
|
|
|
|
# System prompt for the LLM
|
|
system_prompt: ""
|
|
|
|
# Custom headers for API requests
|
|
custom_headers: ""
|
|
`)
|
|
|
|
// generateRandomString generates a random alphanumeric string of the given length
|
|
func generateRandomString(length int) string {
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
|
b[i] = charset[n.Int64()]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// GlobalVar represents a single global variable with optional env export
|
|
type GlobalVar struct {
|
|
Value string `yaml:"value"`
|
|
AsEnv *bool `yaml:"as_env,omitempty"` // pointer to distinguish unset (defaults true) from false
|
|
}
|
|
|
|
// IsAsEnv returns true if this variable should be exported to environment
|
|
// Defaults to true if not explicitly set
|
|
func (g GlobalVar) IsAsEnv() bool {
|
|
if g.AsEnv == nil {
|
|
return true // default
|
|
}
|
|
return *g.AsEnv
|
|
}
|
|
|
|
// GlobalVarsConfig holds all global variables
|
|
type GlobalVarsConfig map[string]GlobalVar
|
|
|
|
// NotificationConfig for multi-provider notifications
|
|
type NotificationConfig struct {
|
|
Provider string `yaml:"provider"` // telegram, webhook
|
|
Enabled bool `yaml:"enabled"`
|
|
Telegram TelegramConfig `yaml:"telegram,omitempty"`
|
|
Webhooks []WebhookConfig `yaml:"webhooks,omitempty"` // Multiple webhook endpoints
|
|
}
|
|
|
|
var (
|
|
globalConfig *Config
|
|
configMu sync.RWMutex
|
|
)
|
|
|
|
// Config holds the complete application configuration
|
|
type Config struct {
|
|
BaseFolder string `yaml:"base_folder"`
|
|
Environments EnvironmentConfig `yaml:"environments"`
|
|
Database DatabaseConfig `yaml:"database"`
|
|
Server ServerConfig `yaml:"server"`
|
|
ScanTactic ScanTacticConfig `yaml:"scan_tactic"`
|
|
Redis RedisConfig `yaml:"redis"`
|
|
GlobalVars GlobalVarsConfig `yaml:"global_vars"`
|
|
Notification NotificationConfig `yaml:"notification"`
|
|
Storage StorageConfig `yaml:"storage"`
|
|
LLM LLMConfig `yaml:"llm_config"`
|
|
Cloud CloudConfig `yaml:"cloud"`
|
|
|
|
// Runtime paths (resolved from templates)
|
|
BinariesPath string `yaml:"-"`
|
|
DataPath string `yaml:"-"`
|
|
ConfigsPath string `yaml:"-"`
|
|
WorkspacesPath string `yaml:"-"`
|
|
WorkflowsPath string `yaml:"-"`
|
|
UIPath string `yaml:"-"` // Resolved UI static files path
|
|
SnapshotPath string `yaml:"-"` // Resolved snapshot directory path
|
|
MarkdownReportTemplatesPath string `yaml:"-"` // Resolved markdown report templates path
|
|
ExternalAgentConfigsPath string `yaml:"-"` // Resolved external agent configs path
|
|
ExternalScriptsPath string `yaml:"-"` // Resolved external scripts path
|
|
}
|
|
|
|
// EnvironmentConfig holds environment path configurations
|
|
type EnvironmentConfig struct {
|
|
ExternalBinariesPath string `yaml:"external_binaries_path"`
|
|
ExternalData string `yaml:"external_data"`
|
|
ExternalConfigs string `yaml:"external_configs"`
|
|
Workspaces string `yaml:"workspaces"`
|
|
Workflows string `yaml:"workflows"`
|
|
Snapshot string `yaml:"snapshot"`
|
|
MarkdownReportTemplates string `yaml:"markdown_report_templates"`
|
|
ExternalAgentConfigs string `yaml:"external_agent_configs"`
|
|
ExternalScripts string `yaml:"external_scripts"`
|
|
}
|
|
|
|
// DatabaseConfig holds database connection settings
|
|
type DatabaseConfig struct {
|
|
DBEngine string `yaml:"db_engine"` // sqlite, postgresql
|
|
DBPath string `yaml:"db_path"` // SQLite file path
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Username string `yaml:"username"`
|
|
Password string `yaml:"password"`
|
|
DBName string `yaml:"db_name"`
|
|
ConnectionTimeout int `yaml:"connection_timeout"`
|
|
SSLMode string `yaml:"ssl_mode"`
|
|
}
|
|
|
|
// ServerConfig holds API server settings
|
|
type ServerConfig struct {
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
UIPath string `yaml:"ui_path"` // Path to serve static UI files
|
|
WorkspacePrefixKey string `yaml:"workspace_prefix_key"` // Random prefix for workspace static files (16 chars)
|
|
SimpleUserMapKey map[string]string `yaml:"simple_user_map_key"` // Map of username:password for authentication
|
|
JWT JWTConfig `yaml:"jwt"` // JWT settings
|
|
License string `yaml:"license"` // License type shown in ServerHeader and /server-info
|
|
EnabledAuthAPI bool `yaml:"enabled_auth_api"` // Enable API key authentication (default: false)
|
|
AuthAPIKey string `yaml:"auth_api_key"` // API key for x-osm-api-key header authentication
|
|
EnableMetrics *bool `yaml:"enable_metrics,omitempty"` // Enable Prometheus metrics endpoint (default: true)
|
|
CORSAllowedOrigins string `yaml:"cors_allowed_origins,omitempty"` // CORS allowed origins (default: "*")
|
|
EventReceiverURL string `yaml:"event_receiver_url,omitempty"` // URL for event receiver (auto-resolved from host:port if empty)
|
|
EnableTriggerViaWebhook bool `yaml:"enable_trigger_via_webhook"` // Enable webhook trigger endpoints (default: false)
|
|
}
|
|
|
|
// IsMetricsEnabled returns true if the metrics endpoint should be enabled.
|
|
// Defaults to true if not explicitly set.
|
|
func (c *ServerConfig) IsMetricsEnabled() bool {
|
|
if c.EnableMetrics == nil {
|
|
return true
|
|
}
|
|
return *c.EnableMetrics
|
|
}
|
|
|
|
// GetCORSAllowedOrigins returns the configured CORS allowed origins.
|
|
// Defaults to "reflect-origin" if not explicitly set, which dynamically
|
|
// echoes back the requesting origin (required for credentials support).
|
|
func (c *ServerConfig) GetCORSAllowedOrigins() string {
|
|
if c.CORSAllowedOrigins == "" {
|
|
return "reflect-origin"
|
|
}
|
|
return c.CORSAllowedOrigins
|
|
}
|
|
|
|
// GetEventReceiverURL returns the event receiver URL.
|
|
// If EventReceiverURL is explicitly set, returns that.
|
|
// Otherwise, constructs URL from Host and Port if both are set.
|
|
// Replaces 0.0.0.0 with 127.0.0.1 for local connectivity.
|
|
// Returns empty string if neither option is available.
|
|
func (c *ServerConfig) GetEventReceiverURL() string {
|
|
if c.EventReceiverURL != "" {
|
|
return c.EventReceiverURL
|
|
}
|
|
if c.Host == "" || c.Port == 0 {
|
|
return ""
|
|
}
|
|
host := c.Host
|
|
if host == "0.0.0.0" {
|
|
host = "127.0.0.1"
|
|
}
|
|
return fmt.Sprintf("http://%s:%d", host, c.Port)
|
|
}
|
|
|
|
// GetServerURL returns the server base URL for API calls.
|
|
// Returns EventReceiverURL if set, otherwise computes from Host:Port.
|
|
// Replaces 0.0.0.0 with 127.0.0.1 for local connectivity.
|
|
func (c *ServerConfig) GetServerURL() string {
|
|
if c.EventReceiverURL != "" {
|
|
return strings.TrimSuffix(c.EventReceiverURL, "/")
|
|
}
|
|
if c.Host == "" || c.Port == 0 {
|
|
return ""
|
|
}
|
|
host := c.Host
|
|
if host == "0.0.0.0" {
|
|
host = "127.0.0.1"
|
|
}
|
|
return fmt.Sprintf("http://%s:%d", host, c.Port)
|
|
}
|
|
|
|
// ScanTacticConfig holds scan aggressiveness levels
|
|
type ScanTacticConfig struct {
|
|
Aggressive int `yaml:"aggressive"`
|
|
Default int `yaml:"default"`
|
|
Gently int `yaml:"gently"`
|
|
}
|
|
|
|
// JWTConfig holds JWT settings
|
|
type JWTConfig struct {
|
|
SecretSigningKey string `yaml:"secret_signing_key"`
|
|
ExpirationMinutes int `yaml:"expiration_minutes"`
|
|
}
|
|
|
|
// RedisConfig holds Redis connection settings for distributed mode
|
|
type RedisConfig struct {
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Username string `yaml:"username"`
|
|
Password string `yaml:"password"`
|
|
DB int `yaml:"db"`
|
|
ConnectionTimeout int `yaml:"connection_timeout"`
|
|
}
|
|
|
|
// TelegramConfig holds Telegram bot settings
|
|
type TelegramConfig struct {
|
|
BotToken string `yaml:"bot_token"`
|
|
ChatID int64 `yaml:"chat_id"`
|
|
Enabled bool `yaml:"enabled"`
|
|
TelegramChannelMap map[string]int64 `yaml:"telegram_channel_map,omitempty"`
|
|
}
|
|
|
|
// WebhookConfig holds configuration for a single webhook endpoint
|
|
type WebhookConfig struct {
|
|
URL string `yaml:"url"`
|
|
Enabled bool `yaml:"enabled"`
|
|
Headers map[string]string `yaml:"headers,omitempty"`
|
|
Timeout int `yaml:"timeout,omitempty"` // seconds, default 30
|
|
RetryCount int `yaml:"retry_count,omitempty"` // default 3
|
|
SkipTLSVerify bool `yaml:"skip_tls_verify,omitempty"` // default false
|
|
Events []string `yaml:"events,omitempty"` // scan_complete, scan_failed, step_failed, etc.
|
|
}
|
|
|
|
// StorageConfig holds cloud storage settings (S3-compatible)
|
|
type StorageConfig struct {
|
|
Provider string `yaml:"provider"` // s3, minio, gcs, r2, spaces, oci
|
|
Endpoint string `yaml:"endpoint"`
|
|
AccessKeyID string `yaml:"access_key_id"`
|
|
SecretAccessKey string `yaml:"secret_access_key"`
|
|
Bucket string `yaml:"bucket"`
|
|
Region string `yaml:"region"`
|
|
UseSSL bool `yaml:"use_ssl"`
|
|
Enabled bool `yaml:"enabled"`
|
|
|
|
// New fields for provider-specific configuration
|
|
AccountID string `yaml:"account_id,omitempty"` // For R2 (account ID) or OCI (namespace)
|
|
PathStyle bool `yaml:"path_style,omitempty"` // Force path-style URLs (needed for some providers)
|
|
PresignExpiry string `yaml:"presign_expiry,omitempty"` // Default presign expiry (e.g., "1h", "30m")
|
|
}
|
|
|
|
// ProviderEndpoint holds endpoint template and defaults for a storage provider
|
|
type ProviderEndpoint struct {
|
|
EndpointTemplate string
|
|
UseSSL bool
|
|
PathStyle bool
|
|
}
|
|
|
|
// ProviderEndpoints maps provider names to their endpoint configurations
|
|
var ProviderEndpoints = map[string]ProviderEndpoint{
|
|
"r2": {"%s.r2.cloudflarestorage.com", true, true}, // account_id
|
|
"gcs": {"storage.googleapis.com", true, false}, // HMAC keys
|
|
"spaces": {"%s.digitaloceanspaces.com", true, false}, // region
|
|
"oci": {"%s.compat.objectstorage.%s.oraclecloud.com", true, true}, // namespace, region
|
|
"s3": {"s3.%s.amazonaws.com", true, false}, // region
|
|
"minio": {"", false, true}, // user-provided
|
|
}
|
|
|
|
// ResolveEndpoint resolves the endpoint URL based on provider type
|
|
func (c *StorageConfig) ResolveEndpoint() string {
|
|
// If endpoint is explicitly set, use it
|
|
if c.Endpoint != "" {
|
|
return c.Endpoint
|
|
}
|
|
|
|
providerInfo, ok := ProviderEndpoints[c.Provider]
|
|
if !ok {
|
|
return c.Endpoint
|
|
}
|
|
|
|
switch c.Provider {
|
|
case "r2":
|
|
if c.AccountID != "" {
|
|
return fmt.Sprintf(providerInfo.EndpointTemplate, c.AccountID)
|
|
}
|
|
case "gcs":
|
|
return providerInfo.EndpointTemplate
|
|
case "spaces":
|
|
if c.Region != "" {
|
|
return fmt.Sprintf(providerInfo.EndpointTemplate, c.Region)
|
|
}
|
|
case "oci":
|
|
if c.AccountID != "" && c.Region != "" {
|
|
return fmt.Sprintf(providerInfo.EndpointTemplate, c.AccountID, c.Region)
|
|
}
|
|
case "s3":
|
|
if c.Region != "" {
|
|
return fmt.Sprintf(providerInfo.EndpointTemplate, c.Region)
|
|
}
|
|
}
|
|
|
|
return c.Endpoint
|
|
}
|
|
|
|
// GetPresignExpiry returns the presign expiry duration with default of 1 hour
|
|
func (c *StorageConfig) GetPresignExpiry() time.Duration {
|
|
if c.PresignExpiry == "" {
|
|
return time.Hour
|
|
}
|
|
|
|
d, err := time.ParseDuration(c.PresignExpiry)
|
|
if err != nil {
|
|
return time.Hour
|
|
}
|
|
return d
|
|
}
|
|
|
|
// ShouldUseSSL returns the SSL setting, considering provider defaults
|
|
func (c *StorageConfig) ShouldUseSSL() bool {
|
|
// If explicitly set in config, use that
|
|
if c.UseSSL {
|
|
return true
|
|
}
|
|
|
|
// Check provider defaults
|
|
if providerInfo, ok := ProviderEndpoints[c.Provider]; ok {
|
|
return providerInfo.UseSSL
|
|
}
|
|
|
|
return c.UseSSL
|
|
}
|
|
|
|
// ShouldUsePathStyle returns whether path-style URLs should be used
|
|
func (c *StorageConfig) ShouldUsePathStyle() bool {
|
|
// If explicitly set in config, use that
|
|
if c.PathStyle {
|
|
return true
|
|
}
|
|
|
|
// Check provider defaults
|
|
if providerInfo, ok := ProviderEndpoints[c.Provider]; ok {
|
|
return providerInfo.PathStyle
|
|
}
|
|
|
|
return c.PathStyle
|
|
}
|
|
|
|
// TemplateEngineConfig holds configuration for the template engine
|
|
type TemplateEngineConfig struct {
|
|
// UseShardedEngine enables the sharded template engine for better concurrency
|
|
// under high parallelism (foreach loops with multiple workers, parallel steps)
|
|
// Default: true
|
|
UseShardedEngine *bool `yaml:"use_sharded_engine,omitempty"`
|
|
|
|
// ShardCount is the number of cache shards (must be power of 2)
|
|
// Higher values reduce lock contention but increase memory usage
|
|
// Default: 16
|
|
ShardCount int `yaml:"shard_count,omitempty"`
|
|
|
|
// ShardCacheSize is the LRU cache size per shard
|
|
// Total cache capacity = ShardCount * ShardCacheSize
|
|
// Default: 64 (total 1024 templates)
|
|
ShardCacheSize int `yaml:"shard_cache_size,omitempty"`
|
|
|
|
// EnablePooling enables sync.Pool for context map reuse
|
|
// Reduces GC pressure during high-throughput rendering
|
|
// Default: true
|
|
EnablePooling *bool `yaml:"enable_pooling,omitempty"`
|
|
|
|
// EnableBatch enables batch template rendering optimization
|
|
// Groups templates by shard to minimize lock acquisitions
|
|
// Default: true
|
|
EnableBatch *bool `yaml:"enable_batch,omitempty"`
|
|
}
|
|
|
|
// IsShardedEngineEnabled returns whether sharded engine should be used
|
|
// Defaults to true if not explicitly set
|
|
func (c *TemplateEngineConfig) IsShardedEngineEnabled() bool {
|
|
if c.UseShardedEngine == nil {
|
|
return true
|
|
}
|
|
return *c.UseShardedEngine
|
|
}
|
|
|
|
// IsPoolingEnabled returns whether context pooling is enabled
|
|
// Defaults to true if not explicitly set
|
|
func (c *TemplateEngineConfig) IsPoolingEnabled() bool {
|
|
if c.EnablePooling == nil {
|
|
return true
|
|
}
|
|
return *c.EnablePooling
|
|
}
|
|
|
|
// IsBatchEnabled returns whether batch rendering is enabled
|
|
// Defaults to true if not explicitly set
|
|
func (c *TemplateEngineConfig) IsBatchEnabled() bool {
|
|
if c.EnableBatch == nil {
|
|
return true
|
|
}
|
|
return *c.EnableBatch
|
|
}
|
|
|
|
// GetShardCount returns the shard count with default
|
|
func (c *TemplateEngineConfig) GetShardCount() int {
|
|
if c.ShardCount <= 0 {
|
|
return 16
|
|
}
|
|
return c.ShardCount
|
|
}
|
|
|
|
// GetShardCacheSize returns the shard cache size with default
|
|
func (c *TemplateEngineConfig) GetShardCacheSize() int {
|
|
if c.ShardCacheSize <= 0 {
|
|
return 64
|
|
}
|
|
return c.ShardCacheSize
|
|
}
|
|
|
|
// LLMProvider holds configuration for a single LLM provider endpoint
|
|
type LLMProvider struct {
|
|
Provider string `yaml:"provider"` // ollama, openai, anthropic, custom, etc.
|
|
BaseURL string `yaml:"base_url"` // API endpoint URL
|
|
AuthToken string `yaml:"auth_token"` // Authentication token (can be blank for local Ollama)
|
|
Model string `yaml:"model"` // Model name/ID
|
|
}
|
|
|
|
// LLMConfig holds LLM (Large Language Model) settings
|
|
type LLMConfig struct {
|
|
LLMProviders []LLMProvider `yaml:"llm_providers"` // List of LLM providers for rotation
|
|
EnabledToolCall bool `yaml:"enabled_tool_call"` // Enable LLM tool call features
|
|
MaxTokens int `yaml:"max_tokens"` // Maximum number of tokens to generate
|
|
Temperature float64 `yaml:"temperature"` // Temperature for sampling
|
|
TopK int `yaml:"top_k"` // Top-k sampling
|
|
TopP float64 `yaml:"top_p"` // Top-p sampling
|
|
N int `yaml:"n"` // Number of completions to generate
|
|
MaxRetries int `yaml:"max_retries"` // Maximum number of retries for failed requests
|
|
Timeout string `yaml:"timeout"` // Timeout for API requests
|
|
Stream bool `yaml:"stream"` // Enable streaming responses
|
|
StructuredJSONFormat bool `yaml:"structured_json_format"` // Enable structured JSON output format
|
|
SystemPrompt string `yaml:"system_prompt"` // System prompt for the LLM
|
|
CustomHeaders string `yaml:"custom_headers"` // Custom headers for API requests
|
|
|
|
// Internal fields for provider rotation (not serialized)
|
|
currentIndex int // Current provider index
|
|
mu sync.Mutex // Mutex for thread-safe rotation
|
|
}
|
|
|
|
// CloudConfig holds cloud infrastructure configuration
|
|
type CloudConfig struct {
|
|
CloudPath string `yaml:"cloud_path"` // {{base_folder}}/cloud
|
|
CloudSettings string `yaml:"cloud_settings"` // {{base_folder}}/cloud/cloud-settings.yaml
|
|
Enabled bool `yaml:"enabled"` // Enable cloud infrastructure features
|
|
}
|
|
|
|
// Load loads configuration from the specified base folder
|
|
func Load(baseFolder string) (*Config, error) {
|
|
settingsPath := filepath.Join(baseFolder, "osm-settings.yaml")
|
|
cfg, err := LoadFromFile(settingsPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Override base folder if provided
|
|
if baseFolder != "" {
|
|
cfg.BaseFolder = baseFolder
|
|
}
|
|
|
|
// Resolve environment paths
|
|
cfg.ResolvePaths()
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
// LoadFromFile loads configuration from a specific file
|
|
func LoadFromFile(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ParseConfig(data)
|
|
}
|
|
|
|
// LoadFromBytes loads configuration from raw YAML bytes
|
|
func LoadFromBytes(data []byte) (*Config, error) {
|
|
return ParseConfig(data)
|
|
}
|
|
|
|
// ResolvePaths resolves template variables in environment paths.
|
|
// This method should be called after changing BaseFolder to recalculate all derived paths.
|
|
func (c *Config) ResolvePaths() {
|
|
baseFolder := c.resolveEnvVars(c.BaseFolder)
|
|
c.BaseFolder = baseFolder
|
|
|
|
// Resolve each path with base_folder substitution
|
|
c.BinariesPath = c.resolvePath(c.Environments.ExternalBinariesPath, baseFolder)
|
|
c.DataPath = c.resolvePath(c.Environments.ExternalData, baseFolder)
|
|
c.ConfigsPath = c.resolvePath(c.Environments.ExternalConfigs, baseFolder)
|
|
c.WorkspacesPath = c.resolvePath(c.Environments.Workspaces, baseFolder)
|
|
c.WorkflowsPath = c.resolvePath(c.Environments.Workflows, baseFolder)
|
|
|
|
// Resolve server paths
|
|
c.UIPath = c.resolvePath(c.Server.UIPath, baseFolder)
|
|
|
|
// Resolve snapshot path
|
|
c.SnapshotPath = c.resolvePath(c.Environments.Snapshot, baseFolder)
|
|
|
|
// Resolve markdown report templates path
|
|
c.MarkdownReportTemplatesPath = c.resolvePath(c.Environments.MarkdownReportTemplates, baseFolder)
|
|
|
|
// Resolve external agent configs path
|
|
c.ExternalAgentConfigsPath = c.resolvePath(c.Environments.ExternalAgentConfigs, baseFolder)
|
|
|
|
// Resolve external scripts path
|
|
c.ExternalScriptsPath = c.resolvePath(c.Environments.ExternalScripts, baseFolder)
|
|
}
|
|
|
|
// resolvePath resolves a single path with variable substitution
|
|
func (c *Config) resolvePath(path, baseFolder string) string {
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
|
|
// Replace {{base_folder}} template variable
|
|
resolved := strings.ReplaceAll(path, "{{base_folder}}", baseFolder)
|
|
|
|
// Resolve environment variables like $HOME
|
|
resolved = c.resolveEnvVars(resolved)
|
|
|
|
return resolved
|
|
}
|
|
|
|
// resolveEnvVars resolves environment variables in a string
|
|
func (c *Config) resolveEnvVars(s string) string {
|
|
return os.ExpandEnv(s)
|
|
}
|
|
|
|
// GetWorkflowsDir returns the workflows directory path
|
|
func (c *Config) GetWorkflowsDir() string {
|
|
return c.WorkflowsPath
|
|
}
|
|
|
|
// GetModulesDir returns the modules directory path
|
|
func (c *Config) GetModulesDir() string {
|
|
return filepath.Join(c.WorkflowsPath, "modules")
|
|
}
|
|
|
|
// GetWorkspacesDir returns the workspaces directory path
|
|
func (c *Config) GetWorkspacesDir() string {
|
|
return c.WorkspacesPath
|
|
}
|
|
|
|
// GetDBPath returns the resolved database file path for SQLite
|
|
func (c *Config) GetDBPath() string {
|
|
if c.Database.DBPath == "" {
|
|
return filepath.Join(c.BaseFolder, "database-osm.sqlite")
|
|
}
|
|
return c.resolvePath(c.Database.DBPath, c.BaseFolder)
|
|
}
|
|
|
|
// IsSQLite returns true if the database engine is SQLite
|
|
func (c *Config) IsSQLite() bool {
|
|
return c.Database.DBEngine == "" || c.Database.DBEngine == "sqlite"
|
|
}
|
|
|
|
// IsPostgres returns true if the database engine is PostgreSQL
|
|
func (c *Config) IsPostgres() bool {
|
|
return c.Database.DBEngine == "postgresql" || c.Database.DBEngine == "postgres"
|
|
}
|
|
|
|
// IsRedisConfigured returns true if Redis is configured
|
|
func (c *Config) IsRedisConfigured() bool {
|
|
return c.Redis.Host != "" && c.Redis.Port > 0
|
|
}
|
|
|
|
// IsDistributedMode returns true if Redis is configured (distributed mode enabled)
|
|
func (c *Config) IsDistributedMode() bool {
|
|
return c.IsRedisConfigured()
|
|
}
|
|
|
|
// GetRedisAddr returns the Redis address in host:port format
|
|
func (c *Config) GetRedisAddr() string {
|
|
return fmt.Sprintf("%s:%d", c.Redis.Host, c.Redis.Port)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Distributed Mode State
|
|
// =============================================================================
|
|
|
|
// WorkerModeState tracks whether we're running as a distributed worker
|
|
var workerModeState struct {
|
|
isWorker bool
|
|
workerID string
|
|
}
|
|
|
|
// SetWorkerMode sets the distributed worker mode flag
|
|
func SetWorkerMode(isWorker bool, workerID string) {
|
|
workerModeState.isWorker = isWorker
|
|
workerModeState.workerID = workerID
|
|
}
|
|
|
|
// IsWorkerMode returns true if running as a distributed worker
|
|
func IsWorkerMode() bool {
|
|
return workerModeState.isWorker
|
|
}
|
|
|
|
// GetWorkerID returns the worker ID if in worker mode
|
|
func GetWorkerID() string {
|
|
return workerModeState.workerID
|
|
}
|
|
|
|
// ShouldUseRedisDataQueues returns true if database writes should be routed to Redis queues.
|
|
// This is true when: Redis is configured AND we're running in worker mode.
|
|
func ShouldUseRedisDataQueues() bool {
|
|
cfg := Get()
|
|
if cfg == nil {
|
|
return false
|
|
}
|
|
return cfg.IsDistributedMode() && IsWorkerMode()
|
|
}
|
|
|
|
// GetDSN returns the PostgreSQL connection string
|
|
func (c *Config) GetDSN() string {
|
|
sslMode := c.Database.SSLMode
|
|
if sslMode == "" {
|
|
sslMode = "disable"
|
|
}
|
|
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
|
c.Database.Username, c.Database.Password,
|
|
c.Database.Host, c.Database.Port,
|
|
c.Database.DBName, sslMode)
|
|
}
|
|
|
|
// IsNotificationConfigured returns true if any notification provider is configured and enabled
|
|
func (c *Config) IsNotificationConfigured() bool {
|
|
if !c.Notification.Enabled {
|
|
return false
|
|
}
|
|
switch c.Notification.Provider {
|
|
case "telegram":
|
|
return c.Notification.Telegram.BotToken != "" && c.Notification.Telegram.ChatID != 0
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// IsTelegramConfigured returns true if Telegram is configured and enabled
|
|
// Deprecated: Use IsNotificationConfigured instead
|
|
func (c *Config) IsTelegramConfigured() bool {
|
|
return c.Notification.Provider == "telegram" &&
|
|
c.Notification.Enabled &&
|
|
c.Notification.Telegram.BotToken != "" &&
|
|
c.Notification.Telegram.ChatID != 0
|
|
}
|
|
|
|
// GetGlobalVar returns a global variable value by name
|
|
func (c *Config) GetGlobalVar(name string) (string, bool) {
|
|
if c.GlobalVars == nil {
|
|
return "", false
|
|
}
|
|
if v, ok := c.GlobalVars[name]; ok {
|
|
return v.Value, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// ExportGlobalVarsToEnv exports variables with as_env=true to environment
|
|
// Variable names are converted to UPPERCASE_WITH_UNDERSCORES
|
|
func (c *Config) ExportGlobalVarsToEnv() {
|
|
if c.GlobalVars == nil {
|
|
return
|
|
}
|
|
for name, v := range c.GlobalVars {
|
|
if v.IsAsEnv() && v.Value != "" {
|
|
envName := strings.ToUpper(strings.ReplaceAll(name, "-", "_"))
|
|
_ = os.Setenv(envName, v.Value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetAllGlobalVars returns all global vars as map[string]string for templates
|
|
func (c *Config) GetAllGlobalVars() map[string]string {
|
|
result := make(map[string]string)
|
|
if c.GlobalVars == nil {
|
|
return result
|
|
}
|
|
for name, v := range c.GlobalVars {
|
|
result[name] = v.Value
|
|
}
|
|
return result
|
|
}
|
|
|
|
// IsStorageConfigured returns true if cloud storage is configured and enabled
|
|
func (c *Config) IsStorageConfigured() bool {
|
|
return c.Storage.Enabled && c.Storage.Endpoint != "" && c.Storage.Bucket != ""
|
|
}
|
|
|
|
// IsLLMConfigured returns true if LLM is configured and enabled
|
|
func (c *Config) IsLLMConfigured() bool {
|
|
return c.LLM.EnabledToolCall && len(c.LLM.LLMProviders) > 0
|
|
}
|
|
|
|
// GetCurrentProvider returns the current active LLM provider (thread-safe)
|
|
// Returns nil if no providers are configured
|
|
func (l *LLMConfig) GetCurrentProvider() *LLMProvider {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
if len(l.LLMProviders) == 0 {
|
|
return nil
|
|
}
|
|
return &l.LLMProviders[l.currentIndex]
|
|
}
|
|
|
|
// RotateProvider advances to the next LLM provider (thread-safe, wraps around)
|
|
// Returns the new current provider, or nil if no providers are configured
|
|
func (l *LLMConfig) RotateProvider() *LLMProvider {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
if len(l.LLMProviders) == 0 {
|
|
return nil
|
|
}
|
|
l.currentIndex = (l.currentIndex + 1) % len(l.LLMProviders)
|
|
return &l.LLMProviders[l.currentIndex]
|
|
}
|
|
|
|
// ResetProviderIndex resets the current provider to the first one (thread-safe)
|
|
func (l *LLMConfig) ResetProviderIndex() {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.currentIndex = 0
|
|
}
|
|
|
|
// GetProviderCount returns the number of configured LLM providers
|
|
func (l *LLMConfig) GetProviderCount() int {
|
|
return len(l.LLMProviders)
|
|
}
|
|
|
|
// GetCurrentProviderIndex returns the current provider index (thread-safe)
|
|
func (l *LLMConfig) GetCurrentProviderIndex() int {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return l.currentIndex
|
|
}
|
|
|
|
// ResolveServerCredentials adds credentials from environment variables
|
|
// OSM_USERNAME and OSM_PASSWORD are added to the user map if both are set
|
|
func (c *Config) ResolveServerCredentials() {
|
|
envUser := os.Getenv("OSM_USERNAME")
|
|
envPass := os.Getenv("OSM_PASSWORD")
|
|
if envUser != "" && envPass != "" {
|
|
if c.Server.SimpleUserMapKey == nil {
|
|
c.Server.SimpleUserMapKey = make(map[string]string)
|
|
}
|
|
c.Server.SimpleUserMapKey[envUser] = envPass
|
|
}
|
|
}
|
|
|
|
// GetThreads returns thread counts for the given scan tactic
|
|
// Returns (threads, baseThreads) where baseThreads is half of threads
|
|
func (c *Config) GetThreads(tactic string) (int, int) {
|
|
var threads int
|
|
switch tactic {
|
|
case "aggressive", "fast":
|
|
threads = c.ScanTactic.Aggressive
|
|
case "gently", "thorough":
|
|
threads = c.ScanTactic.Gently
|
|
default: // normal, default
|
|
threads = c.ScanTactic.Default
|
|
}
|
|
if threads <= 0 {
|
|
threads = 10 // fallback default
|
|
}
|
|
baseThreads := threads / 2
|
|
if baseThreads < 1 {
|
|
baseThreads = 1
|
|
}
|
|
return threads, baseThreads
|
|
}
|
|
|
|
// Set sets the global configuration
|
|
func Set(cfg *Config) {
|
|
configMu.Lock()
|
|
defer configMu.Unlock()
|
|
globalConfig = cfg
|
|
}
|
|
|
|
// Get returns the global configuration
|
|
func Get() *Config {
|
|
configMu.RLock()
|
|
defer configMu.RUnlock()
|
|
return globalConfig
|
|
}
|
|
|
|
// DefaultConfig returns a default configuration
|
|
func DefaultConfig() *Config {
|
|
homeDir, _ := os.UserHomeDir()
|
|
baseFolder := filepath.Join(homeDir, "osmedeus-base")
|
|
|
|
return &Config{
|
|
BaseFolder: baseFolder,
|
|
Environments: EnvironmentConfig{
|
|
ExternalBinariesPath: "{{base_folder}}/external-binaries",
|
|
ExternalData: "{{base_folder}}/external-data",
|
|
ExternalConfigs: "{{base_folder}}/external-configs",
|
|
Workspaces: "{{base_folder}}/workspaces",
|
|
Workflows: "{{base_folder}}/workflows",
|
|
Snapshot: "{{base_folder}}/snapshot",
|
|
ExternalScripts: "{{base_folder}}/external-scripts",
|
|
},
|
|
Database: DatabaseConfig{
|
|
DBEngine: "sqlite",
|
|
DBPath: "{{base_folder}}/database-osm.sqlite",
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
Username: "osmedeus",
|
|
Password: "osmedeus",
|
|
DBName: "osmedeus",
|
|
ConnectionTimeout: 60,
|
|
SSLMode: "disable",
|
|
},
|
|
Server: ServerConfig{
|
|
Host: "0.0.0.0",
|
|
Port: 8002,
|
|
UIPath: "{{base_folder}}/ui/",
|
|
WorkspacePrefixKey: generateRandomString(16),
|
|
SimpleUserMapKey: map[string]string{
|
|
"osmedeus": generateRandomString(12),
|
|
},
|
|
JWT: JWTConfig{
|
|
SecretSigningKey: generateRandomString(64),
|
|
ExpirationMinutes: 1440, // 1 day
|
|
},
|
|
License: "open-source",
|
|
EnabledAuthAPI: true,
|
|
AuthAPIKey: generateRandomString(32),
|
|
},
|
|
ScanTactic: ScanTacticConfig{
|
|
Aggressive: 40,
|
|
Default: 10,
|
|
Gently: 5,
|
|
},
|
|
Redis: RedisConfig{
|
|
Host: "",
|
|
Port: 6379,
|
|
Username: "",
|
|
Password: "",
|
|
DB: 0,
|
|
ConnectionTimeout: 60,
|
|
},
|
|
GlobalVars: GlobalVarsConfig{
|
|
"GITHUB_API_KEY": {Value: ""},
|
|
"SHODAN_API_KEY": {Value: ""},
|
|
"CENSYS_API_KEY": {Value: ""},
|
|
"PASSIVETOTAL_API_KEY": {Value: ""},
|
|
// Add more default placeholders as needed
|
|
},
|
|
Notification: NotificationConfig{
|
|
Provider: "telegram",
|
|
Enabled: false,
|
|
Telegram: TelegramConfig{
|
|
BotToken: "",
|
|
ChatID: 0,
|
|
Enabled: false,
|
|
},
|
|
},
|
|
Storage: StorageConfig{
|
|
Provider: "s3",
|
|
Endpoint: "",
|
|
AccessKeyID: "",
|
|
SecretAccessKey: "",
|
|
Bucket: "",
|
|
Region: "us-east-1",
|
|
UseSSL: true,
|
|
Enabled: false,
|
|
},
|
|
LLM: LLMConfig{
|
|
LLMProviders: []LLMProvider{
|
|
{
|
|
Provider: "ollama",
|
|
BaseURL: "http://localhost:11434/v1/chat/completions",
|
|
AuthToken: "",
|
|
Model: "gpt-oss:120b-cloud",
|
|
},
|
|
},
|
|
EnabledToolCall: false,
|
|
MaxTokens: 1000,
|
|
Temperature: 0.7,
|
|
TopK: 50,
|
|
TopP: 0.9,
|
|
N: 1,
|
|
MaxRetries: 3,
|
|
Timeout: "120s",
|
|
Stream: false,
|
|
StructuredJSONFormat: false,
|
|
SystemPrompt: "",
|
|
CustomHeaders: "",
|
|
},
|
|
}
|
|
}
|
|
|
|
// EnsureConfigExists creates osm-settings.yaml if it doesn't exist
|
|
// Uses the embedded example configuration file as template
|
|
func EnsureConfigExists(baseFolder string) error {
|
|
settingsPath := filepath.Join(baseFolder, "osm-settings.yaml")
|
|
|
|
// Check if file already exists
|
|
if _, err := os.Stat(settingsPath); err == nil {
|
|
return nil // File exists, nothing to do
|
|
}
|
|
|
|
// Create base folder if needed
|
|
if err := os.MkdirAll(baseFolder, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate random values and replace blank placeholders in template
|
|
configContent := string(exampleConfigYAML)
|
|
configContent = strings.Replace(configContent,
|
|
"workspace_prefix_key: \"\"",
|
|
fmt.Sprintf("workspace_prefix_key: \"%s\"", generateRandomString(16)),
|
|
1)
|
|
|
|
// Generate random auth_api_key (32 chars)
|
|
configContent = strings.Replace(configContent,
|
|
"auth_api_key: \"\"",
|
|
fmt.Sprintf("auth_api_key: \"%s\"", generateRandomString(32)),
|
|
1)
|
|
|
|
// Generate random secret_signing_key (64 chars)
|
|
configContent = strings.Replace(configContent,
|
|
"secret_signing_key: \"\"",
|
|
fmt.Sprintf("secret_signing_key: \"%s\"", generateRandomString(64)),
|
|
1)
|
|
|
|
// Generate random password for default osmedeus user (12 chars)
|
|
configContent = strings.Replace(configContent,
|
|
"osmedeus: \"\"",
|
|
fmt.Sprintf("osmedeus: \"%s\"", generateRandomString(12)),
|
|
1)
|
|
|
|
// Write the config file with generated values
|
|
return os.WriteFile(settingsPath, []byte(configContent), 0644)
|
|
}
|