공부/EFK

Opendistro와 OIDC(Keycloak) 연동 on Kubernetes

토고미 2021. 4. 16. 10:27

EFK에 OIDC(Keycloak)를 연동하는 일을 맡았다.

 

구글에 분명히 정보는 많은데, 전부 제각각에다가 안 되는 경우가 대부분이었다.

그래도 어찌저찌 약 2주간의 삽질 끝에 성공시켜서 그 정보를 공유하고자 한다.

 

0. 주의사항

들어가기에 앞서, 기본 ElasticSearch와 Kibana는 OIDC를 사용할 수 없다.

정확히 말하면 사용할 수는 있지만 무료 라이센스는 사용할 수 없고, 최소 플래티넘 라이센스는 가지고 있어야 사용 가능하다.

dockerhub에서 제공하는 공식 image는 trial과 basic 라이센스만 지원하기 때문에 안된다.

아래 링크에 들어가서 OpenID Connect를 검색해보면 알 수 있다.

 

www.elastic.co/kr/subscriptions

 

그러므로 opendistro-elasticsearch와 opendistro-kibana를 사용해야만 한다.

opendistro는 엔터프라이즈급의 보안 기능을 제공하면서도, 100% 오픈 소스인 소프트웨어이다.

당연히 OIDC 기능도 포함되어있다.

 

opendistro 이미지는 아마존에서 제공하고 있다.

hub.docker.com/r/amazon/opendistro-for-elasticsearch

hub.docker.com/r/amazon/opendistro-for-elasticsearch-kibana

 

1. 환경

  • 이 글은 Kubernetes 환경에서 Opendistro-elasticsearch, Opendistro-Kibana의 OIDC로서 keycloak을 연동시키는 것을 목적으로 한다.

Kubernetes 환경이 아니거나 keycloak이 아니라면 약간씩 다를 수 있으니, 참고만 하길 바란다.

 

덧붙여서, 혼란을 최소화하기 위해 OIDC 로그인 까지만 설명할 것이고,

로그인 이후의 권한 문제는 다음 글에서 설명하도록 할 것이다.

 

  • opendistro 이미지는 1.9.0 버전을 사용했다.

처음에는 1.13.0 버전으로 테스트했는데, 자꾸 keycloak으로 로그인하면 401 unauthorized이 떴다.

구글링 해보니 버전을 다운그레이드 하면 된다길래 1.9.0 버전으로 하니 해당 이슈는 사라졌고,

그 이후 작업은 계속 1.9.0으로 해서 완성시켰다.

 

완성시킨 이후 1.13.0 버전으로 업그레이드 시켜보진 않아서 장담할 순 없으므로,

낮은 버전으로 할 것을 추천한다.

 

2. Keycloak 설정

  • Client 생성

keycloak 접속 -> realm 선택 -> clients -> create 으로 들어가서 아래와 같이 설정한다.

Keycloak client 설정

검은 네모 부분에 kibana URL을 적으면 된다.

빨간 줄은 선택 사항으로, basePath를 설정했을 시에만 추가로 적어주면 된다.

후에 kibana 설정에서 나오겠지만, 나는 basePath를 /api/kibana로 설정했기 때문에 추가했다.

 

참고로 구글링 하면 Access Type을 confiential로 해서 secret key를 이용하곤 하는데,

간단히 설명하기 위해 public으로 설정했다.

 

  • Users 생성

로그인 시 사용할 계정을 만드는 단계이다.

이미 keycloak에 계정이 있다면 건너뛰어도 괜찮다.

 

keycloak 접속 -> Users 선택 -> Add user로 들어가서 아래와 같이 설정한다.

Keycloak user 생성

 

롤 매핑에 대해서는 다음 글에서 설명하도록 하겠다.

 

3. 인증서와 키 설정

삽질하면서 가장 많이 시간을 잡아먹은 부분이면서, 가장 결정적인 문제가 인증서 관련 문제였다.

혹시 이미 인증서와 키를 제대로 설정해두었다면 이 부분은 넘어가도 좋다.

 

나의 경우엔 따로 root-ca가 존재했기 때문에, 이를 이용해야 했다.

root-ca로부터 인증서, 키를 발급받는 방법은 구글링 하길 바란다.

바로 .pem으로 발급받을 수 있다면 .pem으로 받는 것을 추천한다.

  • elasticsearch를 위한 인증서와 키를 2세트 발급받는다.

.crt .key 파일을 발급 받았다면 .pem으로 변환시켜야 한다.

간단하다. 확장자만 바꾸면 된다.

(crt와 key파일도 사용가능하지만, 나의 경우엔 오류가 나서 pem파일 형식으로 사용했다.)

 

아래는 transport layer의 TLS를 위한 .pem 이다.

transport layer는 TLS는 required 항목이므로 무조건 만들어줘야 하며,

elasticsearch 노드별로 따로 인증서와 키를 발급해줘야 한다.

$ cat some-cert.crt > opendistro-els-0.pem
$ cat some-key.key > opendistro-els-0-key.pem

 

아래는 REST layer의 TLS를 위한 .pem이다.

옵션 항목이므로 필수는 아니다.

