LDAP/SASL/GSSAPI/Kerberos编程API(4)--krb5客户端(续1--本地)

上篇<编程API(2)--krb5客户端>介绍了通过krb5_get_init_creds_password()函数输入用户、口令经krb5服务器认证的本地应用例子,'客户端'是指认证是作为krb5的客户端,其应用仍是本地,没该应用的服务端.如unix本地登录PAM插件libpam-krb5
本文同样是本地应用,但是假设已有票据的情景下,不需再输入用户、口令便可验证

如:su命令

linlin@debian:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: [email protected]

Issued Expires Principal
Jul 27 07:24:53 2020 Jan 25 22:24:48 2021 krbtgt/[email protected]
linlin@debian:~$
linlin@debian:~$ su krblinlin
Password: 输入krb5主体用户krblinlin的密码
krblinlin@debian:/home/linlin$

本来期望su不再输入口令,但经测试,krb5化了的su在已存在票据情况下仍是提示要输入口令,不知是不是配置问题?还是我理解错了su的krb5用法?
libpam-krb5里明明有krb5_kuserok()函数,本文就是参考libpam-krb5和MIT krb5源码包测试用例/src/krb5/1.18.3-4/src/tests/localauth.c

MIT Kerberos文档解释krb5_kuserok:
Determine if a principal is authorized to log in as a local user.
此句英文是不是说明krb5_kuserok用途用来验证主体用户是否是本地用户?而不是作为认证?

1.源代码
//源文件名:testlocalkrb.c

#include <stdio.h>
#include <stdlib.h>
#include <krb5.h>

int main()
{ 
    krb5_context context = NULL;
    krb5_error_code krberr;
    krb5_principal kprincpw = NULL;

    const char * errmsg;
    krberr = krb5_init_context(&context);

    if (krberr) {
            errmsg = krb5_get_error_message(NULL, krberr);
            printf("Err: Kerberos context initialization failed -> %s\n", errmsg);
            goto cleanup;
        }

    krberr = krb5_parse_name(context, "[email protected]", &kprincpw);//#1
/*
    //#3
    krb5_ccache ccdef;
    krberr = krb5_cc_default(context, &ccdef);
    krberr = krb5_cc_get_principal(context, ccdef, &kprincpw );
*/   
    if (krberr) {
            errmsg = krb5_get_error_message(context, krberr);
            printf("Err: Failed to parse princpal %s -> %s\n", errmsg);
            goto cleanup;
        }

    // krb5_kuserok返回的是krb5_boolean  
    if (!krb5_kuserok(context , kprincpw , "krblinlin")) //#2
    { 
        printf("Err: Failed\n " );
        goto cleanup;
    }
    printf("krb5_kuserok\n " );

cleanup: 
    if (kprincpw) krb5_free_principal(context, kprincpw);
    if (context) krb5_free_context(context);

    return 0;   
}

2.解析
MIT Kerberos文档
krb5_boolean krb5_kuserok(krb5_context context, krb5_principal principal, const char * luser)

param:

[in] context - Library context

[in] principal - Principal name

[in] luser - Local username

3.编译
linlin@debian:~$ gcc -o testlocalkrb testlocalkrb.c -lkrb5

4.配置
linlin@debian:~$ cat /etc/krb5.conf
[libdefaults]
default_realm = CTP.NET
linlin@debian:~$

5.添加krblinlin本地用户
本地用户krblinlin密码可和krblinlin主体用户不一样
linlin@debian:~$ cat /etc/passwd |grep linlin
linlin:x:1000:1000:,,,:/home/linlin:/bin/bash
krblinlin:x:1001:1001:,,,:/home/krblinlin:/bin/bash
linlin@debian:~$

6.运行
先生成票据
linlin@debian:~$kinit --no-forwardable krblinlin
[email protected]'s Password:
linlin@debian:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: [email protected]
...
linlin@debian:~$

运行testlocalkrb程序已验证成功
linlin@debian:~$ ./testlocalkrb
krb5_kuserok
linlin@debian:~$

linlin@debian:~$ strace ./testlocalkrb
...
openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3
lseek(3, 0, SEEK_CUR) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=1869, ...}) = 0
read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1869
close(3) = 0
access("/home/krblinlin/.k5login", F_OK) = -1 ENOENT (没有那个文件或目录)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
write(1, "krb5_kuserok\n", 13krb5_kuserok) = 13
...
linlin@debian:~$

可见,通过krb5_kuserok()函数,本地应用在已存在票据时不必输入口令了

7.跟踪调试
下面整理调试步骤顺序
1)没有票据
linlin@debian:~$ klist
klist: No ticket file: /tmp/krb5cc_1000
linlin@debian:~$ ./testlocalkrb
Err: Failed
linlin@debian:~$
运行失败

