Layer 7 정책

Layer 7 정책 (Layer 7 Policies)

HTTP, DNS 같은 애플리케이션 프로토콜 수준의 세밀한 접근 제어를 제공하는 Layer 7 정책을 설명하는 문서예요. HTTP 규칙, DNS 정책 및 IP 발견, DNS 프록시를 다뤄요.

출처: Layer 7 Policies

본문

Layer 7 정책 규칙은 Layer 4 규칙에 포함되며 ingress와 egress에 지정할 수 있어요. L7Rules 구조는 프로토콜별 필드의 열거형을 포함하는 기본 타입이에요.

// L7Rules is a union of port level rule types. Mixing of different port
// level rule types is disallowed, so exactly one of the following must be set.
// If none are specified, then no additional port level rules are applied.
type L7Rules struct {
        // HTTP specific rules.
        //
        // +optional
        HTTP []PortRuleHTTP `json:"http,omitempty"`

        // DNS-specific rules.
        //
        // +optional
        DNS []PortRuleDNS `json:"dns,omitempty"`
}

이 구조는 유니온으로 구현되며, 즉 포트당 멤버 필드 하나만 사용할 수 있어요. 동일한 PortProtocol을 가진 여러 toPorts 규칙이 겹치는 엔드포인트 목록을 선택하면, Layer 7 규칙이 같은 타입이라면 결합돼요. 타입이 다르면 정책이 거부돼요. 각 멤버는 애플리케이션 프로토콜 규칙 목록으로 구성돼요. 규칙 중 하나라도 일치하면 Layer 7 요청이 허용돼요. 규칙이 지정되지 않으면 모든 트래픽이 허용돼요.

정책에 Layer 4 규칙이 지정되고, Layer 7 규칙이 있는 유사한 Layer 4 규칙도 지정되면, 후자 규칙의 Layer 7 부분은 효과가 없어요.

참고: Layer 3·Layer 4 정책과 달리, Layer 7 규칙 위반은 패킷 드롭으로 이어지지 않아요. 대신 가능하면 애플리케이션 프로토콜별 access denied 메시지가 만들어져 반환돼요. 예를 들어 정책을 위반하는 HTTP 요청에는 HTTP 403 access denied가, DNS 요청에는 DNS REFUSED 응답이 보내져요.

참고: Layer 7 규칙은 DNS 규칙을 제외하고 포트 범위를 지원해요.

참고: Host Policies, 즉 Node Selector를 사용하는 정책에서는 현재 DNS Layer 7 규칙만 기능해요. 다른 유형의 Layer 7 규칙은 Host Policies에 지정할 수 없어요. Host L7 DNS 정책은 베타 기능이에요. 문제가 발생하면 피드백을 주고 GitHub issue를 제출해 주세요.

참고: Layer 7 정책은 노드 로컬 Envoy 인스턴스를 통해 트래픽을 프록시해요. 이 인스턴스는 DaemonSet으로 배포되거나 에이전트 파드에 포함돼요. Envoy가 에이전트 파드에 포함되면, 정책이 대상으로 하는 Layer 7 트래픽은 Cilium 에이전트 파드의 가용성에 의존해요.

참고: SNATed IPv6 트래픽(예: 파드-외부)에 대한 L7 정책은 수정이 적용된 커널이 필요해요. 수정이 적용된 안정 커널 버전은 6.14.1, 6.12.22, 6.6.86, 6.1.133, 5.15.180, 5.10.236이에요. 참고로 GitHub issue 37932를 확인하세요.

HTTP

다음 필드를 일치시킬 수 있어요.

  • Path: Path는 요청의 경로와 일치시키는 확장 POSIX 정규식이에요. 현재 RFC 3986이 정의한 URL의 기존 "path" 부분에서 허용되지 않는 문자를 포함할 수 있어요. 경로는 /로 시작해야 해요. 생략하거나 비우면 모든 경로가 허용돼요.
  • Method: Method는 요청의 메서드와 일치시키는 확장 POSIX 정규식이에요. 예: GET, POST, PUT, PATCH, DELETE, … 생략하거나 비우면 모든 메서드가 허용돼요.
  • Host: Host는 요청의 host 헤더와 일치시키는 확장 POSIX 정규식이에요. 예: foo.com. 생략하거나 비우면 host 헤더 값이 무시돼요.
  • Headers: Headers는 요청에 반드시 있어야 하는 HTTP 헤더 목록이에요. 생략하거나 비우면 헤더와 무관하게 요청이 허용돼요. 또한 헤더 값에 대한 더 고급 헤더 일치도 가능해요. HeaderMatches는 반드시 있어야 하고 주어진 값과 일치해야 하는 HTTP 헤더 목록이에요. Mismatch 필드는 일치하지 않을 때 무엇을 할지 지정하는 데 사용할 수 있어요.

GET /public 허용 (Allow GET /public)

