콘텐츠로 이동

LDAP 사용자 지정 인증 및 Func 속성 가공(배포 플랜)

배포 플랜에서는 LDAP 인증을 고객이 유지 관리하는 Func 함수에 위임하거나 기본 제공 LDAP 인증을 유지한 채 Func에서 인증 성공 후 사용자 속성만 가공할 수 있습니다. 두 방식 모두 기존 LDAP 로그인 진입점과 SSO 계정, 워크스페이스 및 역할 매핑 규칙을 재사용합니다.

사용 전에 Studio 백엔드 버전이 LDAPClientSet.authMode, LDAPClientSet.external 및 네이티브 Func 함수 인터페이스의 응답 형식을 지원하는지 확인하세요. secret 암호화를 활성화하려면 브랜드 무관 v2 프로토콜을 지원하는 Studio와 Func core가 함께 필요합니다. 다운로드 스크립트만 업데이트한다고 이전 Studio가 v2를 지원하게 되지는 않습니다. 기존 배포에서 authMode: builtin(또는 미구성)을 유지하고 비어 있지 않은 server.hosts를 활성화하지 않으면 기존의 단일 주소 링크를 계속 사용합니다. 업그레이드를 위해 Func 또는 secret을 구성할 필요는 없습니다.

모드 선택

authMode 비밀번호 검증 주체 Func 수신 내용 Func 성공 시 반환 내용
builtin(기본값) Studio 기본 제공 LDAP Func 호출 안 함 해당 없음
builtin_transform Studio 기본 제공 LDAP, 실패 시 Func 호출 안 함 사용자 이름, 인증 완료된 userinfo, 이번 로그인 비밀번호는 포함하지 않음 사용자 속성의 최상위 패치
external 고객 Func 사용자 이름, 비밀번호 전체 userinfo

builtin_transform은 기존 LDAP 연결, TLS, 검색 및 매핑 구성을 공유하며 다중 도메인 컨트롤러도 함께 구성할 수 있습니다. 새 인증 실행 경로를 사용하므로 hosts를 구성하지 않은 경우에도 다중 도메인 컨트롤러 문서의 새 경로 요구 사항에 따라 SIMPLE 사용자 비밀번호 검증, SAFE_SYNC 및 시작 환경을 확인해야 합니다.

external은 Studio 구성의 LDAP 서버에 더 이상 연결하지 않으며 고객 Func가 실제 인증을 담당합니다. Studio는 여전히 LDAPClientSet.mapping으로 반환된 속성을 해석합니다. 함수가 사용자 정보 대신 Studio token, 계정 UUID 또는 역할 UUID를 반환해서는 안 됩니다.

두 외부 모드는 필요에 따라 선택하며 로그인할 때마다 external.url에 지정된 함수만 호출합니다. external이 authenticate를 가리키면 transform이 자동으로 다시 호출되지 않습니다. 고객 함수가 인증과 속성 가공을 동시에 수행해야 한다면 인증 성공 후 전체 userinfo를 가공하여 반환하면 됩니다. Studio 기본 제공 LDAP 인증을 유지하면서 속성만 사용자 지정하려면 builtin_transform을 사용하고 URL을 transform으로 지정하세요.

Func 스크립트 준비

다음 예시는 표준 LDAP 비밀번호 검증과 속성 가공의 두 진입점을 포함하며 고객 스크립트의 시작점으로 사용할 수 있습니다.

  • core.py 다운로드: LDAP 연결, 프로토콜 코덱 및 속성 검증.
  • main.py 다운로드: Func 함수 진입점, authenticate(body="")와 transform(body="") 포함.
  • 출처 및 검증 정보 다운로드. 두 스크립트는 Studio 구현과 동일한 소스를 사용하며 프로토콜을 업그레이드할 때 함께 업데이트해야 합니다.

  • 고객이 관리할 수 있는 Func 환경에서 스크립트 세트를 생성합니다(예: ldap_client_demo).

  • ID가 core인 하위 스크립트를 생성하고 core.py를 붙여넣습니다. 그다음 ID가 main인 하위 스크립트를 생성하고 main.py를 붙여넣습니다. main의 import __core as core는 동일한 스크립트 세트의 core를 참조하므로 인터넷에서 코드를 다운로드하도록 변경하지 마세요.
  • Func Python 환경에 호환되는 버전의 ldap3, cryptography, requests가 설치되어 있는지 확인하세요. Func Python 버전과 호환되는 의존성을 선택하고 실행 환경에서 다른 스크립트의 의존성을 덮어쓰지 마세요. 프라이빗 CA 파일은 Func 실행 컨테이너에 마운트해야 합니다.
  • 아래 Func 환경 변수를 구성한 후 core를 먼저 게시하고 main을 게시합니다. Func 호출은 게시된 스크립트에 의존하므로 초안만 저장해서는 안 됩니다.
  • 필요한 함수에 대해 동기 함수 API를 생성합니다. 외부 인증은 ldap_client_demo__main.authenticate를, 속성 가공은 ldap_client_demo__main.transform을 사용합니다. 함수 파라미터 이름은 body, 유형은 문자열입니다. 동기 호출 URL을 복사하고 비동기 작업 인터페이스는 사용하지 마세요.

