Skip to content

[My Journey to CCIE Automation #9] Applying OWASP Secure Coding Practices

Bjørnar Lintvedt 30 desember, 2026
My journey continues

👋 Hi, I’m Bjørnar Lintvedt

I’m a Senior Network Consultant at Bluetree, working at the intersection of networking and software development.

As mentioned in my first blog post, I’m preparing for the CCIE Automation lab exam — Cisco’s most advanced certification for network automation and programmability. I’m documenting the journey here with weekly, hands-on blog posts tied to the blueprint.

During my studies I’ve been building a real application called Nautix — a modular, container-based automation platform that ties together everything I’ve learned so far.

Link to my GitLab Repo

 

Blog post #9

This week, I went back to one of my own scripts and did what every engineer eventually has to do: look at lab code with production eyes. The script was written to solve a problem quickly during a lab—authenticate to Catalyst Center, pull topology data, and visualize it—and it did exactly that. It worked. End of story. Or so I thought.

As it turns out, “works in the lab” and “designed with security in mind” are two very different things.

In this post, I take that very real, very imperfect script and walk through it using OWASP secure coding principles. The goal isn’t to shame my lab code (we all have it), but to show how common shortcuts—around input handling, credentials, TLS, error handling, and logging—can be identified and improved once you start thinking like a platform engineer instead of a lab survivor.

This is about turning quick-and-dirty code into intentionally designed code, one security improvement at a time.

 

OWASP and Secure Coding Principles

Before diving into the script itself, it’s worth briefly explaining why OWASP keeps showing up when we talk about secure coding.

OWASP (Open Web Application Security Project) is a community-driven organization focused on improving software security. 

CCIE Automation blue print lists:

  5.1 Leverage OWASP secure coding practices into all solutions to meet given requirements
    5.1.a Input validation
    5.1.b Authentication and password management
    5.1.c Access control
    5.1.d Cryptographic practices
    5.1.e Error handling and logging
    5.1.f Communication security

 

 

Hardening My Topology Script with OWASP Secure Coding Practices

The script fetches physical topology from Catalyst Center and outputs it either as a PyVis drawing or pretty-printed JSON. It’s a great real-world example because it touches many common secure-coding pitfalls: user input, credentials, TLS, error handling, and logging.

Script summary

  • Takes --ip, --username, --password

  • Connects to Catalyst Center via ApiCatcenter

  • Fetches topology JSON

  • Converts it to a PyVis-friendly format

  • Outputs either a HTML visualization (topology.html) or JSON to stdout

Now let’s apply OWASP secure coding practices to it.


1) Input Validation

Even though this is a CLI script, everything passed in is untrusted input.

Where input enters

  • --ip

  • --username

  • --password

  • --type

I already validate --type with click.Choice. The missing part is validating the Catalyst Center address.

❌ Current (no validation)

@click.option('--ip', '-ip', required=True, help='IP address of Catalyst Center')
...
api_catcenter = ApiCatcenter(ip, username, password)

If the user mistypes, you get runtime failures (or worse, in other contexts: SSRF-style issues when inputs become URLs).

 

✅ Better (syntactic validation)

Validate that --ip is a real IP or a hostname (depending on what you want to support). For strict IP-only:

import ipaddress

def validate_ip(ctx, param, value):
    try:
        ipaddress.ip_address(value)
        return value
    except ValueError:
        raise click.BadParameter("Must be a valid                                           IPv4/IPv6 address")

@click.option('--ip', '-i', required=True, callback=validate_ip, help='Catalyst Center IP')
 

✅ Add semantic validation (context rules)

If you know Catalyst Center must be inside certain ranges (e.g., lab subnet), enforce it:

allowed = ipaddress.ip_network("10.0.0.0/8")

if ipaddress.ip_address(value) not in allowed:
    raise click.BadParameter("IP must be within the lab                                 range")

That’s OWASP’s “syntax + semantics” in practice.

 


2) Authentication

My script authenticates to Catalyst Center using username/password. The OWASP improvement is less about “change auth method” and more about reducing exposure.

 

Biggest issue: password as CLI argument

Passing secrets via --password is risky because it can appear in:

  • shell history

  • process lists (ps)

  • CI logs

