유저 인증을 위해 어플리케이션에서 인증 메카니즘을 분리 시키는 PAM(Pluggable Authentication Module) 을 사용하는 법에 대해 배워 봅시다.

순서 소개

이글의 첫번째 시리즈에서 우리는 패스워드 기반 사용자 인증의 기본에 대해 다루었습니다. 인증(authentication) 인가(authorisation) 두가지 용어를 정의 했고, 로컬 파일 기반 패스워드 저장소에 대해 알아 봤으며 암호화 알고리즘과 솔라리스가 패스워드를 읽고 함호화 하는데에 사용하는 API 들에 대해 설명했습니다. 마지막으로 예제 프로그램을 통해서 실행자의 패스워드를 입력받고 그 것을 로그인 패스워드와 비교해 보았습니다.

솔라리스는 유저 인증을 위해서 PAM(Pluggable Authentication Module) 이라고 불리는 확장가능한 메카니즘을 제공합니다. 이글에서 우리는 PAM 설비에 대한 기본적인 내용을 살펴 보고 첫번쨰 글에서 작성했던 프로그램의 PAM-기반 버전을 개발합니다. 비록 유저 인증을 위해 PAM 을 사용하는 어플리케이션이 좀 더 복잡하지만 PAM 을 사용함으로써 인증 메카니즘을 어플리케이션에서 분리시키는 장점을 가질 수 있습니다. 이것은 즉 새로운 인증 메카니즘(생체 스캔 같은) 혹은 기존에 존재하는 것들을 어플리케이션 수정 없이도 쉽게 추가 할 수 있다는 것을 의미 합니다.

맨위로 가기


PAM 개요

PAM 설비는 두개의 부분으로 구성되어 있습니다: API 와 SPI(Service Provider Interface) 를 제공하는 PAM 라이브러리와 쌓는 형태로 사용할 수 있는 다양한 서비스 모듈들. /etc/pam.conf 라고 명명된 설정 파일은 다양한 시스템 서비스 즉 login or cron 같은 것들의 서비스 모듈을 설정하는데에 사용 됩니다. 어플리케이션은 PAM 라이브러리와 API 를 통해 교신하고 서비스 모듈은 SPI 를 사용합니다.

맨위로 가기


PAM 서비스 모듈

서비스 모듈 은 다음의 하나 혹은 그 이상의 인증과 보안 서비스들을 제공하는 공유 라이브러리 입니다:

  • Authentication. 이 서비스 모듈은 계정 혹은 서비스의 접근을 인증하는데 사용되고 유저의 암호를 설정합니다.
  • Account management. 이 서비스 모듈은 현재 유저 계정의 유효성을 판단합니다. 예를 들어 이 모듈은 패스워드 혹은 계정의 만료를 체크할 수 있고 접근 시간 제한을 강제할 수도 있습니다.
  • Session management. 이 서비스 모듈은 로그인 세션을 설정하고 해지 합니다.
  • Password management. 이 서비스 모듈은 패스워드 길이와 이전-사용 규칙을 강제할 수 있고 인증 토큰 업데이트를 수행할 수 있습니다.

이상적으로 PAM 서비스들은 간단하게 잘 정의된 작업을 가진 서비스 모듈 내에서 구현되어야 합니다. 그러므로 설정의 복잡성이 증가 합니다. 이후에 서비스 모듈은 /etc/pam.conf 에 적절한 정의에 의해서 필요에 따라 사용될 수 있습니다.

맨위로 가기


PAM 설정 파일

이전에 언급한대로 PAM 설비는 설정 파일 시스템 관리자에 의해 관리되는 /etc/pam.conf 에 의해 설정 됩니다. 각 시스템 서비스는 하나 혹은 그 이상의 항목을 pam.conf 내에 가질 수 있습니다. pam.conf 에 항목들의 순서는 매우 중요합니다. 왜냐하면 잘못 설정된 pam.conf 파일은 멀티유저 모드에서 모든 유저의 접근을 막을 수 있기 때문입니다. 고유의 항목이 정의되지 않은 시스템 서비스들은 "other" 서비스를 위해 정의된 항목을 사용 합니다.