단, transport layer에서 쓰이는 인증서, 키와는 다른 것을 사용해야만 한다.

$ cat some-http-cert.crt > esnode.pem
$ cat some-http-key.key > esnodeKey.pem

 

이후, 중요한 점이 있다. elasticsearch의 key는 반드시 pkcs8 형식으로 변환시켜줘야 한다.

위에서 발급받은 두 가지의 key를 모두 pkcs8 형식으로 바꿔주자.

$ openssl pkcs8 -topk8 -inform PEM -in esnodeKey.pem -out esnode-key.pem -nocrypt
$ openssl pkcs8 -topk8 -inform PEM -in opendistro-els-0-key.pem -out opendistro-els-0-key-8.pem -nocrypt

 

  • kibana는 .crt와 .key를 사용해서 설정할 것이므로 tls.crt와 tls.key파일을 발급받는다.

kibana의 key는 pkcs8 형식으로 바꿀 필요 없다.

 

  • 만든 인증서와 키를 컨테이너에 마운트 하기 위해 secret으로 생성하자.
$ kubectl create secret generic esnode-secret \
    --from-file=esnode-key.pem \
    --from-file=esnode.pem

$ kubectl create secret generic opendistro-secret \
    --from-file=opendistro-els-0-key-8.pem \
    --from-file=opendistro-els-0.pem
    
$ kubectl create secret generic opendistro-tls-secret \
    --from-file=tls.key \
    --from-file=tls.crt
    
// root-ca가 따로 있다면
$ kubectl create secret generic root-ca \
    --from-file=root-ca.pem

 

관련 공식문서는 아래 링크를 참조 바란다.

opendistro.github.io/for-elasticsearch-docs/old/0.9.0/docs/security/tls-configuration/

 

4. Opendistro-ElasticSearch 설정

두 가지 파일을 설정해서 마운트 한다. elasticsearch.yml과 config.yml이다.

  • /usr/share/elasticsearch/config 경로에 있는 elasticsesarch.yml 변경
kind: ConfigMap
apiVersion: v1
data:
  elasticsearch.yml: |
    network.host: 0.0.0.0

    ## A
    opendistro_security.ssl.transport.pemcert_filepath: stack-certs/opendistro-els-0.pem
    opendistro_security.ssl.transport.pemkey_filepath: stack-certs/opendistro-els-0-key-8.pem
    opendistro_security.ssl.transport.pemtrustedcas_filepath: root-ca.pem

    ## B
    opendistro_security.ssl.http.enabled: false
    opendistro_security.ssl.http.pemcert_filepath: certs/esnode.pem
    opendistro_security.ssl.http.pemkey_filepath: certs/esnode-key.pem
    opendistro_security.ssl.http.pemtrustedcas_filepath: root-ca.pem
    opendistro_security.allow_default_init_securityindex: true
	opendistro_security.ssl.transport.enforce_hostname_verification: false
    
    ## C
    opendistro_security.nodes_dn:
      - CN=opendistro-els-0,O=Org,L=San Francisco,ST=CA,C=KR
    
    ## D
    opendistro_security.authcz.admin_dn:
      - CN=opendistro-els,O=Org,L=San Francisco,ST=CA,C=KR

    opendistro_security.audit.type: internal_elasticsearch
    opendistro_security.enable_snapshot_restore_privilege: true
    opendistro_security.check_snapshot_restore_write_privileges: true
    opendistro_security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
    opendistro_security.protected_indices.enabled: true
    opendistro_security.protected_indices.indices: [".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opendistro-notifications-*", ".opendistro-notebooks", ".opendistro-asynchronous-search-response*"]
    cluster.routing.allocation.disk.threshold_enabled: false
    node.max_local_storage_nodes: 3
   
metadata:
  name: opendistro-els-config

A와 B에 위에서 발급받은 pem을 설정해준다.

 

나는 REST layer의 TLS를 false로 했지만, 설정값을 보여주기 위해 적어놓았다.

참고로 root-ca.pem은 내가 사용하는 root-ca이므로 각자의 상황에 맞게 수정해주자.

 

C는 A의 인증서에 관한 설정이며, elasticsearch 노드끼리 통신을 위한 것이다.

난 하나의 노드만 있으므로 위와 같이 설정했다.

 

마찬가지로 D는 B의 인증서에 관한 설정이며, REST api 권한에 관련된 것이다.

 

C와 D에 적힌 CN=..., O=...., L=...., 등은 본인이 발급받은 인증서에 따라 다르므로,

확인해서 수정해줘야 한다.

 

  • /usr/share/elasticsearch/plugins/opendistro_security/securityconfig 경로에 있는 config.yml 변경
