IdP dex에 대해 알아보자!

DEX란?

  • 여러 IdP와 호환되는 플러그인들을 제공하여 일종의 connector와 같이 동작
  • client app은 dex만 바라보고 인증로직을 짜면되고, 뒷단의 IdP는 dex에서 관리
  • 제공하는 플러그인은 github, LDAP, SAML, 등등

Document : https://dexidp.io/docs/
OIDC에 대한 설명 : 호롤리/호다닥 공부해보는 SSO와 친구들 (SAML, OAuth, OIDC)

Tutorial

1. dex 빌드 및 실행

(21.11.17) go 버전은 1.15이상이어야 합니다.
dex github -> https://github.com/dexidp/dex

$ git clone https://github.com/dexidp/dex.git
$ cd dex/
$ make build

dex는 실행시킬 때 config파일을 파라미터로 받아서 실행됩니다.
클론받은 dex/example폴더에는 여러 예제들이 있고 지금은 example/config-dev.yaml파일을 가지고 실행시켜보겠습니다.

$ pwd
/home/user/dex

$ ./bin/dex  serve examples/ldap/config-ad.yaml

실행시키면 dex서버는 5556번 포트를 사용하여 올라오게 됩니다.

2. sample client app 실행

example app 빌드

$ pwd
/home/user/dex

$ make example

실행

$ ./bin/example-app
2022/11/07 06:18:15 listening on http://127.0.0.1:5555

3. 로그인 테스트

Login버튼을 누르면 dex로 redirect됩니다.

Log in with Email 선택

초기 계정 -> admin@example.com/password

Client app에서 요청하는 권한이 무엇인지 보여주는 페이지

허가하면 dex에서 받은 토큰값들을 Client 페이지에서 확인 가능

Config파일 살펴보기

샘플을 한번 돌려보았으니, 위에서 사용했던 sample config파일을 살펴보도록 하겠습니다.

Github -> https://github.com/dexidp/dex/blob/master/examples/config-dev.yaml

# 외부(Client)에서 접근 가능한 dex위치
issuer: http://127.0.0.1:5556/dex

# dex 상태저장용 db configuration
storage:
  type: sqlite3
  config:
    file: examples/dex.db

# dex HTTP endpoint
web:
  http: 0.0.0.0:5556

# Configuration for telemetry
telemetry:
  http: 0.0.0.0:5558

# Client 정보, clientId, redirect uri, secret 등
staticClients:
- id: example-app
  redirectURIs:
  - 'http://127.0.0.1:5555/callback'
  name: 'Example App'
  secret: ZXhhbXBsZS1hcHAtc2VjcmV0

# IdP 정보, 현재는 sample이라 mock처리됨
connectors:
- type: mockCallback
  id: mock
  name: Example

# Let dex keep a list of passwords which can be used to login to dex.
enablePasswordDB: true

# A static list of passwords to login the end user. By identifying here, dex
# won't look in its underlying storage for passwords.
#
# If this option isn't chosen users may be added through the gRPC API.
staticPasswords:
- email: "admin@example.com"
  # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
  hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
  username: "admin"
  userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

Ex1) Active Directory와 연결하기

✔ Active Directory는 이미 구축된 것을 전제
✔ Active Directory는 LDAP기반이라 LDAP용 config를 그대로 사용합니다.
LDAP용 sample config -> https://github.com/dexidp/dex/blob/master/examples/ldap/config-ldap.yaml

AD에서의 유저 정보를 파악하기 위해 ldapsearch 커맨드를 사용합니다.

$ ldapsearch -h {LDAP_SERVER} -b "CN=test,CN=Users,DC=adfs,DC=local" -D "Administrator@adfs.local" -w "76PPIWZBHpoQL339tH9O"

# extended LDIF
#
# LDAPv3
# base <CN=test,CN=Users,DC=adfs,DC=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# test, Users, adfs.local
dn: CN=test,CN=Users,DC=adfs,DC=local
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: test
givenName: test
distinguishedName: CN=test,CN=Users,DC=adfs,DC=local
instanceType: 4
whenCreated: 20221109161554.0Z
whenChanged: 20221110155359.0Z
displayName: test
uSNCreated: 12816
memberOf: CN=testGroup,CN=Users,DC=adfs,DC=local
uSNChanged: 13827
name: test
objectGUID:: W8IMeqB/akqJR4X8reHI8Q==
userAccountControl: 512
badPwdCount: 0
codePage: 0
countryCode: 0
badPasswordTime: 133125689091224229
lastLogoff: 0
lastLogon: 133125692391848639
pwdLastSet: 133124841542578900
primaryGroupID: 513
objectSid:: AQUAAAAAAAUVAAAAxjuNl/RArVbl+IgvUAQAAA==
accountExpires: 9223372036854775807
logonCount: 0
sAMAccountName: test
sAMAccountType: 805306368
userPrincipalName: test@adfs.local
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=adfs,DC=local
dSCorePropagationData: 16010101000000.0Z
lastLogonTimestamp: 133125692391848639

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

