보안 모범 사례
보안 모범 사례 (Security Best Practices)
Istio 보안 기능은 강력한 신원(identity), 강력한 정책, 투명한 TLS 암호화, 그리고 인증·권한 부여·감사(AAA) 도구를 제공해서 서비스와 데이터를 보호해요. 하지만 이러한 기능을 안전하게 완전히 활용하려면 모범 사례를 따르는 데 주의를 기울여야 해요. 시작하기 전에 Security 개요를 검토하는 것이 좋아요.
출처: Istio 문서
본문
상호 TLS (Mutual TLS)
Istio는 가능하면 언제나 Mutual TLS를 사용해 트래픽을 자동으로 암호화해요. 그러나 프록시는 기본적으로 permissive 모드로 설정되어 있어서 상호 TLS와 평문(plaintext) 트래픽을 모두 수용해요.
이것은 점진적인 도입이나 Istio 사이드카가 없는 클라이언트의 트래픽을 허용하는 데 필요하지만, 보안 수준을 약화시키기도 해요. 가능하면 strict 모드로 마이그레이션해서 상호 TLS가 강제되도록 하는 것을 권장해요.
그러나 상호 TLS만으로는 트래픽을 완전히 보호하기에 항상 충분하지 않아요. 상호 TLS는 인증(authorization이 아닌 authentication)만 제공하기 때문이에요. 즉, 유효한 인증서를 가진 사람이라면 누구든 서비스에 접근할 수 있다는 뜻이에요.
트래픽을 완전히 잠그려면 권한 부여 정책을 구성하는 것이 좋아요. 이 정책은 트래픽을 허용하거나 거부하는 미세한 정책을 만들 수 있게 해줘요. 예를 들어, app 네임스페이스의 요청만 hello-world 서비스에 접근하도록 허용할 수 있어요.
권한 부여 정책 (Authorization policies)
Istio 권한 부여는 Istio 보안에서 중요한 역할을 해요. 클러스터를 가장 잘 보호하려면 올바른 권한 부여 정책을 구성하는 데 노력이 필요해요. Istio는 모든 사용자에게 적절한 권한 부여를 결정할 수 없기 때문에, 이러한 구성의 영향력을 이해하는 것이 중요해요. 이 섹션 전체를 따라가 주세요.
더 안전한 권한 부여 정책 패턴 (Safer Authorization Policy Patterns)
default-deny 패턴 사용하기
클러스터의 보안 자세를 강화하기 위해 Istio 권한 부여 정책을 default-deny 패턴으로 정의하는 것을 권장해요. default-deny 권한 부여 패턴이란 시스템이 기본적으로 모든 요청을 거부하고, 요청이 허용되는 조건을 정의하는 방식이에요. 일부 조건을 놓치게 되면 트래픽이 예기치 않게 허용되는 대신 예기치 않게 거부돼요. 후자는 일반적으로 보안 사고인 반면, 전자는 나쁜 사용자 경험, 서비스 중단, 또는 SLO/SLA 불일치를 초래할 수 있어요.
예를 들어, HTTP 트래픽에 대한 권한 부여 태스크에서 allow-nothing이라는 이름의 권한 부여 정책은 기본적으로 모든 트래픽이 거부되도록 보장해요. 거기서부터 다른 권한 부여 정책들이 특정 조건에 따라 트래픽을 허용해요.
waypoints가 있는 default-deny 패턴
Istio의 새로운 ambient 데이터 플레인 모드는 새로운 분리형 데이터 플레인 아키텍처를 도입했어요. 이 아키텍처에서 waypoint 프록시는 Kubernetes Gateway API를 사용해 구성되는데, 이 API는 parentRef와 targetRef를 사용해 게이트웨이에 더 명시적으로 바인딩해요. waypoint는 Kubernetes Gateway API의 원칙을 더 가까이 따르기 때문에, 정책이 waypoint에 적용될 때 default-deny 패턴이 다소 다른 방식으로 활성화돼요.
Istio 1.25부터 AuthorizationPolicy 리소스를 istio-waypoint GatewayClass에 바인딩할 수 있어요. AuthorizationPolicy를 GatewayClass에 바인딩함으로써 해당 GatewayClass를 구현하는 모든 게이트웨이에 기본 정책을 구성할 수 있어요. 중요한 점은 GatewayClass는 클러스터 범위(cluster-scoped) 리소스이고, 네임스페이스 범위 정책을 여기에 바인딩하는 것은 특별한 주의가 필요하다는 거예요. Istio는 GatewayClass에 바인딩된 정책이 루트 네임스페이스(일반적으로 istio-system)에 있어야 한다고 요구해요.
waypoints에 대한 표준 allow-nothing 정책은 다음과 같아요:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-nothing-istio-waypoint
namespace: istio-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: GatewayClass
name: istio-waypoint
ALLOW-with-positive-matching과 DENY-with-negative-match 패턴 사용하기
가능할 때마다 ALLOW-with-positive-matching 또는 DENY-with-negative-matching 패턴을 사용하세요. 이러한 권한 부여 정책 패턴은 정책 불일치 시 최악의 결과가 권한 부여 정책 우회 대신 예기치 않은 403 거부이기 때문에 더 안전해요.
ALLOW-with-positive-matching 패턴은 ALLOW 액션을 긍정 일치 필드(예: paths, values)와만 사용하고 부정 일치 필드(예: notPaths, notValues)를 사용하지 않는 것이에요.
DENY-with-negative-matching 패턴은 DENY 액션을 부정 일치 필드(예: notPaths, notValues)와만 사용하고 긍정 일치 필드(예: paths, values)를 사용하지 않는 것이에요.
예를 들어, 아래 권한 부여 정책은 ALLOW-with-positive-matching 패턴을 사용해 /public 경로로의 요청을 허용해요:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: foo
spec:
action: ALLOW
rules:
- to:
- operation:
paths: ["/public"]
위 정책은 허용된 경로(/public)를 명시적으로 나열해요. 이는 요청 경로가 요청을 허용하려면 /public와 정확히 같아야 한다는 뜻이에요. 다른 모든 요청은 기본적으로 거부되어, 알 수 없는 정규화 동작으로 인한 정책 우회 위험을 제거해요.
다음은 DENY-with-negative-matching 패턴을 사용해 동일한 결과를 얻는 예시예요:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: foo
spec:
action: DENY
rules:
- to:
- operation:
notPaths: ["/public"]
권한 부여 정책의 경로 정규화 이해하기
권한 부여 정책의 강제 지점은 백엔드 애플리케이션의 일반적인 리소스 접근 지점 대신 Envoy 프록시예요. Envoy 프록시와 백엔드 애플리케이션이 요청을 다르게 해석하면 정책 불일치(policy mismatch)가 발생해요.
불일치는 예기치 않은 거부 또는 정책 우회를 초래할 수 있어요. 후자는 일반적으로 즉시 수정해야 할 보안 사고이며, 이것이 권한 부여 정책에 경로 정규화가 필요한 이유이기도 해요.
예를 들어, /data/secret 경로의 요청을 거부하는 권한 부여 정책을 생각해보세요. /data//secret 경로의 요청은 경로에 추가 슬래시 /가 있어서 권한 부여 정책에 정의된 경로와 일치하지 않기 때문에 거부되지 않아요.
이 요청은 통과하고, 이후 백엔드 애플리케이션은 이중 슬래시 //를 단일 슬래시 /와 동일한 것으로 간주해 /data//secret을 /data/secret으로 정규화하기 때문에 /data/secret 경로에 대해 반환하는 것과 같은 응답을 반환해요.
이 예시에서 정책 강제 지점(Envoy 프록시)은 리소스 접근 지점(백엔드 애플리케이션)과 경로에 대한 이해가 달랐어요. 그 다른 이해가 불일치를 일으켰고 결국 권한 부여 정책의 우회로 이어졌어요.
이것은 다음과 같은 요인 때문에 복잡한 문제가 돼요:
- 정규화에 대한 명확한 표준 부재
- 서로 다른 레이어의 백엔드와 프레임워크는 자신만의 특별한 정규화를 가짐
- 애플리케이션은 자신의 사용 사례를 위해 임의의 정규화를 가질 수도 있음
Istio 권한 부여 정책은 문제를 더 잘 해결하는 데 도움이 되도록 다양한 기본 정규화 옵션에 대한 내장 지원을 구현해요:
- 경로 정규화 옵션 구성 가이드라인을 참조해서 어떤 정규화 옵션을 사용할지 이해하세요.
- 시스템 맞춤 설정 - 경로 정규화를 참조해서 각 정규화 옵션의 세부 사항을 이해하세요.
- 지원되지 않는 정규화가 필요한 경우 지원되지 않는 정규화의 완화를 참조해서 대안을 확인하세요.
경로 정규화 옵션 구성 가이드라인 (Guideline on configuring the path normalization option)
사례 1: 정규화가 전혀 필요 없는 경우
정규화 구성의 세부 사항에 들어가기 전에 정규화가 필요한지 먼저 확인해야 해요.
권한 부여 정책을 사용하지 않거나 권한 부여 정책이 어떤 path 필드도 사용하지 않으면 정규화가 필요하지 않아요.
모든 권한 부여 정책이 더 안전한 권한 부여 패턴을 따른다면 정규화가 필요하지 않을 수도 있어요. 그 패턴은 최악의 경우 정책 우회 대신 예기치 않은 거부를 초래하기 때문이에요.
사례 2: 정규화는 필요하지만 어떤 옵션을 쓸지 모르는 경우
정규화가 필요하지만 어떤 옵션을 사용해야 할지 전혀 모른다면, 가장 안전한 선택은 권한 부여 정책에서 최대 수준의 정규화를 제공하는 가장 엄격한(strictest) 정규화 옵션이에요.
복잡한 다중 레이어 시스템 때문에 강제 지점 너머에서 요청에 실제로 무슨 정규화가 일어나는지 알아내는 것이 사실상 불가능한 경우가 많기 때문에 이 경우가 흔해요.
이미 요구 사항을 충족하고 그 영향력을 확신한다면 덜 엄격한 정규화 옵션을 사용할 수 있어요.
어떤 옵션이든, 특히 요구 사항에 맞게 긍정 및 부정 테스트를 모두 작성해서 정규화가 예상대로 동작하는지 확인하세요. 테스트는 요청에 일어나는 정규화에 대한 오해나 불완전한 지식으로 인한 잠재적 우회 문제를 잡아내는 데 유용해요.
정규화 옵션 구성에 대한 자세한 내용은 시스템 맞춤 설정 - 경로 정규화를 참조하세요.
사례 3: 지원되지 않는 정규화 옵션이 필요한 경우
Istio가 아직 지원하지 않는 특정 정규화 옵션이 필요하다면, 지원되지 않는 정규화의 완화를 따라 맞춤 정규화 지원을 받거나 Istio 커뮤니티에 기능 요청을 만들어주세요.
시스템 맞춤 설정 - 경로 정규화 (Customize your system on path normalization)
Istio 권한 부여 정책은 HTTP 요청의 URL 경로를 기반으로 할 수 있어요. 경로 정규화 (a.k.a., URI 정규화)는 수신 요청의 경로를 수정하고 표준화해서, 정규화된 경로를 표준 방식으로 처리할 수 있게 해줘요. 구문적으로 다른 경로라도 경로 정규화 후에는 동일할 수 있어요.
Istio는 권한 부여 정책과 대조하고 요청을 라우팅하기 전에 요청 경로에 대해 다음 정규화 방식을 지원해요:
| 옵션 | 설명 | 예시 |
|---|---|---|
NONE |
정규화가 수행되지 않음. Envoy가 받은 그대로 어떤 백엔드 서비스에도 전달됨. | ../%2Fa../b가 권한 부여 정책으로 평가되고 서비스로 전송됨. |
BASE |
이것이 현재 Istio 기본 설치에서 사용되는 옵션이에요. Envoy 프록시에 normalize_path 옵션을 적용하며, RFC 3986에 역슬래시를 슬래시로 변환하는 추가 정규화를 따름. |
/a/../b는 /b로 정규화됨. \\da는 /da로 정규화됨. |
MERGE_SLASHES |
BASE 정규화 후 슬래시가 병합됨. | /a//b는 /a/b로 정규화됨. |
DECODE_AND_MERGE_SLASHES |
기본적으로 모든 트래픽을 허용할 때 가장 엄격한 설정. 이 설정을 권장하지만, 권한 부여 정책 라우트를 철저히 테스트해야 한다는 주의가 필요해요. 퍼센트 인코딩된 슬래시와 역슬래시 문자(%2F, %2f, %5C, %5c)가 MERGE_SLASHES 정규화 전에 / 또는 \\로 디코딩됨. |
/a%2fb는 /a/b로 정규화됨. |
강조하자면, 정규화 알고리즘은 다음 순서로 수행돼요:
%2F,%2f,%5C,%5c퍼센트 디코딩- Envoy의
normalize_path옵션이 구현한 RFC 3986 및 기타 정규화 - 슬래시 병합
지원되는 정규화의 전체 목록은 권한 부여 정책 정규화를 참조하세요.
구성 예시
Envoy가 백엔드 서비스의 기대와 일치하도록 요청 경로를 정규화하도록 보장하는 것은 시스템 보안에 중요해요. 다음 예시는 시스템을 구성하기 위한 참고 자료로 사용할 수 있어요. 정규화된 URL 경로(또는 NONE이 선택된 경우 원래 URL 경로)는 다음과 같이 사용돼요:
- 권한 부여 정책과 대조하는 데 사용
- 백엔드 애플리케이션으로 전달
| 애플리케이션이... | 다음을 선택... |
|---|---|
| 프록시가 정규화를 하도록 의존 | BASE, MERGE_SLASHES 또는 DECODE_AND_MERGE_SLASHES |
| RFC 3986에 따라 요청 경로를 정규화하고 슬래시를 병합하지 않음 | BASE |
| RFC 3986에 따라 요청 경로를 정규화하고 슬래시를 병합하지만 퍼센트 인코딩된 슬래시는 디코딩하지 않음 | MERGE_SLASHES |
| RFC 3986에 따라 요청 경로를 정규화하고 퍼센트 인코딩된 슬래시를 디코딩하고 슬래시를 병합 | DECODE_AND_MERGE_SLASHES |
| RFC 3986과 호환되지 않는 방식으로 요청 경로를 처리 | NONE |
구성 방법
istioctl을 사용해 mesh config를 갱신할 수 있어요:
$ istioctl upgrade --set meshConfig.pathNormalization.normalization=DECODE_AND_MERGE_SLASHES
또는 operator overrides 파일을 수정해서:
$ cat <<EOF > iop.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
pathNormalization:
normalization: DECODE_AND_MERGE_SLASHES
EOF
$ istioctl install -f iop.yaml
또는 mesh config를 직접 편집하고 싶다면, istio-system 네임스페이스의 istio-<REVISION_ID> configmap인 mesh config에 pathNormalization을 추가할 수 있어요. 예를 들어 DECODE_AND_MERGE_SLASHES 옵션을 선택한다면 mesh config를 다음과 같이 수정해요:
apiVersion: v1
data:
mesh: |-
...
pathNormalization:
normalization: DECODE_AND_MERGE_SLASHES
...
지원되지 않는 정규화의 완화 (Mitigation for unsupported normalization)
이 섹션은 지원되지 않는 정규화를 위한 다양한 완화 방법을 설명해요. Istio가 지원하지 않는 특정 정규화가 필요할 때 유용할 수 있어요.
일부 완화 방법은 Istio의 범위 밖에 있고 Istio도 지원하지 않는 것들에 의존하기 때문에, 완화 방법을 철저히 이해하고 신중하게 사용하세요.
사용자 정의 정규화 로직
WASM 또는 Lua 필터를 사용해 사용자 정의 정규화 로직을 적용할 수 있어요. 공식적으로 지원되고 Istio도 사용하는 WASM 필터를 사용하는 것을 권장해요. Lua 필터는 빠른 개념 증명 데모에는 사용할 수 있지만, Istio가 지원하지 않기 때문에 프로덕션에서는 Lua 필터 사용을 권장하지 않아요.
사용자 정의 정규화 예시 (대소문자 정규화)
일부 환경에서는 권한 부여 정책의 경로를 대소문자를 구분하지 않는 방식으로 비교하는 것이 유용할 수 있어요. 예를 들어 https://myurl/get과 https://myurl/GeT을 동일하게 취급하는 경우를 생각해보세요.
이런 경우 아래 표시된 EnvoyFilter를 사용해 Lua 필터를 삽입해서 경로를 소문자로 정규화할 수 있어요. 이 필터는 비교에 사용되는 경로와 애플리케이션에 제공되는 경로를 모두 변경해요.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: ingress-case-insensitive
namespace: istio-system
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: INSERT_FIRST
value:
name: envoy.lua
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
inlineCode: |
function envoy_on_request(request_handle)
local path = request_handle:headers():get(":path")
request_handle:headers():replace(":path", string.lower(path))
end
호스트 일치 정책 작성하기
Istio는 호스트 이름 자체와 일치하는 모든 포트에 대해 호스트 이름을 생성해요. 예를 들어 example.com 호스트에 대한 virtual service나 Gateway는 example.com과 example.com:*과 일치하는 구성을 생성해요. 그러나 완전 일치(exact match) 권한 부여 정책은 hosts 또는 notHosts 필드에 주어진 정확한 문자열과만 일치해요.
호스트와 일치하는 권한 부여 정책 규칙은 완전 일치 대신 접두사 일치(prefix match)로 작성해야 해요. 예를 들어 example.com 호스트 이름에 대해 생성된 Envoy 구성과 일치하는 AuthorizationPolicy에는 아래 AuthorizationPolicy에 표시된 것처럼 hosts: ["example.com", "example.com:*"]를 사용하면 돼요.
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ingress-host
namespace: istio-system
spec:
selector:
matchLabels:
app: istio-ingressgateway
action: DENY
rules:
- to:
- operation:
hosts: ["example.com", "example.com:*"]
또한 host와 notHosts 필드는 일반적으로 메시로 진입하는 외부 트래픽에 대한 게이트웨이에서만 사용해야 하고, 메시 내부 트래픽에 대한 사이드카에서는 사용하지 않아야 해요. 그 이유는 서버 쪽 사이드카(권한 부여 정책이 강제되는 곳)는 요청을 애플리케이션으로 리디렉션할 때 Host 헤더를 사용하지 않기 때문이에요. 이는 사이드카에서 host와 notHost를 무의미하게 만들며, 클라이언트가 서비스 이름 대신 명시적 IP 주소와 임의의 Host 헤더로 애플리케이션에 도달할 수 있기 때문이에요.
어떤 이유로든 사이드카에서 Host 헤더를 기반으로 접근 제어를 강제해야 한다면, default-deny 패턴을 따라 클라이언트가 임의의 Host 헤더를 사용하면 요청을 거부하게 하세요.
전문 Web Application Firewall (WAF)
많은 전문 Web Application Firewall (WAF) 제품은 추가 정규화 옵션을 제공해요. Istio 인그레스 게이트웨이 앞에 배포해서 메시로 진입하는 요청을 정규화할 수 있어요. 그러면 정규화된 요청에 대해 권한 부여 정책이 강제돼요. 정규화 옵션 구성은 특정 WAF 제품 문서를 참조하세요.
Istio에 기능 요청하기
Istio가 특정 정규화를 공식적으로 지원해야 한다고 생각한다면, 취약점 신고 페이지를 따라 특정 정규화에 대한 기능 요청을 Istio 제품 보안 워크 그룹에 보내 초기 평가를 받을 수 있어요.
해당 문제가 개인적으로 수정해야 할 보안 취약점으로 간주될 수 있기 때문에, Istio 제품 보안 워크 그룹에 먼저 연락하지 않고 공개적으로 issue를 열지 마세요.
Istio 제품 보안 워크 그룹이 기능 요청을 보안 취약점이 아닌 것으로 평가하면, 기능 요청에 대한 추가 논의를 위해 공개적으로 issue가 열릴 거예요.
알려진 제한 사항 (Known limitations)
이 섹션은 권한 부여 정책의 알려진 제한 사항을 나열해요.
Server-first TCP 프로토콜은 지원되지 않음
Server-first TCP 프로토콜이란 서버 애플리케이션이 클라이언트로부터 어떤 데이터도 받기 전에 TCP 연결을 수락하자마자 첫 번째 바이트를 보내는 것을 뜻해요.
현재 권한 부여 정책은 인바운드 트래픽에 대한 접근 제어만 지원하고 아웃바운드 트래픽은 지원하지 않아요.
또한 서버 애플리케이션이 클라이언트로부터 어떤 데이터도 받기 전에 첫 번째 바이트를 보내기 때문에 server-first TCP 프로토콜도 지원하지 않아요. 이 경우 서버가 보낸 초기 첫 바이트는 권한 부여 정책의 접근 제어 검사를 거치지 않고 클라이언트에게 직접 반환돼요.
server-first TCP 프로토콜이 보내는 첫 바이트에 적절한 권한 부여로 보호해야 할 민감한 데이터가 포함된다면 권한 부여 정책을 사용하지 말아야 해요.
첫 바이트에 민감한 데이터가 없다면 이 경우에도 권한 부여 정책을 여전히 사용할 수 있어요. 예를 들어 첫 바이트가 어떤 클라이언트에게나 공개적으로 접근 가능한 데이터로 연결을 협상하는 데 사용된다면, 첫 바이트 이후 클라이언트가 보내는 후속 요청에 대해서는 권한 부여 정책이 평소처럼 동작해요.
트래픽 캡처 제한 이해하기
Istio 사이드카는 인바운드 트래픽과 아웃바운드 트래픽을 모두 캡처해서 사이드카 프록시를 통해 보내는 방식으로 동작해요.
그러나 모든 트래픽이 캡처되는 것은 아니에요:
- 리디렉션은 TCP 기반 트래픽만 처리해요. 모든 UDP 또는 ICMP 패킷은 캡처되거나 수정되지 않아요.
- 인바운드 캡처는 사이드카가 사용하는 많은 포트와 포트 22에서 비활성화돼요. 이 목록은
traffic.sidecar.istio.io/excludeInboundPorts같은 옵션으로 확장할 수 있어요. - 아웃바운드 캡처도
traffic.sidecar.istio.io/excludeOutboundPorts같은 설정이나 다른 수단으로 줄일 수 있어요.
일반적으로 애플리케이션과 사이드카 프록시 사이에는 최소한의 보안 경계가 존재해요. 사이드카 구성은 파드 단위로 허용되고, 둘 다 같은 네트워크/프로세스 네임스페이스에서 실행돼요. 따라서 애플리케이션은 리디렉션 규칙을 제거하고 사이드카 프록시를 제거, 변경, 종료, 또는 교체할 수 있는 능력을 가질 수 있어요. 이로 인해 파드가 의도적으로 아웃바운드 트래픽에 대해 사이드카를 우회하거나 인바운드 트래픽이 사이드카를 우회하도록 허용할 수 있어요.
결과적으로 Istio가 모든 트래픽을 무조건 캡처한다고 의존하는 것은 안전하지 않아요. 대신 보안 경계는 클라이언트가 다른 파드의 사이드카를 우회할 수 없다는 것이에요.
예를 들어, 포트 9080에서 reviews 애플리케이션을 실행한다면, productpage 애플리케이션의 모든 트래픽이 Istio 인증·권한 부여 정책이 적용될 수 있는 사이드카 프록시에 의해 캡처된다고 가정할 수 있어요.
NetworkPolicy로 심층 방어 (Defense in depth)
트래픽을 더 보호하기 위해 Istio 정책은 Kubernetes Network Policies와 계층화할 수 있어요. 이는 메시의 보안을 더욱 강화하는 데 사용할 수 있는 강력한 심층 방어 전략을 가능하게 해줘요.
예를 들어, reviews 애플리케이션의 포트 9080으로의 트래픽만 허용하도록 선택할 수 있어요. 파드가 손상되거나 클러스터에 보안 취약점이 발생하는 경우, 이것이 공격자의 진행을 제한하거나 멈출 수 있어요.
실제 구현에 따라 네트워크 정책의 변경은 Istio 프록시의 기존 연결에 영향을 미치지 않을 수 있어요. 정책을 적용한 후 Istio 프록시를 재시작해서 기존 연결이 닫히고 새 연결이 새 정책의 적용을 받도록 해야 할 수 있어요.
이그레스 트래픽 보호하기
outboundTrafficPolicy: REGISTRY_ONLY 같은 옵션이 선언되지 않은 서비스로의 모든 접근을 막는 보안 정책처럼 동작한다는 것은 흔한 오해예요. 그러나 이것은 위에서 언급한 것처럼 강력한 보안 경계가 아니며, 최선의 노력(best-effort)으로 간주해야 해요.
우발적 의존성을 방지하는 데는 유용하지만, 이그레스 트래픽을 보호하고 모든 아웃바운드 트래픽이 프록시를 통과하도록 강제하려면 Egress Gateway에 의존해야 해요. Network Policy와 결합하면 모든 트래픽 또는 그 일부가 이그레스 게이트웨이를 통과하도록 강제할 수 있어요. 이렇게 하면 클라이언트가 우발적으로든 악의적으로든 사이드카를 우회하더라도 요청이 차단되도록 보장해요.
TLS 오리지네이션 시 DestinationRule에서 TLS 검증 구성하기
Istio는 사이드카 프록시나 게이트웨이에서 TLS를 오리지네이트하는 기능을 제공해요. 이를 통해 평문 HTTP 트래픽을 보내는 애플리케이션이 투명하게 HTTPS로 "업그레이드"될 수 있어요.
DestinationRule의 tls 설정에서 caCertificates, subjectAltNames, sni 필드를 지정할 때 주의를 기울여야 해요. caCertificate는 Istiod에서 환경 변수 VERIFY_CERTIFICATE_AT_CLIENT=true를 활성화하면 시스템 인증서 저장소의 CA 인증서로 자동 설정될 수 있어요. OS CA 인증서를 자동으로 사용하는 것이 특정 호스트에서만 필요하다면, Istiod에서 환경 변수 VERIFY_CERTIFICATE_AT_CLIENT=false로 하고 원하는 DestinationRule(들)에서 caCertificates를 system으로 설정할 수 있어요. DestinationRule에서 caCertificates를 지정하면 우선 순위가 높아지고 OS CA 인증서는 사용되지 않아요.
기본적으로 이그레스 트래픽은 TLS 핸드셰이크 중에 SNI를 보내지 않아요. 호스트가 요청을 올바르게 처리하도록 하려면 DestinationRule에서 SNI를 설정해야 해요.
서버 인증서를 검증하려면 caCertificates와 subjectAltNames가 모두 설정되는 것이 중요해요. 서버가 제시한 인증서를 CA에 대해 검증하는 것만으로는 충분하지 않으며, Subject Alternative Names도 검증해야 해요.
VERIFY_CERTIFICATE_AT_CLIENT가 설정되었지만 subjectAltNames가 설정되지 않았다면 모든 자격 증명을 검증하고 있지 않은 거예요. CA 인증서가 사용되지 않으면 subjectAltNames는 설정 여부와 무관하게 사용되지 않아요.
예를 들어:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: google-tls
spec:
host: google.com
trafficPolicy:
tls:
mode: SIMPLE
caCertificates: /etc/ssl/certs/ca-certificates.crt
subjectAltNames:
- "google.com"
sni: "google.com"
게이트웨이 (Gateways)
Istio 게이트웨이를 실행할 때 관련된 리소스는 몇 가지가 있어요:
Gateway— 게이트웨이의 포트와 TLS 설정을 제어해요.VirtualService— 라우팅 로직을 제어해요. 이들은gateways필드에서 직접 참조하고Gateway와VirtualService의hosts필드에 대한 상호 동의를 통해Gateway와 연결돼요.
Gateway 생성 권한 제한하기
Gateway 리소스의 생성을 신뢰할 수 있는 클러스터 관리자로 제한하는 것을 권장해요. 이는 Kubernetes RBAC 정책이나 Open Policy Agent 같은 도구로 달성할 수 있어요.
과도하게 넓은 hosts 구성 피하기
가능하면 Gateway에서 과도하게 넓은 hosts 설정을 피하세요.
예를 들어, 다음 구성은 어떤 VirtualService든 Gateway에 바인딩되도록 허용해서, 예기치 않은 도메인이 노출될 수 있어요:
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
이것은 특정 도메인이나 특정 네임스페이스만 허용하도록 잠가야 해요:
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "foo.example.com" # Allow only VirtualServices that are for foo.example.com
- "default/bar.example.com" # Allow only VirtualServices in the default namespace that are for bar.example.com
- "route-namespace/*" # Allow only VirtualServices in the route-namespace namespace for any host
민감한 서비스 격리하기
민감한 서비스에 대해 더 엄격한 물리적 격리를 강제하고 싶을 수 있어요. 예를 들어, 민감한 payments.example.com 용으로 전용 게이트웨이 인스턴스를 실행하고, 덜 민감한 blog.example.com과 store.example.com 같은 도메인에는 단일 공유 게이트웨이 인스턴스를 사용할 수 있어요. 이는 더 강력한 심층 방어를 제공하고 특정 규제 준수 지침을 충족하는 데 도움이 될 수 있어요.
완화된 SNI 호스트 매칭에서 민감한 http 호스트 명시적으로 비활성화하기
다른 호스트에 대해 상호 TLS와 단순 TLS를 정의하기 위해 여러 개의 Gateway를 사용하는 것이 합리적일 수 있어요. 예를 들어, SNI 호스트 admin.example.com에는 상호 TLS, SNI 호스트 *.example.com에는 단순 TLS를 사용해요.
kind: Gateway
metadata:
name: guestgateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- "*.example.com"
tls:
mode: SIMPLE
---
kind: Gateway
metadata:
name: admingateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- admin.example.com
tls:
mode: MUTUAL
이런 구성이 필요하다면, *.example.com에 연결되는 VirtualService에서 http 호스트 admin.example.com을 명시적으로 비활성화하는 것을 강력히 권장해요. 그 이유는 현재 envoy 프록시는 SNI 제약을 따라야 하는 http 1 헤더 Host나 http 2 의사 헤더 :authority를 요구하지 않기 때문이에요. 즉 공격자가 guest-SNI TLS 연결을 재사용해서 admin VirtualService에 접근할 수 있어요. http 응답 코드 421은 이러한 Host SNI 불일치를 위해 설계되었고, 이 비활성화를 충족시키는 데 사용할 수 있어요.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: disable-sensitive
spec:
hosts:
- "admin.example.com"
gateways:
- guestgateway
http:
- match:
- uri:
prefix: /
fault:
abort:
percentage:
value: 100
httpStatus: 421
route:
- destination:
port:
number: 8000
host: dest.default.cluster.local
프로토콜 감지 (Protocol detection)
Istio는 보이는 트래픽의 프로토콜을 자동으로 결정해요. 예상치 못한 트래픽 동작을 초래할 수 있는 우발적이거나 의도적인 오탐지를 피하기 위해, 가능한 경우 프로토콜을 명시적으로 선언하는 것을 권장해요.
CNI
모든 트래픽을 투명하게 캡처하기 위해 Istio는 istio-init initContainer가 구성한 iptables 규칙에 의존해요. 이렇게 하면 파드에 NET_ADMIN과 NET_RAW capability가 있어야 한다는 요구 사항이 추가돼요.
파드에 부여되는 권한을 줄이기 위해, Istio는 이 요구 사항을 제거하는 CNI 플러그인을 제공해요.
하드닝된 Docker 이미지 사용하기
컨트롤 플레인, 게이트웨이, 사이드카 프록시가 실행하는 Istio의 기본 docker 이미지는 ubuntu를 기반으로 해요. 여기에는 bash와 curl 같은 다양한 도구가 포함되어 있어서, 편의성과 공격 표면 증가를 맞바꾼 셈이에요.
Istio는 이미지의 의존성을 줄이는 distroless 이미지 기반의 더 작은 이미지도 제공해요.
릴리스 및 보안 정책 (Release and security policy)
클러스터가 알려진 취약점에 대한 최신 보안 패치를 받도록 하기 위해서는, Istio의 최신 패치 릴리스에 머물고 여전히 보안 패치를 받는 지원 릴리스임을 확인하는 것이 중요해요.
잘못된 구성 감지하기
Istio는 리소스가 생성될 때 검증을 제공하지만, 이러한 검사가 메시에 구성이 배포되는 것을 막는 모든 문제를 잡아낼 수는 없어요. 이로 인해 예기치 않게 무시되는 정책이 적용되어 원치 않는 결과가 발생할 수 있어요.
- 구성을 적용하기 전이나 후에
istioctl analyze를 실행해서 유효한지 확인하세요. - 컨트롤 플레인에서 거부된 구성을 모니터링하세요. 이러한 구성은 로그 외에도
pilot_total_xds_rejects메트릭으로 노출돼요. - 구성을 테스트해서 예상한 결과가 나오는지 확인하세요.
보안 정책의 경우, 실수로 트래픽을 너무 많이 또는 너무 적게 제한하지 않도록 긍정 및 부정 테스트를 실행하는 것이 유용해요.
알파 및 실험적 기능 피하기
모든 Istio 기능과 API에는 기능 상태가 지정되어 있어서, 안정성, 지원 중단 정책, 보안 정책을 정의해요.
알파와 실험적 기능은 그렇게 강력한 보안 보장이 없기 때문에 가능하면 사용을 피하는 것을 권장해요. 이러한 기능에서 발견된 보안 문제는 즉시 수정되지 않거나 표준 보안 취약점 프로세스를 따르지 않을 수 있어요.
클러스터에서 사용 중인 기능들의 기능 상태를 확인하려면 Istio 기능 목록을 참조하세요.
포트 잠그기
Istio는 보안을 개선하기 위해 잠글 수 있는 다양한 포트를 구성해요.
컨트롤 플레인 (Control Plane)
Istiod는 디버깅과 모니터링을 위한 여러 포트를 노출해요. 기본적으로 디버그 엔드포인트는 이제 인증을 요구해요:
- 포트
8080은 디버그 인터페이스를 노출하며, 클러스터 상태의 다양한 세부 정보에 대한 읽기 접근을 제공해요. Istiod에서 환경 변수ENABLE_DEBUG_ON_HTTP=false를 설정해서 비활성화할 수 있어요. 경고: 많은istioctl명령이 이 인터페이스에 의존하기 때문에 비활성화하면 동작하지 않을 거예요. - 포트
15010은 XDS 서비스를 평문 gRPC로 노출해요. 이 포트의 XDS 디버그 엔드포인트(syncz,config_dump)는 기본적으로 인증을 요구해서, 활성화되면 평문 접근을 효과적으로 차단해요. 인증된 XDS 디버그 접근에는 포트 15012(TLS)를 사용하세요. 평문 XDS 서비스 자체는 istiod 배포에--grpcAddr=""플래그를 추가해서 비활성화할 수 있어요. 참고: 인증서 서명 및 배포 서비스 같은 매우 민감한 서비스는 절대 평문으로 제공되지 않아요. - 포트
15012은 XDS 서비스를 TLS/mTLS gRPC로 노출해요 (프로덕션에 권장). XDS 디버그 엔드포인트는 자동 mTLS 인증으로 이 포트를 통해 사용할 수 있어요. - 포트
15014은 HTTP(평문)로 디버그 엔드포인트(/debug/syncz,/debug/registryz,/debug/config_dump등)를 노출해요. 이러한 엔드포인트는 기본적으로istio-caaudience를 가진 서비스 계정 토큰을 통한 인증을 요구해요.
디버그 엔드포인트 인증은 ENABLE_DEBUG_ENDPOINT_AUTH 환경 변수로 제어되고 (기본 활성화), 활성화되면 네임스페이스 기반 권한 부여가 비시스템 네임스페이스를 같은 네임스페이스 프록시의 특정 엔드포인트(config_dump, ndsz, edsz)로만 제한해요. 인증을 비활성화하고 레거시 동작을 복원하려면 istiod에 ENABLE_DEBUG_ENDPOINT_AUTH=false를 설정하세요.
통합에서 디버그 엔드포인트에 접근하는 방법에 대한 자세한 내용은 통합 가이드를 참조하세요.
데이터 플레인 (Data Plane)
프록시는 다양한 포트를 노출해요. 외부로 노출되는 것은 포트 15090(텔레메트리), 포트 15021(헬스 체크), 포트 15020(Istio 에이전트, Envoy, 애플리케이션의 병합된 Prometheus 텔레메트리)이에요. 포트 15000은 디버깅 엔드포인트를 제공하고 localhost에서만 노출돼요. 결과적으로 프록시와 같은 파드에서 실행되는 애플리케이션은 이에 접근할 수 있지만, 사이드카와 애플리케이션 사이에는 신뢰 경계가 없어요.
서드 파티 서비스 계정 토큰 구성하기
Istio 컨트롤 플레인과 인증하기 위해 Istio 프록시는 서비스 계정 토큰을 사용해요. Kubernetes는 이러한 토큰의 두 가지 형태를 지원해요:
- 서드 파티 토큰 (범위가 정해진 audience와 만료가 있음)
- 퍼스트 파티 토큰 (만료가 없고 모든 파드에 마운트됨)
퍼스트 파티 토큰의 속성이 덜 안전하기 때문에 Istio는 기본적으로 서드 파티 토큰을 사용해요. 그러나 이 기능은 모든 Kubernetes 플랫폼에서 활성화되지는 않아요.
istioctl로 설치한다면 지원 여부가 자동으로 감지돼요. 수동으로도 할 수 있으며, --set values.global.jwtPolicy=third-party-jwt 또는 --set values.global.jwtPolicy=first-party-jwt를 전달해서 구성할 수 있어요.
클러스터가 서드 파티 토큰을 지원하는지 확인하려면 TokenRequest API를 찾아보세요. 응답이 나오지 않으면 해당 기능이 지원되지 않는 거예요:
$ kubectl get --raw /api/v1 | jq '.resources[] | select(.name | index("serviceaccounts/token"))'
{
"name": "serviceaccounts/token",
"singularName": "",
"namespaced": true,
"group": "authentication.k8s.io",
"version": "v1",
"kind": "TokenRequest",
"verbs": [
"create"
]
}
대부분의 클라우드 제공자는 이 기능을 지원하지만, 많은 로컬 개발 도구와 커스텀 설치의 경우 Kubernetes 1.20 이전에는 지원하지 않을 수 있어요. 이 기능을 활성화하려면 Kubernetes 문서를 참조하세요.
다운스트림 연결 제한 구성하기
기본적으로 Istio(와 Envoy)는 다운스트림 연결 수에 제한이 없어요. 이는 악의적인 행위자에 의해 악용될 수 있어요 (보안 게시판 2020-007 참조). 이를 완화하려면 환경에 적절한 연결 제한을 구성해야 해요.
global_downstream_max_connections 값 구성하기
다음 구성은 설치 중에 제공할 수 있어요:
meshConfig:
defaultConfig:
runtimeValues:
"overload.global_downstream_max_connections": "100000"