설정파일의 각 항목은 몇가지 공백으로 구분된 필드들로 이루어져 있고 첫번째 4개 항목은 필수 입니다:

  • Service name. 이것은 이 항목이 속한 서비스의 이름입니다. 예로써 cron, login, 그리고 passwd 를 들 수 있습니다. 어플리케이션은 하나 혹은 그 이상의 서비스 이름을 사용할 수 있고 필요에 따라 결정할 수 있습니다. 서비스 이름 other 는 와일드카드로써 예약되어 있습니다: 만약 어플리케이션에 의해 지정된 서비스 이름이 설정 파일에 없다면 other 항목이 사용 됩니다. 하나 이상의 서비스 이름을 사용하고 있는 어플리케이션의 예로 SSH 데몬, sshd 이 있고 인증 방법 키워드 당 이름을 사용하고 있습니다. (sshd 멘 페이지의 SECURITY 섹션에서 자세한 정보를 확인할 수 있습니다.)
  • Module type. 이러한 종류의 서비스 모듈은 account, auth, password, 혹은 session 등이 있습니다. account 서비스 모듈은 계정의 유효성을 검증하고(예를 들어 패스워드 혹은 계정 만료 그리고 접근 제한 시간등), auth 서비스 모듈은 계정 접근을 인증하고 유저의 권한을 설정하며, password 서비스 모듈은 유저의 패스워드 변경을 관리하는데 사용 되며 session 서비스 모듈은 로그인 세션을 생성하고 삭제하는데에 사용 됩니다.
  • Control flag. 서비스의 성공과 실패를 판단하여 모듈의 롤을 결정합니다. 유효한 값은 binding, optional, required, requisite, 그리고 sufficient 입니다. 다음 섹션에서 좀 더 자세히 살펴볼 것입니다.
  • Module path. 이것은 서비스 모듈을 구현한 공유 오브젝트들의 시스템 경로 입니다. 상대 경로 이름들은 /usr/lib/security/$ISA 로 가정되는데 여기서 $ISA 는 PAM 라이브러리가 어플리케이션의 특정한 아키텍쳐를 위한 디렉토리를 찾도록 하는 매크로 입니다.
  • Module options. 이 것은 서비스 모듈에 전달되는 모든 가능한 옵션들을 나열하고 모듈의 멘페이지에 기술되어야 합니다.
맨위로 가기

PAM 모듈들 배열하기

이전에 언급한대로 어플리케이션은 복수개의 서비스 모듈을 사용할 수 있습니다. 그리고 각 모듈에 대한 항목은 /etc/pam.conf 에 존재합니다. 어플리케이션이 이중의 하나를 호출하고자 할때 PAM 라이브러리는 pam.conf 를 읽어서 어떠한 모듈을 사용해야 할지 결정합니다:

pam_authenticate
pam_acct_mgmt
pam_setcred
pam_open_session
pam_close_session
pam_chauthtok


만약 서비스가 pam.conf 에 딱한개의 항목만 존재 한다면 이 모듈의 실행 결과가 작업의 수행결과를 결정하게 됩니다. 그러나 만약 여러개의 항목이 존재 한다면 모듈은 쌓이게 되고 작업의 결과는 서비스의 PAM 스택에 있는 모든 모듈들의 포함된 프로세스에 의해 결정됩니다.

