인증 현대화: GNU Guile로 Linux-PAM 모듈 작성하기

30년 넘게 Linux-PAM (Pluggable Authentication Modules)은 Linux 시스템 인증의 근간 역할을 해왔습니다. 그 설계는 당시로서는 혁신적이었으며, sulogin과 같은 보안 관련 애플리케이션이 정책이 변경될 때마다 재컴파일할 필요 없이 일관된 정책을 따를 수 있도록 해주었습니다. 그러나 공유 C 객체에 대한 의존성은 종종 커스텀 모듈을 작성하고 유지 관리하는 과정을 번거롭고 불투명하게 만듭니다.

Guile-PAM은 현대적인 대안으로 등장하여, 시스템 관리자가 Scheme의 방언인 GNU Guile를 사용하여 PAM 모듈을 작성, 구성 및 유지 관리할 수 있도록 합니다. 저수준 PAM API와 고수준 인터프리터 언어 사이의 간극을 메움으로써, Guile-PAM은 인증 로직을 정적인 컴파일된 바이너리에서 유연하고 읽기 쉬운 스크립트로 변환합니다.

Guile-PAM의 아키텍처

핵심적으로 Guile-PAM은 특별히 제작된 공유 객체인 pam_guile.so를 통해 작동합니다. 이 객체는 게이트웨이 역할을 하며, 인증 작업을 처리하기 위해 GNU Guile 스크립트를 호출합니다. 이 아키텍처는 세 가지 뚜렷한 통합 수준을 허용합니다:

  1. 작업 특정 모듈: pam_guile.so를 표준 Linux-PAM 공유 객체로 사용하여 Guile에서 단일 특정 작업을 수행합니다.
  2. 서비스 교체: pam_guile.so를 유일한 공유 객체로 만들고 모든 로직을 Guile 내에서 처리함으로써 전체 PAM 레시피(특정 서비스)를 교체합니다.
  3. 시스템 전체 오케스트레이션: /etc/pam.d의 모든 서비스를 pam_guile.so로 교체하여, 레시피 선택과 로직을 모두 Guile 환경으로 효과적으로 이동시킵니다.

"Pamdas"의 이해

Guile-PAM에서 로직의 기본 단위는 Pamda (PAM과 Lambda의 합성어)입니다. Pamda는 다음과 같은 시그니처를 가진 익명 프로시저입니다:

(lambda (action handle flags options) ...)
  • Action: PAM 엔트리 포인트를 나타내는 6가지 심볼 중 하나입니다 (예: pam_sm_authenticate, pam_sm_open_session).
  • Handle: 사용자 이름 및 대화 함수와 같은 내부 PAM 데이터에 접근할 수 있는 불투명한 변수입니다.
  • Flags: 정수 비트마스크입니다.
  • Options: 서비스 정의에서 전달되는 문자열 리스트입니다.

PAM 스택의 재구현

Guile-PAM의 가장 강력한 기능 중 하나는 Linux-PAM 스택을 Scheme으로 완전히 재구현했다는 점입니다. Linux-PAM의 정적인 구성 파일에 의존하는 대신, 개발자는 프로그래밍 방식으로 "gate"와 "plan"을 정의할 수 있습니다.

Gates와 Plans

A gate 프로시저는 pamda를 래핑하고 그에 plan (예: required, optional, sufficient, 또는 requisite)을 할당합니다. 그런 다음 stack 프로시저가 이러한 gate들을 순서대로 평가합니다.

전통적인 설정을 사용하는 사용자가 마이그레이션하는 경우, Guile-PAM은 표준 Linux-PAM 제어 문자열 (예: success=ok ignore=ignore default=bad)을 Guile-PAM plan으로 직접 변환하는 control-string->plan을 제공합니다.

하이브리드 스택

