카카루1331
아틀리에 카카루!
카카루1331
전체 방문자
오늘
어제
  • 전체 (44)
    • 공학 (41)
      • 정보성 (11)
      • 에세이 (13)
      • 단상 (16)
      • 놀이 (1)
    • 인문 (3)
      • 생각 (3)

공지사항

  • 반갑습니다

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
카카루1331

아틀리에 카카루!

Istio 인증/인가
공학/정보성

Istio 인증/인가

2022. 7. 23. 21:10

주저리

microservice!

microservice는 작디작은 서비스다.

그리고 Microservice Architecture(MSA)는 microservice 집합으로 어플리케이션을 이룬다.

 

From Monolith application to Microservice application

한편 현존하는 과반수의 앱의 구조는 MSA와는 거리가 멀 것이다.

(우리 회사 포함)

대대적인 개편의 바람이 불어올 때 그제서야 MSA를 차용할 거란 말이다.

 

한편 MSA 적용은 마치 18세기 유럽의 산업 혁명의 모습과 비슷하다.

아래와 같은 점에서 말이다.

 

  • 새로운 도구의 활용 (Docker, Kubernetes, Istio, Argo 등)
  • 새로운 분업 체계 (bounded context)

 

따라서 MSA의 적용은 단순히 서비스의 개수가 여러 개로 늘어나는 것뿐만 아니다.

환골탈태 수준의 부가적인 변화를 동반한다.

 

한편 Microservice 전환 시 고려사항 중 중요한 것은

'cross-cutting concern'이다.

복수의 서비스에 공통적으로 필요한 것 말이다.

인증/인가는 그 대표적인 예시다.

end user의 request 검증은 어느 서비스나 필요할 테니까 말이다.

 

여하튼 서론은 슬슬 마치고 본론으로 들어간다.

이 글의 제목과 주제를 밝힌다.

제목은 Microservice 인증/인가로 하겠습니다.
근데 이제 Istio를 곁들인.

분석 및 설계

Microservice는 인증이 필요해

서비스는 요청 정보를 반드시 검증해야 한다.

검증 과정이 없다면 외부 이용자에 의해 시스템 리소스가 엉망진창이 될 위험이 있기 때문이다.

 

 

위 예시에서는 인증/인가(authn/z) 역할을 각각의 서비스에 구현했다.

이를 통해 외부 요청으로부터 시스템과 리소스를 안전하게 지킬 수 있다.

하지만 불편함이 눈에 띈다.

'중복'이라는 불편함이 말이다.

 

Istio 인증/인가

인증/인가를 별도의 서비스로 분리하자!

Istio를 이용해서 다음과 같은 구조를 차용할 수 있다.

 

이전보다 두 가지 컴포넌트가 추가됐다.

하나씩 그 역할을 알아보자.

 

Ingress Gateway

모든 외부 요청은 Istio Ingress Gateway를 거친다.

외부 요청 정보를 분석한다.

별도의 인증이 필요할 경우 인증 서비스에 요청을 전달한다.

 

예컨대 다음과 같은 두 가지 요청에 대해 생각해보자.

 

GET /user/123
Host: www.example.com

특정 사용자의 개인정보 조회는 회원 인증이 필요할 수 있다.

또한 회원은 자신의 정보만 조회할 수 있다면, 권한 확인(인가) 또한 필요하다.

Ingress Gateway는 해당 요청을 authN/Z 서비스로 전달한다.

 

GET /catalog/81
Host: www.example.com

상품 정보는 누구나 조회할 수 있다. 인증 처리가 불필요하다.

외부 요청을 곧바로 catalog 서비스로 전달한다.

 

authN/Z

요청에 대한 인증 및 인가 처리를 담당한다.

로그인 또는 사용자 권한 등을 확인하는 것이 그 예다.

 


구현

상세 코드는 아래 Github에서 확인 가능

Istio

  • https://github.com/kakaru1331/istio-sandbox/tree/main/task/external-authorization/custom

인증 서비스

  • https://github.com/kakaru1331/xauth

 

환경 설정

구현에 앞서 아래와 같은 환경 설정이 선행되어야 한다.

 

  • kubernetes 클러스터
  • kubectl
  • Istio 설치
    • https://istio.io/latest/docs/setup/install/istioctl/ 참고
  • istioctl
  • 테스트용 모의 서비스
    • htttpbin
    • sleep