주어진 서비스에 대해서 각 모듈은 pam.conf 에 나타난 순서대로 실행 됩니다. 각 모듈의 컨트롤 플래그에 값에 의해 결정되는 성공과 실패는 전체 실행 결과에 통합됩니다. 이 컨트롤 플래그는 다음의 5가지 값중에 하나가 됩니다:

  • binding. required 컨트롤 플래그가 지정된 이전의 어떠한 모듈도 실패하지 않았다면 이 모듈의 요구조건을 성공적으로 만족하는 순간 스택 내의 다른 모듈들을 실행하지 않고 바로 성공 결과를 어플리케이션에 리턴해 줍니다. 실패는 "required failure"가 기록되고 이후 모듈이 실행 됩니다.
  • optional. 만약 이 모듈이 실패 하면 "optional failure" 가 기록되고 스택의 이후 모듈들이 실행됩니다.
  • required. 서비스를 사용하기 위해서는 이 모듈의 요구조건이 반드시 충족시켜져야 합니다. 만약 그렇지 못하다면 "required failure" 가 기록되고 스택의 이후 모듈들이 순차적으로 실행 됩니다. 오직 모든 required 혹은 binding 모듈들이 아무런 에러 없이 성공해야 어플리케이션에 성공이 리턴 됩니다.
  • requisite. 서비스를 사용하기 위해서 이 모듈의 요구조건이 반드시 충족시켜져야 합니다. 만약 그렇지 못하다면 "required failure" 가 곧바로 어플리케이션에 리턴 되고 스택내의 이후 모듈들은 실행되지 않습니다. 충족한다면 이후의 모듈들은 실행됩니다. 만약 모든 requisite 모듈들이 어떠한 에러도 없다면 어플리케이션에 성공이 리턴됩니다.
  • sufficient. 만약 이 모듈이 실패하면 "optional failure" 이 기록됩니다. 그렇지 않다면 이전에 어떠한 에러도 기록되지 않았을때 어플리케이션에 성공이 곧바로 리턴 되고 스택 내의 이후 모듈들은 실행되지 않습니다.

이전의 설명은 꽤 복잡합니다. 따라서 실제 pam.conf 의 실제 예제로 rlogin 을 살펴 보도록 하겠습니다.pam.confrlogin 항목입니다:

rlogin  auth sufficient         pam_rhosts_auth.so.1	
rlogin  auth requisite          pam_authtok_get.so.1
rlogin  auth required           pam_dhkeys.so.1
rlogin  auth required           pam_unix_cred.so.1
rlogin  auth required           pam_unix_auth.so.1


첫번째로 살펴 봤을때 우리는 rlogin 의 모든 모듈들이 인증을 위한 것으로 이루어져 있음을 알 수 있습니다. 그리고 첫번째는 (pam_rhosts_auth) sufficient 로 플래깅 되어 있습니다. 이것은 rlogin 서비스가 인증을 요청했을때 pam_rhosts_auth 의 긍정적인 리턴 값은 바로 여기서 프로세스를 중단하고 어플리케이션에 바로 성공을 리턴합니다.

만약 pam_rhosts_auth 가 실패하면 다음 모듈이 (pam_authtok_get) 실행됩니다. 이것은 requisite 으로 플래깅 되어 졌는데 즉 pam_authtok_get 이 실패 하면 프로세스가 종료되고 실패가 곧바로 어플리케이션에 리턴되는 것을 의미 합니다. 만약 pam_authtok_get 가 성공하면 나머지 3모듈 (pam_dhkeys, pam_unix_cred, pam_unix_auth) 이 순차적으로 실행됩니다. 3모듈 각각은 모두 required 로 플래깅되어 있는데 이것은 3가지 모두 성공해야 어플리케이션에 성공이 리턴된다는 것을 의미 합니다. 만약 그중에 하나라도 실패 하면 바로 기록이 되고 3가지 모듈이 모두 실행이 된 다음에 어플리케이션에 실패가 리턴 됩니다.

pam_unix_auth 이 실행되면 실행해야할 모듈이 더이상 존재하지 않음으로 성공 혹은 실패 값이 어플리케이션에 리턴 됩니다. 만약 실패로 결정되었다면 유저는 rlogin 으로 시스템에 접근하지 못한다는 것을 의미 합니다.

맨위로 가기

몇몇 API 함수들

이제 PAM 프레임워크에 대해 살펴 보고 어떻게 설정되었으며 몇몇 PAM API 함수들에 대해서도 살펴 봅시다. 아주 간단한 PAM 을 사용하는 어플리케이션을 위해서 우리는 3가지 함수들을 고려해 보아야 합니다: pam_start, pam_end, 그리고 pam_authenticate.