다음 예제는 env=prod 라벨의 엔드포인트에서 app=service 라벨의 엔드포인트로의 URL /public에 대한 GET 요청을 허용해요. 하지만 다른 URL이나 다른 메서드를 사용한 요청은 거부돼요. 80이 아닌 포트의 요청은 버려져요.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "Allow HTTP GET /public from env=prod to app=service"
  endpointSelector:
    matchLabels:
      app: service
  ingress:
  - fromEndpoints:
    - matchLabels:
        env: prod
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "GET"
          path: "/public"

헤더가 설정됐을 때 모든 GET /path1 및 PUT /path2 (All GET /path1 and PUT /path2 when header set)

다음 예제는 app=myService 라벨을 가진 모든 엔드포인트가 TCP를 사용해 80 포트에서만 패킷을 받을 수 있도록 제한해요. 이 포트에서 통신하는 동안 허용되는 API 엔드포인트는 HTTP 헤더 X-My-Header가 true로 설정된 GET /path1과 PUT /path2뿐이에요.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l7-rule"
spec:
  endpointSelector:
    matchLabels:
      app: myService
  ingress:
  - toPorts:
    - ports:
      - port: '80'
        protocol: TCP
      rules:
        http:
        - method: GET
          path: "/path1$"
        - method: PUT
          path: "/path2$"
          headers:
          - 'X-My-Header: true'

DNS 정책 및 IP 발견 (DNS Policy and IP Discovery)

정책은 DNS 트래픽에 적용될 수 있으며, 특정 DNS 쿼리 이름이나 이름 패턴을 허용/비허용할 수 있어요(쿼리 타입 같은 다른 DNS 필드는 고려하지 않아요). 이 정책은 L3 DNS 기반 toFQDNs 규칙을 채우는 데 사용되는 IP를 수집하는 데도 사용되는 DNS 프록시를 통해 적용돼요.

위험: Layer 7 DNS 정책을 사용할 때는 kube-dns 같은 클러스터 DNS 서비스로의 DNS 트래픽만 가로채는 것을 강력히 선호해요. 다른 해석기로의 DNS 트래픽을 가로채면 DNS 기반 정책 결정의 신뢰 경계가 넓어져, 신뢰도가 낮은 DNS 서버가 toFQDNs 규칙에 대해 학습된 IP에 영향을 줄 수 있어요. 이 권장 사항은 위협 모델에 문서화된 신뢰 가정과 일치해요.

참고: Layer 7 DNS 정책은 다른 Layer 3 규칙 없이 적용될 수 있지만, Layer 7 규칙(Layer 3·4 구성 요소 포함)의 존재는 다른 트래픽을 차단해요.

DNS 정책은 다음을 통해 적용될 수 있어요.

  • matchName: matchName과 정확히 일치하는 도메인에 대한 쿼리를 허용해요. 여러 개의 서로 다른 이름을 별도의 matchName 항목에 포함할 수 있으며, 어떤 matchName과 일치하는 도메인에 대한 쿼리가 허용돼요.
  • matchPattern: 와일드카드를 고려해 matchPattern의 패턴과 일치하는 도메인에 대한 쿼리를 허용해요. 패턴은 도메인 이름에 허용되는 리터럴 문자(a-z, 0-9, ., -)로 구성돼요. *는 여러 편의 동작이 있는 와일드카드로 허용돼요.
    • 도메인 안의 *는 . 구분자를 제외하고 0개 이상의 유효한 DNS 문자를 허용해요. *.cilium.io는 sub.cilium.io와 일치하지만 cilium.io와는 일치하지 않아요. part*ial.com은 partial.com과 part-extra-ial.com과 일치해요.
    • *만 단독으로는 모든 이름과 일치하고, DNS 응답의 모든 IP를 cilium-agent DNS 캐시에 삽입해요.

이 예제에서 L7 DNS 정책은 cilium.io, cilium.io의 모든 하위 도메인, api.cilium.io의 모든 하위 도메인에 대한 쿼리를 허용해요. 다른 DNS 쿼리는 허용되지 않아요.

별도의 L3 toFQDNs egress 규칙은 cilium.io, sub.cilium.io, service1.api.cilium.io에 대한 DNS 쿼리에서 반환된 IP와 special*service.api.cilium.io의 어떤 일치 항목(예: special-region1-service.api.cilium.io이지만 region1-service.api.cilium.io는 아님)으로의 연결을 허용해요. anothersub.cilium.io에 대한 DNS 쿼리는 허용되지만, 이를 선택하는 L3 toFQDNs 규칙이 없으므로 반환된 IP로의 연결은 허용되지 않아요. L4와 L7 정책도 적용할 수 있어요(DNS based 참고). 이 경우 TCP 80 포트로의 연결을 제한해요.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: "tofqdn-dns-visibility"
spec:
  endpointSelector:
    matchLabels:
      any:org: alliance
  egress:
  - toEndpoints:
    - matchLabels:
       "k8s:io.kubernetes.pod.namespace": kube-system
       "k8s:k8s-app": kube-dns
    toPorts:
      - ports:
         - port: "53"
           protocol: ANY
        rules:
          dns:
            - matchName: "cilium.io"
            - matchPattern: "*.cilium.io"
            - matchPattern: "*.api.cilium.io"

  - toFQDNs:
      - matchName: "cilium.io"
      - matchName: "sub.cilium.io"
      - matchName: "service1.api.cilium.io"
      - matchPattern: "special*service.api.cilium.io"
    toPorts:
      - ports:
         - port: "80"
           protocol: TCP

