외부 인가

외부 인가 (External Authorization)

이 작업은 action 필드의 새 값인 CUSTOM을 사용해서 접근 제어를 외부 인가 시스템에 위임하는 Istio 인가 정책을 설정하는 방법을 보여드려요. OPA 인가, oauth2-proxy, 자체 커스텀 외부 인가 서버 등과 통합할 수 있어요.

출처: Istio 문서

본문

이 작업은 action 필드의 새 값인 CUSTOM을 사용해서 접근 제어를 외부 인가 시스템에 위임하는 Istio 인가 정책을 설정하는 방법을 보여드려요. 이는 OPA 인가, oauth2-proxy, 자체 커스텀 외부 인가 서버 등과 통합하는 데 사용할 수 있어요.

시작하기 전에 (Before you begin)

이 작업을 시작하기 전에 다음을 수행하세요.

  • Istio 인가 개념을 읽으세요.
  • Istio 설치 가이드를 따라 Istio를 설치하세요.
  • 테스트 워크로드 배포하기: 이 작업은 foo 네임스페이스에 배포된 httpbin과 curl 두 워크로드를 사용해요. 두 워크로드 모두 Envoy 프록시 사이드카로 실행돼요. 다음 명령으로 foo 네임스페이스와 워크로드를 배포하세요.
$ kubectl create ns foo
$ kubectl label ns foo istio-injection=enabled
$ kubectl apply -f @samples/httpbin/httpbin.yaml@ -n foo
$ kubectl apply -f @samples/curl/curl.yaml@ -n foo
  • 다음 명령으로 curl이 httpbin에 접근할 수 있는지 확인하세요.
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n"
200

[!note] 작업을 따르면서 예상 출력이 안 보이면 몇 초 후에 다시 시도하세요. 캐싱과 전파 오버헤드로 인해 지연이 발생할 수 있어요.

외부 인가자 배포하기 (Deploy the external authorizer)

먼저 외부 인가자를 배포해야 해요. 이를 위해 메시의 독립 파드에 샘플 외부 인가자를 배포하면 돼요.

  1. 다음 명령으로 샘플 외부 인가자를 배포하세요.
$ kubectl apply -n foo -f https://raw.githubusercontent.com/istio/istio/release-1.31/samples/extauthz/ext-authz.yaml
service/ext-authz created
deployment.apps/ext-authz created
  1. 샘플 외부 인가자가 실행 중인지 확인하세요.
$ kubectl logs "$(kubectl get pod -l app=ext-authz -n foo -o jsonpath={.items..metadata.name})" -n foo -c ext-authz
2021/01/07 22:55:47 Starting HTTP server at [::]:8000
2021/01/07 22:55:47 Starting gRPC server at [::]:9000

또는 외부 인가자를 외부 인가가 필요한 애플리케이션과 같은 파드의 별도 컨테이너로 배포하거나, 메시 밖에 배포할 수도 있어요. 어느 경우든 서비스를 메시에 등록하고 프록시가 접근할 수 있도록 하는 서비스 항목(service entry) 리소스를 만들어야 해요. 다음은 외부 인가가 필요한 애플리케이션과 같은 파드의 별도 컨테이너에 배포된 외부 인가자에 대한 예시 서비스 항목이에요.

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: external-authz-grpc-local
spec:
  hosts:
  - "external-authz-grpc.local" # The service name to be used in the extension provider in the mesh config.
  endpoints:
  - address: "127.0.0.1"
  ports:
  - name: grpc
    number: 9191 # The port number to be used in the extension provider in the mesh config.
    protocol: GRPC
  resolution: STATIC

외부 인가자 정의하기 (Define the external authorizer)

인가 정책에서 CUSTOM 액션을 사용하려면 메시에서 사용이 허용된 외부 인가자를 정의해야 해요. 이는 현재 메시 구성의 확장 프로바이더(extension provider)에서 정의돼요. 현재 지원되는 유일한 확장 프로바이더 유형은 Envoy ext_authz 프로바이더예요. 외부 인가자는 해당 Envoy ext_authz check API를 구현해야 해요. 이 작업에서는 헤더 x-ext-authz: allow가 있는 요청을 허용하는 샘플 외부 인가자를 사용할 거예요.

  1. 다음 명령으로 메시 구성을 편집하세요.
$ kubectl edit configmap istio -n istio-system
  1. 편집기에서 아래 표시된 확장 프로바이더 정의를 추가하세요. 다음 내용은 같은 서비스 ext-authz.foo.svc.cluster.local을 사용하는 두 개의 외부 프로바이더 sample-ext-authz-grpc와 sample-ext-authz-http를 정의해요. 서비스는 Envoy ext_authz 필터가 정의한 HTTP와 gRPC check API를 모두 구현해요. 다음 단계에서 서비스를 배포할 거예요.
