Skip to content

필사 모드: Istio Security in Practice — mTLS, AuthorizationPolicy, and the Zero Trust Mesh

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

The second biggest reason teams adopt a service mesh is security. The inside of a Kubernetes cluster is commonly treated as a "trusted network," but in reality it is often a flat network where compromising a single pod gives plaintext access to every service in the cluster. The starting point of zero trust is to invert that assumption: do not trust the internal network either, and require encryption, authentication, and authorization for every service-to-service call.

The problem is that implementing this in application code means putting TLS certificate issuance and renewal logic, peer verification logic, and permission checks into every service. Keeping that consistent across dozens of services and multiple language stacks is practically impossible. Istio pulls these three concerns (encryption, workload authentication, authorization) down into the infrastructure layer and applies them uniformly without code changes.

This article starts with how Istio security actually works (SPIFFE identities, the istiod CA, automatic certificate rotation), then walks through the staged STRICT migration of PeerAuthentication, least-privilege AuthorizationPolicy design, JWT validation with RequestAuthentication, OPA integration via external authorization, custom CA integration, and finally policy debugging and an adoption roadmap — in the order you would apply them in practice. Examples assume sidecar mode; in Ambient mode the only difference is that L4 security (mTLS) is handled by ztunnel and L7 authorization by the waypoint, while the APIs stay the same.

The Mesh Security Model — Identity Is the Foundation of Everything

SPIFFE identity

The core of Istio security is giving every workload a cryptographically verifiable identity. That identity follows the SPIFFE (Secure Production Identity Framework For Everyone) standard format.

spiffe://cluster.local/ns/payments/sa/payments-api

------------- -------- ------------

trust domain namespace service account

In other words, a workload identity in Istio is not an IP address or a pod name — it is the Kubernetes service account. IPs change when pods restart and can be spoofed, but a service-account-based SPIFFE ID is embedded in the certificate and cryptographically verified during the mTLS handshake. The principals field of AuthorizationPolicy, covered later, references exactly this SPIFFE ID.

The istiod CA and the certificate issuance and renewal flow

istiod doubles as the certificate authority (CA) of the mesh. Here is the flow in which workload certificates are issued and renewed.

+----------------------------------------------------------------------+

| Control plane |

| +----------------------------------------------------------------+ |

| | istiod (built-in CA) | |

| | - holds root/intermediate CA keys (istio-ca-secret or cacerts)| |

| | - signs CSRs, writes the SPIFFE ID into the SAN | |

| +----------------------------------------------------------------+ |

| ^ | |

| | 3. submit CSR | 4. signed cert |

| | (authenticated by SA token) | (24h default) |

+--------|----------------------------------------|--------------------+

| v

+--------|----------------------------------------------------------+

| Workload pod |

| +--------------------------+ +------------------------------+ |

| | istio-agent (pilot-agent) |<-->| Envoy sidecar | |

| | 1. generate key pair | SDS | - performs mTLS handshake | |

| | 2. create CSR | | with the issued cert | |

| | 5. deliver via SDS | | - verifies the SPIFFE ID | |

| | 6. auto-renew before | | in the peer certificate | |

| | expiry | +------------------------------+ |

| +--------------------------+ |

+--------------------------------------------------------------------+

The operationally important points are:

- The private key never leaves the pod. istio-agent generates the key pair inside the pod and sends only the CSR to istiod.

- Certificates are valid for 24 hours by default, and istio-agent automatically re-issues them before expiry (starting around half of the lifetime). No human ever performs a renewal.

- Envoy receives certificates via SDS (Secret Discovery Service), so certificate rotation requires no connection draining and no pod restarts.

- The SPIFFE ID is written into the SAN (Subject Alternative Name) of the certificate, and that is what authorization policies match against.

Trust chain and trust domain

In a default installation, istiod generates a self-signed root CA (the istio-ca-secret in the istio-system namespace). In a multi-cluster mesh, all clusters must share the same root for cross-cluster mTLS to work, so in production the standard practice is to inject a common root CA via the cacerts secret from the start. We return to this in the custom CA section below.

PeerAuthentication — mTLS Modes and the STRICT Migration Strategy

The three modes

PeerAuthentication defines how a workload demands mTLS on incoming traffic.

| Mode | Behavior | When to use |

| --- | --- | --- |

| PERMISSIVE | Accepts both mTLS and plaintext | Migration period (default) |