이제 요 정보를 기반으로 config를 수정해줄겁니다.

dex config파일 :

issuer: http://127.0.0.1:5556/dex
storage:
  type: sqlite3
  config:
    file: examples/dex.db
web:
  http: 0.0.0.0:5556

# AD configuration
connectors:
- type: ldap
  name: OpenLDAP
  id: ldap
  config:
    # 1) Plain LDAP, without TLS:
    # 기본적으로 AD의 port는 389
    host: xx.xx.xx.xx:389
    insecureNoSSL: true
    #
    # 2) LDAPS without certificate validation:
    #host: localhost:636
    #insecureNoSSL: false
    #insecureSkipVerify: true
    #
    # 3) LDAPS with certificate validation:
    #host: YOUR-HOSTNAME:636
    #insecureNoSSL: false
    #insecureSkipVerify: false
    #rootCAData: 'CERT'
    # ...where CERT="$( base64 -w 0 your-cert.crt )"

    # LDAP search를 진행할 인증용 계정
    bindDN: cn=Administrator,cn=Users,dc=adfs,dc=local
    bindPW: 76PPIWZBHpoQL339tH9O

    # Login 화면에서 보여줄 label
    usernamePrompt: AD Username

    userSearch:
      # User search의 baseDN
      baseDN: cn=Users,dc=adfs,dc=local

      # 아래 두개는 검색 필터 
      filter: "(objectClass=user)"
      username: name #({username}=입력한ID)

      # user정보와 매핑되는 attribute들
      idAttr: sAMAccountType
      emailAttr: userPrincipalName
      nameAttr: sAMAccountName

    groupSearch:
      # BaseDN to start the search from. It will translate to the query
      # "(&(objectClass=group)(member=<user uid>))".
      baseDN: dc=adfs,dc=local
      filter: "(objectClass=group)"
      userMatchers:
        # A user is a member of a group when their DN matches
        # the value of a "member" attribute on the group entity.
      - userAttr: distinguishedName
        groupAttr: member
      # The group name should be the "cn" value.
      nameAttr: cn

# Client 정보
staticClients:
- id: example-app
  redirectURIs:
  - 'http://127.0.0.1:5555/callback'
  name: 'Example App'
  secret: ZXhhbXBsZS1hcHAtc2VjcmV0

이 config로 dex를 띄우고나서 로그인을 시도하면 아래와 같은 로그를 확인할 수 있습니다.

time="2022-11-10T11:10:39Z" level=info msg="performing ldap search cn=Users,dc=adfs,dc=local sub (&(objectClass=user)(name=test))"
time="2022-11-10T11:10:39Z" level=info msg="username \"test\" mapped to entry CN=test,CN=Users,DC=adfs,DC=local"
time="2022-11-10T11:10:39Z" level=info msg="login successful: connector \"ldap\", username=\"test\", preferred_username=\"\", email=\"test@adfs.local\", groups=[]"

Client App에서 필요한 유저정보가 다를 수 있고 search 기준이 다를 수 있기 때문에 거기에 맞춰서 userSearch항목과 groupSearch항목을 수정하면 되겠습니다.

Appendix

IdP Metadata 얻기

dex는 OIDC기반이므로 다른 OIDC서비스들과 마찬가지로 metadata를 .well-known/openid-configuration path로 얻을 수 있습니다.

$ curl x.x.x.x:5556/dex/.well-known/openid-configuration

{
  "issuer": "http://x.x.x.x:5556/dex",
  "authorization_endpoint": "http://x.x.x.x:5556/dex/auth",
  "token_endpoint": "http://x.x.x.x:5556/dex/token",
  "jwks_uri": "http://x.x.x.x:5556/dex/keys",
  "userinfo_endpoint": "http://x.x.x.x:5556/dex/userinfo",
  "device_authorization_endpoint": "http://x.x.x.x:5556/dex/device/code",
  "grant_types_supported": [
    "authorization_code",
    "refresh_token",
    "urn:ietf:params:oauth:grant-type:device_code"
  ],
  "response_types_supported": [
    "code"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "code_challenge_methods_supported": [
    "S256",
    "plain"
  ],
  "scopes_supported": [
    "openid",
    "email",
    "groups",
    "profile",
    "offline_access"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_basic",
    "client_secret_post"
  ],
  "claims_supported": [
    "iss",
    "sub",
    "aud",
    "iat",
    "exp",
    "email",
    "email_verified",
    "locale",
    "name",
    "preferred_username",
    "at_hash"
  ]
}

댓글남기기