data:
  mesh: |-
    # Add the following content to define the external authorizers.
    extensionProviders:
    - name: "sample-ext-authz-grpc"
      envoyExtAuthzGrpc:
        service: "ext-authz.foo.svc.cluster.local"
        port: "9000"
    - name: "sample-ext-authz-http"
      envoyExtAuthzHttp:
        service: "ext-authz.foo.svc.cluster.local"
        port: "8000"
        includeRequestHeadersInCheck: ["x-ext-authz"]

또는 확장 프로바이더를 수정해서 외부 인가자로 보낼 헤더, 애플리케이션 백엔드로 보낼 헤더, 오류 시 반환할 상태 등과 같은 ext_authz 필터의 동작을 제어할 수 있어요. 예를 들어 다음은 oauth2-proxy와 함께 사용할 수 있는 확장 프로바이더를 정의해요.

data:
  mesh: |-
    extensionProviders:
    - name: "oauth2-proxy"
      envoyExtAuthzHttp:
        service: "oauth2-proxy.foo.svc.cluster.local"
        port: "4180" # The default port used by oauth2-proxy.
        includeRequestHeadersInCheck: ["authorization", "cookie"] # headers sent to the oauth2-proxy in the check request.
        headersToUpstreamOnAllow: ["authorization", "path", "x-auth-request-user", "x-auth-request-email", "x-auth-request-access-token"] # headers sent to backend application when request is allowed.
        headersToDownstreamOnAllow: ["set-cookie"] # headers sent back to the client when request is allowed.
        headersToDownstreamOnDeny: ["content-type", "set-cookie"] # headers sent back to the client when request is denied.

외부 인가로 활성화하기 (Enable with external authorization)

외부 인가자는 이제 인가 정책이 사용할 준비가 됐어요.

  1. 다음 명령으로 외부 인가를 활성화하세요. 다음 명령은 httpbin 워크로드에 대해 CUSTOM 액션 값을 가진 인가 정책을 적용해요. 정책은 sample-ext-authz-grpc로 정의된 외부 인가자를 사용해서 /headers 경로에 대한 요청에 외부 인가를 활성화해요.
$ kubectl apply -n foo -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: ext-authz
spec:
  selector:
    matchLabels:
      app: httpbin
  action: CUSTOM
  provider:
    # The provider name must match the extension provider defined in the mesh config.
    # You can also replace this with sample-ext-authz-http to test the other external authorizer definition.
    name: sample-ext-authz-grpc
  rules:
  # The rules specify when to trigger the external authorizer.
  - to:
    - operation:
        paths: ["/headers"]
EOF

런타임 시 httpbin 워크로드의 /headers 경로에 대한 요청은 ext_authz 필터에 의해 일시 중지되고, 요청을 허용할지 거부할지 결정하기 위해 check 요청이 외부 인가자로 전송돼요. 2. 헤더 x-ext-authz: deny가 있는 /headers 경로에 대한 요청이 샘플 ext_authz 서버에 의해 거부되는지 확인하세요.

$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -H "x-ext-authz: deny" -s
denied by ext_authz for not found header `x-ext-authz: allow` in the request
  1. 헤더 x-ext-authz: allow가 있는 /headers 경로에 대한 요청이 샘플 ext_authz 서버에 의해 허용되는지 확인하세요.
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/headers" -H "x-ext-authz: allow" -s | jq '.headers'
...
  "X-Ext-Authz-Check-Result": [
    "allowed"
  ],
...
  1. /ip 경로에 대한 요청이 허용되고 외부 인가를 트리거하지 않는지 확인하세요.
$ kubectl exec "$(kubectl get pod -l app=curl -n foo -o jsonpath={.items..metadata.name})" -c curl -n foo -- curl "http://httpbin.foo:8000/ip" -s -o /dev/null -w "%{http_code}\n"
200
  1. 샘플 ext_authz 서버의 로그를 확인해서 두 번 호출됐는지(두 요청) 확인하세요. 첫 번째는 허용, 두 번째는 거부됐어요.
