Loading... # 0x0 前言 PAM (Pluggable Authentication Modules) 系统的一部分,它是一个用于 Linux 系统认证的模块化框架。 如果给 ssh 登录添加 `TOTP` 验证一般会用到 `google-authenticator` 模块,这个模块就是实现和使用 PAM API 实现的。 PAM 不光可以在 ssh 登录时使用在其他大多数需要验证的时候也可以使用,甚至可以在自己程序中调用,总之 PAM 是一个非常好用的系统认证框架。 本篇文章将使用 Go 实现相关功能,而 PAM 的接口是用 C 语言实现的。因此将会使用 Go 的 `CGO` 进行编译,并且会尽量减少 C 语言的部分。 # 0x1 准备 因为 PAM 是 Linux 系统的认证框架,所以在 Linux 上开发最方便 首先安装依赖库: Debian/Ubuntu ```sh apt install libpam0g-dev ``` Centos ```sh yum install pam-devel ``` 安装后 include 文件在 `/usr/include/security/` 目录下 # 0x2 开发 PAM 认证模块 首先在 Go 中引入 C 的库, Go 是在 `import "C"` 上方使用注释的方式引入 C 的内容 注意 `import "C"` 上方不能有空行 `#cgo LDFLAGS: -lpam` 是 `CGO` 的命令,`LDFLAGS: -lpam` 是表示链接 PAM 静态库 `typedef const char cchar_t;` 是将 `cchar_t` 自定义为 `const char` 关键字,因为后面讲 C 转写成 GO 的时候 `const xx` 无法转写,因此需要提前定义一个关键字 `#include "pam_prompt_wrapper.h"` 是引入一个本地自定义的库,后面会后详解 ```golang /* #cgo LDFLAGS: -lpam #include <stdio.h> #include <stdlib.h> #include <security/pam_appl.h> #include <security/pam_modules.h> #include <security/pam_ext.h> #include "pam_prompt_wrapper.h" typedef const char cchar_t; */ import "C" ``` ## 函数说明 `pam_sm_authenticate`是认证逻辑的接口函数,也是认证的核心,函数定义在`pam_modules.h`里 *接口函数是需要开发人员自己实现的函数,在函数内可以实现各种自定义的功能* 函数签名为 ```c_cpp int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv); ``` 使用 Go 实现,因为这个函数是要被外部调用的,因此需要在上方添加 `//export` 的注释,`CGO` 转写成 C 的时候会转成 `extern` 关键字 ```golang //export pam_sm_authenticate func pam_sm_authenticate(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int { return C.PAM_SUCCESS } ``` `pam_sm_acct_mgmt`是用户识别管理接口函数,用户判断用户否有权限,ssh登录中需要,函数定义也在`pam_modules.h`里,函数签名为 ```c_cpp int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv); ``` 使用 Go 实现 ```golang //export pam_sm_acct_mgmt func pam_sm_acct_mgmt(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int { return C.PAM_SUCCESS } ``` `pam_prompt`是认证过程中用于交互的函数,改函数直接调用,函数定义在 `pam_ext.h` 中,函数签名为 ```c_cpp extern int PAM_FORMAT((printf, 4, 5)) PAM_NONNULL((1,4)) pam_prompt (pam_handle_t *pamh, int style, char **response, const char *fmt, ...); ``` **注意** 该函数中带有 `...` 表示可以接受可变参数,而 `CGO` 中则不支持调用可变参数函数。因此需要使用 C 将该函数简单包装成一个固定参数的函数 ```c_cpp int pam_prompt_wrapper (pam_handle_t *pamh, int style, char **response,const char *str) { return pam_prompt(pamh, style, response, "%s", str) } ``` 并且该函数要额外使用 C 文件存储,并在 Go 中引入 ## 代码文件 pam_prompt_wrapper.h ```c_cpp #include <security/pam_appl.h> int pam_prompt_wrapper(pam_handle_t *pamh, int style, char **response, const char *str); ``` pam_prompt_wrapper.c ```c_cpp #include <security/pam_appl.h> #include <security/pam_ext.h> #include <security/pam_modules.h> #include <stdarg.h> int pam_prompt_wrapper(pam_handle_t *pamh, int style, char **response, const char *str) { return pam_prompt(pamh, style, response, "%s", fmt); } ``` main.go ```golang package main /* #cgo LDFLAGS: -lpam #include <stdio.h> #include <stdlib.h> #include <security/pam_appl.h> #include <security/pam_modules.h> #include <security/pam_ext.h> #include "pam_prompt_wrapper.h" typedef const char cchar_t; */ import "C" import ( "fmt" "os" "unsafe" ) //export pam_sm_authenticate func pam_sm_authenticate(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int { // 实现认证功能 // 例如: // 认证时会显示 input > // 输入 123 后认证成功,否则失败 a, _ := qa(pamh, false, "input >") if a == "123" { return C.PAM_SUCCESS } return C.PAM_AUTH_ERR } //export pam_sm_acct_mgmt func pam_sm_acct_mgmt(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int { // 这里直接返回成功,也可以根据自己需求添加更多功能 return C.PAM_SUCCESS } // 简单封装了交互函数 func qa(pamh *C.pam_handle_t, echo bool, query string) (string, C.int) { var f C.int f = C.PAM_PROMPT_ECHO_OFF if echo { f = C.PAM_PROMPT_ECHO_ON } input := C.CString("") defer C.free(unsafe.Pointer(input)) prompt := C.CString(query) defer C.free(unsafe.Pointer(prompt)) code := C.pam_prompt_wrapper(pamh, f, &input, prompt) return C.GoString(input), code } func main() { } ``` ## 编译 需要将 Go 编译成 C 的共享库 ```sh go build -buildmode=c-shared -o pam_example.so ``` # 0x3 使用 需要将模块放置到指定目录 ```sh sudo cp pam_example.so /lib/x86_64-linux-gnu/security ``` 然后启动,这里展示在 ssh 登录验证中使用我们的模块 首先编辑 `/etc/ssh/sshd_config` ,修改 `ChallengeResponseAuthentication` 或 `KbdInteractiveAuthentication `选项的值将其设为 `yes` 重启 `sshd` 服务 `sudo service sshd restart` 编辑 `/etc/pam.d/sshd` ,在文件添加 `auth required pam_example.so` Last modification:October 20, 2023 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 0 如果觉得我的文章对你有用,请随意赞赏
5 comments
pam_prompt_wrapper.c中最后应该是str而非fmt吧
请教一下博主,main.go的作用是什么呢,编译的so库里面似乎没用到这个文件
main.go是Go的文件,也是这篇使用Go开发编译实现PAM的主文件,go build 命令就是编译该目录下的*.go文件
哦,我最开始以为main.go是测试代码,先入为主了
hhh