| STRICT | Accepts mTLS only, rejects plaintext | Target state |

| DISABLE | mTLS disabled | Exceptions such as behind an external TLS terminator |

It matters that the default is PERMISSIVE. Once sidecars are injected, in-mesh traffic automatically becomes mTLS, but plaintext traffic from sidecar-less pods or from outside the mesh is still accepted. PERMISSIVE means "encrypted, but not enforced" — and as long as a plaintext path remains, you cannot call it zero trust.

Scope: mesh-wide, namespace, workload

1) Mesh-wide default — placed in istio-system (the root namespace)

apiVersion: security.istio.io/v1

kind: PeerAuthentication

metadata:

name: default

namespace: istio-system

spec:

mtls:

mode: STRICT

2) Namespace level — applies to all workloads in the namespace

apiVersion: security.istio.io/v1

kind: PeerAuthentication

metadata:

name: default

namespace: payments

spec:

mtls:

mode: STRICT

3) Workload level — selector for specific workloads, per-port exceptions possible

apiVersion: security.istio.io/v1

kind: PeerAuthentication

metadata:

name: legacy-metrics-exception

namespace: payments

spec:

selector:

matchLabels:

app: legacy-batch

mtls:

mode: STRICT

portLevelMtls:

9090: # allow plaintext only on the port scraped by out-of-mesh Prometheus

mode: PERMISSIVE

Precedence: workload level beats namespace level, and namespace level beats mesh-wide. Remember it as "the narrower scope always wins."

From PERMISSIVE to STRICT — staged, namespace by namespace

Flipping the entire mesh to STRICT in one shot is a gamble. If there is even one sidecar-less client (an out-of-mesh cron job, a legacy VM, a custom probe that is not the kubelet health check, and so on), that path breaks immediately. The recommended procedure:

Step 0: Observe the current state (stay PERMISSIVE)

- Check the plaintext traffic ratio in Grafana/Kiali

- Metric: the connection_security_policy label on

istio_requests_total (mutual_tls / none)

- Inventory every source producing none

→ is it a pod without a sidecar, or an out-of-mesh client?

Step 1: Eliminate plaintext sources

- Add the injection label to namespaces missing sidecars

- Bring out-of-mesh clients into the mesh, or route them

through the ingress gateway

Step 2: Apply STRICT starting with non-critical namespaces

- Order: dev → staging → non-critical prod → critical prod

- One explicit PeerAuthentication per namespace

- After applying, watch 5xx/connection-error dashboards for a while

Step 3: Switch the mesh-wide default to STRICT

- Set the default PeerAuthentication in istio-system to STRICT

- State remaining exceptions narrowly via portLevelMtls

or workload-level policies

Step 4: Re-review the exception list periodically

- Manage DISABLE/PERMISSIVE exceptions with expiry dates

To find out where plaintext traffic comes from during the migration:

Check the mTLS status of a workload and which policy applies

istioctl x describe pod payments-api-6c9f7d-abcde -n payments

Check for authentication policy conflicts/gaps in a namespace

istioctl analyze -n payments

Track plaintext requests in Prometheus (none must reach 0 before STRICT)

istio_requests_total{connection_security_policy="none"}

AuthorizationPolicy — Designing Least-Privilege Authorization

If mTLS proves "who you are," AuthorizationPolicy decides "what you are therefore allowed to do." You must first understand the evaluation rules precisely.

AuthorizationPolicy evaluation order (per request):

1. CUSTOM policies → if the external authorizer denies, deny immediately

2. DENY policies → if any matches, deny immediately

3. ALLOW policies

- if the target workload has no ALLOW policy at all → allow (open by default)

- if at least one ALLOW policy exists → a match is required to allow,

otherwise deny (switches to default-deny)

The last line is the key. The moment an ALLOW policy exists, the workload enters whitelist mode. Default-deny is built on exactly this property.

Default deny — the starting point of zero trust

An ALLOW policy with an empty spec = allows nothing = namespace default deny

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: deny-all

namespace: payments

spec: {}

An empty spec means "an ALLOW policy exists but has no matching rules," so every request is denied. Least-privilege design then opens only the necessary paths explicitly from this state.

Least privilege between services — allow only along the call graph

If only the orders service may call the payments API, write it like this.

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: payments-api-allow

namespace: payments

spec:

selector:

matchLabels:

app: payments-api

action: ALLOW

rules:

- from:

- source:

principals:

- cluster.local/ns/commerce/sa/orders-api # based on SPIFFE ID

to:

- operation:

methods: ["POST"]

paths: ["/v1/charges", "/v1/refunds"]

- from:

- source:

principals:

- cluster.local/ns/observability/sa/prometheus

to:

- operation:

methods: ["GET"]

paths: ["/metrics"]

Because principals match against the SPIFFE ID in the mTLS certificate, this policy only works when mTLS is enabled. Remember that a request arriving in plaintext under PERMISSIVE has an empty principal and therefore never matches a principals condition. This is why the STRICT migration is a prerequisite for authorization design.

Method/path restrictions and using DENY

Build the whitelist with ALLOW, but for things that must be blocked no matter what, lay down an extra DENY layer. DENY is evaluated before ALLOW, so it acts as a safety net against mistakes in ALLOW policies.

Admin paths may never be called from inside the mesh, regardless of source

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: deny-admin-paths

namespace: payments

spec:

selector:

matchLabels:

app: payments-api

action: DENY

rules:

- to:

- operation:

paths: ["/admin/*", "/internal/*"]

Block all traffic coming from a specific namespace (e.g. sandbox isolation)

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: deny-from-sandbox

namespace: payments

spec:

action: DENY

rules:

- from:

- source:

namespaces: ["sandbox"]

Fine-grained control with when conditions

Allow only external traffic that came through the ingress gateway,

and only requests carrying a specific header

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: frontend-via-gateway-only

namespace: commerce

spec:

selector:

matchLabels:

app: storefront

action: ALLOW

rules:

- from:

- source:

principals:

- cluster.local/ns/istio-ingress/sa/ingress-gateway

when:

- key: request.headers[x-api-version]

values: ["v1", "v2"]

Besides request headers, when conditions support source.ip, connection.sni, request.auth.claims (JWT claims), and more. JWT claim conditions in particular combine with RequestAuthentication in the next section.

RequestAuthentication — JWT End-User Authentication

If the mTLS principal answers "which workload made the call," a JWT answers "on behalf of which end user (or which client app)." RequestAuthentication validates the signature, issuer, and lifetime of the JWT and exposes the verified claims as authorization conditions.

apiVersion: security.istio.io/v1

kind: RequestAuthentication

metadata:

name: jwt-auth

namespace: commerce

spec:

selector:

matchLabels:

app: storefront-api

jwtRules:

- issuer: "https://idp.example.com/" # must match the iss claim

audiences:

- "storefront-api" # validates the aud claim

jwksUri: "https://idp.example.com/.well-known/jwks.json"

forwardOriginalToken: true # forward the token to the backend

The most common misunderstanding here: with RequestAuthentication alone, requests without a JWT still pass. The resource only enforces "if a JWT is present, it must be valid." To make the JWT mandatory, you must combine it with an AuthorizationPolicy.

Allow only requests with a valid JWT principal → no token means deny

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: require-jwt

namespace: commerce

spec:

selector:

matchLabels:

app: storefront-api

action: ALLOW

rules:

- from:

- source:

requestPrincipals: ["https://idp.example.com//*"] # iss/sub format

Claim-based granularity: writes require the admin scope

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: write-requires-admin-scope

namespace: commerce

spec:

selector:

matchLabels:

app: storefront-api

action: ALLOW

rules:

- to:

- operation:

methods: ["GET"]

- to:

- operation:

methods: ["POST", "PUT", "DELETE"]

when:

- key: request.auth.claims[scope]

values: ["storefront:admin"]

Three operational tips:

- Envoy periodically caches and refreshes the keys from jwksUri. When rotating keys at the IdP, follow the standard procedure — add the new key to the JWKS first, run both keys in parallel for a while, then remove the old one — and the rotation is zero-downtime.

- If you skip audiences validation, a token issued by the same IdP for a different service can be replayed against yours. Separate audiences per service and always validate them.

- The common architecture is to validate JWTs once at the entry point (the ingress gateway or the frontmost service), and to handle internal propagation via mTLS principals plus forwardOriginalToken.

External Authorization — Integrating OPA via ext-authz

The built-in AuthorizationPolicy is fast and declarative, but it has limits for data-dependent decisions ("is the order amount within the users limit?") or for reusing organization-wide policy code. For these, the CUSTOM action delegates the decision to an external authorizer (OPA, an in-house authorization service, etc.).