$ kubectl logs "$(kubectl get pod -l app=ext-authz -n foo -o jsonpath={.items..metadata.name})" -n foo -c ext-authz
2021/01/07 22:55:47 Starting HTTP server at [::]:8000
2021/01/07 22:55:47 Starting gRPC server at [::]:9000
2021/01/08 03:25:00 [gRPCv3][denied]: httpbin.foo:8000/headers, attributes: source:{address:{socket_address:{address:"10.44.0.22"  port_value:52088}}  principal:"spiffe://cluster.local/ns/foo/sa/curl"}  destination:{address:{socket_address:{address:"10.44.3.30"  port_value:80}}  principal:"spiffe://cluster.local/ns/foo/sa/httpbin"}  request:{time:{seconds:1610076306  nanos:473835000}  http:{id:"13869142855783664817"  method:"GET"  headers:{key:":authority"  value:"httpbin.foo:8000"}  headers:{key:":method"  value:"GET"}  headers:{key:":path"  value:"/headers"}  headers:{key:"accept"  value:"*/*"}  headers:{key:"content-length"  value:"0"}  headers:{key:"user-agent"  value:"curl/7.74.0-DEV"}  headers:{key:"x-b3-sampled"  value:"1"}  headers:{key:"x-b3-spanid"  value:"377ba0cdc2334270"}  headers:{key:"x-b3-traceid"  value:"635187cb20d92f62377ba0cdc2334270"}  headers:{key:"x-envoy-attempt-count"  value:"1"}  headers:{key:"x-ext-authz"  value:"deny"}  headers:{key:"x-forwarded-client-cert"  value:"By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=dd14782fa2f439724d271dbed846ef843ff40d3932b615da650d028db655fc8d;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/curl"}  headers:{key:"x-forwarded-proto"  value:"http"}  headers:{key:"x-request-id"  value:"9609691a-4e9b-9545-ac71-3889bc2dffb0"}  path:"/headers"  host:"httpbin.foo:8000"  protocol:"HTTP/1.1"}}  metadata_context:{}
2021/01/08 03:25:06 [gRPCv3][allowed]: httpbin.foo:8000/headers, attributes: source:{address:{socket_address:{address:"10.44.0.22"  port_value:52184}}  principal:"spiffe://cluster.local/ns/foo/sa/curl"}  destination:{address:{socket_address:{address:"10.44.3.30"  port_value:80}}  principal:"spiffe://cluster.local/ns/foo/sa/httpbin"}  request:{time:{seconds:1610076300  nanos:925912000}  http:{id:"17995949296433813435"  method:"GET"  headers:{key:":authority"  value:"httpbin.foo:8000"}  headers:{key:":method"  value:"GET"}  headers:{key:":path"  value:"/headers"}  headers:{key:"accept"  value:"*/*"}  headers:{key:"content-length"  value:"0"}  headers:{key:"user-agent"  value:"curl/7.74.0-DEV"}  headers:{key:"x-b3-sampled"  value:"1"}  headers:{key:"x-b3-spanid"  value:"a66b5470e922fa80"}  headers:{key:"x-b3-traceid"  value:"300c2f2b90a618c8a66b5470e922fa80"}  headers:{key:"x-envoy-attempt-count"  value:"1"}  headers:{key:"x-ext-authz"  value:"allow"}  headers:{key:"x-forwarded-client-cert"  value:"By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=dd14782fa2f439724d271dbed846ef843ff40d3932b615da650d028db655fc8d;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/curl"}  headers:{key:"x-forwarded-proto"  value:"http"}  headers:{key:"x-request-id"  value:"2b62daf1-00b9-97d9-91b8-ba6194ef58a4"}  path:"/headers"  host:"httpbin.foo:8000"  protocol:"HTTP/1.1"}}  metadata_context:{}

로그에서 ext-authz 필터와 샘플 ext_authz 서버 사이의 연결에 mTLS가 활성화되어 있다는 것을 알 수 있어요. 소스 원칙이 spiffe://cluster.local/ns/foo/sa/curl 값으로 채워져 있기 때문이에요. 이제 누가 샘플 ext_authz 서버에 접근할 수 있는지 제어하는 또 다른 인가 정책을 적용할 수 있어요.

정리 (Clean up)

  1. 구성에서 foo 네임스페이스를 제거하세요.
$ kubectl delete namespace foo
  1. 메시 구성에서 확장 프로바이더 정의를 제거하세요.

성능 기대치 (Performance expectations)

성능 벤치마킹 문서를 참고하세요.

더 알아보기 (Learn more)

  • Envoy ext_authz 필터와 check API에 대한 자세한 내용은 Envoy 문서를 참고하세요.
  • 인가 정책의 CUSTOM 액션과 확장 프로바이더 설정은 인가 개념과 MeshConfig 문서를 참고하세요.