Programming-[Backend]/Keycloak

Keycloak - 7. 인가 전략, 프로덕션 환경 구성, 캐시 클러스터링

컴퓨터 탐험가 찰리 2024. 8. 3. 10:45
728x90
반응형

KeyCIoak - 모던 애플리케이션을 위한 ID 및 접근관리 | 에이콘출판(주) | 스티안 토르거센, 페드로 이고르 실바 지음, 최만균 옮김

서적을 참고하여 요약한 시리즈 글이다.

 


 

1. 인가 이해하기

 

1.1 인가 승인 여부 판단 요소

인가 승인 여부를 판단할 때 고려해야할 요소는 보통 다음과 같다.

  • 사용자는 누구인가?
  • 사용자와 관련된 데이터는 무엇인가?
  • 리소스에 접근하기 위한 조건은 무엇인가?

 

1.2 접근 제한 구현 패턴

보호된 리소스에 적용되는 접근 제한을 구현하는 주요 인가 패턴은 두 가지가 존재한다. 두 가지는 상호 배타적이지 않으며 둘 다 동시에 적용할 수도 있다.

  1. 메타데이터 및 설정을 사용하여 선언적, 프로그래밍적 방식으로 애플리케이션 수준에서 제어
  2. 통합 인가: 애플리케이션의 외부 서비스에 접근 허가 여부를 위임

Keycloak을 이용하여 다양한 인증 메커니즘을 살펴본다.

 

1.2.1 RBAC(Role-Based Access Control) 활용

 

Keycloak에서 role의 종류는 두 가지이다. realm roles는 realm 레벨에서 정의된다. realm에 존재하는 다양한 클라이언트에 상관없이 조직 전체 내에서의 사용자의 역할을 나타낸다. client roles는 클라이언트에 따라 다르게 적용되는 role이다.

 

또한 역할을 너무 많이 설정하여 무분별한 역할 사용(role explosion)이 일어나는 것을 피해야한다. 역할을 생성할 때는 역할이 관련된 범위와 역할과 관련된 권한의 세분성(graularity)을 고려하여 매우 신중하게 생성해야한다. 또한 세분화된 인가를 위해서 역할을 생성하면 안된다. 역할을 인가를 위해 사용하면 안된다.

 

그룹을 활용하면 특정 권한을 개별 사용자들에게 일일이 부여하지 않아도 되기 때문에, 역할 관리 이슈를 일부 해결할 수 있다.

 

Keycloak에서는 복합 역할(composite role) 개념도 제공한다. 복합 역할을 부여받은 사용자는 chain에 포함된 모든 역할이 자동으로 부여된다. 다만 너무 확산되면 관리 효율성이 떨어질 수 있으므로 되도록이면 그룹을 사용하는 것이 더 적절하다.

 

1.2.2 GBAC(Group-Based Access Control) 활용

 

Keycloak의 그룹은 계층 구조이다. 예를 들어 human resource 그룹이 있을 수 있고 하위에 manager 그룹이 있을 수 있다.

 

그룹 멤버십을 토큰에 매핑 해보기

역할과 달리 그룹 정보는 자동으로 토큰에 포함되지 않는다. 만약 포함시키고 싶다면 mapper를 원하는 client에서 설정해줘야한다. 

 

다음 클라이언트와 유저를 생성한다. alice 유저는 이전 장에서 이미 만들었다. localhost:8180에 떠있는 컨테이너를 그대로 사용해도 된다.

  • Client ID: myclient
  • Username: alice

 

myclient -> Client Scopes -> myclient-dedicated로 이동해서 mapper를 추가한다.

  • Name: groups
  • Mapper Type: Group Membership
  • Token Claim Name: groups

 

groups을 추가한다.

  • Name: Project Management Office

 

alice 유저를 상기 생성한 group에 포함시킨다. users -> alice의 상세 페이지에 들어가서 하면 된다.

 