First, register the external authorization provider in the mesh config.

MeshConfig (IstioOperator or the istio ConfigMap)

apiVersion: install.istio.io/v1alpha1

kind: IstioOperator

spec:

meshConfig:

extensionProviders:

- name: opa-ext-authz

envoyExtAuthzGrpc:

service: opa.authz-system.svc.cluster.local

port: 9191

timeout: 0.5s # authorizer latency adds directly to request latency — keep it short

Then scope it with a CUSTOM policy.

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: opa-authz

namespace: payments

spec:

selector:

matchLabels:

app: payments-api

action: CUSTOM

provider:

name: opa-ext-authz

rules:

- to:

- operation:

paths: ["/v1/charges"] # expensive external authz only where needed

The skeleton of the OPA-side policy (Rego) looks like this.

Example Rego policy (envoy.authz package)

package envoy.authz

default allow := false

Allow if called by the orders service and the JWT tier claim is premium

allow if {

input.attributes.source.principal ==

"spiffe://cluster.local/ns/commerce/sa/orders-api"

claims := input.attributes.metadata_context.filter_metadata["envoy.filters.http.jwt_authn"].fields

claims["tier"] == "premium"

}

Design principle: external authorization costs one extra network hop per request. Do not blanket the whole mesh with it; apply it narrowly to the paths that genuinely need complex decisions. Simple "who may call what" is far cheaper and faster with the built-in ALLOW/DENY. Also, the authorizer itself becomes a single point of failure, so you must explicitly decide the behavior when it is down (fail-close is the default) and the timeout.

Custom CA Integration — cert-manager and Corporate PKI

The default self-signed CA is fine for a PoC, but production environments — especially regulated industries — bring these requirements: integrate the mesh CA into the corporate PKI hierarchy, keep root keys somewhere safe such as an HSM, and audit certificate issuance history.

Option 1: Inject an intermediate CA via the cacerts secret

The simplest integration. Obtain an intermediate CA for the mesh from your corporate root CA and inject it into istiod.

Build the cacerts secret from the intermediate CA issued by corporate PKI

kubectl create secret generic cacerts -n istio-system \

--from-file=ca-cert.pem \

--from-file=ca-key.pem \

--from-file=root-cert.pem \

--from-file=cert-chain.pem

After restarting istiod, new workload certs are issued under this chain

The upside is simplicity; the downside is that the intermediate CA private key lives inside the cluster as a Kubernetes secret. You must review etcd encryption and secret-access RBAC together with this approach.

Option 2: Delegate signing to cert-manager via istio-csr

With the cert-manager istio-csr project, istiod no longer signs directly — workload CSRs are forwarded to a cert-manager Issuer. The key does not stay in a cluster secret, and you can connect to external issuers such as Vault, AWS Private CA, or corporate PKI.

cert-manager Issuer example: delegate signing to a Vault PKI backend

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: vault-istio-ca

spec:

vault:

server: https://vault.corp.example:8200

path: pki_int_mesh/sign/istio-workload

auth:

kubernetes:

role: istio-csr

mountPath: /v1/auth/kubernetes

secretRef:

name: istio-csr-vault-token

key: token

Issuance flow with istio-csr in place:

workload istio-agent

→ CSR → istiod

→ istio-csr (registered as the gRPC CA server)

→ cert-manager CertificateRequest

→ ClusterIssuer (Vault / Private CA / corporate PKI)

→ the signed certificate flows back in reverse

Effects:

- the mesh CA key is kept outside the cluster (HSM/Vault)

- issuance history is recorded as cert-manager resources, auditable

- swapping issuers reduces to swapping the Issuer

Certificate lifetime and rotation operations

| Item | Default | Production recommendation |

| --- | --- | --- |

| Workload cert lifetime | 24 hours | Keep 24 hours (shorter limits theft impact) |

| Renewal start point | About half the lifetime | Keep the default |

| Intermediate CA lifetime | 10 years (self-signed) | 1-3 years plus a documented rotation procedure |

| Root CA lifetime | 10 years | Per PKI policy, stored in an HSM |

Workload certificates renew automatically, so they carry no operational burden, but rotating intermediate and root CAs requires planning. Intermediate CA rotation can be zero-downtime in this order: inject the new intermediate CA → restart istiod → workloads naturally renew within 24 hours and switch to the new chain. Root rotation must pass through a transition period where both old and new roots are trusted (combined root), so a rehearsal beforehand is strongly recommended. Monitor expiry with the following command and metric.

