Zero Trustsecurity

Why Zero Trust Architecture

Never trust, always verify—even inside the network

v1.2·11 min read·Kenneth Pernyér
securityzero-trustauthenticationnetworkarchitecturegrpcnatsmtls

The Problem

Traditional network security assumes a perimeter. Inside the firewall is trusted; outside is untrusted. Build a strong wall, and you're safe.

This model is broken.

The perimeter no longer exists.

Remote work means employees connect from anywhere. Cloud services mean data lives outside your network. Microservices mean components communicate across trust boundaries constantly.

A single compromised credential, a single vulnerable service, and attackers move laterally through your "trusted" network with impunity. The perimeter was always an illusion—now it's an obviously false one.

We needed a security model that:

  • Assumes the network is already compromised
  • Verifies every request, regardless of source
  • Limits blast radius when breaches occur
  • Works across cloud, on-premise, and hybrid environments

Current Options

OptionProsCons
Perimeter SecurityTraditional firewall-based approach. Trust the internal network.
  • Well-understood model
  • Existing tooling and expertise
  • Lower complexity for simple setups
  • Single point of failure
  • Lateral movement after breach
  • Incompatible with modern architectures
  • VPN creates false sense of security
Zero TrustVerify every request. No implicit trust based on network location.
  • Defense in depth by default
  • Limits breach impact
  • Works for distributed systems
  • Aligns with modern cloud architecture
  • Higher implementation complexity
  • Requires identity infrastructure
  • Performance overhead for verification
  • Cultural shift required
Hybrid ApproachPerimeter for legacy, Zero Trust for new systems.
  • Gradual migration path
  • Protects existing investments
  • Reduces immediate disruption
  • Complexity of two models
  • Security gaps at boundaries
  • Technical debt accumulates
  • Incentive to delay full migration

Future Outlook

Zero Trust is becoming the only trust model that makes sense.

Every major framework now assumes Zero Trust.

NIST, CISA, and industry standards all recommend Zero Trust architecture. Cloud providers build their services around it. The question isn't whether to adopt Zero Trust but how fast.

The convergence of AI and security accelerates this. AI-powered attacks are more sophisticated, faster, and more targeted. AI-powered defenses require the granular visibility that Zero Trust provides.

The future is identity-centric, context-aware, continuous verification. Every request carries identity, context, and intent. Every response is scoped to minimum necessary access.

Our Decision

Why we chose this

  • Defense in depthMultiple verification layers mean single failures don't cause breaches
  • Limited blast radiusCompromised credentials can only access what they're explicitly authorized for
  • Cloud-native compatibilityWorks naturally with distributed, multi-cloud architectures
  • Compliance alignmentMeets modern regulatory frameworks and audit requirements

×Trade-offs we accept

  • Implementation complexityRequires identity providers, policy engines, and continuous verification
  • Performance overheadEvery request requires authentication and authorization checks
  • Cultural changeTeams must shift from 'trusted network' thinking

Motivation

We build systems that handle business-critical data. A breach isn't just embarrassing—it's potentially catastrophic for the businesses that trust us.

Zero Trust isn't paranoia; it's realism. We assume our network could be compromised. We assume credentials could be stolen. We assume any component could be malicious.

Every service-to-service call carries authentication. Every request is authorized against policy. Every action is logged for audit. This isn't overhead—it's the cost of doing business responsibly.

Recommendation

Implement Zero Trust for Converge's gRPC + NATS architecture:

1. mTLS everywhere

  • gRPC services: tonic with rustls for client/server certificates
  • NATS connections: TLS with client certificate authentication
  • No plaintext service communication, ever

2. Service identity via SPIFFE/SPIRE

  • Each service gets a cryptographic identity (SVID)
  • Identity verified on every connection
  • Short-lived certificates, automatic rotation

3. Request-level authorization

  • gRPC interceptors validate JWT claims + service identity
  • NATS: subject-based permissions tied to service identity
  • Policy engine (Cedar or OPA) for complex rules

4. Audit everything

  • Every gRPC call logged with identity context
  • NATS JetStream for durable audit trail
  • Correlation IDs across service boundaries

Don't build custom auth. Use: Firebase Auth (users), SPIFFE (services), Cloud IAM (infrastructure).

Examples

src/interceptor/auth.rsrust
use tonic::{Request, Status, service::Interceptor};

/// Zero Trust interceptor - verify identity on every gRPC call
#[derive(Clone)]
pub struct AuthInterceptor {
    jwt_validator: JwtValidator,
    policy_engine: PolicyEngine,
}

impl Interceptor for AuthInterceptor {
    fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
        // 1. Extract service identity from mTLS (already verified by TLS layer)
        let peer_cert = request.peer_certs()
            .ok_or_else(|| Status::unauthenticated("no client certificate"))?;
        let service_id = extract_spiffe_id(peer_cert)?;

        // 2. Extract user identity from JWT (if present)
        let user_id = request.metadata()
            .get("authorization")
            .and_then(|h| h.to_str().ok())
            .and_then(|token| self.jwt_validator.validate(token).ok());

        // 3. Check policy - service + user + method = allowed?
        let method = request.uri().path();
        let decision = self.policy_engine.check(PolicyRequest {
            service_id: &service_id,
            user_id: user_id.as_deref(),
            method,
        })?;

        if !decision.allowed {
            return Err(Status::permission_denied(decision.reason));
        }

        // 4. Attach verified identity for downstream use
        request.extensions_mut().insert(VerifiedIdentity {
            service_id,
            user_id,
            permissions: decision.permissions,
        });

        Ok(request)
    }
}

gRPC interceptor verifies both service identity (mTLS/SPIFFE) and user identity (JWT). Every call is authenticated and authorized—no implicit trust.

src/nats/secure_client.rsrust
use async_nats::{Client, ConnectOptions};

/// Create NATS client with mTLS - Zero Trust for messaging
pub async fn connect_nats(config: &NatsConfig) -> Result<Client, NatsError> {
    let tls_config = rustls::ClientConfig::builder()
        .with_root_certificates(load_ca_certs(&config.ca_cert)?)
        .with_client_auth_cert(
            load_cert_chain(&config.client_cert)?,
            load_private_key(&config.client_key)?,
        )?;

    let client = ConnectOptions::new()
        .tls_config(tls_config)
        .user_and_password(config.nkey_user.clone(), config.nkey_seed.clone())
        .connect(&config.servers)
        .await?;

    // NATS server enforces subject permissions based on client cert identity
    // e.g., service "pricing" can only publish to "pricing.>" subjects
    Ok(client)
}

NATS connections use mTLS for identity. Server enforces subject-level permissions per service—no service can publish/subscribe outside its authorized subjects.

Related Articles

Stockholm, Sweden

Version 1.2

Kenneth Pernyér signature