【GlusterFS学习之五】:trashdir回收站目录只读权限以及白名单的设计与实现

       gluster3.7版本正式发出了回收站功能,今天就对这个功能进行一个简单的介绍以及设计一个增强的功能来进一步的保证数据安全,再介绍一下对代码的一些理解。


一、Trash translator for GlusterFS

在这里主要介绍一下trash translator的功能,Trash translator为从glusterfs卷里面删除的文件提供一个临时存放的地方,就是相当于为删除的文件提供一个回收站,可以帮助用户获取和恢复临时被删除的数据。每个块都会保留一个隐藏的目录.trash,它将会被用于存放被从各个块删除的文件。这个translator以后还会增强功能来支持被删除文件的恢复。

回收站的目录名应该是可配置的。trash translator也会被用于内部操作比如自卷的自修复以及再平衡。trash translator是设计来拦截unlink(文件删除),truncate(文件压缩),ftruncate(文件夹压缩),rmdir(目录删除)等操作,将目标文件在trash目录中做一份拷贝,然后在原文件上执行相应的操作。

在删除的操作过程中,trash translator将会拦截到unlink调用,然后检查是否和排除模式相匹配(eliminate pattern),如果要操作的文件和排除模式匹配,那么这个translator则继续向下执行unlink调用,直接删除文件,而不会把它保存到.trash目录下面。

然而,如果要操作的文件和排除模式不匹配,文件执行stat操作,成功之后,trash translator将会在.trash目录下面建立相同的路径作为文件的路径。

truncate和ftruncate操作是压缩或者扩大一个文件的大小到一个固定的值。唯一的区别就是在truncate操作下,我们提供文件路径名然而在ftruncate情况下我们使用文件描述符。因此,ftruncate可以处理已经打开的文件。

当trash translator拦截到一个truncate/ftruncate调用,一个新的文件将会在trash中建立,原来文件的内容也通过使用readv和writev函数调用拷贝到新的文件中。一旦所有内容被拷贝晚,trash translator将会继续在原文件上执行truncate调用。

保存在trash目录中的文件都是有时间戳的,为了保持版本,以防同一个文件被一次又一次的执行truncated/deleted操作。


二、需求分析

        之前知道了trash的基本的功能,那么现在要为回收站目录设置一个只读权限以及白名单的功能,也就说在白名单之外的客户端看不到.trash里面的内容并且只有只读权限,而在白名单内的客户端才能执行正常的查看.trash内部文件,删除里面的文件的权限。


三、概要设计

3.1增加两个option:
(1)trashdir-read-only,by default off,当在on的状态下只能对".trashbin"目录进行读操作;
(2)trashdir-whilte-list,通过gluster volume set vol option IP1,IP2..进行设置,即只有在白名单中的IP1,IP2可以对".trashbin"目录里面的内容进行正常的操作。

3.2权限设置说明:
(1)首先还是设置白名单中的IPs对.trashcan进行操作,其他IP只有read-only权限。
(2)再者还是让白名单以外的IP不能看到.trashcan,对ls相关的操作进行权限设置。既要设置.trashbin可见又要设计其只读的原因在于:白名单以外的IPs即使不能看到.trashcan,如果其知道trash目录为.trashcan,而其依旧有操作权限的话,同样可以删除.trashcan目录,因此还是危险,不符合我们设计的初衷。

3.3注册操作:
注册的相应操作有:unlink/truncate/ftruncate/rmdir/mkdir/rename/mknod/symlink/link/open/writev/setxattr/fsetxattr/removexattr/fsyncdir/create/setattr/fsetattr/xattrop/fxattrop/inodelk/finodelk/entrylk/fentrylk/lk/opendir.

3.4操作流程图
【GlusterFS学习之五】:trashdir回收站目录只读权限以及白名单的设计与实现_第1张图片

流程图说明:首先操作请求执行到trash translator的时候进行相应的判断,如果trashdir-read-only在off的状态,那么说明这种情
况下允许所有client对.trashcan进行操作的,如果在on的状态,说明trashdir只读(实际上是卷只读),则要进一步判断;如果
client IP在trash-white-list也就是所谓的白名单里面,那么可以继续进行操作,如果client IP不在trash-white-list里面,那么则要
进一步判断操作的文件是不是在.trashcan内部,如果在内部则只读不允许做其他操作,直接stack_unwind()返回,如果不
在.trashcan里面,那么可以做相应的操作。

