misterli's Blog.

SSO之使用Pomerium保护您的网站

字数统计: 2.4k阅读时长: 10 min
2022/06/15

简介

Pomerium 是一种身份感知代理,可实现对内部应用程序的安全访问。Pomerium 提供了一个标准化的接口来为应用程序添加访问控制,无论应用程序本身是否具有授权或身份验证。

Pomerium 可用于:

  • 为内部应用程序提供单点登录网关。

  • 基于上下文身份设备身份实施动态访问策略

  • 聚合访问日志和遥测数据。

  • 为自定义、本地和托管应用程序和服务添加统一访问和身份

  • 为基于服务的授权系统执行委托的用户授权:

    Istio

    谷歌云

  • 作为VPN 替代品

架构

Pomerium 位于最终用户和需要强身份验证的服务之间。在通过您的身份提供商 (IdP) 验证身份后,Pomerium 使用可配置的策略来决定如何路由用户的请求以及他们是否有权访问该服务。

pomerium 架构图

Pomerium 由 4 个逻辑组件组成:

  • 代理服务
    • 所有用户流量都流经代理
    • 使用身份验证服务验证所有请求
    • 将用户定向到身份验证服务以建立会话身份
    • 处理策略以确定外部/内部路由映射
  • 认证服务
    • 根据需要处理您的 IdP 的身份验证流程
    • 在初始身份验证后处理身份验证
    • 建立用户会话 cookie
    • 将用户 OIDC 令牌存储在数据代理服务中
  • 授权服务
    • 处理策略以确定每个服务的权限
    • 处理所有用户会话的授权检查
    • 指示代理服务根据需要启动身份验证流程
    • 为上游服务提供额外的安全相关标头以供使用
  • 数据代理服务
    • 检索与身份提供者相关的数据,例如组成员身份
    • 存储和刷新身份提供者访问和刷新令牌
    • 向授权服务提供流式权威会话和身份数据
    • 将会话和身份数据存储在持久存储中

在生产部署中,建议您单独部署每个组件。这允许您限制外部攻击面,以及独立扩展和管理服务。

在测试部署中,所有四个组件都可以从单个二进制文件和配置运行。

pomerium 架构图

认证流程

Pomerium 在来自新用户的完全身份验证期间的内部和外部组件交互如下图所示。

在提供会话令牌的初始身份验证后,仅发生授权检查交互。

pomerium 架构图

使用

日常工作中,我们可能会遇到很多应用没有内置用户模块进行身份验证,比如prometheus、alertmanager、虽然我们可以使用nginx的basic-auth 做登陆身份验证,这种虽然简单但是不利于实际使用中我们针对用户做限制,比如不希望某些人访问,basic-auth 无法实现类似的限制功能,我们尝试使用Pomerium 对我们的网站进行保护。

这里我们还是使用gitlab作为身份提供者,不要问我为啥都是用gitlab作为身份提供者,因为我不喜欢用ldap。

使用docker-compose启动pomerium和一个不具有身份验证的服务verify,verify服务可以显示我们通过身份提供者获取到的用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3"
services:
pomerium:
image: pomerium/pomerium:latest
volumes:
- ./config.yaml:/pomerium/config.yaml:ro
ports:
- 80:80
- 443:443
## https://verify.localhost.pomerium.io --> Pomerium --> http://verify
verify:
image: pomerium/verify:latest
expose:
- 80

准备配置文件

准备签名密钥

签名密钥是用于签署用户证明 JWT 的私钥,上游应用程序可以使用它来传递识别用户信息,如用户名、id 和组。

如果设置,签名密钥的公钥将可以通过点击 Pomerium 的/.well-known/pomerium/jwks.json端点来检索,该端点位于身份验证服务上。否则,端点将返回一个空键集。

没加这个访问/.well-known/pomerium/jwks.json会返回 {keys:”null”} ,这里坑了我很久。

1
2
3
[root@es-01 pomerium]# openssl ecparam  -genkey  -name prime256v1  -noout  -out ec_private.pem
[root@es-01 pomerium]# cat ec_private.pem | base64 -w0
ewogICJ***dWU1MyNWNtb2E5eEhNUW1VQjdQIgp9Cg==

这里base64编码后的值为config.yaml文件内signing_key的值

使用gitlab作为身份提供者

身份提供者配置我们需要在gitlab上创建一个应用,记录应用的id和secret,后面config.yaml里会用到。

