亚马逊AWS官方博客

在 AWS 中国区对 Amazon Elasticsearch Kibana 进行身份认证的解决方案

概述

Kibana 是一种流行的开源可视化工具,专为与 Elasticsearch 结合使用而设计。Amazon ES 为每个 Amazon ES 域默认安装 Kibana。目前ES内置的Kibana不支持IAM,对于Kibana的访问控制主要有两个方面:在VPC内访问可以通过安全组进行控制,在VPC外访问可以通过结合Amazon Cognito的User Pool和Identity Pool来进行身份验证或者使用基于IP的策略。

由于AWS中国区目前还不支持Cognito User Pool,因此需要在VPC外进行公开访问只能通过基于IP的策略来进行控制。当需要访问ES Kibana的员工发生变化或者人数变多时,基于IP的策略进行管理控制就显得不那么方便了。因此我们可以用一个Nginx服务器作为中间代理,将这台Nginx的IP加入Kibana的访问策略中,客户端通过Nginx的反向代理来访问Kibana,而关于身份验证的部分在Nginx上实现。

基于以上分析,关于中国区Amazon Elaticsearch Kibana身份验证问题,提出了用Nginx作为代理并且在Nginx做身份验证的三种解决方案。关于每个方案的具体架构和原理以及适用场景分析具体查看通篇博文。

  • 方案一:使用 Nginx 作为代理,做 HTTP 基本认证,实现简单但是安全系数不高。
  • 方案二:使用 Nginx 作为代理,做 OIDC 认证(这里以 Okta 为例),使用免费版本 Nginx 即可实现 OAuth 和 OIDC 后端认证。
  • 方案三:使用 Nginx plus 作为代理,以 Okta 为 Idp 做 OIDC 认证,使用付费版本 Nginx Plus 实现 OIDC 后端认证。

 

前提条件

本博文下三个方案的演示都在AWS中国宁夏区域完成。在方案开始之前,请准备好以下AWS基本环境:

  • 在AWS中国区创建一个 Elasticsearch 域,在步骤3“配置访问和安全”中的网络配置选择 “公有访问权限”,在访问配置中选择 “允许对域进行公开访问”,如下图所示。
  • 在AWS中国区公有子网下启动一台带公有IP的 EC2 Linux,本博客起的是Amazon Linux 2,安全组的入站规则打开80端口(中国区需要备案才能使用80端口)。

方案一:在Nginx上做HTTP基本认证

利用Nginx的auth_basic模块,也就是HTTP Basic Authentication,是HTTP服务器对WEB浏览器进行基本身份认证的方法。

原理

当客户端向HTTP服务器进行数据请求时,如果客户端未被认证,则HTTP服务器将通过基本认证过程对客户端的用户名及密码进行验证,以决定用户是否合法。客户端在接收到HTTP服务器的身份验证要求后,会提示用户输入用户名密码,然后将用户名密码以BASE64加密,加密后的密文将附加于请求信息。并于每次请求数据时,将密文附加于请求头(Request Header)中。HTTP服务器在每次收到请求包后,根据协议取得客户端附加的用户信息(BASE64加密的用户名和密码),解开请求包,对用户名及密码进行验证。

优缺点

优点:提供简单的用户认证功能,其实现和认证过程都简单明了,适合于对安全要求不高的系统
缺点:

  • 没有灵活可靠的认证策略,如无法提供域认证功能
  • BASE64的加密强度非常低,可以说仅能防止搜索引擎将其搜到
  • 没有统一可以进行用户管理的地方

实现过程

在EC2上安装Nginx

sudo yum update -y
# 在yum中添加nginx官网yum源
sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
sudo yum install -y nginx
sudo systemctl start nginx
sudo systemctl status nginx

使用以下内容直接覆盖Nginx的配置文件/etc/nginx/nginx.conf