테스트용 모의 서비스는 다음과 같이 배포한다.

sample 파일은 istioctl에 포함된 것을 참고한다.

kubectl create ns foo
kubectl label ns foo istio-injection=enabled
kubectl apply -f samples/httpbin/httpbin.yaml -n foo
kubectl apply -f samples/sleep/sleep.yaml -n foo

 

서비스 공개

현재까지의 설정으로는 Kubernetes 클러스터 내부 통신만 가능하다.

몇 가지 설정을 추가해서 외부에 서비스를 공개해보자.

 

Gateway

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

 

kubectl apply -f https://github.com/kakaru1331/istio-sandbox/blob/main/task/external-authorization/custom/gateway.yaml -n foo

클러스터 외부 요청을 받기 위해 Gateway를 생성한다.

80 port 대상의 요청을 처리할 수 있다.

 

VirtualSerivce

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "*"
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        port:
          number: 8000
        host: httpbin

 

k apply -f https://raw.githubusercontent.com/kakaru1331/istio-sandbox/main/task/external-authorization/custom/virtualservice.yaml -n foo

 

Virtual Service는 특정 Gateway로 들어온 요청을 골라서 처리할 수 있다.

httpbin-gateway로 들어온 모든 요청을 httpbin 서비스로 전달한다.

 

여기까지 설정을 마쳤다면 위와 같이 httpbin 페이지를 확인할 수 있을 것이다.

 

중간 정리

 

Gateway, VirtualService, httpbin 등을 등록했다.

덕분에 클러스터 외부의 요청을 접수하여 downstream 서비스로 전달할 수 있다.

위 diagram과 같은 구조로 통신이 이루어진다.

 

 

인증/인가 서비스

인증/인가 서비스를 추가한다.

해당 서비스의 역할은 사용자 요청 header 검증을 하는 것이다.

요청에 따라서 다음과 같이 세 가지 유형의 응답을 반환한다.

 

  1. 인증 header 없음 -> 인증 실패
    HTTP Status Code: 301 (redirect)
    google 로그인 페이지로 redirect  처리

  2. 인증 값 유효하지 않음 -> 인증 실패
    HTTP Status Code: 403 (unauthorized)

  3. 인증 성공
    HTTP Status Code: 200 (OK)

인증 성공 조건은 아래와 같다.

Request Headers:
  x-ext-authz: allow

 

인증 서비스는 Java로 작성했다.

package me.kakaru.xauth;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;

@RestController
public class WebController {

    String AUTH_HEADER = "x-ext-authz";
    String ALLOWED_VALUE = "allow";
    String DENY_BODY = String.format("denied by ext_authz for not found header %s: %s", AUTH_HEADER, ALLOWED_VALUE);
    String OAUTH_URI = "https://accounts.google.com/signin";

    @RequestMapping(value = "**", method = { RequestMethod.GET, RequestMethod.POST})
    public ResponseEntity index(HttpServletRequest request) {
        String authHeader = request.getHeader(AUTH_HEADER);

        if (StringUtils.isEmpty(authHeader)) {
            return ResponseEntity
                    .status(HttpStatus.MOVED_PERMANENTLY)
                    .location(URI.create(OAUTH_URI))
                    .build();
        } else if (checkAuthHeader(authHeader)) {
            return ResponseEntity.status(HttpStatus.OK).build();
        } else {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(DENY_BODY);
        }
    }

    boolean checkAuthHeader(String authHeader) {
        if (StringUtils.isEmpty(authHeader)) {
            return false;
        } else if (!ALLOWED_VALUE.equals(authHeader)) {
            return false;
        }

        return true;
    }
}

 

해당 서비스의 container image는 Github registry에 등록해두었다.

이를 활용하여 인증 서비스를 클러스터에 배포한다.

apiVersion: v1
kind: Service
metadata:
  name: custom-authz
  labels:
    app: custom-authz
spec:
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  selector:
    app: custom-authz
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-authz
spec:
  replicas: 1
  selector:
    matchLabels:
      app: custom-authz
  template:
    metadata:
      labels:
        app: custom-authz
    spec:
      containers:
      - image: ghcr.io/kakaru1331/xauth:latest
        imagePullPolicy: IfNotPresent
        name: custom-authz
        ports:
        - containerPort: 8080