image-20220614154328607

image-20220615003718032

这里Redirect URI格式为 https://${authenticate_srvice_url}/oauth2/callback

范围勾选openid, profile, email

如果我们使用gitlab里的group在Pomerium中进行访问控制,则还需要再gitlab里创建一个访问令牌,用于配置config.yaml中的idp_service_accountimage-20220615004002096

创建后记录生成的访问令牌的token值

image-20220615004112926

idp_service_account是 base64 编码的 JSON 文档,格式如下:

1
2
3
{
"private_token": "GITLAB_TOKEN"
}

我们生成我们使用的idp_service_account

1
2
[root@es-01 pomerium]# cat json.tmp |base64 -w 0
ewogICJwcml2YXRlX3Rxxx*****Xp0YVBncW1zQl9RIgp9Cg==

这里用的config.yaml文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 开启debug日志,调试使用
pomerium_debug: true
proxy_log_level: debug

#外部可访问的url
authenticate_service_url: https://pomerium.harmay.com
# ssl证书位置
certificate_file: /pomerium/cert.pem
certificate_key_file: /pomerium/privkey.pem
# 使用Let's Encrypt证书,注意每次重启容器都会重新签发证书,调试时候可以考虑用autocert_use_staging先进行测试。 Autocert 要求端口80/443可以从 Internet 访问以完成TLS-ALPN-01 质询。
#autocert: true

#身份提供者配置
idp_provider: "gitlab"
idp_client_id: "8cc7c514b88a76a871****88dc4f2ec3bc2dbe6510e0087a"
idp_client_secret: "58cd65ff96aea26***b9d5cf1312975ecf41031c06229d11e4d46a"
idp_scopes: "openid,profile,email"
idp_provider_url: "https://gitlab.lishuai.fun"
idp_service_accOUnt: "ewogICJ***dWU1MyNWNtb2E5eEhNUW1VQjdQIgp9Cg=="
#idp_refresh_directory_interval: 1m
jwt_claims_headers:
- user
- email
- name
- groups
#参考上述的准备签名密钥
signing_key: LS0tLS******BLRVktLS0tLQo=



# Generate 256 bit random keys e.g. `head -c32 /dev/urandom | base64`
cookie_secret: W9Ir8DThVHcD3sTbCck8+G4O7yx6ubk/C8VqA8zluUs=

## 路由配置
routes:
- from: http://test.harmay.com
to: http://verify:8000
policy:
- allow:
or:
- email:
is: 912988434@qq.com
- groups:
has: "jenkins"
- deny:
or:
- groups:
has: "gitlab-ci"
pass_identity_headers: true


routes包含后端服务的特定访问和控制定义。每个routes至少定义一个fromto字段,以及一个policy定义授权逻辑的key,这里我们定义了访问https://test.xxx.com路由到内部的http://verify:8000这个地址,并且允许邮箱为912988434@qq.com的用户以及属于jenkins这个组的用户访问,拒绝属于gitlab-ci这个组的用户访问

启动服务

1
2
3
4
5
6
7
8
[root@es-01 pomerium]# docker-compose up -d 
Creating network "pomerium_default" with the default driver
Creating pomerium_pomerium_1 ... done
Creating pomerium_verify_1 ... done
[root@es-01 pomerium]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c5dd17fa201f pomerium/verify:latest "/bin/verify" 2 minutes ago Up 2 minutes 80/tcp pomerium_verify_1
345da0092980 pomerium/pomerium:latest "/bin/pomerium -conf…" 2 minutes ago Up About a minute 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp pomerium_pomerium_1

验证

验证允许邮箱登陆

我们先打开https://test.harmay.com访问,首先会跳转到gitlab进行登陆,使用邮箱为912988434@qq.com的用户后通过验证进入如下页面,会显示我们的用户信息。

image-20220615010120951

第一次登陆可能会出现如下的页面,选择Authorize即可。

image-20220614181921879

我们在准备签名密钥里说的访问/.well-known/pomerium/jwks.json这里也可以验证一下

image-20220615014142484

验证拒绝gitlab-ci组中用户登录

我们gitlab中有个用户叫lishuai ,该用户邮箱为lishuai@liangla.mobi我们先将这个用户加入到gitlab-ci这个组里

image-20220615010556675

此时我们访问https://test.harmay.com 会跳转到gitlab做验证,此时就会被拒绝访问。

image-20220614155306212

