Stop Letting Every Service Handle Its Own Security

by Eric Hanson, Backend Developer at Clean Systems Consulting

The inconsistency problem at scale

With five services, inconsistent security implementations are a maintenance annoyance. With twenty services owned by eight teams, they become a genuine risk. Team 3 hardened their TLS configuration because someone on that team read a security advisory. Teams 1, 2, and 4 through 8 didn't. Team 6 rotates secrets quarterly. Teams 1 through 5 and 7 through 8 haven't rotated since initial deployment. Team 2 runs their containers as root because it was easier at the time.

This is not a people problem. It is a system design problem. When security is delegated to individual service teams, security posture varies with team knowledge, bandwidth, and priorities — all of which are inconsistent across teams and over time. The solution is to move security controls to the platform layer where they apply uniformly, automatically, and don't require every team to make correct independent decisions.

What the platform layer should enforce

TLS everywhere, enforced by infrastructure: a service mesh (Istio in STRICT mTLS mode, Linkerd) encrypts all inter-service traffic and enforces mutual authentication without any service needing to implement it. Teams write their service code; the mesh handles TLS. Teams cannot opt out, forget, or misconfigure it because the control is not in their hands.

Container security baselines via admission control: Kubernetes admission controllers (OPA Gatekeeper, Kyverno) enforce security policies at deploy time. A policy that prevents containers from running as root applies to every service without every team needing to remember to set securityContext.runAsNonRoot: true:

# Kyverno policy: enforce non-root containers cluster-wide
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-non-root-containers
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-runAsNonRoot
    match:
      resources:
        kinds: [Pod]
    validate:
      message: "Containers must run as non-root"
      pattern:
        spec:
          containers:
          - =(securityContext):
              runAsNonRoot: true

This policy applies at admission — a deployment that violates it is rejected before it ever reaches the cluster. No per-team action required.

Secrets management as infrastructure: every service gets secrets from Vault (or AWS Secrets Manager, GCP Secret Manager) via a platform-managed integration. The platform team configures the Vault sidecar injector; service teams annotate their deployments to request the secrets they need. Rotation is automatic. No service team owns a kubectl edit secret workflow.

# Pod annotation: request secrets from Vault, injected by platform
annotations:
  vault.hashicorp.com/agent-inject: "true"
  vault.hashicorp.com/role: "order-service"
  vault.hashicorp.com/agent-inject-secret-db: "secret/data/order-service/database"

Network policies as default-deny: the platform provisions a default-deny NetworkPolicy for every namespace. Service teams request explicit ingress/egress rules for the connections they need. The default state is no connectivity, not full connectivity. This inverts the security model from "everything allowed unless explicitly blocked" to "nothing allowed unless explicitly permitted."

The platform team contract

Centralizing security enforcement only works if the platform team treats service teams as customers with a support contract:

  • Policy changes must be communicated in advance with migration paths
  • Emergency security patches (critical CVE, compromised credentials) must be applied quickly without requiring per-team action
  • Service teams must be able to understand why a deployment was rejected and what they need to change
  • Escape hatches (audit-mode policies, temporary exceptions) must exist for legitimate edge cases, with clear approval and expiry processes

Without this contract, service teams work around security enforcement. They find ways to deploy without the admission controller, they use alternative namespaces with looser policies, they run secrets in environment variables because getting Vault access takes three weeks. Platform security that's too restrictive to work with is worse than no platform security, because it creates an adversarial relationship between security and engineering.

What remains with service teams

Centralized platform security does not mean service teams have no security responsibility. The platform handles transport security, secrets injection, container policy, and network isolation. Service teams own:

  • Authorization logic for their own resources (who can read or write what data)
  • Input validation and sanitization (SQL injection, deserialization vulnerabilities)
  • Dependency security (keeping libraries updated, responding to CVEs in direct dependencies)
  • Secure coding practices (no hardcoded credentials, no logging of sensitive data, proper error handling that doesn't leak internal state)

These are domain-specific concerns that the platform cannot enforce for you. They require engineering discipline and code review, not admission controllers.

The division is clear: the platform enforces the infrastructure security baseline uniformly. Service teams own the application security concerns specific to their domain. Neither delegates to the other. Both are necessary.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Why Software Projects Fail — And What Professionals Do About It

“We had a plan… so how did it end up like this?” Most failures don’t come from one big mistake — they come from many small ones ignored.

Read more

Designing with Java Enums — When They're the Right Model and When They're Not

Java enums are more capable than most developers use them for, but that capability has limits. Here is a clear-eyed look at what enums do well, where they break down, and the design decisions that determine which side you end up on.

Read more

Why Your Commit History Tells More About You Than Your Code Does

Your code shows what you built. Your commit history shows how you think — whether you work in logical units, whether you communicate intent, and whether you consider the people who come after you.

Read more

What Actually Happens When Spring Boot Starts Up

Spring Boot startup involves auto-configuration, bean registration, context refresh, and lifecycle callbacks — in a specific order that determines when your code runs and why some startup bugs are hard to diagnose.

Read more