환경 변수, 스크립트 게시 및 함수 API에 대한 자세한 내용은 Func 기본 개념 및 함수 작성 및 호출을 참조하세요.

Func 환경 변수

이름 설명
LDAP_CLIENT_DEMO_SECRET 구성하지 않아도 되며 기본값은 빈 문자열입니다. 요청 본문을 암호화해야 하는 경우 Studio external.secret과 동일한 비어 있지 않은 값으로 구성합니다. 안전하게 생성된 무작위 공유 값을 사용하는 것이 좋으며 스크립트, URL 또는 로그에 기록하지 마세요.
LDAP_CLIENT_DEMO_CONFIG JSON 개체 또는 JSON 문자열이며 내용은 LDAPClientSet 내부의 LDAP 구성입니다. LDAPClientSet을 다시 감싸지 않습니다. 외부 인증에는 디렉터리 구성이 필요하며 속성 가공만 수행하는 경우에도 Studio와 일치하는 mapping을 구성해야 합니다.

표준 LDAP 인증 함수를 사용할 때 LDAP_CLIENT_DEMO_CONFIG 예시:

{
  "server": {
    "hosts": ["ldaps://ldap-a.example.com:636", "ldaps://ldap-b.example.com:636"],
    "connect_timeout": 6,
    "use_ssl": true,
    "get_info": "SCHEMA",
    "tls": {"validate": "CERT_REQUIRED", "ca_certs_file": "/config/ldap/ca.pem"}
  },
  "connection": {"authentication": "SIMPLE", "client_strategy": "SAFE_SYNC", "receive_timeout": 5},
  "startTls": false,
  "authTimeoutSeconds": 20,
  "baseDN": "dc=example,dc=com",
  "bindDN": "cn=studio-reader,ou=service,dc=example,dc=com",
  "bindPassword": "<서비스 계정 비밀번호>",
  "searchAttribute": "uid",
  "personObjectClass": "inetOrgPerson",
  "mapping": {"exterId": "uid", "username": "uid", "email": "mail", "name": "displayName"}
}

예시를 실제 디렉터리 구성으로 교체하고 ID 필드를 기존 배포와 일치하게 유지하세요. 구성에 bindPassword가 포함된 경우 전체 JSON 문자열을 Func 환경 변수의 password 유형으로 저장합니다. 스크립트가 해당 문자열을 파싱하므로 일반 JSON 변수 목록에 서비스 비밀번호가 표시되지 않습니다. LDAP_CLIENT_DEMO_SECRET도 password 유형을 사용합니다. 이 구성은 Studio 다중 도메인 컨트롤러와 동일한 유한 전환 규칙을 사용하므로 사용자 비밀번호 제출 후 자동으로 다른 노드로 전환하여 재시도하지 않습니다. 속성 가공만 수행하는 경우에는 다음만 구성하면 됩니다:

{"mapping": {"exterId": "uid", "username": "uid", "email": "mail", "name": "displayName"}}

두 모드 모두 main.py의 _settings()를 사용하므로 속성 가공에서도 유효한 JSON 개체를 제공해야 합니다. 요청 파라미터에서 서비스 계정, LDAP 주소 또는 secret을 수신하지 마세요.

함수 API 액세스 제어

