es节点利用ldap做认证

简介

nginx的auth_request模块可以集成第三方的认证,具体原理可以参考大神(freedomkk_qfeng的文章) :用 Nginx 的 auth_request 模块集成 LDAP 认证

本文主要是介绍如何用该模块做es节点的权限控制,es节点目前支持的认证工具(shiled x-pack),前者在5.0以后就废弃了,目前使用的是x-pack,但两者都是收费的,虽然x-pack号称已经开源了部分,但是对接ldap等系统的功能并未开源,但是我们可以根据请求过来的url,做一些处理,提取出索引,操作类型,从而做进一步的权限管控

原理

原理比较简单如下:

1. 先将es节点监听在127.0.0.1:9201上面

2. 自己写一个代理程序监听在0.0.0.0:9200上,由他去接收用户过来的请求

3. 提取出url,ip等信息,做一些权限控制,如果符合,则放行

功能点

1. 基于IP的权限控制,能根据用户IP做控制

2. 用户认证信息集中化管理,只需要更新用户表即可,不用想shiled那样每次新增角色还要修改整个集群所有节点

3. 对接ldap,但同时又不会频繁查询ldap,防止影响到ldap的性能

4. 容器化,降低部署成本

技术选型

nginx 的auth_request模块

auth_request的作用就是在访问指定路径的时候,先去请求某服务,在根据返回的状态码执行特定的操作,比如跳转,放行等,状态码目前仅支持(200/401/403),但是nginx默认是不带该模块的,需要独立安装

安装auth_request

yum安装必要组件


yum -y install geoip ca-certificates geoip-dev pcre libxslt gd gd-dev libxslt-dev libgcc patch \

gcc libc-dev make openssl-dev pcre-dev zlib-dev linux-headers jemalloc-dev

编译

这里使用的nginx版本为:nginx-1.12.2,具体可以在github中看到


tar -xf nginx-1.12.2.tar.gz \

&& cd nginx-1.12.2 \

&& sh configure \

    --prefix=/usr/local/nginx \

    --conf-path=/etc/nginx/nginx.conf \

    --user=nginx \

    --group=nginx \

    --error-log-path=/var/log/nginx/error.log \

    --http-log-path=/var/log/nginx/access.log \

    --pid-path=/var/run/nginx/nginx.pid \

    --lock-path=/var/lock/nginx.lock \

    --with-http_ssl_module \

    --with-http_stub_status_module \

    --with-http_gzip_static_module \

    --with-http_flv_module \

    --with-http_mp4_module \

    --http-client-body-temp-path=/var/tmp/nginx/client \

    --http-proxy-temp-path=/var/tmp/nginx/proxy \

    --http-fastcgi-temp-path=/var/tmp/nginx/fastcgi \

    --with-http_auth_request_module \

&& make \

&& make install 

创建nginx缓存目录

mkdir -p /var/cache/nginx 

mkdir -p /var/tmp/nginx

修改nginx配置文件

#nginx
#user  nobody;
worker_cpu_affinity auto;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    proxy_cache_path cache/ keys_zone=auth_cache:10m;

    #access_log  logs/access.log  main;
    client_max_body_size 20m;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    upstream elk_9200 {
        server 127.0.0.1:9201; # ES监听端口
    }
    upstream backend {
        server 127.0.0.1:5000; # 程序监听的端口
    }
    server {
        listen       9200;
        server_name  localhost;

        location / {
            auth_request /auth-proxy; # 请求 auth-proxy这个模块
            error_page 401 @error401; 
            error_page 403 @error403;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            #add_header URI  $request_uri;
            proxy_pass http://elk_9200/;
        }
        location = /auth-proxy {
            internal;
            proxy_set_header X-real-url "$scheme://$server_addr$request_uri"; # 将url写入头部,方便后续的分析
            proxy_set_header X-real-method $request_method; # 请求的方法也写入header
            proxy_set_header X-real-ip   $remote_addr; # 将IP写入header(因为用了 

          #反向代理后程序如果直接使用request.RemoteAddr拿到的永远是127.0.0.1,而且 
          # 用nginx添加的话还能防止用户伪造remote_addr)
            proxy_pass http://backend/auth-proxy; # 请求过来后,先去请求127.0.0.1:5000/auth-proxy 这个url
            proxy_pass_request_body off;
            proxy_set_header Content-Length "";
            # nginx认证缓存配置,如果开启了该配置意味着认证通过后在指定时间内,不会再次发起认证
            #proxy_cache auth_cache; # 是否开启缓存
            
            #proxy_cache_valid 200 10m; # 缓存时间
        }

        location @error401 {
            return 401;
        }
        location @error403 {
            return 403;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }


}