2)以下的调试先生成票据
linlin@debian:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: [email protected]
...
linlin@debian:~$

3)未有krblinlin本地用户
运行testlocalkrb程序失败

linlin@debian:~$ strace ./testlocalkrb
...
openat(AT_FDCWD, "/etc/krb5.conf", O_RDONLY) = 3
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=133, ...}) = 0
read(3, "[libdefaults]\n\n\tdefault_realm = "..., 4096) = 133
...
openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3
lseek(3, 0, SEEK_CUR) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=1817, ...}) = 0
read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1817
lseek(3, 0, SEEK_CUR) = 1817
read(3, "", 4096) = 0
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
write(1, "Err: Failed\n", 12Err: Failed) = 12
...
linlin@debian:~$
经strace跟踪,是krb5_kuserok需读取/etc/passwd 文件,而本地没krblinlin用户而失败,说明krb5_kuserok是必须有本地unix用户

linlin@debian:~$ cat /etc/passwd |grep linlin
linlin:x:1000:1000:,,,:/home/linlin:/bin/bash
linlin@debian:~$

4)添加krblinlin本地用户
运行testlocalkrb程序已验证成功

5)关闭kdc服务器
在客户机运行testlocalkrb也验证成功

6)源代码#2处"krblinlin"改为"linlin",重新编译
运行testlocalkrb程序失败
linlin@debian:~$ strace ./testlocalkrb
...
openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3
lseek(3, 0, SEEK_CUR) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=1869, ...}) = 0
read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1869
close(3) = 0
access("/home/linlin/.k5login", F_OK) = -1 ENOENT (没有那个文件或目录)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
write(1, "Err: Failed\n", 12Err: Failed) = 12
...
linlin@debian:~$
是不是有验证krb5主体名和unix用户名映射要匹配?缺省主体名称和unix用户名称相同视同同一匹配用户?

7)源代码#2处"krblinlin"改为NULL
运行段错误

8)源代码#2处改回"krblinlin"

9)验证票据过期情况
客户机与kdc服务器断开连接
客户机的日期
linlin@debian:~$ date
2020年 12月 15日 星期二 06:31:53 UTC
linlin@debian:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: [email protected]

Issued Expires Principal
Jul 17 01:12:47 2020 Jan 15 16:12:44 2021 krbtgt/[email protected]
linlin@debian:~$ ./testlocalkrb
krb5_kuserok
linlin@debian:~$
显示票据有效日期到2021年

修改客户机日期为2022年,使票据过期
linlin@debian:~$ date
2022年 02月 15日 星期二 06:34:39 UTC
linlin@debian:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: [email protected]

Issued Expires Principal
Jul 17 01:12:47 2020 >>>Expired<<< krbtgt/[email protected]

linlin@debian:~$ ./testlocalkrb
krb5_kuserok
linlin@debian:~$

可见票据已过期,仍能验证成功

将代码#1改为#3,仍然过期能验证成功

10)改回代码#1

11)当/etc/krb5.conf不存在,或没配置default_realm
运行testlocalkrb程序失败
linlin@debian:~$ strace ./testlocalkrb
...
stat("/etc/krb5.conf", 0x7fff510fcbb0) = -1 ENOENT (没有那个文件或目录)当krb5.conf不存在
...
openat(AT_FDCWD, "/tmp/krb5cc_1000", O_RDONLY|O_CLOEXEC) = 3
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
fcntl(3, F_OFD_SETLKW, {l_type=F_RDLCK, l_whence=SEEK_SET, l_start=0, l_len=0}) = 0
fcntl(3, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE)
fstat(3, {st_mode=S_IFREG|0600, st_size=807, ...}) = 0
read(3, "\5\4\0\0\0\0\0\1\0\0\0\1\0\0\0\7CTP.NET\0\0\0\tkrbli"..., 4096) = 807
...
openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3
lseek(3, 0, SEEK_CUR) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=1869, ...}) = 0
read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1869
close(3) = 0
access("/home/krblinlin/.k5login", F_OK) = -1 ENOENT (没有那个文件或目录)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
write(1, "Err: Failed\n", 12Err: Failed) = 12
...
linlin@debian:~$

经测试,只需配置default_realm = CTP.NET 就正常,能不能在程序里设置default_realm ?

12)su命令测试
root@debian:/# apt-get install libpam-krb5
正在读取软件包列表... 完成

根root用户su到普通用户是不需输入口令的,所以下面测试su命令是在普通用户linlin下进行
linlin@debian:~$ klist
klist: No ticket file: /tmp/krb5cc_1000
linlin@debian:~$
显示无票据