Guile-PAM은 기존 C 기반 모듈을 완전히 포기할 필요를 요구하지 않습니다. call-shared-object를 사용하여 Guile-PAM 스택은 레거시 .so 파일을 호출할 수 있습니다. 많은 레거시 모듈이 자신이 구현하지 않는 작업에 대해 호출될 때 PAM_SERVICE_ERR를 반환하므로, 안정성 문제를 방지하기 위해 Guile-PAM은 개발자가 #:implements 키워드를 사용하여 공유 객체가 정확히 어떤 작업을 구현하는지 지정할 수 있도록 합니다.

고급 사용 사례: 단순 인증을 넘어

Guile-PAM은 시스템에 접근할 수 있는 인터프리터 언어이기 때문에, 표준 PAM 모듈로는 어렵거나 불가능한 일들을 할 수 있습니다.

예시: 사용자 수준 마운트

A primary motivation으로 Guile-PAM이 개발된 주요 동기는 root가 아닌 실제 사용자로 볼륨을 마운트할 수 있는 능력이었습니다. pam_mount.so가 존재하지만, 일반적으로 root로 마운트하므로 FUSE 폴더나 kerberized NFSv4에서는 실패합니다.

Guile-PAM은 pam_sm_authenticate 중에 받은 토큰을 저장하고, 그 다음 pam_sm_open_session 중에 이를 사용하여 비밀 값을 gocryptfs와 같은 사용자 공간 도구에 파이프로 전달하여, 로그인 과정 중에 사용자를 대신해 홈 디렉터리를 마운트하는 워크플로우를를 가능하게 합니다.

보안 및 성능 고려 사항

인증 로직을 인터프리터 언어 환경으로 옮기는 것은 특정 고려 사항을 도입합니다:

보안

  • 가독성: 저자는 Scheme 코드가 일반적으로 C보다 감사하기 쉽다고 주장하며, 이는 동료 검토의 가능성을 높입니다.
  • 주입 위험: 공격을 방지하기 위해, 개발자는 알려지지 않은 입력에 대해 Guile의 read 프로시저를 사용하는 것을 주의해야 합니다.
  • 환경 격리: pam_guile.so는 승인되지 않은 모듈 교체를 방지하기 위해 GUILE_LOAD_PATH와 같은 환경 변수를 격리합니다.

성능

실제 테스트 결과 Guile-PAM은 적절한 속도로 작동함을 보여줍니다. 흥미롭게도, 저자는 인증 과정에서 극단적인 속도는 항상 목표가 아니라고 언급합니다. 무차별 대입 공격을antiovert(방지)하기 위해 의도적인 지연이 종약적으로 내장되어 있기 때문입니다.

구현상의 주의 사항

사용자는 Guile-PAM이 현재 alpha stage에 있음을 인지해야 합니다. 성숙한 Linux-PAM과 비교하여 몇 가지 핵심적인 동작 차열이 있습니다:

  • Action Grouping: Linux-PAM은 특정 작업 (예: authsetcred)을 그룹화하고 첫 번째 작업이 PAM_IGNORE를 반환하면 두 번째 작업을 건너뛸 수 있습니다. Guile-PAM의 (pam stack) 구현은 모든 작업에 대해 모든 모듈를 평가합니다.
  • Skip Counts: 명시적인 skip counts가 있는 레거시 인스트럭션 세트에서, 잠금 해제된 Guile-PAM 스택은 항상 결과를 획득하지만, Linux-PAM의 부작용(side effects)은 작업에 따라 다릅니다.

결론

Guile-PAM은 더 투명하고 유연한 시스템 관리를 향할한 방향성을 나타냅니다. 인증을 바이너리 덩어리의 구성이 아닌 프로그래밍 가능한 로직 문제로 취급함으로써, 새로운 분기 전략과 OpenBSD 스크립트와 같은 다른 자유 소프트웨어 시스템과의 더 깊은 통합을 가능히 합니다. GNU Guix를 사용하는 사용자라면 통합이 더욱 원활하며, 전체 인증 스택을 시스템 구성 내에서 정의할 수 있습니다.

Sources