尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Docker 部署 Consul 后如何正确配置 ACL 权限控制

Docker 部署 Consul 后如何正确配置 ACL 权限控制 服务注册与发现方案里Consul 一直是我比较顺手的选择尤其是配合 Docker 部署时一条docker run就能把服务端拉起来。不过很多入门教程只讲到“启动容器、访问 8500 端口看到 UI”把 ACL 权限控制这一层完全漏掉了。没有 ACL 的 Consul 等于把 API 裸奔在网络上数据中心里任何能访问端口的进程都能随便读写 KV、注册假服务、把别人的服务反注册掉。今天这篇就从 Docker 创建 Consul 开始带着你把 ACL 权限控制完整配起来整个过程会尽量讲清楚“为什么这么做”而不是只丢命令。1. 整体设计与思路拆解1.1 为什么用 Docker 部署 Consul我最早是在虚拟机里直接解压二进制部署 Consul后来切到 Docker 之后就不太想回去了。Docker 部署最大的优势是环境隔离和版本切换方便镜像里已经带好了 Consul 运行所需的依赖宿主机装了什么、缺什么库都不重要。想升级版本只需要换一下镜像 tag 重建容器旧数据放在独立 volume 里风险可控。还有一个很实际的好处试错成本低。ACL 配置这件事很少有人一次写对。用容器启动一个临时节点配错了直接删容器、保留数据卷重来比在服务器上反复改配置和重启 systemd 服务要舒服得多。单节点开发环境可以这么做生产集群也可以沿用同一套配置思想只是把docker run换成 docker-compose 或容器编排平台而已。1.2 ACL 到底在防什么很多团队上了 Consul但是一直没开 ACL理由通常是“内网环境别人进不来”。这个想法风险挺大的。任何一个能访问 8500 端口的内部服务、脚本、或者被攻破的跳板机都能直接调 API 做这些事读取、修改、删除 KV 存储里的配置数据查询所有服务注册信息泄漏服务拓扑注册一个同名假服务把流量引到恶意节点反注册掉正常服务引发服务发现异常。ACL 做的事就是给 Consul 的 API 加一道门禁。每个请求都要带着一个 TokenToken 后面绑定一个或多个 PolicyPolicy 规定这个 Token 能读哪些资源、能写哪些资源。默认情况下没有 Token 的匿名请求会被拒绝。这道门禁加完之后即使内网有别的服务扫到了 8500 端口它拿不到有效 Token 也只能吃 403。1.3 本次权限控制的整体流程第一次配置 ACL 时我踩过一个比较深的坑直接在配置里把default_policy设成deny然后启动 Consul结果 agent 自己都报权限错误节点选主、服务同步都出了问题。原因是 ACL 系统还没有初始化出管理 Tokenagent 也没有合适的身份把自己锁在了门外。所以这次我采用的顺序是先松后紧分三个阶段走第一阶段用enabled true和default_policy allow启动让 ACL 系统初始化同时 agent 能正常工作第二阶段执行consul acl bootstrap创建管理 Token再基于管理 Token 创建 agent 专用 Token 和业务 Token第三阶段把 agent Token 写进配置然后把默认策略改成deny重启容器验证收紧效果。这个流程不容易卡住也方便新手理解每个 Token 的用途。1.4 版本选择与目录规划演示使用官方镜像hashicorp/consul:1.19.1这是当前比较稳定的一个版本ACL 相关的配置字段都已经很成熟。如果你的环境版本更老注意有些字段名不一致比如旧版本用acl_token一级配置新版本已经归到acl.tokens下面了。目录和卷的规划也很简单。我习惯在宿主机建一个consul-config目录里面放配置文件启动时挂载到容器的/consul/config。Consul 会自动加载这个目录下的.json和.hcl文件。数据目录使用 Docker 命名卷consul-data挂载到容器的/consul/data。这么做的好处是容器删了数据还在ACL 初始化状态不会因为重建容器而丢失。开发环境端口映射到本机回环地址就够了HTTP 端口是 8500DNS 端口是 8600/UDP。如果生产环境需要暴露到局域网也应该单独走防火墙规则或反向代理而不是简单把 8500 映射到0.0.0.0。2. 核心细节解析与实操要点2.1 配置文件中 ACL 关键字段Consul 的配置文件是 JSON 格式ACL 相关的配置集中在acl字段里。这一段配置值得逐字段理解而不是直接复制粘贴{ acl: { enabled: true, default_policy: deny, enable_local_agent_tokens: true, tokens: { agent: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx } } }enabled是整个 ACL 功能的总开关。default_policy有两个值allow和deny。allow表示没有匹配到任何 Policy 的请求默认放行deny表示默认拒绝。生产环境必须用deny否则 ACL 形同虚设。enable_local_agent_tokens建议开启它的作用是让 agent 使用自己本地配置的 Token而不是全局同步的 Token。多节点环境下每个 agent 维护自己独立的身份权限隔离更干净。tokens.agent是 agent 自己用的 Token节点注册、服务注册、健康检查等 agent 发起的请求全靠它。注意这个 Token 不是管理 Token它只需要节点和服务相关的写权限就够了。管理 Token 千万别写进 agent 配置权限太大一旦配置文件泄漏整个集群都失控。2.2 ACL 规则语言快速入门Policy 的规则用 HCL 或 JSON 写常见的是 HCL。规则由资源类型、匹配范围和动作组成。资源类型包括node、service、key、session、acl等。匹配范围可以用精确名也可以用前缀。下面这个是我给 agent Token 用的规则核心就是允许注册和修改所有节点与服务信息node_prefix { policy write } service_prefix { policy write }node_prefix 里的空字符串表示匹配所有节点policy write表示允许读写。如果你的环境里 agent 只负责一个特定节点可以把空字符串改成具体节点名收窄范围。不过单节点演示环境用前缀通配更省事。生产环境建议遵循最小权限原则。比如一个微服务只需要注册自己的服务、读写某个前缀下的 KV那它的 Token 就不应该给它全量service_prefix write权限。否则 Token 泄露后攻击者能随意注册和注销所有服务影响面非常大。2.3 Token、Policy、Role 之间的关系这三者的关系可以用公司门禁卡来类比。Policy 是权限清单Token 是门禁卡Role 是门禁卡模板。一张 Token 可以绑定多个 Policy也可以绑定多个 RoleRole 本身是 Policy 的集合。日常工作里我习惯用 Policy Token 两层就够了Role 更适合团队人数多、权限组合固定、需要批量管理时使用。下面这张表是常见的 Policy 组合和对应场景场景推荐规则说明Agent Tokennode_prefix writeservice_prefix write供 Consul agent 自身使用只读 Tokennode/service/key/session前缀read供 UI 或监控系统查看服务注册 Token指定service_prefix write给具体微服务注册用KV 写入 Token指定key_prefix app/ write给配置中心客户端用管理 Token 是 bootstrap 时生成的它不通过 Policy 赋予权限而是隐式拥有全部权限。管理 Token 只应该在一个安全的地方保存用于创建和修改 Policy、Token不应该被普通业务代码使用。2.4 端口映射与 Docker 挂载的注意点启动容器时我建议把宿主机端口映射到127.0.0.1尤其是 8500。命令这样写docker run -d --name consul \ -p 127.0.0.1:8500:8500 \ -p 127.0.0.1:8600:8600/udp \ ...如果写成-p 8500:8500Docker 默认绑定宿主机所有网卡局域网内的机器直接就能访问。对于开发机来说不是大问题但如果是云服务器安全组稍微配错Consul 的 API 就暴露在公网上了。加上 ACL 之后虽然能挡住一部分但也没必要给攻击者一个“试试密码”的机会。数据卷挂载这块最容易被忽略的是容器删掉后 ACL 初始化状态丢失。如果没挂数据卷docker rm后重新起一个容器ACL 系统又变成未初始化状态之前的 Token 全部失效业务方拿着旧 Token 调用全部 403。所以数据卷必须挂。配置目录用 bind mount 比较好改配置方便容器重启就能生效。3. 实操过程与核心环节实现3.1 准备目录和配置文件先准备目录mkdir -p ./consul-config第一阶段配置文件写./consul-config/consul.json内容是允许模式先把系统转起来{ datacenter: dc1, data_dir: /consul/data, log_level: INFO, ui: true, server: true, bootstrap_expect: 1, client_addr: 0.0.0.0, acl: { enabled: true, default_policy: allow } }这里bootstrap_expect表示期望的 server 节点数单节点填 1。如果准备组集群每个 server 节点都要填同一个值等节点数达到预期后会自动完成选主。这个字段不是越多越好填错会导致集群一直等不到足够节点无法选主。3.2 启动 Consul 容器用下面的命令启动容器docker run -d --name consul \ -p 127.0.0.1:8500:8500 \ -p 127.0.0.1:8600:8600/udp \ -e CONSUL_BIND_INTERFACEeth0 \ -v $(pwd)/consul-config:/consul/config \ -v consul-data:/consul/data \ hashicorp/consul:1.19.1 agent -server -bootstrap-expect1 -uiCONSUL_BIND_INTERFACE这个环境变量可以帮容器自动检测绑定 IP。在容器里如果不指定绑定地址Consul 有时会选到127.0.0.1导致其他节点访问不到。指定成eth0一般就能拿到容器的主网卡 IP。启动之后确认一下状态docker logs consul docker exec consul consul members curl http://127.0.0.1:8500/v1/status/leader看到本机地址加端口说明服务端已经正常选主。这时候 UI 也能打开但还没有任何 Token 也能访问因为默认策略还是allow。3.3 初始化 ACLbootstrap 管理 Token在允许模式下ACL 系统已经启用了但还没有初始化。接下来执行docker exec consul consul acl bootstrap输出里会包含AccessorID和SecretID其中SecretID就是管理 Token非常重要的凭据。它只会在 bootstrap 时完整显示一次以后任何命令都无法再查到明文。立刻把它存到密码管理器里同时也导出到当前 shellexport CONSUL_HTTP_TOKEN这里的SecretID之后所有需要管理权限的命令都通过docker exec -e CONSUL_HTTP_TOKEN$CONSUL_HTTP_TOKEN把 Token 传给容器内的 consul CLIdocker exec -e CONSUL_HTTP_TOKEN$CONSUL_HTTP_TOKEN consul consul acl token list能返回一个空列表或包含当前 Token说明管理凭证有效。3.4 创建 Agent Token 并写入配置agent 自己也需要一个身份。之前 agent 能在默认允许模式下工作是因为所有请求都被放行。一旦切到默认拒绝如果没有 agent Tokenagent 内部的很多操作都会失败。先写 agent 规则文件cat ./consul-config/agent-policy.hcl EOF node_prefix { policy write } service_prefix { policy write } EOF创建 Policydocker exec -e CONSUL_HTTP_TOKEN$CONSUL_HTTP_TOKEN consul consul acl policy create \ -name agent-policy \ -description policy for consul agent \ -rules /consul/config/agent-policy.hcl后面的路径是容器内路径所以规则文件必须放在挂载目录里。之后创建 agent Tokendocker exec -e CONSUL_HTTP_TOKEN$CONSUL_HTTP_TOKEN consul consul acl token create \ -policy-name agent-policy \ -description agent token把输出里的SecretID记为AGENT_TOKEN。然后把第一阶段配置改成收紧模式把 agent Token 填进去{ datacenter: dc1, data_dir: /consul/data, log_level: INFO, ui: true, server: true, bootstrap_expect: 1, client_addr: 0.0.0.0, acl: { enabled: true, default_policy: deny, enable_local_agent_tokens: true, tokens: { agent: 把AGENT_TOKEN粘贴到这里 } } }保存后重启容器docker restart consul docker logs consul --tail 50日志里如果没有 Permission denied 这类报错说明 agent Token 生效agent 和服务端之间的通信恢复正常。3.5 创建业务与只读 Token权限控制不能只靠一个 agent Token。实际业务里至少需要两类 Token一类给 UI 或监控系统做只读访问一类给具体微服务做服务注册和 KV 读写。只读 Token 的规则文件cat ./consul-config/read-policy.hcl EOF node_prefix { policy read } service_prefix { policy read } key_prefix { policy read } session_prefix { policy read } EOF创建 Policy 和 Tokendocker exec -e CONSUL_HTTP_TOKEN$CONSUL_HTTP_TOKEN consul consul acl policy create \ -name read-policy \ -description read only for ui and monitor \ -rules /consul/config/read-policy.hcl docker exec -e CONSUL_HTTP_TOKEN$CONSUL_HTTP_TOKEN consul consul acl token create \ -policy-name read-policy \ -description read only token只读 Token 可以用来登录 UI能看服务列表、节点状态和 KV但不能改任何数据。业务 Token 可以更精细。比如一个叫myapp的服务只允许它注册myapp服务、读写config/myapp/前缀的 KVcat ./consul-config/myapp-policy.hcl EOF service myapp { policy write } key_prefix config/myapp/ { policy write } EOF创建方式一样只是-name和-rules路径不同。创建 Token 时绑定的 Policy 换成myapp-policy。这个 Token 分发给myapp服务后即使它的代码环境里藏着恶意脚本能影响的也只有myapp自己的服务和配置前缀。3.6 验证权限控制是否生效重点验证三件事无 Token 被拒绝、只读 Token 不能写、业务 Token 只能碰自己的范围。无 Token 访问curl http://127.0.0.1:8500/v1/kv/app返回内容里应该有Permission denied说明默认拒绝策略生效。带只读 Token 访问curl -H X-Consul-Token: $READ_TOKEN http://127.0.0.1:8500/v1/kv/app如果存在能读出来然后尝试写入curl -X PUT -H X-Consul-Token: $READ_TOKEN http://127.0.0.1:8500/v1/kv/app -d test应该返回 403说明只读 Token 的权限边界正确。带业务 Token 访问自己的服务和 KVcurl -X PUT -H X-Consul-Token: $MYAPP_TOKEN http://127.0.0.1:8500/v1/kv/config/myapp/hello -d world curl -H X-Consul-Token: $MYAPP_TOKEN http://127.0.0.1:8500/v1/kv/config/team/secret第一条应该成功第二条应该被拒绝。这样就能确认业务只能访问自己该访问的资源。4. 常见问题与排查技巧实录4.1 没有保存 bootstrap Token 怎么办这是最惨的情况没有之一。ACL 系统初始化之后consul acl bootstrap就不允许再次执行了。你再跑一次会看到类似ACL bootstrap no longer allowed的报错。管理 Token 的明文只显示一次丢了就是丢了。解决办法分情况。如果 Consul 里还没有重要数据直接把数据卷删掉重建重新 bootstrap如果有重要数据只能从开启 ACL 之前的 snapshot 恢复。所以我的习惯是bootstrap 成功后立刻把 SecretID 写进密码管理器同时导出一份到本地加密文件而不是只放在 shell 变量里。4.2 开启 ACL 后 403 Forbidden 一直出现这类问题九成是 Token 没传对。排查顺序固定先用管理 Token 执行docker exec -e CONSUL_HTTP_TOKEN$CONSUL_HTTP_TOKEN consul consul acl token list确认管理 Token 本身有效。检查请求头。Consul API 支持X-Consul-Token头我一般固定用它。检查 Token 绑定的 Policy 是否覆盖了目标资源。比如一个只有 KV 读权限的 Token去调v1/agent/self接口返回 403 是正常现象不是配置坏了。看 agent 日志。docker logs consul里如果有Permission denied先确认 agent Token 有没有写进配置里的acl.tokens.agent字段有没有用docker restart重新加载。4.3 UI 登录与 Token 传递方式Consul 开启 ACL 后访问http://127.0.0.1:8500/ui页面会要求输入 Token。这里输入的是 SecretID。只要 Token 有对应资源的读权限UI 就能看到对应数据。不建议直接在浏览器 URL 后面拼?tokenxxx来登录。Token 会留在浏览器历史记录、反向代理日志、HTTP 访问日志里这种泄漏是完全可以避免的。程序调用时用X-Consul-Token请求头本地命令行就靠CONSUL_HTTP_TOKEN环境变量尽量别把 Token 写在脚本参数里。4.4 Docker 重启后 ACL Token 失效或配置丢失很多人遇到的问题是容器重启后之前的 Token 全部不可用。最常见原因是数据卷没挂。docker run的时候如果没写-v consul-data:/consul/dataACL 的初始化状态存在容器可写层里容器删除后就没了。重建容器等于重新初始化旧 Token 当然全部作废。另一个原因是 agent Token 只通过命令行方式临时设置没有写进配置。比如执行过consul acl set-agent-token agent xxx重启后这个设置就丢了。想持久化要么把 Token 写进acl.tokens.agent配置字段要么开启enable_token_persistence并确认数据卷没丢。我自己的习惯是直接写配置简单直接重启不慌。4.5 端口暴露安全提醒最后提一个容易被忽略的点。就算 ACL 配好了Token 在 HTTP 明文传输过程中也有被截获的风险。Docker 演示环境无所谓生产环境还是要给 Consul 配 TLS或者放在内网安全网络里。还有8500 端口不要映射到公网。我见过有些云主机安全组把 8500 端口开给0.0.0.0/0虽然 Consul 有 ACL但配合未加密的 Token 传输很容易被中间人嗅探。权限控制是一个整体ACL 负责“谁能访问”TLS 负责“传输中不被偷看”两者缺一不可。另外定期检查 Token 列表也是个好习惯。consul acl token list能看出有哪些 Token 还活着那些长期不用的旧 Token 及时删掉别让权限越堆越多。权限这种东西越清理越安全。这次实际操作下来我的体会是Consul 的 ACL 并没有想象中复杂真正容易出问题的不是规则怎么写而是流程顺序。先 allow 初始化、建好管理 Token 和 agent Token、再改 deny整个过程会顺畅很多。还有一条建议配完之后一定要用docker restart完整验证一遍确保 agent 能自己带着 Token 起来别等业务凌晨报错再回来补课。权限控制这种事越早做越省心。
返回列表