다음으로 myclient -> Client Scope 페이지에 들어가서 평가 도구(Evaluate)를 사용하여 그룹 정보가 토큰에 추가되는 것을 확인한다.

 

 

 

1.2.3 OAuth2 범위 활용

Keycloak은 OAuth2 인가 서버로써 동작한다. 그리고 Keycloak을 OAuth2 인가 서버로 활용하기 좋은 상황은 서드 파티 애플리케이션의 리소스에 접근 여부에 대한 결정을 사용자에게 위임하고 싶을 때이다. OAuth2의 범위를 사용한 권한 부여는 전적으로 사용자 동의를 기반으로 한다.

 

 

1.2.4 ABAC 활용(Attribute-Based Access Control)

토큰에 포함되어있는 claims를 기반으로 접근 제한을 하는 방식이다. 인증 컨텍스트에 대한 정보 뿐아니라 ID와 관련된 다양한 속성들을 사용하여 리소스에 대한 접근 제한을 수행할 수 있다.

 

 

1.2.5 통합 Keycloak 인가 서버 활용

통합 인가는 외부 인가 서비스를 활용하여 애플리케이션의 접근 관리 및 의사 결정을 외부화한다.

 

예를 들어 아래와 같은 코드가 있다면,

If (User.hasRole("manager")) {
  //자원에 접근 가능
}

 

이것은 RBAC 방식이다. 이렇게 구성한 경우 요구사항이 변경되어 특정 사용자에게도 이런 권한을 준다던가, 다른 역할이 부여된 사용자에게 이 리소스에 접근할 수 있는 권한을 줄려면 코드를 변경해야한다.

 

통합 인가의 경우 아래와 같은 코드로 구성될 수 있다.

If(User.canAccess("Manager Resource")) {
  //접근 가능
}

 

애플리케이션 코드에 영향은 미치지 않으면서, Manager Resource에 접근할 수 있는 정책을 변경하기만 하면 된다.

 

Keycloak은 Authorization Services를 통해서 통합 인가를 지원한다. 모든 작업은 Keycloak 관리 콘솔과 REST API를 통해 관리된다. 그리고 이를 위해 ABAC 방식을 사용한다. 이런 방식의 일반적인 이슈는 접근 인가 결정을 하기 위해서 추가적인 통신이 필요하다는 것이다. 다만 토큰 기반 인가 방식을 사용하면 애플리케이션은 내부적으로 토큰을 검증하는 것 이외에는 네트워크 요청을 더 할 필요가 없다.

 

 

 

2. 프로덕션 환경을 위한 Keycloak 설정

 

2.1 Keycloak 호스트 네임 설정

 

keycloak은 백엔드, 프론트엔드, 관리자용 그룹의 엔드포인트를 통해 관리 및 통신을 한다. 이 통신의 기본이 되는 각 그룹의 base URL이 중요한 역할을 한다. 토큰 발급, 유효성 검증 및 사용자 리다이렉트 등의 기준이 되는 위치이기 때문이다.

 

2.1.1 프론트엔드 URL 설정(hostname)

 

여러 인스턴스가 있는 경우에도 frontend URL은 같은 곳을 바라봐야한다. 또한 단일 발급자 이름(issuer name)으로 그룹화되어야한다. Keycloak에서 생성된 토큰과 쿠키가 모든 인스턴스에서 유효하게 하기 위함이다. 다시 말해 요청을 처리하는 노드에 관계없이 베이스 URL이 동일해야하고 Keycloak이 사용하는 공용 도메인 이름과 일치해야한다.

 

책에서는 ch9/configure-hostname.cli를 실행하여 프론트엔드 URL을 설정한다. container 내부의 jboss.sh를 통해 실행하라고 하는데, keycloak 22 버전부터 jboss.sh가 사라진 것 같다. 그래서 공식 문서를 참고하여 설정하는 방법에 대해서 알아봤다.

 