참고: Kubernetes에서 DNS 정책을 적용할 때 service.namespace.svc.cluster.local.에 대한 쿼리는 matchPattern: *.*.svc.cluster.local.로 명시적으로 허용해야 해요. 마찬가지로 DNS 검색 목록에 의존해 FQDN을 완성하는 쿼리는 전체적으로 허용해야 해요. 예를 들어 servicename.namespace.svc.cluster.local.으로 성공하는 servicename에 대한 쿼리는 후자를 matchName 또는 matchPattern으로 허용해야 해요. Alpine/musl 배포 및 DNS Refused 참고.

참고: DNS 정책은 포트 범위를 지원하지 않아요.

toFQDNs에 사용할 DNS 데이터 얻기 (Obtaining DNS Data for use by toFQDNs)

IP는 프록시로 DNS 요청을 가로채 얻어져요. 이 IP는 toFQDN 규칙으로 선택할 수 있어요. DNS 응답은 TTL을 존중하며 Cilium 에이전트 안에 캐시돼요.

DNS 프록시 (DNS Proxy)

에이전트의 DNS 프록시는 egress DNS 트래픽을 가로채고 응답에서 본 IP를 기록해요. 이 가로채기 자체는 DNS 요청을 규율하는 별도의 정책 규칙이며, 별도로 지정해야 해요. DNS 요청에 정책을 시행하고 DNS 프록시를 구성하는 방법에 대한 자세한 내용은 Layer 7 Policies를 참고하세요.

위험: 이 프록시를 toFQDNs와 함께 사용할 때, 보안 모델은 가로챈 DNS 응답이 신뢰할 수 있는 클러스터 DNS 서버에서 온다고 가정해요. 위협 모델을 참고하세요.

애플리케이션에 대한 가로챈 DNS 응답에 있는 IP만 Cilium 정책 규칙에서 허용돼요. 주어진 도메인 이름에 대해 Cilium 인스턴스가 관리하는 모든 파드에 대한 응답의 IP가 정책에 의해(TTL 존중) 허용돼요. 이는 허용된 IP가 애플리케이션에 반환된 것과 일치하도록 보장해요. DNS 프록시는 와일드카드 L7 DNS matchPattern 규칙이 허용한 응답의 IP를 toFQDNs 규칙에서 사용하도록 허용하는 유일한 방법이에요.

다음 예제는 DNS 요청을 차단하지 않고 가로채서 DNS 데이터를 얻어요. cilium.io, sub.cilium.io, sub.cilium.io의 모든 하위 도메인에 대한 L3 연결을 허용해요.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: "tofqdn-dns-visibility"
spec:
  endpointSelector:
    matchLabels:
      any:org: alliance
  egress:
  - toEndpoints:
    - matchLabels:
       "k8s:io.kubernetes.pod.namespace": kube-system
       "k8s:k8s-app": kube-dns
    toPorts:
      - ports:
         - port: "53"
           protocol: ANY
        rules:
          dns:
            - matchPattern: "*"
  - toFQDNs:
      - matchName: "cilium.io"
      - matchName: "sub.cilium.io"
      - matchPattern: "*.sub.cilium.io"

참고: DNS 정책은 포트 범위를 지원하지 않아요.

Alpine/musl 배포 및 DNS Refused (Alpine/musl deployments and DNS Refused)

일부 일반적인 컨테이너 이미지는 DNS 프록시가 쿼리를 거부할 때 DNS Refused 응답을 더 일반적인 실패로 취급해요. 이는 /etc/resolv.conf에 정의된 검색 목록의 탐색을 중지시켜요. 파드가 DNS 쿼리에 .svc.cluster.local.을 추가해 검색하는 것은 흔한 일이에요. 이런 경우 cilium.io에 대한 조회는 처음에 cilium.io.namespace.svc.cluster.local.로 시도되고 프록시에 의해 거부될 수 있어요. 파드는 계속해서 결국 cilium.io. 단독만 시도하는 대신, DNS 조회를 실패한 것으로 취급해요.

이것은 --tofqdns-dns-reject-response-code 옵션으로 완화할 수 있어요. 기본값은 refused이지만 nameError를 선택하면 프록시가 거부 쿼리에 NXDomain 응답을 반환해요.

더 파드 특화적인 해결 방법은 dnsConfig를 통해 각 파드에 ndots를 적절히 구성해, 그럴 필요가 없는 DNS 조회에 검색 목록이 사용되지 않게 하는 거예요. 지침은 Kubernetes 문서를 참고하세요.

더 알아보기 (Learn more)