pam_start함수

pam_start 함수는 PAM 인증 트렌젝션을 초기화 할때 사용됩니다.

>
#include <security/pam_appl.h>
int pam_start (const char service, const char user,
	const struct pam_conv pam_conv, pam_handle_t pamh);


이 함수는 각각 serviceuser 에 지정한 서비스와 유저를 위한 인증 트렌젝션을 초기화 하는 함수 입니다. pam_conv 매개변수는 사용될 대화 함수를 지정 합니다. 호출이 성공하면 pamh 는 이다음 PAM 함수 사용시에 필요한 핸들에 대한 포인터를 리턴합니다. 즉 파일에서 사용되는 파일 디스크립터와 동일한 방법이라고 보시면 됩니다. 이 시리즈의 다음 글에서 우리는 대화 함수(conversation function) 에 대해 다룰 것입니다. 그러므로 여기서 대화 함수 이름이 의미하는 그대로 유저와의 대화를 다루는 함수라고 생각하시면 됩니다(예를 들어 패스워드 입력 프롬프트를 출력하는 것등).

pam_end 함수

pam_end 함수는 PAM 인증 트렌젝션을 종료하는데에 사용 됩니다.

#include <security/pam_appl.h>

int pam_end (pam_handle_t *pamh, int status);


pamh 에 지정된 PAM 인증 트렌젝션이 종료 됩니다. 그리고 status 매개변수가 PAM 핸들에 저장된 청소 함수(만약 존재한다면) 에 전달 됩니다.

pam_authenticate 함수

pam_authenticate 함수는 현재 유저를 인증하는데 사용됩니다.

#include <security/pam_appl.h>

int pam_authenticate (pam_handle_t *pamh, int flags);


이 함수는 pam_start 를 호출하면서 pamh 핸들이 생성되었을때 지정된 현재 유저를 인증하는데에 사용 됩니다. 정확하게 유저가 인증되는 방법은 모듈 마다 틀립니다. 그러나 인증은 보통 유저에게 패스워드를 물어보고, 일회성 패스워드 혹은 토큰을 요구하거나 유저의 아이덴티티를 확인하는 다른 방법을 물어 봅니다. 다른 인증 메카니즘으로는 생체 스캔및 스마트 카드등이 있습니다. flags 매개변수가 인증 서비스에서 사용되는 다양한 플래그들을 지정하는데에 사용될 수 있습니다.

맨위로 가기

PAM 어플리케이션 예제

인증을 위해 PAM 을 사용하는 예제 어플리케이션을 살펴 보는데에 충분한 정보를 다루었습니다. 이전의 글처럼 이 예제는 현재 유저(프로세스의 실제 유저 ID 에 의해 결정됨) 패스워드를 반복적으로 맞을때 까지 물어 봅니다. 아래에 우리의 예제 프로그램의 소스가 있습니다.

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <pwd.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 #include <string.h>
 7 #include <security/pam_appl.h>
 8 extern int check_conv (int num_msg, struct pam_message **msg,
 9 struct pam_response *resp, void *app_data);
10 int main (void)
11 {
12     struct passwd *pwd_info;
13     struct pam_conv conv = {check_conv, NULL};
14     pam_handle_t *ph;
15     int error;
16     if ((pwd_info = getpwuid (getuid ())) == NULL) {
17             fprintf (stderr, "Call to getpwuid failed\n");
18             exit (1);
19     }
20     if ((error = pam_start ("check_pass", pwd_info -> pw_name,
21         &conv, &ph)) != PAM_SUCCESS) {
22         fprintf (stderr, "Call to pam_start failed: %s\n",
23             pam_strerror (ph, error));
24         exit (1);
25     }
26     for (;;) {
27         error = pam_authenticate (ph, 0);
28         if (error == PAM_SUCCESS) {
29             printf ("Passwords match.\n");
30             break;
31         } else {
32             printf ("Passwords don't match.\n");
33         }
34     }
35     pam_end (ph, 0);
36     return (0);
37 }