https://www.keycloak.org/server/hostname

 

Configuring the hostname (v2) - Keycloak

As demonstrated in the previous example, the scheme and port are not explicitly required. In such cases, Keycloak automatically handles these aspects. For instance, the server would be accessible at https://my.keycloak.org:8443 in the given example. Howeve

www.keycloak.org

 

 

 

해당 페이지를 읽어본 내용은 대략 아래와 같다.

 

hostname 설정의 중요성

기본적으로 Keycloak이 hostname을 자동으로 설정해준다. hostname을 설정하면 그 이름대로 사용자의 비밀번호 재설정 email용 redirect 주소 등이 설정된다. 이 설정이 만약 유동적이라면 해커가 도메인을 조작할 수도 있을 것이다. 명시적으로 hostname을 설정해서 해킹의 위험을 피할 수 있다. 설정은 아래 명령어로 할 수 있다.

 

production 환경에서 container를 띄울 때 문제 👿

나의 경우 keycloak/keycloak docker container를 실행 중이라서, container를 하나 새로 띄웠다. 여기서 중요한 부분은 start-dev로 해서는 kc.sh start 명령어가 작동하지 않는다는 것이다. 그래서 이전에 사용했던 container 외에 production용으로 새로 container를 띄웠다.

 

책에서는 jboss 및 cli 파일들을 이용하는데, 버전이 바뀌어서 그런지 jboss는 kc.sh로 통합된 것 같았다. 그리고 각종 설정 해줄 내용들이 많아서, 명령어로 처리하는 것이 아니라 Dockerfile 및 docker-compose.yml 파일로 production용 container를 띄웠다. 기본적인 코드는 공식 가이드 문서를 참고했다. 꽤나 애를 먹었다.

https://www.keycloak.org/server/hostname

 

Dockerfile

FROM quay.io/keycloak/keycloak:latest as builder

# Enable health and metrics support
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true

# Configure a database vendor
ENV KC_DB=postgres

WORKDIR /opt/keycloak

# for demonstration purposes only, please make sure to use proper certificates in production instead
RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore
RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:latest
COPY --from=builder /opt/keycloak/ /opt/keycloak/

# change these values to point to a running postgres instance
ENV KC_DB=postgres
ENV KC_DB_URL=jdbc:postgresql://host.docker.internal:5432/admin
ENV KC_DB_USERNAME=admin
ENV KC_DB_PASSWORD=admin
ENV KC_HOSTNAME=localhost
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start", "--optimized"]

 

중요 내용😎

  • postgres로 DB를 설정하는 것을 볼 수 있다. 해당 Dockerfile에 연결되는 docker-compose 파일에서 postgres를 실행시킬 것이다. DB도 container로 띄울 것이기 때문에 host.docker.internal의 주소로 설정했다. localhost:5432로 하면 Connection Refussed가 걸린다.
  • keytool을 이용해서 keystore를 생성하고, WORKDIR인 /opt/keycloak 하위에 /conf/server.keystore에다가 저장하는 것을 볼 수 있다. 이 production 환경은 TLS를 적용한 HTTPS를 사용하기 때문에 인증키 및 인증서가 필요하다. 주석문에 나와있는 것처럼 실제 운영 환경에서의 인증서(certificate)는 따로 만들어서 keystore에 넣어주어야한다.
  • kc.sh start 명령어를 entrypoint로 잡는다. kc.sh --help를 실행해보면 이미지만 만들어주는 build, 이전에 실습한 start-dev 등 다양한 lauch 옵션이 있는 것을 확인할 수 있다.
  • --optimized 옵션을 적용하면 기본 최적화된 keycloak 서버를 실행한다. 특히, health check과 metric을 위한 endpoint를 열어준다. 위 KC_HEALTH_ENABLED, KC_METRICS_ENABLED와 상관없이 --optimized 옵션을 줘야한다.

 

docker-compose.yml

