Stop reading whenever you have enough

Egress control, in twelve questions.

Each one is the question the previous answer makes you ask. The ones that matter come with the manifest or the command, because that is the part you meet at work.

Not technical? The first three questions are written for you, and they are enough to follow any conversation about this.

Rungs
12
Hands on
7
Read
~8 min
01

What it is

0103

Your services also call things outside the building: card processors, cloud APIs. This is who is allowed to call out, and to where.

three minutes, no cluster needed · for anyone

  1. 01

    What problem is this solving?

    Not knowing this costs

    Data leaves the building through calls nobody wrote down.

    • Services call out: payment APIs, webhooks, clouds
    • By default the mesh lets all of it through, unlisted
    • Unlisted traffic is invisible to policy and metrics
  2. 02

    What does the mesh know about api.stripe.com?

    Not knowing this costs

    Your most business critical dependency is the one the mesh cannot see.

    • Nothing. It only knows cluster services
    • Outside hosts pass through a catch all by default
    • No retries, no metrics per host, no policy
  3. 03

    What does a ServiceEntry change?

    Not knowing this costs

    Everything the other ladders taught only applies to hosts on the map.

    • It adds the external host to the mesh map
    • Now it gets metrics, timeouts, policy, a name
    • The external world becomes configurable like the internal one

Before you scroll on

0/3

You should now be able to

02

The YAML you will see

0406

One mesh setting closes the doors. One resource per external service reopens them by name.

the lockdown and the allowlist · for whoever writes the manifests

  1. 04

    How do I close the doors?

    hands on

    Not knowing this costs

    This switch, flipped early, is a self inflicted multi team outage.

    • outboundTrafficPolicy REGISTRY_ONLY, in mesh config
    • Unlisted destinations stop resolving to anywhere
    • They land in a built in blackhole instead
    manifestthe lockdown switch
    # IstioOperator / meshConfig
    meshConfig:
      outboundTrafficPolicy:
        mode: REGISTRY_ONLY   # default is ALLOW_ANY
    
    Flip this after the inventory in rung 10, never before. Every external call not yet declared fails the moment this lands.
  2. 05

    What does declaring an external API look like?

    hands on

    Not knowing this costs

    The entries you write are the audit list of everything the mesh may call.

    • Hosts, ports, and resolution DNS
    • MESH_EXTERNAL marks it as outside
    • One entry per external dependency, owned in git
    manifeststripe, on the map
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: stripe-api
      namespace: payments
    spec:
      hosts:
        - api.stripe.com
      location: MESH_EXTERNAL
      resolution: DNS
      ports:
        - number: 443
          name: https
          protocol: TLS
    
    protocol TLS with port 443 passes the encrypted stream through untouched. The proxy sees the hostname via SNI and nothing else, which is usually exactly right.
  3. 06

    Can external calls get timeouts and retries too?

    hands on

    Not knowing this costs

    External dependencies without deadlines set your worst case latency for you.

    • Only if the proxy can read the HTTP, not just the SNI
    • That means the entry is HTTP and the proxy originates TLS
    • Then the card processor finally gets a deadline
    manifesta deadline for the outside world
    # 1. the entry offers an HTTP port that targets their 443
    #    ports: [{ number: 80, name: http, protocol: HTTP, targetPort: 443 }]
    # 2. a DestinationRule originates TLS on that port
    #    portLevelSettings: [{ port: { number: 80 }, tls: { mode: SIMPLE } }]
    # 3. now, and only now, HTTP policy applies:
    
      http:
        - timeout: 4s
          retries:
            attempts: 2
            perTryTimeout: 1500ms
            retryOn: connect-failure,refused-stream
          route:
            - destination:
                host: api.stripe.com
    
    The trap is expecting this on a protocol TLS entry. There the proxy only ever sees the SNI, so it can route but not time out, and a timeout you wrote is silently no timeout. Your app calls http://api.stripe.com and the proxy does the TLS.

Before you scroll on

0/3

You should now be able to

03

Proving it works

0708

One allowed host, one forbidden host, one curl each.