예시의 두 @DFF.API 진입점 모두 require_api_auth=False로 설정되어 있으므로 함수 인터페이스를 생성할 때 Func API 인증을 바인딩하지 않아도 됩니다. 동기 실행, 20초 타임아웃 및 cache_result=0을 유지하고 비밀번호 인증 결과를 캐시하지 마세요.

Studio는 아래의 kwargs.body JSON 요청을 고정적으로 전송하며 사용자 지정 인증 Header를 추가하지 않으므로 추가 API 인증이 바인딩되지 않은 위 Func 동기 진입점에 직접 연결할 수 있습니다:

Studio → 통제된 내부 네트워크의 Func 동기 함수 API

데모 인터페이스는 통제된 내부 네트워크에서만 열고 출처와 요청 속도를 제한하며 인증 파라미터와 요청 본문 수집을 꺼야 합니다. 실제 비밀번호를 전송할 때는 신뢰할 수 있는 HTTPS를 사용하세요. Func API 인증 강제를 해제해도 함수 내부의 LDAP 비밀번호 검증이 건너뛰어지지 않으며 Func 전역 인증이 꺼지지 않습니다. secret은 요청 본문의 선택적 암호화만 제어하며 LDAP 검증, TLS 또는 네트워크 액세스 제어를 대체하지 않습니다.

이미 함수 인터페이스에 API 인증이 바인딩되어 있다면 스크립트를 업데이트해도 자동으로 바인딩이 해제되지 않습니다. 인터페이스 관리자가 실제 연동 정책에 따라 처리해야 합니다. 추가 Header를 요구하는 인터페이스 인증을 계속 유지한다면 고객이 배포한 통제된 진입점에서 이를 처리해야 합니다. 이는 예시의 직접 연결 모드에서 필수 단계가 아닙니다. 선택적 구성은 Func API 인증을 참조하세요.

headersFile, keyFile 또는 기타 Studio 인증 필드를 추가할 필요가 없으며 Func의 MCP 프로그래밍 주소나 개발 Access-Token을 로그인 서비스 진입점으로 사용하지 마세요.

Studio에서 구성

애플리케이션 구성 설명에 따라 Launcher forethought-core의 Core 구성을 수정하고 관련 서비스를 다시 시작하세요.

외부 인증

LDAPClientSet:
  enable: true
  authMode: external
  authTimeoutSeconds: 20
  # Func가 반환한 속성을 해석합니다. 기존 사용자 및 Func 구성과 일치해야 합니다.
  mapping:
    exterId: uid
    username: uid
    email: mail
    name: displayName
  external:
    # authenticate의 전체 동기 HTTP(S) 함수 인터페이스 주소입니다. Studio 서비스에서 접근할 수 있어야 합니다.
    # LDAP 주소, Func 콘솔 페이지 또는 MCP 프로그래밍 주소가 아닙니다. 리디렉션은 허용되지 않습니다.
    url: "https://func.example.com/ldap/authenticate"
    # 구성하지 않아도 됩니다. 활성화하면 Func 환경 변수의 값과 동일하며 요청과 함께 전송되지 않습니다.
    secret: ""
    # 선택 사항, Studio 컨테이너 내 HTTPS 프라이빗 CA 경로입니다. 빈 값은 시스템 신뢰 저장소를 사용합니다.
    caCertsFile: ""

external 모드에서 Studio는 server, connection, bindDN 등 로컬 LDAP 파라미터를 사용하지 않습니다. 롤백에 대비해 원래 구성을 유지할 수 있습니다. 실제 디렉터리 구성은 Func 환경 변수로 유지 관리됩니다.

기본 제공 인증 후 속성 가공

기존 server, connection, startTls, 디렉터리 검색 및 ID 매핑 구성을 유지하고 모드와 외부 진입점만 다음과 같이 변경합니다:

# 다음은 LDAPClientSet 아래에서 병합해야 하는 구성 조각입니다. 전체 LDAP 구성을 덮어쓰는 데 사용하지 마세요.
LDAPClientSet:
  authMode: builtin_transform
  authTimeoutSeconds: 20
  external:
    # transform 함수의 동기 HTTP(S) 인터페이스 주소입니다.
    url: "https://func.example.com/ldap/transform"
    secret: ""
    caCertsFile: ""