我们访问https://pomerium.harmay.com/ 也可以看到用户的信息,此时用户的信息都被拿到但是因为邮箱不是912988434@qq.com或属于jenkins组所以无法登录到https://test.harmay.com

image-20220615010722242

验证允许jenkins组中用户登录

我们再将这个用户从gitlab-ci这个组中移除,加入到jenkins这个组内

image-20220615011023783

此时我们访问https://test.harmay.com 是可以拿到用户信息并正常进入

image-20220615011111709

监控

如果我们想监控Pomerium,我们可以修改config.yaml文件,添加如下配置暴露mtrics指标

1
2
3
4
......
metrics_address: ":9090"
......

修改docker-compose.yaml,添加端口9090的映射

1
2
3
4
5
6
......
ports:
- 80:80
- 443:443
- 9090:9090
........

访问指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@es-01 pomerium]# curl localhost:9090/metrics|tail -n 20
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 584k 0 584k 0 0 15.8M 0 --:--:-- --:--:-- --:--:-- 15.8M
# TYPE envoy_listener_manager_listener_modified counter
envoy_listener_manager_listener_modified{service="pomerium",installation_id="",hostname="df87ae62087c"} 0
# TYPE envoy_vhost_harmay_com_vcluster_other_upstream_rq_timeout counter
envoy_vhost_harmay_com_vcluster_other_upstream_rq_timeout{service="pomerium",envoy_virtual_host="pomerium",installation_id="",hostname="df87ae62087c"} 0
envoy_vhost_harmay_com_vcluster_other_upstream_rq_timeout{service="pomerium",envoy_virtual_host="test",installation_id="",hostname="df87ae62087c"} 0
# TYPE envoy_listener_manager_workers_started gauge
envoy_listener_manager_workers_started{service="pomerium",installation_id="",hostname="df87ae62087c"} 1
# TYPE envoy_server_memory_physical_size gauge
envoy_server_memory_physical_size{service="pomerium",installation_id="",hostname="df87ae62087c"} 1.7430454e+07
# TYPE envoy_cluster_http2_outbound_control_flood counter
envoy_cluster_http2_outbound_control_flood{service="pomerium",envoy_cluster_name="pomerium-control-plane-grpc",installation_id="",hostname="df87ae62087c"} 0
envoy_cluster_http2_outbound_control_flood{service="pomerium",envoy_cluster_name="pomerium-databroker",installation_id="",hostname="df87ae62087c"} 0
# TYPE envoy_listener_admin_downstream_cx_total counter
envoy_listener_admin_downstream_cx_total{service="pomerium",installation_id="",hostname="df87ae62087c"} 2
# TYPE envoy_listener_manager_lds_init_fetch_timeout counter
envoy_listener_manager_lds_init_fetch_timeout{service="pomerium",installation_id="",hostname="df87ae62087c"} 0
# TYPE envoy_vhost_vcluster_upstream_rq_timeout counter
envoy_vhost_vcluster_upstream_rq_timeout{service="pomerium",envoy_virtual_cluster="other",envoy_virtual_host="catch-all",installation_id="",hostname="df87ae62087c"} 0
envoy_vhost_vcluster_upstream_rq_timeout{service="pomerium",envoy_virtual_cluster="other",envoy_virtual_host="grpc",installation_id="",hostname="df87ae62087c"} 0
envoy_vhost_vcluster_upstream_rq_timeout{service="pomerium",envoy_virtual_cluster="other",envoy_virtual_host="metrics",installation_id="",hostname="df87ae62087c"} 0

总结

实际使用中,我们可以将config.yaml中routes中的地址换成我们实际需要保护的地址,例如prometheus地址或者alertmanager地址等,Pomerium不仅可以保护http协议的服务还可以保护tcp协议的服务,有时间给大家继续分享使用Pomerium如何保护我们的redis和mysql等tcp协议服务。

参考

https://www.pomerium.com/

CATALOG
  1. 1. 简介
  2. 2. 架构
  3. 3. 认证流程
  4. 4. 使用
    1. 4.1. 准备配置文件
      1. 4.1.1. 准备签名密钥
      2. 4.1.2. 使用gitlab作为身份提供者
  5. 5. 验证
    1. 5.1. 验证允许邮箱登陆
    2. 5.2. 验证拒绝gitlab-ci组中用户登录
    3. 5.3. 验证允许jenkins组中用户登录
  6. 6. 监控
  7. 7. 总结
  8. 8. 参考