Sicra Header Logo
  • Careers
  • About us
  • People
EnglishNorsk
Talk to us
  1. Knowledge
  2. Insights
  3. Blog
Blog
30.12.2025
min read

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

In blog #9, I walk through an existing Python script using OWASP secure coding principles as a framework. By examining input validation, authentication, TLS, error handling, and logging, I show how lab code can be improved when security is considered in the design.

<span id="hs_cos_wrapper_name" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="text" >[My journey to CCIE Automation #9] Applying OWASP Secure Coding Practices</span>
bilde
Bjørnar LintvedtSenior Network Engineer

Senior Network Engineer focused on networking, security, and automation.

(This article was originally published on Bluetree.no. Following the merger of Sicra and Bluetree, content from Bluetree has now been migrated to Sicra.)

[My journey to CCIE Automation #9] Applying OWASP Secure Coding Practices is part of an ongoing CCIE Automation series. In the previous post, I worked with monitoring and visibility using ThousandEyes. This time, I revisit an existing Python script and improve it using OWASP secure coding practices.

Blog #9

This time, 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

  • Connect to Catalyst Center via ApiCatcenter

  • Fetches topology JSON

  • Convert it to a PyVis-friendly format

  • Outputs wither 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

Å sende secrets via --password er risikabelt fordi det kan dukke opp i:

  • 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

  • GitLab Repo – My CCIE Automation Code
  • OWASP Secure coding practices

Blog series

  • [My journey to CCIE Automation #1] Intro + building a Python CLI app

  • [My journey to CCIE Automation #2] Inventory REST API and microservices architecture

  • [My journey to CCIE Automation #3] Orchestration API and NETCONF

  • [My journey to CCIE Automation #4] Automating network discovery and reports with Python and Ansible

  • [My journey to CCIE Automation #5] Building network pipelines for reliable changes with pyATS and GitLab CI

  • [My journey to CCIE Automation #6] Automating Cisco ACI deployments with Terraform, Vault and GitLab CI

  • [My journey to CCIE Automation #7] Exploring Model-Driven Telemetry for real-time network insights

  • [My journey to CCIE Automation #8] Exploring ThousandEyes and automating Enterprise Agent deployment

  • [My journey to CCIE Automation #10] From Docker Compose to Kubernetes

Need Assistance?

We are happy to have a non-binding conversation. 
Contact us

Explore more

Cyber Threat Landscape 2026: Insights from Arctic Wolf’s threat report
Blog

Cyber Threat Landscape 2026: Insights from Arctic Wolf’s threat report

Arctic Wolf Threat Report 2026: Ransomware remains the #1 threat.
IAM for dummies
Blog

IAM for dummies

A simple, practical introduction to IAM and why correct access is critical.
Cost reduction in Microsoft Sentinel and Defender XDR
Blog

Cost reduction in Microsoft Sentinel and Defender XDR

Costs and choices for logging in Microsoft Sentinel and Defender XDR.
Sicra’s security triangle: Holistic IT and OT security through leadership, monitoring, and expertise
Blog

Sicra’s security triangle: Holistic IT and OT security through leadership, monitoring, and expertise

Sicra’s security triangle provides holistic security across IT, OT, and leadership.

Stay updated
Receive the latest news

Links
SustainabilityFAQPartnersCertifications and awardsCareerPress & brand
Contact
Tel: +47 648 08 488
E-mail: firmapost@sicra.no
Posthuset, Biskop Gunnerus’ gate 14A, 0185 Oslo, Norway
Follow us on LinkedIn
Certifications
iso27001-white
ISO 27001 compliance
miljofyrtarnlogo-hvit-rgb
Eco-Lighthouse
Sicra Footer Logo
Sicra © 2025
Privacy Policy