kubectl apply -f https://raw.githubusercontent.com/kakaru1331/istio-sandbox/main/task/external-authorization/custom/ext-authz-custom.yaml -n foo

 

배포 후 아래 명령어를 사용해서 인증 서비스를 검증한다.

인증 header key & value가 없기 때문에 301 Status Code를 반환한다.

kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath='{.items..metadata.name}')" -c sleep -n foo -- curl http://custom-authz.foo:8080/ -s -o /dev/null -w "%{http_code}\n"
301

 

Extension Provider

인증 서비스를 Extension Provider로 등록한다.

추후 Authorization Policy에서 해당 provider를 사용할 것이다.

data:
  mesh: |-
    # extensionProviders 추가
    extensionProviders:
    - name: "custom-authz-http"
      envoyExtAuthzHttp:
        service: "custom-authz.foo.svc.cluster.local"
        port: "8080"
        includeRequestHeadersInCheck: ["x-ext-authz"]
    accessLogFile: /dev/stdout
    defaultConfig:
      discoveryAddress: istiod.istio-system.svc:15012
      proxyMetadata: {}
      tracing:
        zipkin:
          address: localhost:9411
    enablePrometheusMerge: true
    enableTracing: false
    outboundTrafficPolicy:
      mode: ALLOW_ANY
    trustDomain: cluster.local
  meshNetworks: 'networks: {}'

 

Istio Authorization Policy

'AuthorizationPolicy'는 특정 서비스로의 요청에 인증 처리를 덧붙일 수 있다.

httpbin workload로 향하는 요청에 인증 정책을 명시한다.

그리고 모든 요청이 아니라, '/headers' path에 해당하는 요청만 인증 처리한다.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: custom-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: custom-authz-http
  rules:
  # The rules specify when to trigger the external authorizer.
  - to:
    - operation:
        paths: ["/headers"]
kubectl apply -f https://raw.githubusercontent.com/kakaru1331/istio-sandbox/main/task/external-authorization/custom/authorization-policy.yaml -n foo

 

리뷰

마침내 서두에서 언급한 대로 구현해냈다.

Microservice 인증/인가
근데 이제 Istio를 곁들인.

 

마지막으로 인증 서비스 검증을 해보자.

 

1. 인증 header 없음 -> 인증 실패
HTTP Status Code: 301 (redirect)
google 로그인 페이지로 redirect  처리

 

2. 인증 값 유효하지 않음 -> 인증 실패
HTTP Status Code: 403 (unauthorized)

kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath='{.items..metadata.name}')" -c sleep -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

 

3. 인증 성공
HTTP Status Code: 200 (OK)

kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath='{.items..metadata.name}')" -c sleep -n foo -- curl http://httpbin.foo:8000/headers -H 
"x-ext-authz: allow" -s
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.foo:8000",
    "User-Agent": "curl/7.81.0-DEV",
    "X-B3-Parentspanid": "884299afdb8f44e4",
    "X-B3-Sampled": "0",
    "X-B3-Spanid": "48b39b00dfe50674",
    "X-B3-Traceid": "715931340af86da8884299afdb8f44e4",
    "X-Envoy-Attempt-Count": "1",
    "X-Ext-Authz": "allow",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/httpbin;Hash=421dc812743a90f4bafae747ead1228b8f44383dcb0dbd98f09e6d8977a3c4cc;Subject=\"\";URI=spiffe://cluster.local/ns/foo/sa/sleep"
  }
}

 

마치며

먹고 살기 쉽지 않음. (ㅠ.ㅠ)

배우랴, 만들랴, 글쓰랴.

여친도 없는데 인생 왜 이리 바쁜지...

여하튼 훗날 이 글을 읽을 독자에게 도움이 되길 바라며. (^3^)

 

References

 

 

External Authorization

Shows how to integrate and delegate access control to an external authorization system.

istio.io

 

저작자표시 비영리 (새창열림)

'공학 > 정보성' 카테고리의 다른 글

Windows kubectl alias 설정  (0) 2022.02.27
코딩 컨벤션: 예시로 알아보자!  (0) 2021.09.05
node 프로젝트 라이브러리 버전 관리법  (0) 2021.01.24
자바스크립트 클로저(closure)  (0) 2020.08.19
오라클 끄기 / 켜기  (0) 2020.08.16
    카카루1331
    카카루1331
    A bitcoin lover

    티스토리툴바