Best PracticesBlogCredential ManagementPRIME FrameworkSecurity
Credential Management in Network Automation
Credential Management in Network Automation: Best Practices for Safety and Scale¶
This post is part of our ongoing series on network automation best practices, grounded in the PRIME Framework and PRIME Philosophy.
Transparency Note
Examples, scenarios, and any outcome figures in this article are provided for education and are based on enterprise delivery experience or anonymised composite scenarios unless explicitly identified as direct Nautomation Prime client outcomes.
Credentials are the keys to your network—and the #1 target for attackers. This post explains why credential management is critical, how to do it safely, and how the PRIME Framework guides best practices for production automation.
Security breach risk: Exposed credentials lead to unauthorized access, data theft, and network compromise
Compliance violations: PCI-DSS, SOX, HIPAA, and other standards require secure credential handling
Audit trail: Every credential access must be logged for compliance and incident investigation
Hardcoded credentials are a common attack vector: They appear in Git history, backups, logs everywhere
Shared credentials prevent accountability: You can't track who made changes or when
Real Cost of a Breach:
- Average breach cost: $4+ million (IBM 2021 study)
- Security incident response: Months of effort
- Reputation damage: Lost customer trust
- Compliance fines: Potentially millions
importosfromdotenvimportload_dotenv# Load from .env file (NEVER COMMIT THIS!)load_dotenv()username=os.environ['DEVICE_USERNAME']password=os.environ['DEVICE_PASSWORD']# For production, set environment variables via CI/CD or deployment platform
Security checklist:
- [ ] .env file is in .gitignore (never commit!)
- [ ] Env vars are set by deployment system (Kubernetes, CI/CD, cloud provider)
- [ ] Different credentials for dev/staging/prod
- [ ] Credentials are rotated regularly
importhvacimportosclassVaultCredentialProvider:"""Fetch credentials from HashiCorp Vault."""def__init__(self,vault_addr:str,vault_token:str):""" Args: vault_addr: Vault server address (e.g., 'https://vault.example.com') vault_token: Token for Vault authentication (from env var) """self.client=hvac.Client(url=vault_addr,token=vault_token)defget_credentials(self,device_id:str)->dict:""" Fetch credentials from Vault. Vault path structure: secret/network/devices/{device_id} -> {username, password, enable_password} """try:secret=self.client.secrets.kv.v2.read_secret_version(path=f'network/devices/{device_id}')creds=secret['data']['data']return{'username':creds['username'],'password':creds['password'],'enable_password':creds.get('enable_password',''),}excepthvac.exceptions.InvalidPathase:raiseCredentialNotFound(f"Device credentials not found: {device_id}")fromeexcepthvac.exceptions.Forbiddenase:raiseCredentialAccessDenied(f"Access denied to device credentials: {device_id}")fromedeflist_credentials(self,path:str)->list:"""List available credentials (no secrets returned)."""returnself.client.secrets.kv.v2.list_secrets(path)defrotate_credential(self,device_id:str,new_password:str)->bool:"""Rotate a device password in Vault."""secret=self.client.secrets.kv.v2.read_secret_version(path=f'network/devices/{device_id}')secret['data']['data']['password']=new_passwordself.client.secrets.kv.v2.create_or_update_secret(path=f'network/devices/{device_id}',secret_data=secret['data']['data'])returnTrue# Usagevault_provider=VaultCredentialProvider(vault_addr=os.environ['VAULT_ADDR'],vault_token=os.environ['VAULT_TOKEN'])creds=vault_provider.get_credentials('router-01')print(creds)# Only username, password, enable_password (no URLs or extra secrets)
Vault Setup & Best Practices:
- Create separate Vault paths for dev/staging/prod
- Use AppRole or JWT authentication (not long-lived tokens)
- Enable audit logging (log every secret access)
- Auto-rotate credentials using Vault workflows
- Limit token TTL (time-to-live)
importboto3importjsonimportosclassAWSSecretProvider:"""Fetch credentials from AWS Secrets Manager."""def__init__(self,region='us-east-1'):self.client=boto3.client('secretsmanager',region_name=region)defget_credentials(self,secret_name:str)->dict:"""Fetch secret from AWS Secrets Manager."""try:response=self.client.get_secret_value(SecretId=secret_name)if'SecretString'inresponse:secret=json.loads(response['SecretString'])returnsecretelse:# Binary secret (rare for network credentials)returnresponse['SecretBinary']exceptself.client.exceptions.ResourceNotFoundException:raiseCredentialNotFound(f"Secret not found: {secret_name}")exceptExceptionase:raiseCredentialAccessDenied(f"Failed to retrieve secret: {e}")defrotate_secret(self,secret_name:str,new_password:str)->bool:"""Update a secret in AWS."""try:response=self.client.update_secret(SecretId=secret_name,SecretString=json.dumps({'password':new_password}))returnTrueexceptExceptionase:print(f"Rotation failed: {e}")returnFalse# Usageaws_provider=AWSSecretProvider()creds=aws_provider.get_credentials('network/router-01')
# Install SOPS and initialize with AWS KMS or GPGbrewinstallsops
# Create encrypted secrets filesops--kms'arn:aws:kms:us-east-1:123456789:key/abc123'secrets.yaml
# GOOD: Credentials pulled from Vault at runtimevault=VaultCredentialProvider(vault_addr=os.environ['VAULT_ADDR'],vault_token=os.environ['VAULT_TOKEN'])device_ips=['10.0.0.1','10.0.0.2']foripindevice_ips:creds=vault.get_credentials(ip)conn=netmiko.ConnectHandler(host=ip,device_type='cisco_ios',username=creds['username'],password=creds['password'],enable_password=creds.get('enable_password',''))print(conn.send_command('show version'))
Advanced Patterns: Rotation, Auditing, and Compliance¶
importasynciofromdatetimeimportdatetime,timedeltaclassCredentialRotationManager:"""Automatically rotate device credentials."""def__init__(self,vault_provider,rotation_interval_days=90):self.vault=vault_providerself.rotation_interval=timedelta(days=rotation_interval_days)asyncdefrotate_all_credentials(self):"""Rotate all device credentials on schedule."""devices=self.vault.list_credentials('network/devices')fordevice_idindevices:try:# Check last rotation datelast_rotated=awaitself.get_last_rotation_date(device_id)ifdatetime.now()-last_rotated>self.rotation_interval:print(f"Rotating credentials for {device_id}")awaitself.rotate_device_credential(device_id)exceptExceptionase:print(f"Rotation failed for {device_id}: {e}")asyncdefrotate_device_credential(self,device_id:str):"""Rotate a single device's credential."""# Generate new passwordnew_password=self.generate_secure_password()# Connect to device and set new passwordcreds=self.vault.get_credentials(device_id)device_ip=self.resolve_device_ip(device_id)awaitself.set_device_password(device_ip,creds,new_password)# Update Vaultself.vault.rotate_credential(device_id,new_password)# Log rotationawaitself.log_rotation_event(device_id)defgenerate_secure_password(self)->str:"""Generate a strong random password."""importsecretsimportstringalphabet=string.ascii_letters+string.digits+string.punctuationpassword=''.join(secrets.choice(alphabet)for_inrange(16))returnpasswordasyncdeflog_rotation_event(self,device_id:str):"""Log credential rotation for compliance."""event={'event':'credential_rotation','device':device_id,'timestamp':datetime.now().isoformat(),'user':os.environ.get('USER','automation'),}# Send to logging system, e.g., Splunk, ELKprint(f"[AUDIT] {event}")