gin web框架

至于为什么选择gin,个人觉得其社区活跃度是目前最高的,也算是个人习惯问题吧,当然也可以使用其他框架,比如iris,beego等


代码逻辑

func Login(c *gin.Context) {
        // 由于是http basic认证,所以从header中提取出认证信息,并解密
    user, password := utils.DecodeBase64UserInfo(c.Request.Header.Get("Authorization"))
    if user == "" || password == ""{
        logger.Alert("Username or password is Nil.")

        c.AbortWithStatus(401)
        return
    }
        // 将用户认证信息加密后存到缓存中
    md5Password := utils.MD5Encode(password)
    // 如果该用户存在缓存中 则与缓存中的数据进行匹配
    cachePass, err := getCachePass(user)
    if err == nil {
        // 如果与缓存中的密码匹配
        if md5Password == cachePass {
            logger.Info(user, " login from cache success")

        } else {
            logger.Alert(user, "password in cache is wrong")
            c.AbortWithStatus(401)
            return
        }

    } else {
                // 否则就
        // 是否开启了ldap
        if g.LdapEnable {
            
            ldapClient := utils.InitLdap()
            ldapClient.Connect()
            err := ldapClient.Auth(user, password)
            if err != nil {
                logger.Alert("Login ldap failed, user: ",user)
                c.AbortWithStatus(401)
                return
            }
            logger.Info("Login ldap success, write user info into cache...")
        }else {
            userPri := g.RealPrivilege.GetUserPrivileges(user)
            if password != userPri.Password{
                logger.Alert("Login from conf failed, user: ",user)
                c.AbortWithStatus(401)
                return
            }
            logger.Info("Login from conf success, write user info into cache...")
        }
                // 加密后写入缓存
        setUserCache(user, utils.MD5Encode(password))
    }
        // 开始匹配用户
    if !matchACL(user, c){
        c.AbortWithStatus(403)
        return
    }
}

定期清空缓存中的认证信息

/*
定期清空
 */
func DeleteUserInfoCache(){
    for {
        ticker := time.NewTicker( time.Duration(g.GetViper().GetInt("emptyUserCacheCycle")) * time.Second)
        select {
        case <- ticker.C:
            logger.Info("Empty User Info Cache...")
            es.DeleteAllUserCache()
        }
        ticker.Stop()
    }
}

/*
定期同步user_role表中的权限数据到内存中
 */
func SyncUserRoleTable(){
    for {
        ticker := time.NewTicker( time.Duration(g.GetViper().GetInt("syncDBCycle")) * time.Second)
        select {
        case <- ticker.C:
            logger.Info("Sync User Role...")
            g.RealPrivilege.SyncUserRole()
        }
        ticker.Stop()
    }

}

基于uri的权限控制

es的url请求都是很有规律的,但具体研究下来其实也比较复杂,结合日常的使用,能简单的分为:索引类操作,集群类操作 具体的权限分为:只读,编辑,全部(除了编辑只读,还有delete等)

集群类操作

以 "_" 开头的都可以认为是集群类操作

索引类操作

非"_"开头的uri 基本上都能提取出索引

缺陷

有一些请求是索引操作,但是也会被识别为集群,比如bulk,但这类少,目前碰到的也只有bulk,而一般使用bulk都是大批量的写入操作,比如logstash等,这类就应该配置高权限的账户了,因为他要对所有索引进行写入

数据库表设计

CREATE TABLE user_role(
  username varchar(256) , # 用户名
  index varchar(256), # 索引
  indexprivilege varchar(64), # 索引权限
  clusterprivilege varchar(64), # 集群权限
  password         varchar(256) # 密码 如果走ldap该字段无意义
);

比如如下数据的意思是elk 拥有所有索引所有权限,也拥有集群所有权限
注意:索引如果为:ALL 代表是全部索引

不同场景

1.公司无ldap
可以将配置文件的ldap.enable改为false,这样认证就会走数据库或者配置文件,如果走数据库的话密码为password字段
2.不想安装数据库
如果无数据库可以将认证写在配置文件中,但是这样的话和shield类似了,每次新增用户所有节点都要更新
参考:https://www.jianshu.com/p/bfde26b46709
代码仓储:https://github.com/peng19940915/nginx_ldap_auth_tool

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容