37줄 의 프로그램을 자세히 살펴 봅시다.

1-9: 헤더파일을 포함시키고 대화 함수를 정의 합니다. 비록 이글의 다음 시리즈 까지 대화 함수에 대해서는 다루지 않지만 이 예제에서 사용한 것을 아래에 공개 함으로써 테스트 해보고 싶은 독자들은 얼마든지 테스트해 보실 수 있습니다.

16-19: getpwuid 을 호출해서 프로세스의 실제 유저 ID 에 의해 인식되는 현재 유저의 패스워드 파일 정보를 얻습니다.getlogin 혹은 cuserid 를 대신 사용할 수도 있지만 보안이 중요한 프로그램에서는 이러한 함수들의 사용을 자제해야 합니다. 왜냐하면 이것들은 /var/adm/utmpx 의 내용을 기반으로 하고 있기 때문입니다. 몇몇 유닉스 플랫폼에서, 비록 솔라리스 플랫폼에서는 아니더라도, 이 파일은 누구든 쓰기가 가능합니다. 그러므로 수상한 유저들이 파일의 항목을 변경할 수 있고 우리의 프로그램은 이러한 것을 발견해 내지 못할 것입니다. 이 작업을 하는 이유는 후에 pam_start 에 유저의 유저이름을 전달해야 하기 때문입니다.

20-25: pam_start 를 호출해서 PAM 세션을 초기화 합니다. "check_pass" 를 서비스 이름으로, 그리고 유저 이름은 이전에 얻어온 것을 그대로 사용 합니다. 주의할 점으로 우리가 사용하는 서비스 이름은 기본 /etc/pam.conf 파일에 존재하지 않음으로 other 서비스 인증 항목이 사용될 것입니다. 만약 에러가 발생한다면 유저에게 알리고 종료 합니다.

26-34: 무한 반복 루프에서 pam_authenticate 를 호출해서 유저를 인증하고 pam_start 를 호출했을때 지정한 대화 함수를 사용합니다. 아주 간단하게 얘기해서 유저에게 암호 입력 프롬프트를 출력하고 입력 받는 것은 이 대화 함수 입니다. 만약 pam_authenticate 이 PAM_SUCCESS 를 리턴하면 패스워드가 맞았다고 출력하고 루프를 빠져 나옵니다. 그렇지 않다면 오류 메세지를 출력하고 재시도 합니다.

35: pam_end 를 호출해서 PAM 트랜젝션을 종료시킵니다.

아래에 우리의 대화 함수의 소스가 있습니다. (이 시리즈의 다음 글에서 자세히 살펴볼 것입니다.)

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <pwd.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 #include <string.h>
 7 #include <security/pam_appl.h>
 8 int check_conv (int num_msg, struct pam_message **msg,
 9     struct pam_response **resp, void *app_data)
10 {
11     struct pam_message *m = *msg;
12     struct pam_response *r;
13     int i;
14     char *ct_passwd;
15     if ((num_msg <= 0) || (num_msg >= PAM_MAX_NUM_MSG)) {
16         fprintf (stderr, "Invalid number of messages\n");
17         *resp = NULL;
18         return (PAM_CONV_ERR);
19     }
20     if ((*resp = r = calloc (num_msg, sizeof (struct pam_response))) == NULL)
21         return (PAM_BUF_ERR);
22     for (i = 0; i < num_msg; i++) {
23         switch (m->msg_style) {
24             case PAM_PROMPT_ECHO_OFF:
25                 ct_passwd = getpassphrase ("Enter password: ");
26                 r->resp = strdup (ct_passwd);
27                 m++;
28                 r++;
29                 break
30             case PAM_PROMPT_ECHO_ON:
31                 if (m->msg)
32                     fputs (m->msg, stdout);
33                 r->resp = NULL;
34                 m++;
35                 r++;
36                 break;
37             case PAM_ERROR_MSG:
38                 if (m->msg)
39                     fprintf (stderr, "%s\n", m->msg);
40                 m++;
41                 r++;
42                 break;
43             case PAM_TEXT_INFO:
44                 if (m->msg)
45                     printf ("%s\n", m->msg);
46                 m++;
47                 r++;
48                 break;
49         }
50         return (PAM_SUCCESS);
51     }
52 }


