From perimeter to zero trust: what changed

Until roughly 2015, most organisations — banks and trading firms included — worked to the perimeter model. The idea was simple: there is a firewall, inside the wall is safe, outside the wall is not. Internal services talked to each other over plain HTTP, with no encryption and no authentication. The assumption was that if an attacker was already past the wall, it was all over. In practice, that assumption was disproved again and again.

The Zero Trust model, as defined in NIST SP 800-207, discards that assumption. Every request — whether it arrives from outside or from inside — has to be authenticated, authorised and encrypted. Nothing is trusted by default just because the request came from the internal network.

In financial systems, the move to zero trust is not merely a question of best practice. Regulators such as MAS (Monetary Authority of Singapore), DORA in Europe, PCI DSS and APRA's CPS 234 here in Australia require encryption of internal traffic. Even if you are not caught by a specific regime, attacks such as the 2016 Bangladesh Bank heist (which stole $81 million) and the SolarWinds hack of 2020 showed that an attacker sitting inside the internal network can do hundreds of millions of dollars of damage.

A real case: JPMorgan Chase in 2024

In early 2024, JPMorgan Chase reported that it had finished a four-year process of moving to a full Zero Trust model. The results: 70% fewer internal security incidents, a response time to attacks three times faster, and full compliance with every regulator. The company spent about $200 million funding the project — but found that the savings on recovering from attacks were larger.

Key point: JPMorgan did not move to Zero Trust in one hit. It did so incrementally — starting with the highest-risk systems, then the rest. That is the right path for large organisations.

mTLS: the foundation of zero trust in practice

mTLS (mutual TLS) is the mechanism by which both sides of a TLS connection — the client and the server — identify themselves using digital certificates. This differs from ordinary TLS (the kind you use when browsing the web), where only the server identifies itself.

How mTLS works in practice

In a typical microservices scenario, service A wants to call service B. With mTLS:

  1. Service A sends a ClientHello with a list of supported cipher suites
  2. Service B returns its own certificate (server certificate)
  3. Service B asks for service A's certificate (CertificateRequest)
  4. Service A sends its certificate
  5. Both sides validate the other side's certificate against a shared CA (Certificate Authority)
  6. The connection is encrypted, and both sides know who is on the other end

Certificate management challenges

mTLS works beautifully in theory. In practice, the big problem is certificate management. If you have 50 microservices and each one needs a certificate — who issues them? Who renews them when they expire? What happens when a certificate has to be rotated?

The usual solution is cert-manager on Kubernetes combined with Vault PKI. cert-manager issues certificates automatically, renews them before expiry and rotates them when needed. Vault PKI serves as the internal CA that issues short-lived certificates (typically 24 hours to 7 days).

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: order-service-cert
  namespace: trading
spec:
  secretName: order-service-tls
  duration: 168h
  renewBefore: 24h
  issuerRef:
    name: vault-pki-issuer
    kind: ClusterIssuer
  commonName: order-service.trading.svc.cluster.local
  dnsNames:
  - order-service.trading.svc.cluster.local

This configuration requests a certificate with a validity of 7 days (168 hours), with automatic renewal 24 hours before expiry. The certificate is issued by Vault PKI and stored as a Kubernetes Secret.

Service mesh: Istio versus Linkerd

When it comes to mTLS at scale, a service mesh is the practical answer. The two common options are Istio and Linkerd. Both provide transparent mTLS (the services themselves do not need to change their code), but their approaches differ.

CriterionIstioLinkerd
Automatic mTLSYes (strict mode)Yes (mTLS by default)
Proxy resourcesEnvoy: ~50-100MB RAM per servicelinkerd-proxy: ~10-20MB RAM
Install complexityHigh — many CRDsLow — minimal install
Policy engineAuthorizationPolicy + OPAServerAuthorization
ObservabilityKiali + Grafana dashboardsBuilt-in dashboard + Prometheus
Suited toLarge organisations with a platform teamSmall to medium teams

Configuring strict mTLS in Istio

The following configuration enforces mTLS on every service in the trading namespace:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: trading-mesh-mtls
  namespace: trading
spec:
  mtls:
    mode: STRICT

STRICT mode means that any connection that is not mTLS is rejected. That matters in financial environments — PERMISSIVE mode also allows unencrypted traffic, which is not acceptable when you are dealing with payment data or trading orders.

Configuring an authorisation policy

mTLS guarantees that traffic is encrypted and that both sides are identified. But that is not enough — you also need to define who is permitted to reach what. The logging service, for instance, has no business touching the trading order database.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-order-to-payment
  namespace: trading
spec:
  selector:
    matchLabels:
      app: payment-service
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/trading/sa/order-service"]
    to:
    - operation:
        methods: ["POST"]
        paths: ["/api/v1/payments"]

Granular RBAC with OPA

Open Policy Agent (OPA) is a CNCF-graduated policy engine that lets you define complex authorisation rules in the Rego language. In financial environments, basic RBAC (role-based access control) is sometimes not enough — you need ABAC (attribute-based) or ReBAC (relationship-based).

For example, a policy that says: "only users from the trading desk, during trading hours (9:00-17:30 AEST), from a permitted IP range, may place orders above $100,000." That is not plain RBAC — it is a combination of role, time, network and transaction attributes.

package trading.authz

default allow = false

allow {
    input.user.department == "trading"
    input.user.role == "senior_trader"
    time.clock(time.now_ns()) >= [9, 0]
    time.clock(time.now_ns()) < [17, 30]
    input.request.amount <= 500000
    net.cidr_contains("192.168.1.0/24", input.user.ip)
}

allow {
    input.user.role == "risk_manager"
    input.request.action == "review"
}

OPA Gatekeeper: policy as code for Kubernetes

If you run workloads on Kubernetes, OPA Gatekeeper is the right tool. It lets you enforce policy at cluster level — for example: "no Pod runs as the root user" or "every Deployment must have resource limits set."

In financial environments, Gatekeeper is used to enforce security policy as part of the CI/CD pipeline — before code reaches production, it passes through automatic policy validation.

What to do when it breaks

No system is immune to failure. In zero trust, when something goes wrong — a certificate expires, the CA is unavailable, a policy is wrong — the services simply stop talking to each other. No graceful degradation, no fallback to HTTP.

In a financial environment, downtime of a trading service is a serious incident. So it is worth doing the following:

  • Active monitoring of certificate expiry — alert when fewer than 48 hours remain
  • Circuit breaker at mesh level — if a service does not respond, the mesh reroutes
  • A DR plan that includes restoring the CA and certificates from backup
  • A documented runbook for handling mTLS failures — the on-call team needs to know exactly what to check
A tip from experience: do not wait for the first outage to discover you have no runbook. In 2024, a fintech company well known in the industry suffered 45 minutes of downtime because the internal CA certificate expired over a weekend. There was nobody around to renew it. Since then they alert on every certificate that comes within 30 days of expiry.

Summary and implementation ladder

If you are starting from zero, this is the order of operations I recommend:

  1. Map the services and the traffic between them (service dependency map)
  2. Stand up an internal CA (Vault PKI or step-ca)
  3. cert-manager on Kubernetes for automatic renewal
  4. Install a service mesh (Linkerd for small environments, Istio for large ones)
  5. Turn on strict mTLS — one namespace at a time, not all at once
  6. Define authorisation policies
  7. Adopt OPA for complex policy

Do not try to do everything in one hit. Zero Trust is a journey, not a project. Every step in the right direction lifts the security baseline.