❌ Current

@click.option('--password', '-p', required=True, help='API password')
 

✅ Better: prompt + hidden input (and/or env var)

@click.option('--password', '-p',
prompt=True, hide_input=True, confirmation_prompt=False,
envvar="CATC_PASSWORD",
help='API password (or set CATC_PASSWORD)')

This keeps the script usable interactively and safer in automation.

 


3) Password Management

Authentication answers “who are you?” — password management answers “how do you handle secrets safely?”

Improvements to apply here

  • Don’t hardcode secrets (I don’t — which is good)

  • Avoid passing secrets in arguments (fixed above)

  • Use a read-only / least-privileged account for topology retrieval

❌ Wrong pattern (not in my code, but common)

password = "P@ssw0rd!"
 

✅ Right pattern (environment + prompting)

  • CATC_PASSWORD environment variable in CI

  • click.prompt(... hide_input=True) locally

  • Consider integrating a secret manager like Vault, which I do for other parts of the application

 


4) Access Control (Authorization)

This script doesn’t implement RBAC itself — but authorization still matters, because the identity you authenticate with determines what the script is allowed to access.

OWASP-style improvement: least privilege

For API reads, the best practice is:

  • Use an account with read-only permissions

  • Avoid accounts that can trigger provisioning / changes

 


5) Cryptographic Practices (TLS / SSL)

This is the most obvious OWASP point in my script.

Disabling SSL warnings is a red flag

I currently do:

urllib3.disable_warnings()
That strongly suggests I'm connecting with invalid/self-signed certs without validation somewhere in the stack (often verify=False in requests).
 

❌ Wrong (common lab shortcut)

  • Disabling warnings + skipping cert verification

  • Trains you into insecure defaults

✅ Better: make TLS verification configurable

A good secure-coding compromise is:

  • Verify TLS by default

  • Allow an explicit --insecure flag for lab use

Example CLI options:

  • --verify/--no-verify (default verify)

Then ApiCatcenter should pass that into the HTTP client.

Takeaway:

The secure default is TLS verification on. If you choose to disable verification in a lab, force it to be an explicit, visible choice.

 


6) Error Handling

Right now the script uses sys.exit(1) and prints user messages. That works, but it’s not ideal because:

  • sys.exit() stops the whole chain abruptly

  • error output isn’t standardized

  • you can accidentally leak sensitive info if exceptions bubble up

❌ Current

click.echo(click.style("Could not connect to catcenter", fg="red"), err=True)
sys.exit(1)
 

✅ Better: use Click exceptions

Click gives you clean CLI behavior automatically:

raise click.ClickException("Could not connect to Catalyst Center")

Also prefer failing with safe, high-level messages while logging detailed context separately (see logging below).

 


7) Logging (without leaking secrets)

Right now I use click.echo() for everything. That’s fine for UX, but not proper logging:

  • no log levels

  • no timestamps

  • not machine-parsable

  • no separation of “user output” vs “audit/debug logs”

OWASP-friendly pattern
  • Keep click.echo() for user-facing status

  • Add Python logging for structured logs

  • Never log passwords/tokens

  • Add correlation IDs if you want to go further

❌ Wrong

click.echo(f"Connecting to Catalyst Center at {ip} with {username}/{password}")
 

✅ Right

logger.info("Connecting to Catalyst Center", extra={"host": ip, "user": username})
And if you must log request/response details, redact secrets.
 

Wrap-Up

This script started life exactly where most automation code does: in a lab, written to solve a problem quickly and move on. It worked, and at the time, that was enough. Revisiting it through the lens of OWASP secure coding practices was a useful reminder that working code and well-designed code are not the same thing.

What stood out most during this exercise is that none of the improvements required a rewrite. By validating input early, handling credentials more carefully, enforcing least privilege, fixing TLS defaults, improving error handling, and separating logging from user output, the script became significantly safer without becoming more complex.

That’s an important takeaway for me as I prepare for the CCIE Automation lab: secure coding isn’t about perfection or paranoia. It’s about making deliberate design choices, even in small tools, and understanding where shortcuts are acceptable—and where they are not.


 

 

🔗 Useful Links

 

Blog series