切换到krblinlin用户
linlin@debian:~$ su krblinlin
Password: 输入主体用户密码
krblinlin@debian:/home/linlin$ klist
Credentials cache: FILE:/tmp/krb5cc_1001_s3gn7n
Principal: [email protected]

Issued Expires Principal
Jul 27 07:39:09 2020 Jul 28 07:39:09 2020 krbtgt/[email protected]
krblinlin@debian:/home/linlin$
显示su后生成了票据,注意票据文件名中的'1001',显然是krblinlin的用户id,而不是linlin的用户id(1000)

从krblinlin退出返回linlin
krblinlin@debian:/home/linlin$ exit
exit
linlin@debian:~$ ls /tmp
linlin@debian:~$
/tmp下已不见krb5cc_1001_s3gn7n票据,应该是退出后libpam-krb5销毁票据吧

先生成票据再su
linlin@debian:~$ kinit --no-forwardable krblinlin
[email protected]'s Password:
linlin@debian:~$ klist
Credentials cache: FILE:/tmp/krb5cc_1000
Principal: [email protected]
...
linlin@debian:~$
linlin@debian:~$ su krblinlin
Password: 乱输
su:鉴定故障
linlin@debian:~$ su krblinlin
Password: 输入krb5主体用户的密码
krblinlin@debian:/home/linlin$
已进入krblinlin

上面只能说明su在已有票据下仍然要提示输入口令,就不知libpam-krb5是否能配置免输入口令
libpam-krb5源码有创建krb5cc_pam_xx这样的票据,但上面su实验看不到
下面使用LXC容器(名:vmcln)测试su,利用LXC资源控制容器cpu使用很低的占用率,观测票据所在目录/tmp的文件变化
宿主上查看容器vmcln缺省cpu占用比例为1024
root@debian:/home/linlin# lxc-cgroup -n vmcln cpu.shares
1024
root@debian:/home/linlin#

到容器上su
linlin@vmcln:~$ su krblinlin
Password: 输入口令,暂不要回车

到宿主,设容器cpu很低占比,设为2
root@debian:/home/linlin# lxc-cgroup -n vmcln cpu.shares 2
root@debian:/home/linlin#

宿主上执行无限死循环
linlin@debian:~$ while true; do true ; done

容器vmcln上的cpu.shares 明显起作用,容器运行已很慢了

在宿主上执行列出容器临时目录的无限死循环,然后到容器按回车,几秒后见输出结果过程
linlin@debian:~$ while true; do ls -l /mnt/vm/lxc/vmcln/tmp |grep krb5 ; done
-rw------- 1 root linlin 0 12月 3 14:39 krb5cc_pam_lU0B9q
...
-rw------- 1 root linlin 48 12月 3 14:39 krb5cc_pam_lU0B9q
...48和506是文件大小字节
-rw------- 1 root linlin 506 12月 3 14:39 krb5cc_pam_lU0B9q
...
-rw------- 1 root 1001 0 12月 3 14:39 krb5cc_1001_xjZF0q
...
-rw------- 1 root 1001 48 12月 3 14:39 krb5cc_1001_xjZF0q
...
-rw------- 1 root linlin 506 12月 3 14:39 krb5cc_pam_lU0B9q
-rw------- 1 root 1001 506 12月 3 14:39 krb5cc_1001_xjZF0q
...票据krb5cc_1001_xjZF0q用户属主由root改为krblinlin(用户id=1001)
-rw------- 1 1001 1001 506 12月 3 14:39 krb5cc_1001_xjZF0q
-rw------- 1 root linlin 506 12月 3 14:39 krb5cc_pam_lU0B9q
-rw------- 1 1001 1001 506 12月 3 14:39 krb5cc_1001_xjZF0q
...票据krb5cc_pam_lU0B9q消失
-rw------- 1 1001 1001 506 12月 3 14:39 krb5cc_1001_xjZF0q

命令su具有setuid位,从上可见su后krb5验证通过后先创建root用户拥有的krb5cc_pam_xx票据,后才创建su入的krblinlin(用户id=1001)拥有的krb5cc_1001_xx票据,随后krb5cc_pam_xx票据被销毁.因此su后的票据是在切换后的用户的会话下

还是没跟踪到krb5_kuserok()函数,不再探究了

7.小结
1)通常的使用krb5认证的应用都是具有应用服务器的典型的C/S结构,其客户端已存在票据时不需再输入用户、口令
2)使用krb5认证的本地应用本人目前只见unix本地登录,目前测试在已存在票据时,su命令仍要输入口令
3)虽然本文已验证krb5_kuserok()函数可用在本地应用已存在票据时不必输入口令,但本人不太清楚krb5_kuserok()函数应用在哪方面,好像是用在和unix用户相关吧
4)票据过期对krb5_kuserok不起作用

猜你喜欢

转载自blog.51cto.com/13752418/2573750