이 프로그램을 컴파일 한다음 실행하면 다음과 같은 일이 벌어 집니다.

>
rich@marrakesh4112# make pam_check_pass
cc -o pam_check_pass -lpam pam_check_pass.c pam_check_pass_conv.c
pam_check_pass.c:
pam_check_pass_conv.c:
rich@marrakesh4113# ./pam_check_pass
Enter password: 
Passwords don't match.
Enter password: 
Passwords don't match.


어떠한 패스워드를 우리가 입력하더라도 우리는 일치하지 않는다는 메세지 만 계속해서 보게 될 것입니다. 이것은 유저 왜냐하면 "rich" 가 권한부여되지 않았기 때문입니다. 이전의 글에서 우리는 shadow 파일을 읽어야만 패스워드를 비교할 수 있었습니다. 왜냐하면 "rich" 는 로컬 유저이고 shadow 파일(/etc/shadow) 은 오직 루트만이 읽을 수 있기 때문입니다. 루트로 로그인 해서 다시 시도해 봅시다.

>
rich@marrakesh4114# su
Password: 
# ./pam_check_pass
Enter password: 
Passwords don't match.
Enter password: 
Passwords match.


이번에는 첫번째 잘못된 패스워드를 입력하고 두번째에서 성공했습니다. 최소 권한을 사용하는 것이 PAM 을 사용하는데에 옵션은 아님을 주의하시기 바랍니다. 이것은 왜냐하면 모든 존 권한이 요구되기 때문입니다.

이전과 마찬가지로 아주 약간의 작업으로 이 예제는 간단한 터미널 락킹 프로그램으로 사용될 수 있습니다. 이 것은 여러분들에게 맡기겠습니다.

맨위로 가기

요약

이글에서 우리는 PAM 의 개요와 PAM 프레임워크의 다양한 부분에 대해 알아 보았습니다. PAM 서비스 모듈과 PAM 설정 파일, 그리고 서비스 모듈이 어떻게 쌓이는지도 알아 보았습니다.

그 다음 PAM API 몇가지 중요한 함수들을 살펴 보았습니다: pam_start, pam_end, 그리고 pam_authenticate. 최종적으로 모든 것을 묶는 예제 프로그램을 작성했습니다: 첫번째 글에서 작성한 프로그램을 PAM 을 이용하는 버전으로 새롭게 수정한 예제 프로그램.

이 시리즈의 다음 글에서 우리는 PAM 대화 함수를 작성하는 법과 몇몇 다른 API 함수들을 자세히 살펴볼 것입니다.

맨위로 가기

감사의 인사

이 글을 리뷰해준 Glenn Brunette 에게 감사의 인사를 전합니다.

맨위로 가기

참고자료 맨위로 가기

저자에 관하여

Rich Teer 는 My Online Home Inventory 의 CEO 이며 독립 솔라리스 컨설턴트로 솔라리스 커뮤니티의 10년 이상 활동한 멤버 입니다. 썬의 베스트 셀러인 Solaris Systems Programming 의 저자이고 다양한 글을 작성했습니다. 오픈솔라리스 파일럿 프로그램의 멤버였고 현재는 오픈솔라리스 Governing Board(OGB) 중에 한명입니다. Rich 는 현재 Britich Columbia, Kelowna 에 그의 와이프 Jenny 와 살고 있습니다. 그의 웹 사이트는 www.rite-group.com/rich 입니다.

맨위로 가기



이 아티클의 영문 원본은
http://developers.sun.com/solaris/artic ··· is2.html
에서 볼수 있습니다.

"개발자코너" 카테고리의 다른 글

2007/10/22 09:31 2007/10/22 09:31
TAG , ,

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/451

댓글을 달아 주세요

[로그인][오픈아이디란?]

◀ Prev 1  ... 194 195 196 197 198 199 200 201 202  ... 624  Next ▶