인증및 보안 서비스를 위한 PAM(Pluggable Authentication Modules) 서비스 모듈을 작성하는 방법에 대해 배워 보고 예제 모듈도 보도록 하겠습니다.
이 시리즈의 앞에 세번째 글에서 (Part 1, Part 2, 그리고 Part 3) 우리들은 패스워드 기반 유저인증과 PAM 의 사용과 관련하여 집중적으로 기본 사항들을 다루었습니다. 어플리케이션(PAM 컨슈머 라고 불림)이 인증을 위해 사용하는 PAM API 에 대해 소개 했고 PAM 대화 함수 작성 방법에 대해서도 보여 드렸습니다.
4번째 글이자 마지막 글로써 PAM 서비스 모듈에 대해 소개하고 작성하는 방법에 대해 알아볼 것입니다.
서비스 모듈 은 공유 라이브러리로써 아래의 사항중에서 하나 혹은 그 이상의 항목을 제공하는 인증 및 보안 서비스입니다:
- 인증(Authentication). 이러한 서비스 모듈들은 계정 혹은 서비스에 관한 접근 인증에 사용되고 사용자 암호를 설정할 때에도 사용됩니다.
- 계정 관리(Account management). 이러한 서비스 모듈들은 계정 접근시의 유효성을 확인합니다. 예를 들어 계정 관리 모듈은 패스워드, 계정 유효 기간 혹은 접근 시간 혹은 계정 잠금 제한 같은 사항들을 확인할 수 있습니다.
- 세션 관리(Session management). 이러한 서비스 모듈들은 PAM 세선을 설정하고 이전에 인증되었던 사용자 혹은 서비스의 PAM 세션을 해지 하는 일을 담당 합니다.
- 암호 관리(Password management). 이러한 서비스 모듈들은 패스워드 길이와 이전 패스워드 사용-불가 규칙등을 준수하도록 하고 인증 토큰 업데이트 수행등의 일을 담당 합니다.
이상적으로 PAM 서비스는 잘 정의된 간단한 임무들을 가지고 있는 서비스 모듈에서 구현 되어야 합니다. 그러므로 설정의 복잡성이 증가 합니다. 서비스 모듈은 이후에 PAM 설정 파일 /etc/pam.conf 에서 적절한 설정을 통해서 사용될 수 있습니다. 우리는 PAM 설정 파일을 이 시리즈의 Part 2 에서 이미 다루었습니다.
이 시리즈의 Part 2 에서 PAM 컨슈머가 유저 인증 및 관련 기능을 수행하기 위해서는 아래의 함수들 중 하나 혹은 그 이상의 함수를 호출해야 한다고 이미 다룬바 있습니다:
pam_authenticatepam_acct_mgmtpam_setcredpam_open_sessionpam_close_sessionpam_chauthtok
각각의 함수들은 pam_ 접두사를 pam_sm_ 으로 바꾼 것 외에는 완전히 동일한 이름을 가진 서브시 모듈 내에서 구현되어 있습니다. 그러므로 pam_authenticate 는 pam_sm_authenticate 에 pam_sm_acct_mgmt 는 pam_acct_mgmt 를 구현하고 있습니다. 우리들이 작성할 서비스 모듈들은 반드시 하나 혹은 그 이상의 이러한 함수들을 제공해야 합니다.
PAM 컨슈머 어플리케이션과의 통신을 위해서 서비스 모듈은 pam_get_item 과 pam_set_item 함수를 아래의 코드 예제 처럼 사용 합니다. (여기서 또 한가지 지적해야 할 것은 PAM 컨슈머는 다른 서비스 모듈들과 통신하는데에 이러한 함수들을 사용할 수 있다는 것입니다.)
#include <security/pam_appl.h> int pam_get_item(const pam_handle_t *pamh, int item_type, void **item); int pam_set_item(pam_handle_t *pamh, int item_type, const void *item); |
pam_set_item 함수는 서비스 모듈이 pamh 라는 핸들에 의해 지정된 PAM 트랜젝션의 정보를 업데이트하는 것을 가능하도록 합니다. item_type 에 의해 기술된 정보의 타입은 pam_set_item 멘페이지에 기술된 12가지 중에 한가지가 될 수 있습니다. 아이템 타입 종류의 예로는 PAM_AUTHTOK, PAM_CONV, PAM_USER, 그리고 PAM_USER_PROMPT 등을 들 수 있습니다. PAM 정보에 설정하길 원하는 값은 item 에 의해 지정됩니다.
유사하게 PAM 트랜젝션의 정보는 pam_get_item 을 호출함으로써 접근될 수 있습니다. 이러한 경우 지정된 타입의 PAM 정보의 포인터는 item 에 저장됩니다.
서비스 모듈들은 pam_get_data 와 pam_set_data 함수를 이용해서 접근 될 수 있고 모듈 특수한 벙보들을 업데이트할 수 있습니다. 이 함수들에 대해 자세히 설명하지는 않을 것입니다. 왜냐하면 우리들은 PAM 서비스 모듈들과 컨슈머간의 통신에 촛점을 맞추고 있기 때문입니다. 관심있는 독자들은 이 함수들의 멘페이지에서 자세한 정보를 참고하시기 바랍니다.
PAM 서비스 모듈은 반드시 PAM 리턴 코드를 컨슈머에게 제공해야 합니다. 이러한 리턴 코드는 다음의 3가지 타입중에 반드시 하나 입니다:
PAM_SUCCESS. 모듈은 요청된 정책의 일부로써 긍정적인 결정을 내립니다.PAM_IGNORE. 모듈은 요청된 정책의 일부로써 결정을 내리지 않습니다.PAM_<error>. 모듈은 요청된 정책의 일부로써 부정적인 결정을 내립니다. 에러 코드는 PAM 인프라스트럭쳐에 범용적이거나(예를 들어PAM_USER_UNKNOWN은 PAM 핸들에 의해 지정된 유저를 알 수 없다는 의미이고PAM_PERM_DENIED는 인증 요청이 모듈에의해 거부 되었다는 것을 뜻한다) 모듈에 특수한 것일 수 있습니다. 후자의 경우 에러 코드는 반드시 각 모듈에서 유일한 것이어야 하고(즉 다른 모듈에서 사용되지 않아야 함) 그러므로 모듈의 멘페이지에 반드시 기술되어야 합니다.
원하지 않는 메시지의 출력을 막기 위해 모든 서비스 모듈은 PAM_SILENT 플래그를 준수해야 합니다. 분석을 위한 디버깅 정보 로깅을 위해서는 디버그 플래그를 사용해서 syslog 설비를 통해 출력할 것을 권장 합니다. syslog 를 이용하여 로그화된 디버깅 메세지들은 LOG_AUTH 설비와 LOG_DEBUG 심각성 레벨을 사용해야 합니다. syslog 를 이용하여 로그된 다른 메시지들은 반드시 LOG_AUTH 설비를 적절한 우선순위레벨과 같이 사용해야 합니다.
중요:syslog-관련 함수들 openlog, closelog, setlogmask 는 반드시 서비스 모듈에서 사용되어서는 안됩니다. 왜냐하면 어플리케이션의 설정에 영향을 받기 때문입니다.
이제까지 서비스 모듈에 관해서 설명했고 무엇을 해야 하는지에 대해서도 설명했습니다. 이제 실제로 하나를 살펴 봅시다. 우리가 작성할 서비스 모듈은 특정 그룹의 어떠한 사용자들이 접근이 거부 되는지를 이용한 메카니즘을 제공합니다. 예를 들어 이것은 웹 호스팅 회사에서 매우 유용할 것입니다: 고객들은 ftp 와 sftp 를 통해서 접속이 허용되지만 로그인 쉘에 의해서는 금지 됩니다. 이러한 접근 정책은 이 모듈을 통해서 강제 할 수 있고 사용자들을 접근금지 그룹에 등재할 수 있습니다.
이러한 종류의 계정 접근 정책은 성공적으로 인증된 유저에게만 적용됩니다. 그러므로 계정 관리로 분류될 수 있습니다. PAM 을 사용가능한 어플리케이션은 이러한 작업을 위해 pam_acct_mgmt 를 호출합니다. 그러므로 우리들의 예쩨 모듈은 pam_sm_acct_mgmt 을 구현하고 이것의 프로토타입은 다음과 같습니다:
#include <security/pam_appl.h> #include <security/pam_modules.h> int pam_sm_acct_mgmt (pam_handle_t *pamh, int flags, int argc, const char **argv); |
pam_start 에 의해 리턴되는 PAM 핸들은 pamh 로 접근됩니다. flags 는 어플리케이션에 의해 모듈에 전달되어지는 어떠한 플래그도 포함하고 argc 와 argv 는 pam.conf 에 의해 지정된 모듈 옵션들의 숫자와 옵션의 목록들을 포함하고 있습니다.
예제 모듈의 소스 코드는 다음과 같습니다.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <grp.h>
4 #include <string.h>
5 #include <syslog.h>
6 #include <security/pam_appl.h>
7 int pam_sm_acct_mgmt (pam_handle_t *ph, int flags, int argc, char **argv)
8 {
9 char *user = NULL;
10 char *host = NULL;
11 char *service = NULL;
12 char *denied_group = "";
13 char group_buf[8192];
14 struct group grp;
15 struct pam_conv *conversation;
16 struct pam_message msg;
17 struct pam_message *msgp = &msg;
18 struct pam_response *resp = NULL;
19 int i;
20 int err;
21 int no_warn = 0;
22 int debug = 0;
23 int ret_val;
24 for (i = 0; i < argc; i++) {
25 if (strcasecmp (argv[i], "nowarn") == 0)
26 no_warn = 1;
27 else if (strcasecmp (argv[i], "debug") == 0)
28 debug = 1;
29 else if (strncmp (argv[i], "group=", 6) == 0)
30 denied_group = &argv[i][6];
31 }
32 if (flags & PAM_SILENT)
33 no_warn = 1;
34 pam_get_user (ph, &user, NULL);
35 pam_get_item (ph, PAM_SERVICE, (void **)&service);
36 pam_get_item (ph, PAM_RHOST, (void **)&host);
37 if (user == NULL) {
38 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: user not set", service);
39 ret_val = PAM_USER_UNKNOWN;
40 goto out;
41 }
42 if (host == NULL)
43 host = "unknown";
44 if (getgrnam_r (denied_group, &grp, group_buf, sizeof (group_buf)) == NULL) {
45 syslog (LOG_AUTH | LOG_NOTICE, "%s: denied_group: group \"%s\" not defined",
46 service, denied_group);
47 ret_val = PAM_SYSTEM_ERR;
48 goto out;
49 }
50 if (grp.gr_mem[0] == '\0') {
51 if (debug)
52 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: group \"%s\" is empty: "
53 "all users allowed", service, grp.gr_name);
54 ret_val = PAM_IGNORE;
55 goto out;
56 }
57 for (; grp.gr_mem[0]; grp.gr_mem++) {
58 if (strcmp (grp.gr_mem[0], user) == 0) {
59 msg.msg_style = PAM_ERROR_MSG;
60 msg.msg = "Access denied: you are not on the access list for this host.";
61 pam_get_item (ph, PAM_CONV, (void **)&conversation);
62 if ((no_warn == 0) && (conversation != NULL)) {
63 err = conversation->conv (1, &msgp, &resp, conversation->appdata_ptr);
64 if (debug && err != PAM_SUCCESS) {
65 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: conversation returned %s",
66 service, pam_strerror (ph, err));
67 }
68 if (resp != NULL) {
69 if (resp->resp)
70 free (resp->resp);
71 free (resp);
72 }
73 }
74 syslog (LOG_AUTH | LOG_NOTICE, "%s: denied_group: Connection for %s "
75 "not allowed from %s", service, user, host);
76 ret_val = PAM_PERM_DENIED;
77 goto out;
78 }
79 }
80 if (debug)
81 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: user %s is not a member of "
82 "group %s. Access granted.", service, user, grp.gr_name);
83
84 ret_val = PAM_SUCCESS;
85 out:
86 return (ret_val);
87 }
80줄 짜리 함수를 차근차근 살펴보도록 하겠습니다. 이 예제의 목적상 임의로 버퍼 group_buf (13번째 줄에 정의됨) 을 8K 문자열로 제한을 두었음을 참고하시기 바랍니다. 실제 프로그램에서는 sysconf 호출 결과로 얻어진 시스템 값의 최대치에 따라 버퍼 사이즈를 동적으로 조정할 것입니다.
1-6:필수 헤더 파일들을 포함합니다.
24-33:모듈 옵션들을 해석하고 디버그 및 no warning 플래그를 적절하게 설정합니다.
34-36:유저, 서비스, 그리고 원격 호스트 이름을 얻어 옵니다.
37-41:유저가 지정되지 않아다면 접근을 거부 합니다.
44-49:지정된 그룹이 정의되지 않은 그룹이라면 접근을 거부 합니다.
50-56:지정된 그룹이 어떠한 멤버도 가지고 있지 않을때 모든 유저에 대해 접근얼 허용합니다.
57-79:유저가 그룹의 멤버인지 확인합니다. 만약 그렇다면 접근을 거부하고, (warning 이 비활성화 되어 있지 않다면) 대화 함수를 호출하여 적절한 에러 메세지를 유저에게 전달 합니다. 거부는 항상 syslog 로 보고 됨을 참고하시기 바랍니다. 간결성을 위해서 여기에서는 strcmp 를 이용해서 사용자 이름을 비교 하였습니다. 실제 어플리케이션에서는 아마도 strncmp 를 이용해서 버퍼 오버플로우를 회피할 것입니다. 66 줄에 pam_strerror 또한 눈여겨 보시기 바랍니다. 이 함수는 두번째 매개변수와 연관된 에러 메세지를 리턴하고, 이것은 strerror 가 일반적인 에러메세지에서 동작하는 것과 완전히 동일한 방법입니다.
80-84:만약 여기에 다다르면 유저는 지정된 그룹의 멤버가 아니고 결국 접근이 허용 됩니다.
85-87:호출자에게 리턴합니다.
서비스모듈들은 공유 객체들입니다. 그러므로 썬 스튜디오 컴파일러를 이용해서 예제를 빌드하기 위해서 다음의 커맨드를 이용합니다.
rich@ultra20# cc -c -Kpic -o pam_service_module.so pam_service_module.c |
(gcc 사용자들은 -Kpic 를 -fpic 로 바꿔줘야 합니다.)
PAM 인프라스트럭쳐는 다양한 보안 체크를 수행 합니다. 그러므로 우리의 공유 객체는 반드시 아래의 예제처럼 root 에 의해 소유되어야 합니다.
rich@ultra20# su - root@ultra20# chown root:root /home/rich/pam_service_module.so |
서비스 모듈을 테스트하고 배치할 준비가 끝나면 보통 우리는 공유 오브젝트를 /usr/lib/security/$ISA 에 위치시키는데 여기서 $ISA 는 타겟 머신의 명령어 셋을 나타 냅니다. 모듈을 위치할 다른 위치는 (패키지 포맷으로 이들을 제공할 경우) /opt/lib/security/$ISA 입니다.
최종적으로 우리의 새로운 모듈을 아래 처럼 pam.conf 에 새로운 항목으로 추가시켜줘야 합니다.
other account required /home/rich/pam_service_module.so group=staff debug |
모든 작업이 성공적으로 끝나면 그룹 staff 의 모든 멤버들의 접근이 거부될 것입니다. 유저 rich 가 그룹 staff 의 멤버 이므로 ssh 를 이용한 로그인이 아래의 예 처럼 실패할 것입니다. (telnet 접근 또한 실패할 것입니다. 그러나 보안에 대한 생각이 있는 사람들은 telnet 을 쓰지 않을 것입니다.)
rich@sunblade1000# ssh ultra20 Connection closed by 192.168.0.2 |
거부 그룹을 변경하여 (예를 들어 root 로) 아래 처럼 사용자 rich 로그인 하도록 허락합니다.
rich@sunblade1000# ssh ultra20 Last login: Sun Dec 2 16:43:05 2007 from sunblade1000 Sun Microsystems Inc. SunOS 5.11 snv_70 October 2007 rich@ultra20# |
staff 그룹에서 rich 사용자를 삭제하는 것도 동일한 효과를 나타 냅니다.
이 글에서 우리는 PAM 서비스 모듈에 대해 설명했고 그들이 반드시 무엇을 해야 하는지 (즉 서비스 모듈에서 어떠한 것들이 기대 되는지) 에 대해 다루었습니다. 서비스 모듈은 해당 모듈이 어떠한 작업을 하느냐에 따라 반드시 다음의 함수들 중 하나 혹은 그 이상의 함수를 구현해야 한다고 배웠습니다: pam_sm_authenticate, pam_sm_acct_mgmt, pam_sm_setcred, pam_sm_open_session, pam_sm_close_session, 그리고 pam_sm_chauthtok. 또한 서비스 모듈과 PAM 컨슈머가 서로 통신하기 위해 사용되는 두가지의 함수에 대해서도 간단히 설명했습니다.
그 다음에 서비스 모듈이 호출자에게 리턴할 3가지 타입의 값들에 대해 설명하였고 이것은 요청이 성공했는지, 무시되었는지 혹은 에러를 유발했는지(오류 정보를 포함) 를 나타 냅니다.
서비스 모듈을 로깅하는 타입들이 무엇을 해야 하는지에 대해 설명했고 특정 메세지를 로깅하지 않아야될때 대해서도 설명했고, 특정 그룹에 명명된 사용자들의 접근을 거부하는 정책을 구현한 서비스 모듈의 예도 살펴 보았습니다.
마지막으로 서비스 모듈을 빌드 하고 설치 하는 방법에 대해 알아 보았습니다.
이 글을 리뷰해준 Glenn Brunette 에게 감사드립니다.
- 관련 글:
- 저자의 책 Solaris Systems Programming [ISBN 0-201-75039-2] 은 솔라리스 어플리케이션을 개발하는 독자에게 필독서 입니다. 저자의 웹사이트 에서 더 자세한 정보를 살펴 보시기 바랍니다.
- the Solaris 10 Software Developer Collection 내의 많은 책들이 읽을 가치가 있습니다.
- Solaris 10 System Administrator Collection 내의 Solaris Security for Developers Guide 와 System Administration Guide: Security Services 도 읽을 가치가 있습니다.
- 권한-인식 어플리케이션에 관심이 있는 독자들은 저자의 글 Programming in the Solaris OS With Privileges 을 참고하시기 바랍니다.
- PAM 에 관한 다음의 썬 블루프린트 북을 참고하시기 바랍니다:
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
에서 보실 수 있습니다.



전체

댓글을 달아 주세요
댓글을 쓰시려면 로그인해주세요.