...    
    config:
    ...
      dynamic:
      ...
        authc:
          kerberos_auth_domain:
            http_enabled: false
            transport_enabled: false
            order: 6
            http_authenticator:
              type: kerberos
              challenge: false
              config:
                # If true a lot of kerberos/security related debugging output will be logged to standard out
                krb_debug: false
                # If true then the realm will be stripped from the user name
                strip_realm_from_principal: true
            authentication_backend:
              type: noop
          basic_internal_auth_domain:
            description: "Authenticate via HTTP Basic against internal users database"
            http_enabled: true
            transport_enabled: true
            order: 0
            http_authenticator:
              type: basic
              challenge: false ## 변경 부분
            authentication_backend:
              type: intern
          ## 이 아래로 추가 부분
          openid_auth_domain:
            http_enabled: true
            transport_enabled: true
            order: 1
            http_authenticator:
              type: openid
              challenge: false
              config:
                subject_key: preferred_username
                roles_key: role
                openid_connect_url: https://{keycloak 주소}/auth/realms/{realm 이름}/.well-known/openid-configuration
            authentication_backend:
              type: noop
...

원래 기본적으로 생성되는 파일에 위와 같이 추가/수정한다.

 

 

기본 생성 파일을 모르겠다면 opendistro-elasticsearch를 그냥 실행시킨 뒤,

컨테이너에 접속해 해당 경로로 들어가서 가져오기 바란다.

귀찮다면 내가 올린 것을 참고해도 좋다.

config.yml
0.01MB

 

중요한 것은 윗부분만 고치면 된다.

 

  • 관련된 파일 마운트
.....
        volumeMounts:
        - name: elasticsearch-config
          mountPath: /usr/share/elasticsearch/config/elasticsearch.yml
          subPath: elasticsearch.yml
        - name: config
          mountPath: /usr/share/elasticsearch/plugins/opendistro_security/securityconfig/config.yml
          subPath: config.yml
        - name: esnode-secret
          mountPath: /usr/share/elasticsearch/config/certs
        - name: opendistro-secret
          mountPath: /usr/share/elasticsearch/config/stack-certs  
        - name: root-ca
          mountPath: /usr/share/elasticsearch/config/root-ca.pem
          subPath: root-ca.pem
......
      volumes:
      - name: elasticsearch-config
        configMap:
          name: opendistro-els-config
      - name: esnode-secret
        secret:
          secretName: esnode-secret
      - name: opendistro-secret
        secret:
          secretName: opendistro-secret
      - name: root-ca
        secret:
          secretName: root-ca
      - name: config
        configMap:
          name: opendistro-config
.....

 

5. Opendistro-Kibana 설정

kibana는 kibana.yml 파일만 수정하면 된다.

  • /usr/share/kibana/config 경로에 있는 kibana.yml 변경
apiVersion: v1
data:
  kibana.yml: |
    server.basePath: /api/kibana # basePath 설정 (Optional)
    server.rewriteBasePath: true # basePath 설정 시 true 값이어야 함

    server.name: kibana
    server.host: "0"
    elasticsearch.hosts: http://opendistro-els:9200
    elasticsearch.ssl.verificationMode: "none"
    elasticsearch.username: kibanaserver
    elasticsearch.password: kibanaserver
    elasticsearch.requestHeadersWhitelist: ["securitytenant","Authorization"]

    server.ssl.enabled: true
    server.ssl.key: config/certs/tls.key
    server.ssl.certificate: config/certs/tls.crt

    opendistro_security.multitenancy.enabled: true
    opendistro_security.multitenancy.tenants.preferred: ["Private", "Global"]
    opendistro_security.readonly_mode.roles: ["kibana_read_only"]

    opendistro_security.openid.root_ca: config/root-ca.pem
    opendistro_security.cookie.secure: false

    opendistro_security.auth.type: "openid"
    opendistro_security.openid.connect_url: "https://{keycloak 주소}/auth/realms/{realm 이름}/.well-known/openid-configuration"
    opendistro_security.openid.client_id: "kibana"
    opendistro_security.openid.base_redirect_url: "https://{kibana 주소}"
kind: ConfigMap
metadata:
  name: opendistro-kibana-config

opendistro_security.openid_connect_url은 OIDC provider마다 형식이 조금씩 다르다.

keycloak은 내가 쓴 형식으로 되어있다.

 

  • 관련된 파일 마운트
.....
        volumeMounts:
        - name: config
          mountPath: /usr/share/kibana/config/kibana.yml
          subPath: kibana.yml
        - name: tls
          mountPath: /usr/share/kibana/config/certs
        - name: root-ca
          mountPath: /usr/share/kibana/config/root-ca.pem
          subPath: root-ca.pem
.....
      volumes:
      - name: config
        configMap:
          name: opendistro-kibana-config
      - name: tls
        secret:
          secretName: opendistro-tls-secret
      - name: root-ca
        secret:
          secretName: root-ca

 

6.  배포 및 접속

이제 만들어 둔, secret, configMap, deployment들을 모두 적용시키면 된다.

그리고 드디어 키바나로 접속을 하면,

키바나 접속 시, keycloak redirect

keycloak으로 redirect 된다!

내가 사용하는 keycloak은 UI가 커스텀 돼서, 약간 달라 보일 수 있다.

그리고 keycloak에 등록돼있는 user로 로그인을 하면

Opendistro-Kibana

Kibana에 성공적으로 들어오게 된다!

 

다음 글에서는 유저별 롤 매핑을 통한 권한 제어를 살펴보도록 하겠다.

 

참고 사이트