3.5主要函数:
+generate_allow_ip_table:生成white list的ip table
+is_client_read_only_trashdir:判断client是否有除了read only之外其他操作的权限,包括trashdir-read-only的状态以及client IP是否在trashdir-white-list中。
+is_trashdir_prefix_of_loc:判断所操作的文件是否在.trashcan目录中。


四、详细设计

4.1 主要函数之一:trash.c : generate_allow_ip_table(xlator *this, trash_private_t *priv, dict_t *options ),在init和reconfigure中进行调用。
/*
 * Function:generate_allow_ip_table
 * Descriptions:generate allow ip table so that the client can do anything not merely read only
 * Parameter:xlator, trash_private_t, dict_t 
 * Return:int32_t
 * */
int32_t
generate_allow_ip_table (xlator_t *this, trash_private_t *priv, dict_t *options)
{
    int                 ret             = 0;
    dict_t             *ip_table        = NULL;
    dict_t             *server_option   = NULL;
    char               *brick_name      = NULL;
    char               *addr_cpy        = NULL;
    char               *addr_str        = NULL;
    char               *searchstr       = NULL;
    char               *trashdir_read_only       = NULL;
    char               *tmp             = NULL;
    data_t             *allow_addr      = NULL;
    xlator_list_t      *parents         = NULL;
    char               *allow_name      = NULL;
    char               *password        = NULL;

    parents = this->parents;

    /*get the root parents xlator:server*/
    while (parents->xlator->parents) {
        parents = parents->xlator->parents;
    }

    if ( strcmp(parents->xlator->type,"protocol/server")!=0 )
        return 0;

    /*get the server xlator info including options,allow name,password and so on*/
    server_option = parents->xlator->options;
    brick_name = FIRST_CHILD(parents->xlator)->name; //brick_name = parents->xlator->children->xlator->name;
    ret = gf_asprintf(&searchstr,"auth.login.%s.allow",brick_name);
    if (-1 == ret){
        gf_log ("features/trashdir-read-only",GF_LOG_WARNING,
                "asprintf failed while setting search string");
        goto out;
    }
    allow_name = gf_strdup ( dict_get (server_option,searchstr)->data );

    ret = gf_asprintf (&searchstr, "auth.login.%s.password",allow_name);
    if (-1 == ret){
        gf_log ("feature/trashdir-read-only",GF_LOG_WARNING,
                "asprintf failed while setting search string");
        goto out;
    }
    password = gf_strdup (dict_get(server_option,searchstr)->data);
    priv->allow_name = allow_name;
    priv->password = password;

    if (this->private)
        priv->ip_table = ( (trash_private_t *)(this->private) )->ip_table;
    this->private = priv;

    /*get the white list from the options*/
    if (options){
        ret = dict_get_str (options,"trashdir-read-only",&trashdir_read_only);
        if(!strcmp(trashdir_read_only,"on")){
            allow_addr = dict_get(options,"trashdir-white-list");
        }
        else 
            return ret;

    }
    else {
        ret = dict_get_str (this->options,"trashdir-read-only",&trashdir_read_only);
        if(!strcmp(trashdir_read_only,"on")){
            allow_addr = dict_get (this->options,"trashdir-white-list");
        }
        else    
            return ret;
    }

    /*extract ips from the white list in the form of ip1,ip2,ip3... using strtok_r*/
    if(allow_addr!=NULL){
        addr_cpy = gf_strdup(allow_addr->data);
        if( NULL == addr_cpy )
            goto out;
        if( strcmp(addr_cpy,"*")==0 ){
            if(ip_table) {
                dict_destroy(ip_table);
                ip_table = NULL;
            }
            goto replace;
        }
        addr_str = strtok_r (addr_cpy,",",&tmp);
        ip_table = dict_new();
        while(addr_str){
            gf_log (this->name, GF_LOG_DEBUG,"allow = \"%s\"",addr_str);
            ret = dict_set_int32 (ip_table, addr_str, 1);
            if (ret != 0){
                dict_destroy(ip_table);        
                goto out;
            }
            addr_str = strtok_r(NULL,",",&tmp);
        }
    }

replace:
    if(priv->ip_table){
        dict_destroy(priv->ip_table);
    }
    priv->ip_table = ip_table;
    this->private = priv;

out:
    GF_FREE(addr_cpy);
    return ret;
}
这个函数的调用位置有两个,一个是在translator初始化的函数init,以及options重置的函数reconfigure:

int32_t
init (xlator *this)
{
    ...
    GF_OPTION_INIT ("trashdir-read-only", priv->trashdir_read_only_enabled, bool, out);
    
    ret = generate_allow_ip_table(this,priv,NULL);

    
    this->private = (void *)priv;

out:
    ...
}

int 
reconfigure (xlator *this, dict_t *options)
{
    ....
    GF_OPTION_RECONF ("trashdir-read-only",trashdir_read_only_enabled, options, bool, out);
    
    priv->trashdir_read_only_enabled = trashdir_read_only_enabled;
    ret = generate_allow_ip_table(this,priv,options);


out:
    ....
}

也就说generate_allow_ip_table这个函数在初始化init以及reconfigure的时候调用,那么它的作用就是通过通过判断trashdir-read-only这个属性的情况来获取得到白名单列表trashdir-white-list,下面来详细介绍一下这个函数的过程:

步骤一:首先通过遍历指针得到指向server translator的指针server_option,通过glusterfs的架构我们知道server translator是server端的第一层次,接受从client端发送过来的rpc请求,然后依次传递到posix层次。得到server xlator的指针后,获取得到块名称brick_name,允许客户端请求通过的名字allow_name,以及password等信息。

步骤二:通过trashdir-read-only以及trashdir-white-list得到白名单列表allow_addr。

步骤三:将allow_addr分割成为多个IP然后写到ip_table里面去。


4.2 主要函数二:trash.c:is_client_read_only_on_trashdir( xlator *this, call_frame_t *frame )。

/*
 * Function:is_client_read_only_on_trashdir
 * Description:check whether the client has the permission to do other ops,except read
 * Parameters:xlator_t, call_frame_t 
 * return:gf_boolean_t 
 * */
gf_boolean_t
is_client_read_only_on_trashdir (xlator_t *this, call_frame_t *frame)
{
    char                *ip = NULL;
    int                  val = 0;
    trash_private_t     *priv = NULL;
    gf_boolean_t         trashdir_read_only = _gf_true;
    char                *name = NULL;
    char                *password = NULL;

    /*crucial judgement which means option trashdir-read-only on or off*/
    priv = this->private;
    trashdir_read_only = priv->trashdir_read_only_enabled;
    if ( !trashdir_read_only ){
        return _gf_false;
    }

    if (frame == NULL){
        return _gf_true;
    }

    if ( frame->root->client->volfile_id &&
            strcmp(frame->root->client->volfile_id,"gluster/nfs")==0 ){
        return _gf_true;
    }

    /*get white list from ip_table*/
    if (priv->ip_table!=NULL){
        ip = frame->root->client->identifier;
        dict_get_int32(priv->ip_table,ip,&val);
        if(val==1){
            return _gf_false;
        }
    }

    if (trashdir_read_only){
        name = frame->root->client->auth.username;
        password = frame->root->client->auth.passwd;
        if (!name||!password){
            return _gf_true;
        }

        /*self access is allowed obviously*/
#if 1
        if( (!strcmp(name,priv->allow_name))
                && (!strcmp(password,priv->password)) )
            trashdir_read_only = _gf_false;
#endif

    }

    return trashdir_read_only;
}
这个函数判断一个客户端是否对trashdir是只读的(实际上是判断客户端对卷是否是只读的,对trashdir只读还要涉及到后面的路径问题),下面来分析一下这个函数的过程:

步骤一:首先通过私有变量priv = this->private; priv->trashdir_read_only_enabled来判断option:trashdir-read-only是开启还是关闭(on/off),this->private是当前translator定义的私有变量,在translator init以及reconfigure的过程中,相应的option的值会有相应的变化,那么就可以将这种变化写入到this->private(在这里是trash_private_t,通常是一个写在头文件中的结构体),那么通过this变量的传递来获取相应的option的值。

步骤二:如果trashdir_read_only变量是off,那么返回false,说明不是只读的;否则,获取ip table。


4.3 主要函数之三:is_trashdir_prefix_of_loc( call_frame_t *frame, xlator_t *this, loc_t *loc ).

/*
 * Function:is_trashdir_prefix_of_loc
 * Description:check whether the file operated by the client is in the trashcan directory
 * Parameters:call_frame_t ,xlator_t ,loc_t
 * Return:gf_boolean_t 
 * */
gf_boolean_t 
is_trashdir_prefix_of_loc(call_frame_t *frame, xlator_t *this,loc_t *loc)
{
    int                 str_len             = 0;
    char                *location_copy      = NULL;
    char                buffer[20]          = {0}; 
    if (frame == NULL){
        gf_log (this->name, GF_LOG_ERROR, "The frame is NULL");
        return _gf_true;
    }
    
    /* judge and compare the prefix of loc to "/.trashcan" */
    str_len = strlen("/.trashcan");
    if( strlen(loc->path) < str_len ){
        return _gf_false;
    }
    else{
        location_copy = gf_strdup( loc->path );
        strncpy(buffer,location_copy,str_len);
        buffer[str_len] = '\0';
        if( !strcmp(buffer,"/.trashcan") ){
            return _gf_true; 
        }
        else{
            return _gf_false;
        }
    }
    GF_FREE(location_copy);
}
这个函数是判断被客户端操作的文件是不是在.trash目录里面。思想非常简单,就是将操作文件的路径loc的前缀和"/.trashcan"进行比较,如果匹配,那么说明在.trash目录里面,否则不在。当然,在此需要提醒的一个问题就是,如果获取所操作的文件的路径,有的fops里面有直接的参数是loc,但是有的只有文件描述符fd,因此问题就是如何通过fd得到loc,在这里提供一个函数将fd-->loc:

/*
 * Funcation:fd_to_loc
 * Descriptions:get loc from fd
 * Parameter:fd_t
 * Return:loc
 * */
loc_t
fd_to_loc(fd_t *fd)
{
    loc_t           loc         = {0,};
    char            *path       = NULL;
    int             ret         = -1;

    loc.inode = inode_ref(fd->inode);
    uuid_copy(loc.gfid, fd->inode->gfid);
    ret = inode_path(fd->inode,NULL,&path);//get path from inode
    loc.path = path;

    return loc;
}
观察一下源码里面的inode_ref,inode_path就可以得到相应的转化结果。

trash.h代码:http://yunpan.cn/cda8yF6Dv4qqQ (提取码:69d1)

trash.c代码:http://yunpan.cn/cda8A9vgCr7st   (提取码:0497)


五、单元测试

5.1 测试方法

>在trash&trashdir-read-only is off的情况下,可以进行touch,mkdir,rm等操作。
>在trash is on&trashdir-read-only off的情况下,可以进行touch,mkdir,rm等操作,rm之后的文件放到了.trashcan里面;在trash is on & trashdir-read-only is on的情况下,touch,mkdir,rm等操作失效。
>在设置了trashdir-white-list的情况下,可以进行touch,mkdir,rm等操作。

5.2 测试文件:
trash-white-list.sh:
#!/bin/bash
#Test case:This test checks the trashdir-read-only option
. $(dirname $0)/../include.rc
. $(dirname $0)/../volume.rc
cleanup;
ALLOWIP=10.23.85.47
file_exists () {
    vol=$1
    shift
    for file in `ls $B0/${vol}1/$@ 2> /dev/null` ; do
        test -e ${file} && return 0
    done
    for file in `ls $B0/${vol}2/$@ 2> /dev/null` ; do
        test -e ${file} && return 0
    done
    return 1
}
 