Inspect the workload certificate chain and expiry

istioctl proxy-config secret deploy/payments-api -n payments -o json

Monitor istiod CA certificate expiry (Prometheus)

Metric: citadel_server_root_cert_expiry_timestamp

Always set an alert rule 30 days before expiry

Policy Debugging — Deny Logs and Dry-Run

Finding out who denied the request

An authorization denial appears to the client as HTTP 403 (or a closed TCP connection). The first step is to find the RBAC denial record in the Envoy logs of the target workload.

1) Enable RBAC debug logging (target pod)

istioctl proxy-config log deploy/payments-api -n payments \

--level rbac:debug

2) Check the denial reason in the sidecar logs

kubectl logs deploy/payments-api -n payments -c istio-proxy --tail=100

prints "RBAC: access denied" along with the principal/path that failed to match

3) List all authorization policies applied to the workload

istioctl x describe pod payments-api-6c9f7d-abcde -n payments

4) Bulk-check policy consistency

istioctl analyze -n payments

The three denial causes you meet most often: first, the request arrived in plaintext under PERMISSIVE, so the principal is empty and fails the principals condition. Second, a typo in the namespace or service account in the policy. Third, forgetting that adding an ALLOW policy switches the workload to default-deny, without having opened the existing paths.

Zero-impact policy validation with dry-run

To preview the effect of a policy without touching production traffic, use the dry-run annotation.

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: deny-all-dryrun

namespace: payments

annotations:

istio.io/dry-run: "true" # evaluate only, do not actually block

spec: {}

The evaluation results of a dry-run policy are recorded as shadow results in Envoy access logs and metrics. The safe rollout procedure is to confirm, with production traffic, "what would have been denied had this policy been live," and remove the annotation only when the denied set matches your intent.

Recommended policy rollout procedure:

1. Deploy the policy in dry-run

2. Collect shadow-denied logs/metrics for a period

(check istio_requests_total and dry-run results in access logs)

3. Review whether the denial list matches your intent

→ if there are unintended denials, refine the rules

4. Remove the dry-run annotation (activate the policy)

5. Watch the 403-rate dashboard closely right after activation

The Compliance Angle — What Mesh Security Buys You

In regulated environments (transport-encryption requirements in financial regulations, PCI DSS encryption of cardholder data in transit, ISO 27001 access controls, and so on), mesh security significantly reduces the cost of audit response.

- Evidence of encryption in transit: wherever STRICT mTLS applies, you can demonstrate "service-to-service traffic is TLS-encrypted" using the policy resources and the connection_security_policy metric. The audit scope becomes simpler than verifying per-application TLS settings one by one.

- Evidence of access control: AuthorizationPolicy is itself a declarative access control list. Keep the policies in Git and the change history plus approval workflow (PR review) doubles as your audit trail.

- Key management: with istio-csr and external PKI integration, the issue/store/rotate controls for keys can be folded into your existing PKI audit regime.

That said, mesh mTLS encrypts the pod-to-pod segment only. The segment before cluster entry (client to load balancer), database connections outside the mesh, and encryption at rest must be covered by separate controls — make that distinction explicit in your audit documentation. This article is a general technical explanation, not legal advice on any specific regulation.

Performance Impact — Speak in Measurements

The cost of mTLS and authorization is frequently overestimated. Based on real measurements:

- The handshake cost is paid once per connection establishment. Envoy-to-Envoy connections are reused via keep-alive, so in steady state the per-request cost is just symmetric encryption/decryption, which is tiny with AES-NI on modern CPUs.

- Most of the latency impact comes not from mTLS itself but from traversing the sidecar proxy (a few milliseconds or less per request). In other words, you mostly paid this cost the moment you chose to use a mesh, and turning mTLS off does not recover much.

- Authorization policy evaluation happens inside Envoy and is negligible at the scale of a few dozen rules. However, giant policies with hundreds of rules, excessive regex path matching, and CUSTOM external authorization (an extra network hop) should be measured before use.

- Certificate renewal is decoupled from the data path (SDS) and does not affect traffic.

Bottom line: postponing STRICT mTLS for performance reasons is usually weakly grounded. If you are worried, compare p99 latency between PERMISSIVE and STRICT on a representative service, then decide.

Adoption Roadmap — From Observation to Zero Trust

+------------------------------------------------------------------+