kc1, kc2, kc3로 keycloak을 3개 띄우는 것은 고가용성을 위한 것이다. 다음 글의 리버스 프록시와 연관되어 있는 것으로, 리버스 프록시를 활용하여 클러스터링된 3개의 인스턴스에 부하 분산을 적용하기 위해 미리 docker-compose.yml 파일을 작성해두었다. 필요 없다면 kc1만 남기고 kc2, kc3는 제거해도 된다.

version: '3.8'

services:
  postgres:
    image: postgres:13
    environment:
      POSTGRES_DB: admin
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin
    ports:
      - 5432:5432
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - keycloak-network

  kc1:
    build: .
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: admin
    ports:
      - 8443:8443
    depends_on:
      - postgres
    command: --https-key-store-file=/opt/keycloak/conf/server.keystore --https-key-store-password=password --hostname=my.keycloak.org
    networks:
      - keycloak-network
  kc2:
    build: .
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: admin
    ports:
      - 8543:8443
    depends_on:
      - postgres
    command: --https-key-store-file=/opt/keycloak/conf/server.keystore --https-key-store-password=password --hostname=my.keycloak.org
    networks:
      - keycloak-network
  kc3:
    build: .
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: admin
    ports:
      - 8643:8443
    depends_on:
      - postgres
    command: --https-key-store-file=/opt/keycloak/conf/server.keystore --https-key-store-password=password --hostname=my.keycloak.org
    networks:
      - keycloak-network

volumes:
  postgres_data:

networks:
  keycloak-network:
    driver: bridge
  • environment 값으로 KEYCLOAK_ADMIN, KEYCLOAK_ADMIN_PASSWORD 값을 넣어준다. 이 값을 넣지 않으면 처음 Keycloak 페이지 접근 시 설정을 하라는 안내문구가 뜬다.
  • command 구문에서 https용 keystore와 password를 Dockerfile에서 지정해준 값으로 처리하는 것을 볼 수 있다.
  • command 구문에서 --hostname 값을 지정한다. 이렇게하면 localhost:8443으로 접속 시 --hostname으로 지정한 도메인으로 리다이렉트 되는 것을 확인할 수 있다.(DNS)
  • localhost에서 DNS를 설정했기 때문에, hostname의 my.keycloak.org로 접속 시 실제로 실행 중인 localhost:8443으로 매핑되도록 해주어야한다. mac의 경우 /etc/hosts에 DNS Mapping 정보를 추가하여 처리할 수 있다. sudo로 실행해야 쓰기 권한이 주어진다.(아래 사진 참고)

 

 

이렇게 해서 BASE URL이 되는 hostname 및 프론트엔드 도메인 주소를 설정하였다.

 

2.1.2 backend-channel 및 administration URL 설정

 

아래 공식 가이드 문서의 background - server endpoints 부분을 참고하면 된다.

https://www.keycloak.org/server/hostname

 

backend-channel

backend-channel URL 설정은 reverse-proxy 등을 타지 않고 local 등에서 client - Keycloak과 통신을 할 때 설정하여 유동적으로 줄 수 있다. 다음과 같이 옵션에 true | false 값으로 줘서 사용한다.

bin/kc.[sh|bat] start --hostname https://my.keycloak.org --hostname-backchannel-dynamic true

 

administration URL

admin console용 URL도 따로 설정할 수 있다. 콘솔을 렌더링하기 위한 링크와 정적 리소스를 사용자가 정의한 URL로만 접근할 수 있도록 하는 목적도 있다.

bin/kc.[sh|bat] start --hostname https://my.keycloak.org --hostname-admin https://admin.my.keycloak.org:8443

 

 

 

3. 클러스터링 설정

 

책에 나오는 클러스터링 설정 실습 내용은 최신 버전의 keycloak에 맞지 않고 실행도 되지 않는다. 클러스터링을 해야한다면 아래 공식 가이드를 참고하여 진행하는 것이 더 좋을 것 같다.