commands that answer yes or no · for whoever has to sign it off

  1. 07

    Is the declared host reachable, and only it?

    hands on

    Not knowing this costs

    Testing only the allowed half proves the map, not the lockdown.

    • Curl the declared host: works
    • Curl any undeclared host: refused
    • Both from a meshed pod
    shellthe pair of proofs
    kubectl exec deploy/sleep -n payments -- \
      curl -s -o /dev/null -w '%{http_code}\n' https://api.stripe.com/v1
    # 401   <- reached Stripe, refused by Stripe: the path works
    
    kubectl exec deploy/sleep -n payments -- \
      curl -s -m 5 -o /dev/null -w '%{http_code}\n' https://example.com
    # 000   <- blackholed: undeclared hosts go nowhere
    
    A 401 from the real API is a pass: the network path works and their auth refused you, which is their job. The 000 is the lockdown doing its job.
  2. 08

    Does external traffic show up in metrics now?

    hands on

    Not knowing this costs

    The blackhole metric is the lockdown’s ongoing completeness check.

    • Declared hosts appear as destination_service
    • Undeclared traffic lumps under BlackHoleCluster or PassthroughCluster
    shellthe outside world, measured
    sum by (destination_service) (
      rate(istio_tcp_sent_bytes_total{
        destination_service=~"api.stripe.com|BlackHoleCluster"}[5m]))
    
    # api.stripe.com     41283.1   <- named, visible, alertable
    # BlackHoleCluster       0.0   <- nothing undeclared even trying
    
    BlackHoleCluster above zero after lockdown is your remaining inventory gap, live: something still calls a host nobody declared.

Before you scroll on

0/3

You should now be able to

04

When it breaks

0910

Lockdown breaks the calls nobody had written down. That is also the point.

the failures you will actually hit · for whoever gets paged

  1. 09

    What does hitting the blackhole look like?

    hands on

    Not knowing this costs

    This ticket bounces between three teams unless someone reads the log.

    • Instant connection failure, near zero milliseconds
    • BlackHoleCluster named in the access log
    • The fix is a ServiceEntry, not a firewall ticket
    shellthe log line that names it
    kubectl logs deploy/payment-svc -c istio-proxy -n payments --tail=50 \
      | grep -i blackhole
    
    # ... "CONNECT ..." 502 - ... cluster=BlackHoleCluster
    
    Teams route this to the network team by reflex. It never left the pod: the caller’s own proxy refused it, and the ServiceEntry is the whole fix.
  2. 10

    How do I find every external call before the flip?

    hands on

    Not knowing this costs

    Lockdown without inventory is scheduling surprise outages by calendar.

    • Run ALLOW_ANY and watch PassthroughCluster
    • Every distinct host there needs an entry or a decision
    • Flip only when the passthrough list is boring
    shellthe inventory, from live traffic
    sum by (destination_service_name) (
      rate(istio_requests_total{
        destination_service_name="PassthroughCluster"}[24h]))
    
    # plus, for raw TLS flows, the SNI in the access logs:
    kubectl logs deploy/payment-svc -c istio-proxy -n payments \
      | grep -o 'SNI: [a-z0-9.-]*' | sort | uniq -c
    
    A week of this is the honest inventory. The calls that only happen monthly are the ones that page you three weeks after the flip.

Before you scroll on

0/3

You should now be able to

05

Where it ends

1112

A name allowlist is real control, but it is not inspection.

the limits, where people get caught · for whoever reviews the design

  1. 11

    What does registry only not inspect?

    Not knowing this costs

    An allowlist bounds where data can go, not what data goes.

    • Names, not content: an allowed host is fully allowed
    • Exfiltration to a declared host looks like traffic
    • Payload inspection is a different tool entirely
  2. 12

    Can a workload bypass all of this?

    Not knowing this costs

    Mesh egress control governs the meshed. The unmeshed need the CNI.

    • A pod without a sidecar has no egress rules
    • This is the injection story again, at the exit
    • NetworkPolicy is the layer below that catches it

Before you scroll on

0/3

You should now be able to

Go deeper

4 links, each earning its place.

Where this leaves you

Rung 10 is the discipline: inventory from live traffic first, lockdown second. Rung 8 is the metric that checks it forever after.

If you keep one thing: the mesh only governs what is on its map. A ServiceEntry is how the outside world gets a place on it.