external.url은 두 외부 모드 모두에서 필수입니다. 기존 builtin 모드는 이 구성을 무시하므로 기본 빈 URL을 유지해도 기존 배포에 영향을 주지 않습니다. URL에 계정 비밀번호나 프래그먼트가 포함되어서는 안 되며 신뢰할 수 있는 HTTPS를 사용하는 것이 좋습니다. HTTP는 전송 계층 보호를 제공하지 않으므로 실제 로그인 비밀번호 전송에 사용해서는 안 됩니다.

authTimeoutSeconds는 새 경로의 인증 및 속성 준비에 대한 총 시간 제한으로 정수 1~60초이며 기본값은 20초입니다. 각 단계별로 개별 측정되지 않으며 후속 계정 트랜잭션을 포함하지 않습니다. Func 예시의 실행 타임아웃은 20초이므로 LDAP, 게이트웨이 및 Studio의 총 시간 제한 구성을 함께 고려하여 무한 대기를 방지해야 합니다. Func 실패, 타임아웃 또는 프로토콜 오류가 발생하면 이번 로그인은 실패하며 다른 인증 모드로 폴백하지 않습니다.

body 요청 및 반환 프로토콜

Func 진입점의 정의는 항상 def authenticate(body="") 또는 def transform(body="")입니다. Studio는 POST로 JSON을 전송하며 외부 레이어는 고정적으로 다음과 같습니다:

{"kwargs": {"body": "요청 본문 문자열"}}

외부 인증의 논리적 요청 개체는 다음과 같습니다:

{"username": "user1", "password": "pass1"}

양쪽 모두 secret을 구성하지 않은 경우 body는 해당 개체를 한 번 직렬화하여 얻은 JSON 문자열입니다. 실제 HTTP 요청 예시:

{"kwargs": {"body": "{\"username\":\"user1\",\"password\":\"pass1\"}"}}

양쪽 모두 동일한 비어 있지 않은 secret을 구성한 경우 body는 v2:... 형식의 암호화 문자열입니다. 다운로드한 core.py가 암호화/복호화를 담당하므로 수동으로 구현할 필요가 없습니다. 한쪽만 구성하거나 양쪽이 일치하지 않거나 암호문 검증에 실패하면 요청이 거부되며 평문으로 폴백하지 않습니다. 공백도 secret 내용에 포함되므로 양쪽이 완전히 동일해야 합니다.

프로토콜은 HKDF-SHA256으로 32바이트 키를 파생하고 AES-256-GCM으로 암호화합니다. HKDF의 salt는 비어 있으며 키 파생 컨텍스트는 ldap-forward/v2, AAD는 ldap-forward/v2/request입니다. 전송 형식은 v2:에 표준 Base64를 더한 형태이며 인코딩 내용은 12바이트 무작위 nonce, 암호문, 16바이트 인증 태그 순서입니다. 요청의 body만 암호화하며 반환 값은 여전히 JSON입니다. 이 프로토콜은 요청 만료 또는 리플레이 중복 제거 기능을 제공하지 않으므로 HTTPS와 진입점 액세스 제어가 여전히 필요합니다.

여기서 AES-GCM 인증 태그는 암호화된 요청 본문의 무결성을 검증하는 데 사용되며 추가 디지털 서명이 아닙니다. Studio는 sign, signature, 서명 Header 또는 AK/SK를 추가하지 않습니다. 고객이 서명 키 파일을 준비할 필요도 없습니다. 양쪽 모두 secret을 구성하지 않은 경우 JSON 문자열만 그대로 전달되며 서명이나 암호화 인증 태그가 추가되지 않습니다.

브랜드 무관 프로토콜: 암호화는 양쪽이 합의한 secret과 위의 브랜드 무관 프로토콜 파라미터만 사용하며 제품 이름, 브랜드 식별자 또는 브랜드 도메인을 읽지 않습니다. 페이지 문구와 도움말 주소는 브랜드에 따라 교체되며 키 파생이나 암호문 검증에 참여하지 않습니다.

속성 가공의 논리적 요청 개체는 다음과 같습니다:

{
  "username": "user1",
  "userinfo": {"uid": ["user1"], "mail": ["user1@example.com"], "memberOf": ["cn=readers,dc=example,dc=com"]}
}