#[1-2]
TEST glusterd
TEST pidof glusterd
#[3-4]
TEST mkdir -p $B0/${V0}{0,1,2,3,4,5,6,7,8,9}
TEST $CLI volume create $V0 disperse 10 redundancy 2 $H0:$B0/${V0}{0,1,2,3,4,5,6,7,8,9}
#[5-7]
EXPECT "$V0" volinfo_field $V0 'Volume Name'
EXPECT 'Created' volinfo_field $V0 'Status'
EXPECT '10' brick_count $V0
#[8-9]
TEST $CLI volume start $V0
EXPECT_WITHIN $PROCESS_UP_TIMEOUT 'Started' volinfo_field $V0 'Status'
#Mount FUSE and create file/dir,create should succeed as the trashdir-read-only
#is off by default
#[10-16]
TEST glusterfs -s $H0 --volfile-id $V0 $M0
TEST mkdir $M0/AA
TEST touch $M0/AA/aa
TEST dd if=/dev/zero of=$M0/bb bs=1024 count=1024
TEST file_exists $V0 AA/aa bb
TEST rm -rf $M0/bb
TEST ! file_exists $V0 bb
# turn on trashdir-read-only option through volume set [17-18]
TEST gluster volume set $V0 trash on
TEST gluster volume set $V0 trashdir-read-only on
# All write operations outside .trashcan directory should succeed now [19-24]
TEST mkdir $M0/BB
TEST touch $M0/BB/bb
TEST dd if=/dev/zero of=$M0/cc  bs=1024 count=1024
TEST file_exists $V0 BB/bb cc
TEST rm -rf $M0/cc $M0/BB
TEST ! file_exists $V0 cc BB/bb
# All write operations inside .trashcan directory should fail now 
TEST ! -e mkdir $M0/.trashcan/DD
TEST ! -e touch $M0/.trashcan/DD/dd
TEST ! -e dd if=/dev/zero of=$M0/.trashcan/dd bs=1024 count=1024
TEST ! -e rm -rf $M0/.trashcan/cc #cc is rm at TEST XX
TEST ! -e rm -rf $M0/.trashcan/BB/bb
TEST ls
TEST ! -e ll -a $M0/.trashcan
# set trashdir-white-list through volume set option [21-23]
TEST gluster volume set $V0 features.trashdir-white-list $ALLOWIP
EXPECT "on" volinfo_field $V0 'features.trashdir-read-only'
EXPECT "$ALLOWIP" volinfo_field $V0 'features.trashdir-white-list'
# All write and remove  operations should succeed now [24-26]
TEST ls -a $M0/.trashcan
TEST file_exists $V0 .trashcan/cc* .trashcan/BB/bb*
TEST rm -rf $M0/.trashcan/cc*
TEST ! file_exists $V0 .trashcan/cc*
TEST file_exists $V0 .trashcan/BB/bb*
## Finish up
TEST $CLI volume stop $V0
EXPECT 'Stopped' volinfo_field $V0 'Status'
TEST $CLI volume delete $V0
TEST ! $CLI volume info $V0
cleanup;
<

测试样例全部通过,在此就不一步一步的分解动作截图了。


六、总结

本设计方案实现了对.trashcan目录的白名单设置以及访问权限功能,保证了数据安全,是trash translator的功能的加强版实现。


下面来讲一讲自己对options设置的进一步理解:
1)对于动态的option选项,有可能在有可能不在,所以你不能在glusterd-volgen.c里面写死让其知己生成,生成的是全局的,应该是在用的时候生成,不用的时候不需要生成。直接在glusterd-volume-set.c里面进行设置,然后修改gluster-op-sm.c相关代码就可以了。
2)translator中reconfigure函数的过程:首先gluster volume set ....,然后volfile被修改,然后是reconfigure重新加载volfile修改相应的options。this->options应该还是保持老的options值,而reconfigure过后的参数options会变成新的值。dict_get得到的是老值还是新值要看从this->options还是options中获取。reconfigure的时候,两个参数xlator_t *this以及dict_t *options会设置将新的key value值写入到options中,而this->options依然是之前保持的内容。



Author:忆之独秀

Email:[email protected]

注明出处:http://blog.csdn.net/lavorange/article/details/47863923 






你可能感兴趣的:(CloudStorage)