| Phase 1. Observe (1-2 weeks) |

| - Sidecar injection complete, keep the PERMISSIVE default |

| - Map the service call graph and plaintext ratio in Kiali/Grafana|

| - Deliverable: the service-to-service call matrix |

| (design input for authorization policies) |

+------------------------------------------------------------------+

| Phase 2. Migrate mTLS to STRICT (per namespace, 2-4 weeks) |

| - Remove plaintext sources → apply stage by stage from dev |

| → mesh-wide STRICT |

| - Keep exceptions narrow via portLevelMtls, manage expiry dates |

+------------------------------------------------------------------+

| Phase 3. Authorization policies (critical services first) |

| - Write ALLOW policies from the call matrix → validate with |

| dry-run → activate |

| - Namespace default-deny only after sufficient dry-run |

| - Add safety nets: DENY on admin paths, force gateway transit |

+------------------------------------------------------------------+

| Phase 4. Go further |

| - JWT validation at the entry point with RequestAuthentication |

| - Data-dependent decisions narrowly via OPA ext-authz |

| - Custom CA / corporate PKI integration, systematic CA expiry |

| monitoring |

+------------------------------------------------------------------+

It is important to define the completion criteria of each phase as metrics. For example, the completion criterion of Phase 2 should be measurable, such as "zero requests with connection_security_policy equal to none for 7 consecutive days."

Checklist

- [ ] The mesh-wide PeerAuthentication default is set to STRICT (exceptions are explicit and time-boxed)

- [ ] Switched to STRICT only after confirming the plaintext traffic metric is zero

- [ ] Critical namespaces have a default-deny (empty ALLOW) policy with only the necessary paths opened

- [ ] AuthorizationPolicy principals are written against service accounts (SPIFFE IDs)

- [ ] Every workload has a dedicated service account (no sharing of the default SA)

- [ ] Both issuer and audiences are specified for JWT validation

- [ ] New authorization policies are validated with dry-run before activation

- [ ] Dashboards/alerts exist for 403 spikes and RBAC denial logs

- [ ] A CA certificate expiry alert (30 days ahead) is in place

- [ ] Production uses corporate PKI / cert-manager integration instead of the self-signed CA

- [ ] External authorization (CUSTOM) is applied only to the paths that need it, with timeout and failure behavior defined

- [ ] Authorization policies are managed in Git so change history doubles as an audit trail

Closing Thoughts

The value of Istio security lies in achieving zero trust without code changes and incrementally. The foundation is service-account-based SPIFFE identity and short-lived, auto-renewing certificates; on top of that, PeerAuthentication (enforced encryption), AuthorizationPolicy (least privilege), and RequestAuthentication (end-user verification) stack layer by layer.

Failed adoptions share one trait: skipping steps. Turning on STRICT without cleaning up plaintext sources, laying down default-deny without knowing the call graph, or pushing authorization policies to production without dry-run. Climb the stairs one at a time — observe → clean up under PERMISSIVE → STRICT → dry-run authorization → activate — and mesh security becomes an attainable goal without major incidents. A mesh where certificates renew themselves daily and call permissions are managed as declarative policies in Git is an asset, not an operational burden, for both the security team and the platform team.

References

- [Istio Security Concepts](https://istio.io/latest/docs/concepts/security/)

- [Istio PeerAuthentication Reference](https://istio.io/latest/docs/reference/config/security/peer_authentication/)

- [Istio AuthorizationPolicy Reference](https://istio.io/latest/docs/reference/config/security/authorization-policy/)

- [Istio RequestAuthentication Reference](https://istio.io/latest/docs/reference/config/security/request_authentication/)

- [Istio External Authorization (ext-authz) Task](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/)

- [Istio mTLS Migration Task](https://istio.io/latest/docs/tasks/security/authentication/mtls-migration/)

- [SPIFFE Standard Documentation](https://spiffe.io/docs/latest/spiffe-about/overview/)

- [cert-manager istio-csr Documentation](https://cert-manager.io/docs/usage/istio-csr/)

- [OPA Envoy Plugin Documentation](https://www.openpolicyagent.org/docs/latest/envoy-introduction/)

- [Envoy External Authorization Filter Documentation](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter)

현재 단락 (1/412)

The second biggest reason teams adopt a service mesh is security. The inside of a Kubernetes cluster...

작성 글자: 0원문 글자: 25,309작성 단락: 0/412