5주차 - 마이크로서비스 통신 보안(2)

728x90

애플리케이션 네트워크 보안의 필요성

 

 

서비스 간 트래픽 인가하기

  • 인가란 인증된 주체가 리소스 접근, 편집, 삭제 같은 작업을 수행하도록 허용됐는지 정의하는 절차
  • 인가란 인증된 주체가 리소스 접근, 편집, 삭제 같은 작업을 수행하도록 허용됐는지 정의하는 절차
  • 이스티오에는 AuthorizationPolicy 리소스가 있는데, 이 리소스는 서비스 메시에 메시 범위, 네임스페이스 범위, 워크로드별 접근 정책을 정의하는 선언전 API

 

 

이스티오에서 인가 이해하기 : AuthorizationPolicy - selector, rules(from, to, when), action

  • 각 서비스와 함께 배포되는 서비스 프록시인가 또는 집행 enforcement 엔진
    • 서비스 프록시요청을 거절하거나 허용할지 여부를 판단하기 위한 정책을 모두 포함
    • 서비스 프록시는 AuthorizationPolicy 리소스로 설정하는데, 이 리소스가 정책을 정의

 

1. 인가 정책의 속성 (PROPERTIES OF AN AUTHORIZATION POLICY)

  • AuthorizationPolicy 리소스 사양에서 정책을 설정하고 정의하는 세 가지 필드
    • selector 필드는 정책을 적용할 워크로드 부분집합을 정의한다.
    • action 필드는 이 정책이 허용(ALLOW)인지, 거부(DENY)인지, 커스텀(CUSTOM)인지 지정한다.
      • action은 규칙 중 하나가 요청과 일치하는 경우에만 적용된다.
    • rules 필드는 정책을 활성화할 요청을 식별하는 규칙 목록을 정의한다.
      • from 필드는 요청의 출처 source 를 다음 유형 중 하나로 지정한다.
        • principals : 출처 ID(mTLS 예제에서 볼 수 있는 SPIFFE ID). 요청이 주체 principal 집합에서 온 것이 아니면 부정 속성인 notprincipals 가 적용된다. 이 기능이 작동하려면 서비스가 상호 인증해야 한다.
        • namespaces : 출처 네임스페이스와 비교할 네임스페이스 목록. 출처 네임스페이스는 참가자의 SVID에서 가져온다. 이런 이유로, 작동하려면 mTLS가 활성화돼야 한다.
        • ipBlocks : 출처 IP 주소와 비교할 단일 IP 주소나 CIDR 범위 목록.
      • to 필드는 요청의 작업을 지정하며, 호스트나 요청의 메서드 등이 있다.
      • when 필드는 규칙이 부합한 후 충족해야 하는 조건 목록을 지정한다.

 

  • 실습 환경 구성
#
kubectl -n istioinaction apply -f services/catalog/kubernetes/catalog.yaml
kubectl -n istioinaction apply -f services/webapp/kubernetes/webapp.yaml
kubectl -n istioinaction apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml
kubectl -n default apply -f ch9/sleep.yaml

# gw,vs 확인 
kubectl -n istioinaction get gw,vs


# PeerAuthentication 설정 : 앞에서 이미 설정함
cat ch9/meshwide-strict-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "default"
  namespace: "istio-system"
spec:
  mtls:
    mode: STRICT
    
kubectl -n istio-system apply -f ch9/meshwide-strict-peer-authn.yaml
kubectl get peerauthentication -n istio-system

cat ch9/workload-permissive-peer-authn.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "webapp"
  namespace: "istioinaction"
spec:
  selector:
    matchLabels:
      app: webapp 
  mtls:
    mode: PERMISSIVE

kubectl -n istioinaction apply -f ch9/workload-permissive-peer-authn.yaml
kubectl get peerauthentication -n istioinaction

 

1. 워크로드에 정책 적용 시 동작 확인

# 로그
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

# 적용 전 확인 
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world # 404 리턴

# AuthorizationPolicy 리소스 적용
kubectl apply -f ch9/allow-catalog-requests-in-web-app.yaml
kubectl get authorizationpolicy -n istioinaction