user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;
    # Load modular configuration files from the /etc/nginx/conf.d directory.
    include /etc/nginx/conf.d/*.conf;
    index   index.html index.htm;
}

修改文件 /etc/nginx/conf.d/default.conf 为以下内容,主要修改两个参数:其中 “EC2_pulicDNS” 为EC2的公有DNS,可以在AWS EC2控制台中查看;“ES_Kibana_url” 为 Elasticsearch Service 中 Kibana 的 url,可以在 Elasticsearch Service 创建的域控制台上查看。

server {
    listen 80;
    server_name {EC2_publicDNS};
    auth_basic "Kibana Auth";
    auth_basic_user_file /etc/nginx/.httpd_secret;
    rewrite ^/$ http://{EC2_publicDNS}/_plugin/kibana redirect;

    location /_plugin/kibana {
        # Forward requests to Kibana
        proxy_pass {ES_Kibana_url};

        # Update cookie domain and path
        proxy_cookie_domain {ES_Kibana_url} {EC2_publicDNS};
        proxy_cookie_path / /_plugin/kibana/;
        proxy_set_header Authorization "";
        # Response buffer settings
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
}

修改完配置文件后使用以下命令重启Nginx

sudo systemctl restart nginx

如下图所示在AWS的Elasticsearch控制台修改 domain的访问策略,加上Nginx也就是EC2的公有IP

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:{AWS-账号}:domain/beckydomain/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": [
            "EC2_pulicIP": {ec2-public-ip}
          ]
        }
      }
    }
  ]
}

测试

# 安装httpd
sudo yum install -y httpd-tools
# 用httpd工具生成kibana-admin访问
sudo htpasswd -c /etc/nginx/.httpd_secret kibana-admin

直接访问EC2的公有DNS,会跳出HTTP基本认证的弹框,使用httpd-tools生成kibana-admin账号的密码登录后就会自动跳转到ES Kibana界面。

 

方案二:在Nginx上做OIDC认证(以Okta为例)

基础

  • Nginx auth_request:模块位于Internet和Nginx将请求传递到的后端服务器之间,并且每当有请求进入时,它首先将请求转发到单独的服务器以检查用户是否已通过身份验证,并使用HTTP响应来决定是否允许请求继续到后端。
  • Vouch:Nginx的auth_request模块没有用户或对任何人进行身份验证的概念,因此在这里我们还需要选择一个身份验证代理,开实际处理登录用户的内容,负责处理HTTP请求,并且根据用户是否登录来返回HTTP 200或者401,如果用户未登录,则需要知道如何让他们登录并且设置会话cookie。Vouch是用Go编写的,可以为Vouch配置OAuth和OpenID Connect后端对用户进行身份验证。
  • Okta:Okta是一个符合OAuth和OIDC的标准身份认证服务,可以进行用户管理,实现SSO和MFA等,在这里作为一个标准的OIDC提供商

架构和原理

方案二的架构和原理如下图所示,在本实例中,kibana.stats.beckyhome.cn为Nginx服务器也就是用户访问Kibana的域名, kibana.login.beckyhome.cn 为Vouch服务器也就是中间做认证跳转的域名,在这里两个域名都指向EC2的公有IP。本博文使用Amazon Route53进行DNS管理。您需要将这两个域名替换成自己的域名,并使用Route53或者您自己的DNS服务将域名指向EC2的公有IP(测试情况下公开访问也需要指定)。

 

  • 终端用户访问 http://kibana.stats.beckyhome.cn
  • Nginx反向代理
    • 收到用户对 kibana.stats.beckyhome.cn的请求
    • 使用auth_request模块配置路径/vouch_validate
    • /vouch_validate通过proxy_pass向http://kibana.stats.beckyhome.cn/validate请求验证
      • 如果/vouch_validate为 200 OK,成功返回
      • 如果/vouch_validate为 401 NotAuthorized,302重定向到http://kibana.login.beckyhome.cn/login?url=http://kibana.stats.beckyhome.cn
  • vouch /vouch_validate验证过程
    • 从Nginx的proxy_pass收到kibana.stats.beckyhome.cn的请求
    • 寻找包含JWT的名为“oursitesSSO”的cookie
    • cookie找到,并且JWT有效:返回200给Nginx,允许访问(用户无感知)
    • cookie没找到,或者KWT无效:返回401 NotAuthorized给Nginx
  • 请求转发给 https://kibana.login.beckyhome.cn/login?url=https://kibana.stats.beckyhome.cn
    • 清除名为“oursitesSSO”的cookie(如果存在)
    • 生成一个随机数并将其存储在会话变量$STATE中
    • 将http://kibana.login.beckyhome.cn查询字符串中的url存储在会话变量$ requestedURL中
    • 使用302重定向Okta的OIDC登录表单(包括$STATE随机数)回给用户
  • Okta验证
    • 用户在okta表单输入用户名和密码登录
    • okat验证后,将用户重定向回$requestedURL
  • 请求转发到ES Kibana
    • 前面Nginx的auth_request验证通过后,请求重定向到Elasticsearch Kibana的url

优缺点

优点:

  • 使用免费社区版Nginx即可
  • 支持各个标准的OAuth和OIDC后端认证(包括Google,Facebook,Github,Amazon Cognito,Okta等)
  • 可以灵活得进行用户和权限的控制

缺点:需要安装第三方开源工具Vouch,且配置过程较为复杂

配置Nginx

在EC2上安装Nginx后,检查是否安装了http_auth_request_module,高版本nginx(1.5.4+)下默认安装

如果没有auth_request模块,需要下载重新编译

# 下载依赖
yum -y install gcc gcc-c++ autoconf automake make zlib zlib-devel openssl openssl-devel pcre pcre-devel libxslt-devel redhat-rpm-config gd-devel perl-devel perl-ExtUtils-Embed geoip-devel gperftools-devel
# 下载源代码
./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' --with-http_auth_request_module
# 编译安装
make
make install

在/etc/nginx/conf.d目录下新建文件nginx.conf,用以下配置替换内容。其中,“kibana.stats.beckyhome.cn” 和 “kibana.login.beckyhome.cn” 请换成您自己的域名。“ES_Kibana_url” 为 Elasticsearch Service 中 Kibana 的url,其他内容保持不变。

server {
  listen 80 default_server;
  server_name {kibana.stats.beckyhome.cn};

  # Any request to this server will first be sent to this URL
  auth_request /vouch-validate;
  
  location = /vouch-validate {
    # This address is where Vouch will be listening on
    proxy_pass http://127.0.0.1:9090/validate;
    proxy_pass_request_body off; # no need to send the POST body
  
    proxy_set_header Content-Length "";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  
    # these return values are passed to the @error401 call
    auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
    auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
    auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
  }
  
  error_page 401 = @error401;
  
  # If the user is not logged in, redirect them to Vouch's login URL
  location @error401 {
    return 302 http://{kibana.login.beckyhome.cn}/login?url=http://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
  }

  rewrite ^/$ http://{kibana.stats.beckyhome.cn}/_plugin/kibana redirect;

  location /_plugin/kibana {
        # Forward requests to Kibana
        proxy_pass {ES_Kibana_url};

        # Update cookie domain and path
        proxy_cookie_domain {ES_Kibana_url} {kibana.stats.beckyhome.cn};
        proxy_cookie_path / /_plugin/kibana/;
        proxy_set_header Authorization "";
        # Response buffer settings
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }

}

在/etc/nginx/conf.d目录下新建文件vouch.conf,用以下配置替换其内容。同样将“kibana.login.beckyhome.cn” 换成您自己的域名。

server {
  listen 80;
  server_name {kibana.login.beckyhome.cn};

  # Proxy to your Vouch instance
  location / {
    proxy_set_header  Host  {kibana.login.beckyhome.cn};
    proxy_pass        http://127.0.0.1:9090;
  }
}

修改完配置文件后使用以下命令重启Nginx

sudo systemctl restart nginx

配置Okta

注册后,在Develop Okta上创建Application选择WEB

 

填写Base URI和Login redirect URIs,同样将“kibana.login.beckyhome.cn” 换成您自己的域名。其他默认不变。

 

下载Client ID和Client secret

 

配置Vouch

# 安装go和git
cd ~
# vouch的所有操作都在root下进行
sudo -s
yum install golang
yum install git
git clone https://github.com/vouch/vouch-proxy.git

在vouch_proxy/config文件夹下新建config.yml文件,用以下内容替换。其中 “client_id” 和 “client_secret” 为上一步下载的ClientID和Client secret,“yourOktaDomain”换成您个人账户的Okta域名(在Okta console中查看, 如”https://dev-123456.okta.com/,可以参考Okta帮助文档) ,将 “kibana.login.beckyhome.cn” 换成您自己的域名,“beckyhome.cn” 换成根域名。

# vouch config

vouch:
  logLevel: info
  testing: false

  listen: 0.0.0.0
  port: 9090
  allowAllUsers: true

  jwt:
    secret: your_random_string
    issuer: Vouch
    maxAge: 240
    compress: true

  cookie:
    name: VouchCookie
    domain: {beckyhome.cn}
    secure: false
    httpOnly: true
    maxAge: 0

  session:
    name: VouchSession
    key: you_random_key

  headers:
    jwt: X-Vouch-Token
    querystring: access_token
    redirect: X-Vouch-Requested-URI

# OAuth Provider:OpenID Connect Okta
oauth:
  provider: oidc
  client_id: {client_id}
  client_secret: {client_secret}
  auth_url: https://{yourOktaDomain}/oauth2/default/v1/authorize
  token_url: https://{yourOktaDomain}/oauth2/default/v1/token
  user_info_url: https://{yourOktaDomain}/oauth2/default/v1/userinfo
  scopes:
    - openid
    - email
  # Set the callback URL to the domain that Vouch is running on
  callback_url: http://{kibana.login.beckyhome.cn}/auth
回到vouch-proxy文件夹下,编译和运行vouch(root下)。
./do.sh goget
./do.sh build
./vouch-proxy

注意:在中国区 EC2 上执行./do.sh goget 会报以下错误

https fetch failed: Get https://golang.org/x/text/...  timeout
https fetch failed: Get https://golang.org/x/sys/..  timeout
https fetch failed: Get https://golang.org/x/oauth2/..  timeout
https fetch failed: Get https://golang.org/x/net/..  timeout
https fetch failed: Get https://cloud.google.com/go/compute/metadata/..  timeout

解决办法:使用国内github的镜像库代替国外镜像库,手动下载完成后不需要再执行goget直接进入build阶段

# 查看go安装的位置GOPATH
go env
export GOPATH=/root/go
echo $GOPATH
# 手动添加文件夹golang.org
mkdir -p $GOPATH/src/golang.org/x 
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/text.git
git clone https://github.com/golang/sys.git
git clone https://github.com/golang/oauth2.git
git clone https://github.com/golang/net.git
# 手动添加文件夹cloud.google.com
mkdir -p $GOPATH/src/cloud.google.com/go 
cd $GOPATH/src/cloud.google.com/go
git clone https://github.com/googleapis/google-cloud-go.git
# 移动文件夹compute到指定位置
mv $GOPATH/src/cloud.google.com/go/google-cloud-go/compute $GOPATH/src/cloud.google.com/go/compute

测试

在Okta中创建一个用户,并attach到kibana的application


访问域名kibana.stats.beckyhome.cn跳转到okta的登录表单

 

登录之后跳转到Kibana界面

 

方案三:在Nginx plus上以Okta为Idp做OIDC认证

方案介绍

方案三的原理与方案二类似,只不过不需要安装第三方身份验证代理,Nginx plus上的nginx-plus-module-njs模块已经官方支持OIDC后端。

优缺点

优点:只需安装Nginx plus的官方模块即可,配并且配置过程简单
缺点:

  1. Nginx plus需付费
  2. 此方案只适用于OIDC 认证

配置过程

方案三由Nginx Plus官方支持,关于Okta以及更详细的配置可以直接参考Nginx Plus官方文档:https://docs.nginx.com/nginx/deployment-guides/single-sign-on/okta/

# 安装nginx-plus-module-njs模块
sudo yum install nginx-plus-module-njs

# 创建一个nginx-openid-connect GitHub存储库的克隆
git clone https://github.com/nginxinc/nginx-openid-connect

# 复制文件到nginx的配置中
cp frontend.conf /etc/nginx/conf.d/frontednss.conf
cp openid_connect.js /etc/nginx/conf.d/openid_connect.js
cp openid_connect.server_conf /etc/nginx/conf.d/openid_connect.server_conf

# 从Okta配置获取授权端点,令牌端点和JSON Web密钥(JWK)文件的URL
$ curl https:// <用户名> -admin.okta.com/.well-known/openid-configuration | python -m json.tool

修改文件/etx/nginx/conf.d/frontend.conf的以下值

set $oidc_authz_endpoint – authorization_endpoint
set $oidc_token_endpoint – token_endpoint
set $oidc_client – {client_id}
set $oidc_client_secret – {client_secret}
set $oidc_token_type – id_token

方案总结

以上提出的三个方案的优缺点总结如下所示。

A 方案一 方案二 方案三
方案说明 Nginx作为代理,做HTTP基本认证

 

Nginx作为代理,做OIDC认证(这里以Okta为例)

 

Nginx plus作为代理,以Okta为Idp做OIDC认证
优点 原理和配置过程都比较简单

· 使用免费社区版Nginx即可;

· 支持各个标准的OAuth和OIDC后端认证(包括Google,Facebook,Github,Amazon Cognito,Okta等);

· 可以灵活得进行用户和权限的控制

只需安装Nginx plus的官方模块即可,配并且配置过程简单
缺点 安全程度低,不能灵活或者集中进行用户的管理和授权

 

需要安装第三方开源工具Vouch,且配置过程较为复杂

 

Nginx plus需付费,并且此方案只适用于OIDC 认证
适用场景 对安全要求不高的小型客户 对安全要求比较高,有多个OAuth和OIDC认证后端,又不想付费购买Nginx Plus的客户 对安全要求高,内部只有OIDC后端,不想安装第三方工具并且有经济实力的客户

 

参考资料

 

本篇作者