Initial documentation structure

This commit is contained in:
2025-09-04 02:19:22 +00:00
commit 66d97011ab
18 changed files with 3114 additions and 0 deletions

View File

@@ -0,0 +1,317 @@
#!/usr/bin/env python3
"""
SRX Configuration Collector
Pulls current configuration from SRX and stores it for AI analysis
"""
import os
import sys
import json
import yaml
import paramiko
from datetime import datetime
from pathlib import Path
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class SRXConfigCollector:
def __init__(self, config_path='/home/netops/orchestrator/config.yaml'):
"""Initialize with orchestrator config"""
with open(config_path, 'r') as f:
self.config = yaml.safe_load(f)
self.srx_config = self.config['srx']
self.config_dir = Path('/shared/ai-gitops/configs')
self.config_dir.mkdir(parents=True, exist_ok=True)
def connect_to_srx(self):
"""Establish SSH connection to SRX"""
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Connect using SSH key
client.connect(
hostname=self.srx_config['host'],
username=self.srx_config['username'],
key_filename=self.srx_config['ssh_key'],
port=22
)
logger.info(f"Connected to SRX at {self.srx_config['host']}")
return client
except Exception as e:
logger.error(f"Failed to connect: {e}")
return None
def get_full_config(self, client):
"""Get complete SRX configuration"""
logger.info("Fetching full SRX configuration...")
stdin, stdout, stderr = client.exec_command('show configuration | no-more')
config_output = stdout.read().decode('utf-8')
if config_output:
logger.info(f"Retrieved {len(config_output)} bytes of configuration")
return config_output
else:
logger.error("Failed to retrieve configuration")
return None
def get_security_config(self, client):
"""Get security-specific configuration"""
logger.info("Fetching security policies...")
commands = [
'show configuration security policies',
'show configuration security zones',
'show configuration security address-book',
'show configuration applications',
'show configuration security nat',
'show configuration interfaces'
]
security_config = {}
for cmd in commands:
stdin, stdout, stderr = client.exec_command(f'{cmd} | no-more')
output = stdout.read().decode('utf-8')
section = cmd.split()[-1] # Get last word as section name
security_config[section] = output
logger.info(f"Retrieved {section} configuration")
return security_config
def analyze_config(self, full_config, security_config):
"""Analyze configuration and extract key information - FIXED VERSION"""
analysis = {
'timestamp': datetime.now().isoformat(),
'zones': [],
'networks': {},
'policies': [],
'policy_count': 0,
'applications': [],
'interfaces': {},
'nat_rules': [],
'address_book': {}
}
# Extract zones - FIXED parsing for your format
if 'zones' in security_config:
zones_content = security_config['zones']
if zones_content:
lines = zones_content.split('\n')
for line in lines:
# Your format: "security-zone WAN {" or "security-zone HOME {"
if 'security-zone' in line and '{' in line:
# Extract zone name between 'security-zone' and '{'
parts = line.strip().split()
if len(parts) >= 2 and parts[0] == 'security-zone':
zone_name = parts[1]
if zone_name != '{': # Make sure it's not just the bracket
analysis['zones'].append(zone_name)
analysis['networks'][zone_name] = []
# Extract address-book entries from zones section
if 'zones' in security_config:
lines = security_config['zones'].split('\n')
current_zone = None
in_address_book = False
for line in lines:
line = line.strip()
# Track current zone
if 'security-zone' in line and '{' in line:
parts = line.split()
if len(parts) >= 2:
current_zone = parts[1]
in_address_book = False
# Check if we're in address-book section
elif 'address-book' in line and '{' in line:
in_address_book = True
# Parse addresses within address-book
elif in_address_book and 'address ' in line and current_zone:
# Format: "address GAMING-NETWORK 192.168.10.0/24;"
parts = line.split()
if len(parts) >= 3 and parts[0] == 'address':
addr_name = parts[1]
addr_value = parts[2].rstrip(';')
if '/' in addr_value or '.' in addr_value:
analysis['address_book'][addr_name] = addr_value
if current_zone in analysis['networks']:
analysis['networks'][current_zone].append(addr_value)
# Extract policies - FIXED for your format
if 'policies' in security_config:
policies_content = security_config['policies']
if policies_content:
lines = policies_content.split('\n')
from_zone = None
to_zone = None
current_policy = None
for line in lines:
line = line.strip()
# Format: "from-zone HOME to-zone WAN {"
if 'from-zone' in line and 'to-zone' in line:
parts = line.split()
if len(parts) >= 4:
from_idx = parts.index('from-zone') if 'from-zone' in parts else -1
to_idx = parts.index('to-zone') if 'to-zone' in parts else -1
if from_idx >= 0 and to_idx >= 0:
from_zone = parts[from_idx + 1] if from_idx + 1 < len(parts) else None
to_zone = parts[to_idx + 1] if to_idx + 1 < len(parts) else None
to_zone = to_zone.rstrip('{') if to_zone else None
# Format: "policy GAMING-VLAN-PRIORITY {"
elif 'policy ' in line and '{' in line and from_zone and to_zone:
parts = line.split()
if len(parts) >= 2 and parts[0] == 'policy':
policy_name = parts[1].rstrip('{')
analysis['policies'].append({
'name': policy_name,
'from_zone': from_zone,
'to_zone': to_zone
})
analysis['policy_count'] += 1
# Extract applications
if 'applications' in security_config:
apps_content = security_config['applications']
if apps_content:
lines = apps_content.split('\n')
for line in lines:
# Format: "application PS5-HTTP {"
if 'application ' in line and '{' in line:
parts = line.strip().split()
if len(parts) >= 2 and parts[0] == 'application':
app_name = parts[1].rstrip('{')
if app_name and app_name != 'application':
analysis['applications'].append(app_name)
# Extract interfaces with IPs
if 'interfaces' in security_config:
interfaces_content = security_config['interfaces']
if interfaces_content:
lines = interfaces_content.split('\n')
current_interface = None
for line in lines:
line = line.strip()
# Interface line (e.g., "ge-0/0/0 {" or "reth0 {")
if (line.startswith('ge-') or line.startswith('reth')) and '{' in line:
current_interface = line.split()[0]
analysis['interfaces'][current_interface] = {'addresses': []}
# IP address line (e.g., "address 192.168.1.1/24;")
elif current_interface and 'address ' in line and '/' in line:
parts = line.split()
for part in parts:
if '/' in part:
addr = part.rstrip(';')
analysis['interfaces'][current_interface]['addresses'].append(addr)
# Extract NAT rules
if 'nat' in security_config:
nat_content = security_config['nat']
if nat_content:
source_nat_count = nat_content.count('source pool')
dest_nat_count = nat_content.count('destination pool')
analysis['nat_rules'] = {
'source_nat': source_nat_count,
'destination_nat': dest_nat_count,
'total': source_nat_count + dest_nat_count
}
return analysis
def save_config(self, full_config, security_config, analysis):
"""Save configuration and analysis"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Save full config
full_config_path = self.config_dir / f'srx_config_{timestamp}.txt'
with open(full_config_path, 'w') as f:
f.write(full_config)
logger.info(f"Saved full config to {full_config_path}")
# Save latest symlink
latest_path = self.config_dir / 'srx_config_latest.txt'
if latest_path.exists():
latest_path.unlink()
latest_path.symlink_to(full_config_path.name)
# Save security config sections
security_config_path = self.config_dir / f'srx_security_config_{timestamp}.json'
with open(security_config_path, 'w') as f:
json.dump(security_config, f, indent=2)
# Save analysis
analysis_path = self.config_dir / f'srx_config_analysis_{timestamp}.json'
with open(analysis_path, 'w') as f:
json.dump(analysis, f, indent=2)
logger.info(f"Saved config analysis to {analysis_path}")
# Save latest analysis symlink
latest_analysis = self.config_dir / 'srx_config_analysis_latest.json'
if latest_analysis.exists():
latest_analysis.unlink()
latest_analysis.symlink_to(analysis_path.name)
return analysis
def collect(self):
"""Main collection process"""
logger.info("Starting SRX configuration collection...")
# Connect to SRX
client = self.connect_to_srx()
if not client:
return None
try:
# Get configurations
full_config = self.get_full_config(client)
security_config = self.get_security_config(client)
if full_config:
# Analyze configuration
analysis = self.analyze_config(full_config, security_config)
# Save everything
self.save_config(full_config, security_config, analysis)
# Print summary
print("\n📊 Configuration Summary:")
print(f"Zones: {', '.join(analysis['zones'])}")
print(f"Networks: {len([n for nets in analysis['networks'].values() for n in nets])} subnets across {len(analysis['zones'])} zones")
print(f"Policies: {analysis.get('policy_count', 0)} security policies")
print(f"Address Book: {len(analysis['address_book'])} entries")
print(f"Interfaces: {len(analysis['interfaces'])} configured")
return analysis
finally:
client.close()
logger.info("Disconnected from SRX")
def main():
collector = SRXConfigCollector()
analysis = collector.collect()
if analysis:
print("\n✅ Configuration collected successfully!")
print(f"Files saved in: /shared/ai-gitops/configs/")
else:
print("\n❌ Failed to collect configuration")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""
Simple deployment script placeholder
Full version will deploy approved configs
"""
import logging
from datetime import datetime
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/orchestrator/deployment.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def main():
logger.info("Deployment check started")
logger.info("Looking for approved configurations...")
# TODO: Implement actual deployment logic
logger.info("No approved configurations found")
logger.info("Deployment check complete")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,309 @@
#!/usr/bin/env python3
"""
SRX Configuration Manager
Handles all interactions with the Juniper SRX device
"""
import subprocess
import logging
import json
from datetime import datetime
from typing import Dict, Optional, List
import re
logger = logging.getLogger(__name__)
class SRXManager:
"""Manages SRX configuration retrieval and deployment"""
def __init__(self, host: str, user: str, ssh_key: str):
"""
Initialize SRX Manager
Args:
host: SRX IP address
user: SSH username
ssh_key: Path to SSH private key
"""
self.host = host
self.user = user
self.ssh_key = ssh_key
def _execute_ssh_command(self, command: str) -> tuple[bool, str]:
"""
Execute command on SRX via SSH
Returns:
(success, output) tuple
"""
ssh_cmd = [
'ssh',
'-i', self.ssh_key,
'-o', 'StrictHostKeyChecking=no',
'-o', 'ConnectTimeout=10',
f'{self.user}@{self.host}',
command
]
try:
result = subprocess.run(
ssh_cmd,
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
logger.info(f"Successfully executed: {command[:50]}...")
return True, result.stdout
else:
logger.error(f"Command failed: {result.stderr}")
return False, result.stderr
except subprocess.TimeoutExpired:
logger.error("SSH command timed out")
return False, "Command timed out"
except Exception as e:
logger.error(f"SSH execution error: {e}")
return False, str(e)
def get_current_config(self, format: str = "set") -> Optional[str]:
"""
Retrieve current SRX configuration
Args:
format: Configuration format ('set', 'json', 'xml')
Returns:
Configuration string or None if failed
"""
format_map = {
"set": "display set",
"json": "display json",
"xml": "display xml"
}
display_format = format_map.get(format, "display set")
command = f"show configuration | {display_format} | no-more"
logger.info(f"Pulling SRX configuration in {format} format")
success, output = self._execute_ssh_command(command)
if success:
logger.info(f"Retrieved {len(output)} characters of configuration")
return output
else:
logger.error("Failed to retrieve configuration")
return None
def get_config_section(self, section: str) -> Optional[str]:
"""
Get specific configuration section
Args:
section: Config section (e.g., 'security policies', 'interfaces')
Returns:
Configuration section or None
"""
command = f"show configuration {section} | display set | no-more"
success, output = self._execute_ssh_command(command)
if success:
return output
return None
def parse_security_policies(self, config: str) -> Dict:
"""
Parse security policies from configuration
Returns:
Dictionary of policies organized by zones
"""
policies = {
"zone_pairs": {},
"total_policies": 0,
"applications": set(),
"addresses": set()
}
# Regex patterns for parsing
policy_pattern = r'set security policies from-zone (\S+) to-zone (\S+) policy (\S+)'
app_pattern = r'set security policies .* application (\S+)'
addr_pattern = r'set security policies .* (source|destination)-address (\S+)'
for line in config.split('\n'):
# Parse policy definitions
policy_match = re.match(policy_pattern, line)
if policy_match:
from_zone, to_zone, policy_name = policy_match.groups()
zone_pair = f"{from_zone}->{to_zone}"
if zone_pair not in policies["zone_pairs"]:
policies["zone_pairs"][zone_pair] = []
if policy_name not in policies["zone_pairs"][zone_pair]:
policies["zone_pairs"][zone_pair].append(policy_name)
policies["total_policies"] += 1
# Parse applications
app_match = re.search(app_pattern, line)
if app_match:
policies["applications"].add(app_match.group(1))
# Parse addresses
addr_match = re.search(addr_pattern, line)
if addr_match:
policies["addresses"].add(addr_match.group(2))
# Convert sets to lists for JSON serialization
policies["applications"] = list(policies["applications"])
policies["addresses"] = list(policies["addresses"])
return policies
def validate_config_syntax(self, config_lines: List[str]) -> tuple[bool, List[str]]:
"""
Validate SRX configuration syntax
Args:
config_lines: List of configuration commands
Returns:
(valid, errors) tuple
"""
errors = []
valid_commands = [
'set security policies',
'set security zones',
'set security address-book',
'set applications application',
'set firewall policer',
'set firewall filter',
'set class-of-service',
'set interfaces',
'set routing-options'
]
for i, line in enumerate(config_lines, 1):
line = line.strip()
# Skip comments and empty lines
if not line or line.startswith('#'):
continue
# Check if line starts with valid command
if not any(line.startswith(cmd) for cmd in valid_commands):
errors.append(f"Line {i}: Invalid command prefix: {line[:50]}")
# Check for required keywords in policies
if 'security policies' in line and 'policy' in line:
if not any(keyword in line for keyword in ['match', 'then', 'from-zone', 'to-zone']):
errors.append(f"Line {i}: Policy missing required keywords: {line[:50]}")
return len(errors) == 0, errors
def test_connectivity(self) -> bool:
"""
Test SSH connectivity to SRX
Returns:
True if connected successfully
"""
logger.info(f"Testing connectivity to {self.host}")
success, output = self._execute_ssh_command("show version | match Junos:")
if success and "Junos:" in output:
version = output.strip()
logger.info(f"Connected successfully: {version}")
return True
else:
logger.error("Connectivity test failed")
return False
def get_traffic_statistics(self) -> Optional[Dict]:
"""
Get interface traffic statistics
Returns:
Dictionary of traffic stats or None
"""
command = "show interfaces statistics | display json"
success, output = self._execute_ssh_command(command)
if success:
try:
# Parse JSON output
stats = json.loads(output)
return stats
except json.JSONDecodeError:
logger.error("Failed to parse traffic statistics JSON")
return None
return None
def create_config_diff(self, current_config: str, proposed_config: List[str]) -> Dict:
"""
Create a diff between current and proposed configurations
Args:
current_config: Current SRX configuration
proposed_config: List of proposed configuration lines
Returns:
Dictionary with additions and analysis
"""
current_lines = set(current_config.split('\n'))
proposed_set = set(proposed_config)
# Find truly new configurations
new_configs = []
duplicate_configs = []
for config in proposed_set:
if config.strip() and not config.startswith('#'):
if config not in current_lines:
new_configs.append(config)
else:
duplicate_configs.append(config)
return {
"new_configurations": new_configs,
"duplicate_configurations": duplicate_configs,
"total_proposed": len(proposed_config),
"total_new": len(new_configs),
"total_duplicates": len(duplicate_configs)
}
# Test function for standalone execution
if __name__ == "__main__":
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Test the SRX Manager
srx = SRXManager(
host="192.168.100.1",
user="netops",
ssh_key="/home/netops/.ssh/srx_key"
)
# Test connectivity
if srx.test_connectivity():
print("✅ Connectivity test passed")
# Get current config
config = srx.get_current_config()
if config:
print(f"✅ Retrieved {len(config)} characters of configuration")
# Parse policies
policies = srx.parse_security_policies(config)
print(f"📊 Found {policies['total_policies']} security policies")
print(f"📊 Zone pairs: {list(policies['zone_pairs'].keys())}")
else:
print("❌ Failed to retrieve configuration")
else:
print("❌ Connectivity test failed")