# 
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json > webapp-listener.json
...
          {
              "name": "envoy.filters.http.rbac",
              "typedConfig": {
                  "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
                  "rules": {
                      "policies": {
                          "ns[istioinaction]-policy[allow-catalog-requests-in-web-app]-rule[0]": {
                              "permissions": [
                                  {
                                      "andRules": {
                                          "rules": [
                                              {
                                                  "orRules": {
                                                      "rules": [
                                                          {
                                                              "urlPath": {
                                                                  "path": {
                                                                      "prefix": "/api/catalog"
                                                                  }
                                                              }
                                                          }
                                                      ]
                                                  }
                                              }
                                          ]
                                      }
                                  }
                              ],
                              "principals": [
                                  {
                                      "andIds": {
                                          "ids": [
                                              {
                                                  "any": true
                                              }
                                          ]
                                      }
                                  }
                              ]
                          }
                      }
                  },
                  "shadowRulesStatPrefix": "istio_dry_run_allow_" #  실제로 차단하지 않고, 정책이 적용됐을 때 통계만 수집 , istio_dry_run_allow_로 prefix된 메트릭 생성됨
              }
          },
...

# 로그 : 403 리턴 체크!
docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level rbac:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-03T10:08:52.918Z] "GET /hello/world HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/8.5.0" "b272b991-7a79-9581-bb14-55a6ee705311" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.3:8080 10.10.0.13:50172 - -

# 적용 후 확인
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/hello/world # 403 리턴
RBAC: access denied

# 다음 실습을 위해 정책 삭제
kubectl delete -f ch9/allow-catalog-requests-in-web-app.yaml

 

정책 적용 전_1
정책 적용 전_2(404Error)

 

AuthorizationPolicy 리소스 적용 확인
정책 적용 후_1
정책 적용 후_2(403Error)

 

  • ALLOW 정책을 워크로드에 적용했을 때만 적용되는 기본 거부 deny-by-default 동작

 

 

2. 전체 정책으로 기본적으로 모든 요청 거부하기 Denying all requests by default with a catch-all policy

  • 보안성을 증가시키고 과정을 단순화하기 위해, ALLOW 정책을 명시적으로 지정하지 않은 모든 요청을 거부하는 메시 범위 정책을 정의해보자.
  • 기본 거부 catch-all-deny-all 정책을 정의
# cat ch9/policy-deny-all-mesh.yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: istio-system # 이스티오를 설치한 네임스페이스의 정책은 메시의 모든 워크로드에 적용된다
spec: {} # spec 이 비어있는 정책은 모든 요청을 거부한다

 

  • 적용 후 요청 테스트
# 적용 전 확인 
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
curl -s http://webapp.istioinaction.io:30000/api/catalog


# 정책 적용
kubectl apply -f ch9/policy-deny-all-mesh.yaml
kubectl get authorizationpolicy -A

# 적용 후 확인 1
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
...
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
[2025-05-03T14:45:31.051Z] "GET /api/catalog HTTP/1.1" 403 - rbac_access_denied_matched_policy[none] - "-" 0 19 0 - "-" "curl/8.5.0" "f1ec493b-cc39-9573-b3ad-e37095bbfaeb" "webapp.istioinaction" "-" inbound|8080|| - 10.10.0.3:8080 10.10.0.13:60780 - -

# 적용 후 확인 2
curl -s http://webapp.istioinaction.io:30000/api/catalog
...
kubectl logs -n istio-system -l app=istio-ingressgateway -f
...

정책 적용 전_1
정책 적용 후

 

  • Catch-all authorization policies : 빈 규칙 rules 은 모든 요청을 허용 의미
# cat ch9/policy-allow-all-mesh.yaml                         
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-all
  namespace: istio-system
spec: 
  rules: 
  - {}

 

3. 특정 네임스페이스에서 온 요청 허용하기 Allowing requests originating from a single namespace

# 
cat << EOF | kubectl apply -f -
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "webapp-allow-view-default-ns"
  namespace: istioinaction # istioinaction의 워크로드
spec:
  rules:
  - from: # default 네임스페이스에서 시작한
    - source:
        namespaces: ["default"]
    to:   # HTTP GET 요청에만 적용 
    - operation:
        methods: ["GET"]
EOF

#
kubectl get AuthorizationPolicy -A
NAMESPACE       NAME                           AGE
istio-system    deny-all                       11h
istioinaction   webapp-allow-view-default-ns   11h

docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json
...
                {
                    "name": "envoy.filters.http.rbac",
                    "typedConfig": {
                        "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
                        "rules": {
                            "policies": {
                                "ns[istio-system]-policy[deny-all]-rule[0]": {
                                    "permissions": [
                                        {
                                            "notRule": {
                                                "any": true
                                            }
                                        }
                                    ],
                                    "principals": [
                                        {
                                            "notId": {
                                                "any": true
                                            }
                                        }
                                    ]
                                },
                                "ns[istioinaction]-policy[webapp-allow-view-default-ns]-rule[0]": {
                                    "permissions": [
                                        {
                                            "andRules": {
                                                "rules": [
                                                    {
                                                        "orRules": {
                                                            "rules": [
                                                                {
                                                                    "header": {
                                                                        "name": ":method",
                                                                        "exactMatch": "GET"
                                                                    }
                                                                }
                                                            ]
                                                        }
                                                    }
                                                ]
                                            }
                                        }
                                    ],
                                    "principals": [
                                        {
                                            "andIds": {
                                                "ids": [
                                                    {
                                                        "orIds": {
                                                            "ids": [
                                                                {
                                                                    "filterState": {
                                                                        "key": "io.istio.peer_principal",
                                                                        "stringMatch": {
                                                                            "safeRegex": {
                                                                                "regex": ".*/ns/default/.*"
...

#
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f

# 호출 테스트
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog
...

 

정책 적용 후

  • Sleep 서비스는 사이트카가 없으므로, ID도 없다. 그러므로 webapp 프록시는 요청이 default 네임이스페이스의 워크로드에서 온 것인지 확인할 수 없다.
  • 이를 해결하려면 다음 중 하나를 할 수 있다.
    1. sleep 서비스에 서비스 프록시 주입하기 → 실습 진행
    2. webapp에서 미인증 요청 허용하기
#
kubectl label ns default istio-injection=enabled
kubectl delete pod -l app=sleep

#
docker exec -it myk8s-control-plane istioctl proxy-status
NAME                                                   CLUSTER        CDS        LDS        EDS        RDS          ECDS         ISTIOD                    VERSION
sleep-6f8cfb8c8f-wncwh.default                         Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED       NOT SENT     istiod-8d74787f-n4c7b     1.17.8
...

# 호출 테스트 : webapp
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # default -> webapp 은 성공
...

kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog 
error calling Catalog service

docker exec -it myk8s-control-plane istioctl proxy-config log deploy/webapp -n istioinaction --level rbac:debug
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f # webapp -> catalog 는 deny-all 로 거부됨
[2025-05-04T02:36:49.857Z] "GET /items HTTP/1.1" 403 - via_upstream - "-" 0 19 0 0 "-" "beegoServer" "669eb3d6-f59a-99e8-80cb-f1ff6c0faf99" "catalog.istioinaction:80" "10.10.0.16:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:33066 10.200.1.46:80 10.10.0.14:48794 - default
[2025-05-04T02:36:49.856Z] "GET /api/catalog HTTP/1.1" 500 - via_upstream - "-" 0 29 1 1 "-" "curl/8.5.0" "669eb3d6-f59a-99e8-80cb-f1ff6c0faf99" "webapp.istioinaction" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:38191 10.10.0.14:8080 10.10.0.17:59998 outbound_.80_._.webapp.istioinaction.svc.cluster.local default


# 호출 테스트 : catalog
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items # default -> catalog 은 성공


# 다음 실습을 위해 default 네임스페이스 원복
kubectl label ns default istio-injection-
kubectl rollout restart deploy/sleep

docker exec -it myk8s-control-plane istioctl proxy-status
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # 거부 확인

 

  • default → webapp 은 성공

 

  • webapp -> catalog 는 deny-all 로 거부

 

  • default -> catalog 은 성공

 

4. 미인증 레거시 워크로드에서 온 요청 허용하기 Allowing requests from non-authenticated legacy workloads

  • 미인증 워크로드에서 온 요청을 허용하려면 from 필드를 삭제해야 한다.
  • 아래 정책을 webapp에만 적용하기 위해 app:webapp 셀렉터를 추가한다.
    • 이렇게 하면 catalog 서비스에는 여전히 상호 인증이 필요
# cat ch9/allow-unauthenticated-view-default-ns.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "webapp-allow-unauthenticated-view-default-ns"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: webapp
  rules:
    - to:
      - operation:
          methods: ["GET"]
#
kubectl apply -f ch9/allow-unauthenticated-view-default-ns.yaml
kubectl get AuthorizationPolicy -A
NAMESPACE       NAME                                           AGE
istio-system    deny-all                                       12h
istioinaction   webapp-allow-unauthenticated-view-default-ns   14s
istioinaction   webapp-allow-view-default-ns                   11h

# 여러개의 정책이 적용 시에 우선순위는?
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction --port 15006 -o json | jq
...
         "name": "envoy.filters.http.rbac",
                        "typedConfig": {
                            "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
                            "rules": {
                                "policies": {
                                    "ns[istio-system]-policy[deny-all]-rule[0]": {
                                        "permissions": [
                                            {
                                                "notRule": {
                                                    "any": true
                                                }
                                            }
                                        ],
                                        "principals": [
                                            {
                                                "notId": {
                                                    "any": true
                                                }
                                            }
                                        ]
                                    },
                                    "ns[istioinaction]-policy[webapp-allow-unauthenticated-view-default-ns]-rule[0]": {
                                        "permissions": [
                                            {
                                                "andRules": {
                                                    "rules": [
                                                        {
                                                            "orRules": {
                                                                "rules": [
                                                                    {
                                                                        "header": {
                                                                            "name": ":method",
                                                                            "exactMatch": "GET"
                                                                        }
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    ]
                                                }
                                            }
                                        ],
                                        "principals": [
                                            {
                                                "andIds": {
                                                    "ids": [
                                                        {
                                                            "any": true
                                                        }
                                                    ]
                                                }
                                            }
                                        ]
                                    },
                                    "ns[istioinaction]-policy[webapp-allow-view-default-ns]-rule[0]": {
                                        "permissions": [
                                            {
                                                "andRules": {
                                                    "rules": [
                                                        {
                                                            "orRules": {
                                                                "rules": [
                                                                    {
                                                                        "header": {
                                                                            "name": ":method",
                                                                            "exactMatch": "GET"
                                                                        }
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    ]
                                                }
                                            }
                                        ],
                                        "principals": [
                                            {
                                                "andIds": {
                                                    "ids": [
                                                        {
                                                            "orIds": {
                                                                "ids": [
                                                                    {
                                                                        "filterState": {
                                                                            "key": "io.istio.peer_principal",
                                                                            "stringMatch": {
                                                                                "safeRegex": {
                                                                                    "regex": ".*/ns/default/.*"
                                                                                }
...

# 호출 테스트 : webapp
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction # default -> webapp 은 성공
...

kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f # webapp -> catalog 는 deny-all 로 거부됨
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog 
error calling Catalog service

# (옵션) 호출 테스트 : catalog
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items

 

  • default → webapp 은 성공

  • webapp catalogsms deny-all로 거부

 

5. 특정 서비스 어카운트에서 온 요청 허용하기 Allowing requests from a single service account

  • 트래픽이 webapp 서비스에서 왔는지 인증할 수 있는 간단한 방법은 트래픽에 주입된 서비스 어카운트를 사용하는 것이다.
  • 서비스 어카운트 정보는 SVID에 인코딩돼 있으며, 상호 인증 중에 그 정보를 검증하고 필터 메타데이터에 저장한다.
  • 다음 정책은 catalog 서비스가 필터 메타데이터를 사용해 서비스 어카운트가 webapp인 워크로드에서 온 트래픽만 허용하도록 설정
# cat ch9/catalog-viewer-policy.yaml
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "catalog-viewer"
  namespace: istioinaction
spec:
  selector:
    matchLabels:
      app: catalog
  rules:
  - from:
    - source: 
        principals: ["cluster.local/ns/istioinaction/sa/webapp"] # Allows requests with the identity of webapp
    to:
    - operation:
        methods: ["GET"]

 

#
kubectl apply -f ch9/catalog-viewer-policy.yaml
kubectl get AuthorizationPolicy -A
NAMESPACE       NAME                                           AGE
istio-system    deny-all                                       13h
istioinaction   catalog-viewer                                 10s
istioinaction   webapp-allow-unauthenticated-view-default-ns   61m
istioinaction   webapp-allow-view-default-ns                   12h

#
docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction --port 15006 -o json
...
            "principals": [
                {
                    "andIds": {
                        "ids": [
                            {
                                "orIds": {
                                    "ids": [
                                        {
                                            "filterState": {
                                                "key": "io.istio.peer_principal",
                                                "stringMatch": {
                                                    "exact": "spiffe://cluster.local/ns/istioinaction/sa/webapp"
                                                }
...

# 호출 테스트 : sleep --(미인증 레거시 허용)--> webapp --(principals webapp 허용)--> catalog
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction
kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog 
kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
kubectl logs -n istioinaction -l app=catalog -c istio-proxy -f

# (옵션) 호출 테스트 : catalog
kubectl exec deploy/sleep -- curl -sSL catalog.istioinaction/items
...

 

  • sleep --(미인증 레거시 허용) → webapp --(principals webapp 허용) catalog
    • kubectl exec deploy/sleep -- curl -sSL webapp.istioinaction/api/catalog

 

 

6. 정책의 조건부 적용 Conditional matching of policies

  • 가끔 어떤 정책은 특정 조건이 충족되는 경우에만 적용되기도 한다.
  • 사용자가 관리자일 때는 모든 작업을 허용하는 식이다.
  • 이는 다음 에제처럼 인가 정책의 when 속성을 사용해 구현할 수 있다.
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "allow-mesh-all-ops-admin"
  namespace: istio-system
spec:
  rules:
  - from:
    - source:
        requestPrincipals: ["auth@istioinaction.io/*"]
    when:
    - key: request.auth.claims[groups] # 이스티오 속성을 지정한다
      values: ["admin"] # 반드시 일치해야 하는 값의 목록을 지정한다
  • 첫째, 토큰은 요청 주체 auth@istioinaction.io/* 가 발급한 것이어야 한다.
  • 둘째, JWT에 값이 ‘admin’인 group 클레임 claim이 포함돼 있어야 한다.
    • 또한 notValues 속성을 사용해 이 정책을 적용하지 않아야 하는 값들을 정의할 수도 있다.

7.  값 비교 표현식 이해하기 Understanding value-match expressions

  • Exact matching of values 일치. 예를 들어 GET은 값이 정확히 일치해야 한다.
  • Prefix matching of values 접두사 (매칭)비교. 예를 들어 /api/catlog* 는 /api/catalog/1 과 같이 접두사로 시작하는 모든 값에 부합한다.
  • Suffix matching of values 접미사 (매칭)비교. 예를 들어 *.istioinaction.io 는 login.istioinaction.io 와 같이 모든 서브도메인에 부합한다.
  • Presence matching 존재성 (매칭)비교. 모든 값에 부합하며 *로 표기한다. 이는 필드가 존재해야 하지만, 값은 중요하지 않아 어떤 값이든 괜찮음을 의미한다.

8.  인가 정책이 평가되는 순서 이해하기 Understanding the order in which authorization policies are evaluated

  1. CUSTOM policies are evaluated first. CUSTOM 정책이 가장 먼저 평가
  2. DENY policies are evaluated next. If no DENY policy is matched . . .
    • 다음으로 DENY 정책이 평가된다. 일치하는 DENY 정책이 없으면…
  3. ALLOW policies are evaluated. If one matches, the request is allowed. Otherwise. . .
    • ALLOW 정책이 평가된다. 일치하는 것이 있으면 허용된다. 그렇지 않으면…
  4. According to the presence or absence of a catch-all policy, we have two outcomes: 일반 정책의 존재 유무에 따라 두 가지 결과가 나타난다.
    1. When a catch-all policy is present, it determines whether the request is approved. 일반 정책이 존재하면, 일반 정책이 요청 승인 여부를 결정한다.
    2. When a catch-all policy is absent, the request is: 일반 정책이 없으면, 요청은 다음과 같다.
      • Allowed if there are no ALLOW policies, or it’s ALLOW 정책이 없으면 허용된다.
      • Rejected when there are ALLOW policies but none matches. ALLOW 정책이 있지만 아무것도 해당되지 않으면 거부된다.
728x90