Initial documentation structure
This commit is contained in:
317
scripts/orchestrator/srx/collect_srx_config.py
Executable file
317
scripts/orchestrator/srx/collect_srx_config.py
Executable 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()
|
||||
28
scripts/orchestrator/srx/deploy_approved.py
Executable file
28
scripts/orchestrator/srx/deploy_approved.py
Executable 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()
|
||||
309
scripts/orchestrator/srx/srx_manager.py
Executable file
309
scripts/orchestrator/srx/srx_manager.py
Executable 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")
|
||||
Reference in New Issue
Block a user