LDAP 조직 데이터
Backstage 카탈로그는 LDAP 호환 서비스에서 직접 조직 데이터(사용자와 그룹)를 가져오도록 설정할 수 있어요. 그 결과는 조직 설정을 그대로 반영한 User 및 Group 종류 엔티티의 계층 구조예요.
출처: 문서
본문
Backstage 카탈로그는 LDAP 호환 서비스에서 직접 조직 데이터(사용자와 그룹)를 가져오도록 설정할 수 있어요. 그 결과는 조직 설정을 그대로 반영한 User 및 Group 종류 엔티티의 계층 구조예요.
지원 벤더
Backstage는 일반적으로 OpenLDAP 호환 벤더와 Active Directory, FreeIPA를 지원해요. 지원되지 않는 것 같은 벤더를 사용 중이라면 이슈를 등록해 주세요.
설치
이 가이드는 엔티티 제공자 엔티티 제공자(Entity Provider) 방법을 사용할게요. 어떤 이유로 프로세서(Processor) 방법을 선호한다면(권장하지 않음), 아래에 별도로 설명돼 있어요.
이 제공자는 기본으로 설치되지 않으므로 백엔드 패키지에 @backstage/plugin-catalog-backend-module-ldap에 대한 종속성을 추가해야 해요.
Backstage 루트 디렉터리에서
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap
note
Processor 대신 Provider를 사용하도록 구성할 때는 LDAP 서버를 가리키는 location을 추가할 필요가 없습니다
백엔드의 카탈로그 플러그인 초기화를 업데이트해 제공자를 추가하고 일정을 잡으세요.
packages/backend/src/plugins/catalog.ts
import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';export default async function createPlugin( env: PluginEnvironment,): Promise<Router> { const builder = await CatalogBuilder.create(env); // The target parameter below needs to match the ldap.providers.target // value specified in your app-config. builder.addEntityProvider( LdapOrgEntityProvider.fromLegacyConfig(env.config, { id: 'our-ldap-master', target: 'ldaps://ds.example.net', logger: env.logger, schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 60 }, timeout: { minutes: 15 }, }), }), ); // ..}
이 후에는, 그 대상에 대해 무엇을 가져올지 설명하는 몇 가지 구성을 app-config에 추가해야 해요.
구성
다음 구성은 회사 LDAP 서버에서 그룹과 사용자를 가져오는 설정이 어떻게 생길 수 있는지 보여주는 작은 예시예요.
ldap: providers: - target: ldaps://ds.example.net bind: dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net secret: ${LDAP_SECRET} users: dn: ou=people,ou=example,dc=example,dc=net options: filter: (uid=*) map: description: l set: metadata.customField: 'hello' groups: dn: ou=access,ou=groups,ou=example,dc=example,dc=net options: filter: (&(objectClass=some-group-class)(!(groupType=email))) map: description: l set: metadata.customField: 'hello'
제공자는 여럿일 수 있고, 각각 전용 제공자 인스턴스의 target과 일치해야 하는 특정 target을 가리켜요. 즉 대상별로 하나의 엔티티 제공자 클래스 인스턴스를 추가해 거기서 수집해요.
이 구성 블록들은 옵션이 많으므로, 블록 안의 각 "root" 키를 따로 설명할게요.
target
이것은 대상 서버의 URL이며, 보통 SSL이 활성화된 서버는 ldaps://ds.example.net, SSL이 없는 경우 ldap://ds.example.net 형식이에요.
target.tls.keys
TLS 옵션의 keys는 LDAP 서버와 연결을 설정하는 데 포함되는 개인 키가 들어 있는 PEM 형식 파일의 위치를 지정해요. 아래의 Google Secure LDAP Service 예시를 참고하세요.
target.tls.certs
TLS 옵션의 certs는 LDAP 서버와 연결을 설정하는 데 포함되는 인증서 체인이 들어 있는 PEM 형식 파일의 위치를 지정해요. 아래의 Google Secure LDAP Service 예시를 참고하세요.
bind
bind 블록은 플러그인이 서버에 대해 어떻게 바인딩(본질적으로 인증)해야 하는지 지정해요. 다음 필드를 가져요.
dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=netsecret: ${LDAP_SECRET}
dn은 플러그인이 자신으로 인증하는 사용자의 전체 LDAP Distinguished Name이에요. 현재는 일반 사용자 기반 인증만 지원돼요.
secret은 같은 사용자의 비밀번호예요. 이 예시에서는 백엔드가 시작될 때 설정되어야 하는 환경 변수 LDAP_SECRET 형태로 주어져요.
users
users 블록은 사용자의 읽기와 해석을 관장하는 설정을 정의해요. 그 필드는 아래 별도 섹션에서 설명해요.
users.dn
사용자가 저장된 DN, 예: ou=people,ou=example,dc=example,dc=net.
users.options
모든 사용자를 읽을 때 서버에 쿼리를 보낼 때 사용할 검색 옵션이에요. 모든 옵션이 기본값과 함께 아래에 표시되지만, 모두 선택 사항이에요.
options: # One of 'base', 'one', or 'sub'. scope: one # The filter is the one that you commonly will want to specify explicitly. It # is a string on the standard LDAP query format. Use it to select out the set # of users that are of actual interest to ingest. For example, you may want # to filter out disabled users. filter: (uid=*) # The attribute selectors for each item, as passed to the LDAP server. attributes: ['*', '+'] # This field is either 'false' to disable paging when reading from the # server, or an object on the form '{ pageSize: 100, pagePause: true }' that # specifies the details of how the paging shall work. paged: false
users.set
이 선택 사항은 (a.b.c 형식의) JSON 경로를 여러 개 지정하고 그 경로에 하드코딩된 값을 설정하게 해줘요. 예를 들어 생성된 엔티티에 네임스페이스 등을 하드코딩하려면 유용할 수 있어요.
set: # Just an example; the key and value can be anything metadata.namespace: 'ldap'
users.map
잘 알려진 엔티티 필드에서 LDAP 속성 이름으로의 매핑이에요. 여기서 각 LDAP 결과 항목의 속성을 어떻게 해석하고 해당 엔티티 필드로 옮길지 정의할 수 있어요. 모든 옵션이 기본값과 함께 아래에 표시되지만, 모두 선택 사항이에요.
선택 매핑을 생략해도 여전히 그 기본값을 사용해 복사돼요. 예를 들어 displayName 필드를 구성에 넣지 않아도, 제공자는 여전히 cn 속성을 엔티티 필드 spec.profile.displayName으로 복사해요.
map: # The name of the attribute that holds the relative # distinguished name of each entry. rdn: uid # The name of the attribute that shall be used for the value of # the metadata.name field of the entity. name: uid # The name of the attribute that shall be used for the value of # the metadata.description field of the entity. description: description # The name of the attribute that shall be used for the value of # the spec.profile.displayName field of the entity. displayName: cn # The name of the attribute that shall be used for the value of # the spec.profile.email field of the entity. email: mail # The name of the attribute that shall be used for the value of # the spec.profile.picture field of the entity. picture: <nothing, left out> # The name of the attribute that shall be used for the values of # the spec.memberOf field of the entity. memberOf: memberOf
groups
groups 블록은 그룹의 읽기와 해석을 관장하는 설정을 정의해요. 그 필드는 아래 별도 섹션에서 설명해요.
groups.dn
그룹이 저장된 DN, 예: ou=people,ou=example,dc=example,dc=net.
groups.options
모든 그룹을 읽을 때 서버에 쿼리를 보낼 때 사용할 검색 옵션이에요. 모든 옵션이 기본값과 함께 아래에 표시되지만, 모두 선택 사항이에요.
options: # One of 'base', 'one', or 'sub'. scope: one # The filter is the one that you commonly will want to specify explicitly. It # is a string on the standard LDAP query format. Use it to select out the set # of groups that are of actual interest to ingest. For example, you may want # to filter out disabled groups. filter: (&(objectClass=some-group-class)(!(groupType=email))) # The attribute selectors for each item, as passed to the LDAP server. attributes: ['*', '+'] # This field is either 'false' to disable paging when reading from the # server, or an object on the form '{ pageSize: 100, pagePause: true }' that # specifies the details of how the paging shall work. paged: false
groups.set
이 선택 사항은 (a.b.c 형식의) JSON 경로를 여러 개 지정하고 그 경로에 하드코딩된 값을 설정하게 해줘요. 예를 들어 생성된 엔티티에 네임스페이스 등을 하드코딩하려면 유용할 수 있어요.
set: # Just an example; the key and value can be anything metadata.namespace: 'ldap'
groups.map
잘 알려진 엔티티 필드에서 LDAP 속성 이름으로의 매핑이에요. 여기서 각 LDAP 결과 항목의 속성을 어떻게 해석하고 해당 엔티티 필드로 옮길지 정의할 수 있어요. 모든 옵션이 기본값과 함께 아래에 표시되지만, 모두 선택 사항이에요.
선택 매핑을 생략해도 여전히 그 기본값을 사용해 복사돼요. 예를 들어 displayName 필드를 구성에 넣지 않아도, 제공자는 여전히 cn 속성을 엔티티 필드 spec.profile.displayName으로 복사해요. 대상 필드가 display name처럼 선택 사항이라면 임포터는 누락된 속성을 받아들이고 대상 필드를 설정하지 않은 채로 둬요. 대상 필드가 엔티티의 name처럼 필수라면, 소스 속성이 없으면 검증이 실패해요.
map: # The name of the attribute that holds the relative # distinguished name of each entry. This value is copied into a # well known annotation to be able to query by it later. rdn: cn # The name of the attribute that shall be used for the value of # the metadata.name field of the entity. name: cn # The name of the attribute that shall be used for the value of # the metadata.description field of the entity. description: description # The name of the attribute that shall be used for the value of # the spec.type field of the entity. type: groupType # The name of the attribute that shall be used for the value of # the spec.profile.displayName field of the entity. displayName: cn # The name of the attribute that shall be used for the value of # the spec.profile.email field of the entity. email: <nothing, left out> # The name of the attribute that shall be used for the value of # the spec.profile.picture field of the entity. picture: <nothing, left out> # The name of the attribute that shall be used for the values of # the spec.parent field of the entity. memberOf: memberOf # The name of the attribute that shall be used for the values of # the spec.children field of the entity. members: member
제공자 커스터마이즈하기
수집된 엔티티를 커스터마이즈하고 싶다면 제공자가 사용자와 그룹에 대한 트랜스포머를 전달하게 할 수 있어요. 여기서는 그룹 트랜스포머를 재정의하는 예시를 보여드릴게요.
트랜스포머 만들기:
export async function myGroupTransformer( vendor: LdapVendor, config: GroupConfig, group: SearchEntry,): Promise<GroupEntity | undefined> { // Transformations may change namespace, change entity naming pattern, fill // profile with more or other details... // Create the group entity on your own, or wrap the default transformer return await defaultGroupTransformer(vendor, config, group);}
트랜스포머로 제공자 구성하기:
const ldapEntityProvider = LdapOrgEntityProvider.fromLegacyConfig( env.config, { id: 'our-ldap-master', target: 'ldaps://ds.example.net', logger: env.logger, groupTransformer: myGroupTransformer, },);
Provider 대신 Processor 사용하기
LDAP 항목을 수집하는 Provider 사용의 대안으로 Processor를 사용할 수 있어요. 이것은 올바른 타입과 대상으로 위치(locations)를 등록해 프로세서가 실행되도록 하는 옛 방식이에요.
이 방법의 단점은 LDAP 서버에서 항목이 삭제될 때마다 고아 Group/User 엔티티가 남게 되고, 다른 프로세서와 별도로 새로고침 빈도를 제어할 수 없다는 거예요.
Processor 설치
LdapOrgReaderProcessor는 기본으로 등록되지 않으므로 카탈로그 플러그인에 등록해야 해요.
packages/backend/src/plugins/catalog.ts
builder.addProcessor( LdapOrgReaderProcessor.fromLegacyConfig(env.config, { logger: env.logger, }),);
Locations로 LDAP Org Processor 수집 구동하기
위치(locations)는 가져오려는 특정 조직을 가리켜요. 이 위치들의 type은 ldap-org여야 하고, target은 대상 LDAP 서버의 정확한 URL(ldap:// 또는 ldaps://로 시작)을 가리켜야 해요. 원한다면 여러 위치 항목을 가질 수 있지만, 보통은 하나만 가져요.
catalog: locations: - type: ldap-org target: ldaps://ds.example.net rules: - allow: [User, Group]
예시 구성
Google Secure LDAP Service
Google Workspace/Cloud Identity 조직 데이터를 backstage의 사용자와 그룹으로 동기화하려면 먼저 Secure LDAP Service를 구성해야 해요.
Secure LDAP Service가 구성되면 아래에서 언급한 대로 LDAP 구성에서 TLS 옵션을 활성화할 수 있어요. keys와 certs는 위에서 Secure LDAP Service를 구성할 때 생성된 파일의 위치를 지정해요.
ldap: providers: - target: ldaps://ldap.google.com:636 tls: rejectUnauthorized: false keys: '/var/secrets/tls/gldap.key' certs: '/var/secrets/tls/gldap.crt' users: # users configuration comes here groups: # groups configuration comes here