여기에는 이번 로그인 비밀번호가 포함되지 않습니다. 함수는 비즈니스 개체만 반환하면 됩니다. 외부 인증 성공 예시:

return {"status": "success", "userinfo": {
    "uid": ["user1"],
    "mail": ["user1@example.com"],
    "displayName": ["예시 사용자"]
}}

비밀번호 오류 또는 로그인 불허 시 {"status": "denied"}를 반환합니다. 디렉터리, 구성 또는 처리 예외 시 {"status": "error"}를 반환합니다. 비밀번호, 예외 스택 또는 내부 구성을 포함하지 마세요. 네이티브 Func 동기 API는 해당 비즈니스 개체를 직접 반환하므로 함수 자체에 ok, data를 다시 감쌀 필요가 없습니다. Studio는 기존 진입점에 대해 다음 래퍼도 호환합니다:

{"ok": true, "data": {"result": {"status": "success", "userinfo": {"uid": ["user1"], "mail": ["user1@example.com"]}}}}

Studio는 HTTP 200을 요구하며 네이티브 비즈니스 개체 또는 위 래퍼의 비즈니스 결과를 검증합니다. 외부 레이어에 ok가 나타나면 래퍼를 엄격히 검증하며 외부 레이어가 실패하거나 형식이 잘못된 경우 네이티브 성공으로 폴백하지 않습니다. 비동기 작업 ID, 리디렉션, HTML 페이지 또는 임의 텍스트는 유효한 인증 결과가 아닙니다. 복호화된 요청 개체, 사용자 속성 개체 및 Func 비즈니스 결과는 각각 최대 256KiB, HTTP 응답 본문은 최대 1MiB이므로 대량의 관련 없는 속성을 반환하지 마세요.

인증 또는 속성 가공 로직 수정

사용자 지정 인증

main.py의 authenticate는 기본적으로 동일 소스의 LDAP 실행기를 호출하여 실제 사용자 이름과 비밀번호를 검증합니다. 다른 ID 서비스와 연동하는 경우 _run()의 인증 분기에서 실제 검증 단계를 교체해야 합니다. 먼저 고객의 신뢰할 수 있는 ID 서비스를 호출하여 비밀번호 또는 인증 자격 증명이 유효한지 확인한 다음 userinfo를 구성합니다. 인증 거부 시 denied를 반환하고 서비스 예외 시 error를 반환합니다. 사용자 이름이 존재한다는 이유만으로 성공을 반환하지 마세요.

userinfo의 필드 이름은 mapping에 의해 결정됩니다. 예를 들어 mapping.exterId: uid인 경우 비어 있지 않은 uid를 반환해야 하며 매핑 가능한 사용자 이름 또는 이메일이 최소 하나 이상 있어야 합니다. ID 필드는 문자열 또는 단일 요소 문자열 배열일 수 있으며 여러 후보 ID를 반환할 수 없습니다. 기타 권한 부여에 필요한 그룹 속성은 기존 구조를 그대로 사용해야 합니다.

여러 독립 ID 소스를 연동할 때 고객 함수가 ID 소스를 결정하고 ID를 검증합니다. exterId는 연동 범위 내에서 전역적으로 안정적이고 고유해야 합니다. 동일한 이름 또는 동일한 이메일이 동일 인물을 의미하지 않으며 기존 LDAP/OIDC 계정이 자동으로 병합되지 않습니다. 기존 사용자를 마이그레이션하기 전에 ID 소스와 기존 바인딩을 확인하여 중복 계정 생성이나 ID 충돌을 방지하세요.

속성 가공

먼저 ldap_client_demo__main.transform에 대한 동기 HTTP 인터페이스를 생성하고 Studio에서 authMode: builtin_transform을 구성한 후 external.url을 해당 인터페이스로 지정합니다. 기존 LDAP 연결, 검색, TLS 및 mapping을 공유합니다. 요청 본문 암호화를 활성화한 경우 Func와 동일한 secret을 계속 사용합니다.

다운로드 예시의 patch = {}는 기본적으로 어떤 속성도 수정하지 않습니다. 고객은 main.py의 transform_only 분기에서 실제 가공 규칙을 작성하고 수정 후 main을 다시 게시합니다. 예:

# 예시: 실제 값은 신뢰할 수 있는 비즈니스 규칙 또는 속성 서비스에서 계산해야 하며 클라이언트가 임의로 지정할 수 없습니다.
patch = {"displayName": ["업무 표시 이름"]}
core.merge_transform(original, patch, config["mapping"])
return {"status": "success", "userinfo": patch}

동시에 Studio와 Func의 mapping에 name: displayName을 구성하고 나머지 원래 매핑은 그대로 유지합니다. 빈 패치 {}는 원래 속성을 유지함을 의미합니다. 패치는 최상위 필드 단위로 병합되며 재귀 병합이 아닙니다. 반환되지 않은 필드는 유지되며 명시적으로 빈 값이나 빈 배열을 반환하면 필드가 지워지고 역할 매핑 결과가 변경될 수 있습니다.

속성 가공은 mapping.exterId에 해당하는 ID 값을 변경해서는 안 됩니다. 다른 속성을 수정할 때도 워크스페이스 및 역할 매핑을 먼저 검증해야 합니다. 중첩 개체를 업데이트할 때는 해당 필드에 필요한 전체 내용을 반환해야 합니다. 비밀번호, token, 개인 키 또는 공유 secret과 같은 자격 증명 필드를 반환하지 마세요.

보안 검증 및 롤백

기존 암호화 프로토콜은 테스트 환경에서만 사용됩니다. v2로 전환할 때 먼저 관련 테스트 인증을 중지하고 처리 중인 요청과 Func 큐가 비워질 때까지 기다린 다음 새 core를 먼저 게시하고 Studio를 업데이트한 후 검증하고 복원합니다. 양쪽 프로토콜 버전이 일치해야 합니다. 기존 암호문을 자동으로 수락하지 않으며 평문으로 폴백하지도 않습니다. secret을 구성하지 않은 JSON 투과 전송은 영향을 받지 않습니다.

출시 전에 먼저 합성 데이터와 전용 테스트 계정으로 검증하세요:

  1. 양쪽 secret이 모두 비어 있으면 Func가 JSON 문자열을 수신합니다. 양쪽 모두 동일한 secret을 구성하면 암호문을 수신하고 인증에 성공합니다. 양쪽이 일치하지 않으면 실패합니다.
  2. 올바른 비밀번호는 성공하고 잘못된 비밀번호는 거부됩니다. Func 중지, 타임아웃, 응답 형식 오류 시 거부되며 이전 인증으로 전환되지 않습니다.
  3. builtin_transform의 잘못된 비밀번호 요청은 Func에 도달하지 않습니다. 빈 패치는 ID 및 그룹 속성을 유지하며 ID 값을 변경하는 패치는 거부됩니다.
  4. 기존 사용자는 여전히 원래 계정으로 진입하며 계정 프로필, 워크스페이스 및 역할이 예상대로 유지됩니다. 기존 정책에 따라 후속 로그인과 MFA도 계속 수행됩니다.
  5. Studio, Func, 게이트웨이, 작업 기록 및 APM의 수집 정책을 확인하여 인증 요청 파라미터, 비밀번호, secret이 로그나 모니터링에 포함되지 않도록 하세요. 예시 진입점은 이러한 내용을 출력하지 않지만 플랫폼의 파라미터 및 작업 수집을 자동으로 끄지 않으며 암호화도 이 검사를 대체하지 않습니다. 일반 사용자 속성도 최소 필요 범위로만 반환해야 합니다.

실패를 조사할 때 먼저 Studio에서 게이트웨이/Func까지의 DNS, 네트워크 및 CA를 확인한 다음 Func API의 동기 방식, 액세스 인증, 양쪽 secret, 매핑 및 반환 구조를 확인하세요. 오류 유형과 trace 정보를 확인하고 전체 요청 본문이나 구성을 출력하여 조사하지 마세요.

롤백이 필요하면 업그레이드 전의 authMode: builtin, 기존 단일 주소 server.host 및 기존 LDAP 구성으로 복원하고 server.hosts를 비우거나 삭제한 후 관련 서비스를 다시 시작하세요. Func 스크립트를 삭제할 필요는 없습니다. 롤백은 가공된 속성을 자동으로 복원하거나 생성된 계정을 병합하지 않으므로 기존 계정과 권한 상태를 별도로 확인해야 합니다.

문서 평가

이 페이지가 도움이 되었나요?