https://www.keycloak.org/high-availability/introduction

 

여기서는 책에 나온 이론적인 내용만 정리해놓는다.

 

클러스터링 및 전체 고가용성을 활성화하기 위해서 다음 내용을 수행해야한다.

  • 고가용성 설정 프로파일을 사용해 서버 실행
  • 리버스 프록시가 여러 인스턴스에 부하를 분산하도록 설정돼 있는지 확인

Keycloak은 데이터베이스에 보관된 영구 데이터 외에도 캐시 계층을 사용해 데이터의 신속한 접근을 돕는다. keycloak에서는 Infinispan이라는 데이터 스토어를 기반으로 동작한다. 캐시에 관한 내용은 아래 문서를 통해 확인할 수 있다.

https://www.keycloak.org/server/caching

https://www.keycloak.org/high-availability/connect-keycloak-to-external-infinispan

 

이 중 첫 번째 문서인 캐시 계층 설정에 대해서 공부해본다.

 

3.1 분산 캐싱 설정

 

kc.sh start 명령어로 production 모드로 keycloak을 실행시키면 caching이 활성화된다. 명시적으로 caching을 설정할려면 아래 명령어를 입력하면 된다. 만약 개발단이나 로컬에서 테스트를 하고 싶다면 --cahce=local로 주면 된다.

bin/kc.[sh|bat] start --cache=ispn

 

그리고 네트워크에 엮여있는 모든 Keycloak 노드들이 찾아진다. 캐싱은 UDP 전송 계층을 기반으로 하기 때문에 UDP IP multicast를 통해 노드들이 찾아지는 원리이다. 원한다면 이 방식 외에 다른 방식으로 node들을 찾을 수도 있다.

 

캐시 설정 옵션

캐시 설정은 conf/cache-ispn.xml에 기본값들이 설정되어있다. 이 값들에 대한 정보는 공식 가이드 문서의 아래 표를 참고하면 된다.

 

 

3.2 캐시 타입과 기본값

defaults를 기본값으로 해석해도 될 지 모르겠다.

 

3.2.1 Local Cache

클러스터 내의 각 노드에 로컬 데이터로써 저장되는 캐시이다. 종류는 다음과 같다.

  • realms: clients, roles, groups와 같은 정보
  • users: granted roles, group memberships 같은 정보
  • authorization: resources, permissions, policies와 같은 정보
  • keys

기본적으로 10,000개의 정보를 캐싱한다. keys의 경우 1,000개이다.

 

로컬 캐시 무효화

각 노드에 저장된 캐시 중 일부가 업데이트되면 다른 노드에도 이 업데이트 사항을 반영해야한다. 업데이트되어 각 노드에 반영되기 전의 캐시를 work cache 라고 부른다. 이 work cache가 각 노드에 업데이트 요청을 보내고, 기존에 남아있던 캐시는 무효화된 후에 work cache가 반영되는 구조이다.

 

Sessions

사용자의 인가, 로그인, 반영구적인(Persistent User Sessions), Offline user sessions 등 다양한 세션 종류가 있다. 이런 세션 값들도 기본적으로 노드당 10,000개가 저장되며 최적값을 설정할 수도 있다. 자세한 내용은 가이드 문서를 참고하도록 한다.

 

 

3.2.2 가용성

몇 개의 노드에 캐시 정보를 저장할 것인가를 설정할 수 있다. 카프카 같은 다른 툴에서도 가용성을 위해 최소 3개의 노드를 두라는 것처럼 Infinispan도 최소 3개의 노드에 대해 이야기하고 있다. owners 라는 속성값으로 어떤 노드들이 cache를 가졌는지 설정할 수 있으며 conf/cache-ispn.xml 파일에 owners=<value> 값으로 줄 수 있다고 한다.

 

 

기타 Transport stack, 보안 및 metrics 부분은 일단 넘어가기로 한다.

728x90
반응형