前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用 Ghostunnel 和 SPIRE 为 NGINX 提供 SPIFFE 认证

用 Ghostunnel 和 SPIRE 为 NGINX 提供 SPIFFE 认证

作者头像
崔秀龙
发布于 2022-11-23 06:43:34
发布于 2022-11-23 06:43:34
60600
代码可运行
举报
文章被收录于专栏:伪架构师伪架构师
运行总次数:0
代码可运行

之前对 SPIFFE 和 SPIRE 进行了一个相对全面/啰嗦的介绍,这一篇就反过来,用一个简单的例子来展示 SPIRE 的基本用法,本文中会以 NGINX 作为服务生产方,使用 Ghostunnel 当做 NGINX 的反向代理,把原有的 HTTP 通信升级为支持定期正顺轮转的双向 TLS 认证协议,并且用 CURL 使用客户端证书来通过 Ghostunnel 安全地访问背后的 NGINX。这里为 CURL 和 NGINX 提供证书以及轮转的,就是 SPIRE 的 Server 和 Agent。

Ghostunnel 是一个简单的 TLS 代理,能为非 TLS 的后端提供双向认证能力。Ghostunnel 能够以服务端(反向代理)或者客户端(代理)的模式进行工作,类似 stunnel。不同的是,他还支持访问控制、证书轮转、ACME 以及最近总在唠叨的 SPIFFE。

本文中会演示的过程实际上是 Ghostunnel 的 SPIFFE DEMO 的一个精简版,会略细致讲述每个步骤涉及的内容。整个过程分为如下一些环节:

  1. 环境准备:准备运行环境,包括 SPIRE Agent/Server 的构建、NGINX 的安装、以及 Ghostunnel 的构建等
  2. 编写 SPIRE Server 配置,并启动
  3. 生成 Ghostunnel 以及 CURL 的 Agent Token,并编写配置文件启动对应的 SPIRE Agent
  4. 启动 Ghostunnel
  5. 获取 CURL 客户端证书并测试连接

环境准备

这里使用的是基于 ARM 的一个 Ubuntu 系统,使用 APT 安装并启动 NGINX。另外后续步骤还需要 GIT 工具以及连接 GITHUB,并使用 GOLANG 构建 SPIRE 以及 Ghostunnel。

GIT 获取 SPIRE 版本,并进行构建:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git clone --single-branch --branch v1.4.0 https://github.com/spiffe/spire.git
Cloning into 'spire'...
...
$ cd spire
$ make bin/spire-agent
Installing go1.18.4...
Building bin/spire-agent...
$ make bin/spire-server
Building bin/spire-server...

接下来获取 Ghostunnel 并进行构建:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ git clone https://github.com/ghostunnel/ghostunnel.git
Cloning into 'ghostunnel'...
...
$ make ghostunnel
go build -ldflags '-X main.version=v1.6.1-25-g8ae18ea' -o ghostunnel .
...

构建成功后,把三个新生成的可执行文件拷贝到可见目录备用。

然后建立测试目录,大致目录结构如下:

  • spire-101
    • certs
    • conf
    • data
    • logs
    • socks

编写 SPIRE Server 配置并启动服务

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
server {
    bind_address = "0.0.0.0"
    bind_port = "8081"
    socket_path = "socks/spire-server.sock"
    trust_domain = "spiffe.dom"
    data_dir = "data/spire-server"
    log_level = "DEBUG"
    ca_ttl = "30m"
    default_svid_ttl = "2m"
    ca_subject = {
        country = ["CN"],
        organization = ["FUNNY"],
        common_name = "",
    }
}

plugins {
    DataStore "sql" {
        plugin_data {
            database_type = "sqlite3"
            connection_string = "data/spire-server/datastore.sqlite3"
        }
    }

    NodeAttestor "join_token" {
        plugin_data {
        }
    }

    KeyManager "disk" {
        plugin_data {
            keys_path = "data/spire-server/keys.json"
        }
    }
}

此处配置文件的几个要点:

  • TCP 监听 0.0.0.0:8081
  • 监听 Socket 路径为 socks/spire-server.sock
  • 使用 spiffe.dom 作为信任域
  • SVID 的默认寿命为 2 分钟
  • 使用 SQLite3 作为数据存储引擎,数据库文件保存在 data/spire-server/datastore.sqlite3
  • 在本地存储 Key,路径为 data/spire-server/keys.json

然后用这个配置文件启动 SPIRE Server:spire-server run -config conf/spire-server.conf > logs/spire-server.log 2>&1 &

启动 Agent

这个小实验需要用到两个 Agent,分别负责服务端和客户端的身份。在运行 Agent 之前,首先要获取 SPIRE Server 的 Trust Bundle:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-server bundle show \
  -socketPath socks/spire-server.sock > conf/bundle.crt

上述命令将 Trunst Bundle 保存到文件 conf/bundle.crt

服务端 Agent 配置文件如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
agent {
    data_dir = "data/server-side-agent"
    log_level = "DEBUG"
    server_address = "127.0.0.1"
    server_port = "8081"
    socket_path ="socks/server-side-agent.sock"
    trust_bundle_path = "conf/bundle.crt"
    trust_domain = "spiffe.dom"
}

plugins {
    NodeAttestor "join_token" {
        plugin_data {
        }
    }
    KeyManager "disk" {
        plugin_data {
            directory = "data/server-side-agent"
        }
   }
   WorkloadAttestor "unix" {
        plugin_data {
             discover_workload_path = true
        }
    }
}

这个配置的要点是:

  • 使用 127.0.0.1:8081 作为 SPIRE Server
  • 监听 socks/spire-server.sock
  • 信任 conf/bundle.crt
  • Unix Workload Attestor 中开放了选项 discover_workload_path,从而可以通过二进制文件位置或者哈希识别调用 Agent 的应用的身份

为这个 Agent 创建一个 Token,用于标识 Agent 的身份:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-server token generate \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/server-node
Token: [Token Hash]

上面命令生成了一个 Token,其 SPIFFE ID 为 spiffe://spiffe.dom/server-node。然后启动服务侧 Agent:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-agent run \
    -config conf/client-side-agent.conf \
    -joinToken [Token Hash] > logs/client-side-agent.log 2>&1 &

接下来启动客户侧的 Agent,配置文件如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
agent {
    data_dir = "data/client-side-agent"
    log_level = "DEBUG"
    server_address = "127.0.0.1"
    server_port = "8081"
    socket_path ="socks/client-side-agent.sock"
    trust_bundle_path = "conf/bundle.crt"
    trust_domain = "spiffe.dom"
}

plugins {
    NodeAttestor "join_token" {
        plugin_data {
        }
    }
    KeyManager "disk" {
        plugin_data {
            directory = "data/client-side-agent"
        }
   }
   WorkloadAttestor "unix" {
        plugin_data {
        }
    }
}

跟上面的类似,我们也需要创建 Token 之后才能启动 Agent:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-server token generate \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/client-node
Token: [Token Hash]

使用上述 Token 和 配置文件启动 Agent:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-agent run \
    -config conf/client-side-agent.conf \
    -joinToken "$TOKEN" > logs/client-side-agent.log 2>&1 &

启动 Ghostunnel

首先要给 Ghostunnel 一个身份,也就是 Entry:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-server entry create \
    -selector unix:path:/usr/local/bin/ghostunnel \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/ghost \
    -parentID spiffe://spiffe.dom/server-node
Entry ID         : fe4b1fd5-9e0a-440b-b08e-5c2c886b6a6e
SPIFFE ID        : spiffe://spiffe.dom/ghost
Parent ID        : spiffe://spiffe.dom/server-node
Revision         : 0
TTL              : default
Selector         : unix:path:/usr/local/bin/ghostunnel

上面的命令参数解释如下:

  • selector:类似 Kubernetes 中的 Label Selector,用 Workload 属性来界定身份,这里使用的是二进制路径:unix:path:/usr/local/bin/ghostunnel,此文件启动之后,可以使用 Workload API 向 Agent 请求 SVID
  • socketPath:指定 SPIRE Server 的监听 Socket
  • spiffeID:Workload 的 SPIFFE ID
  • parentID:Node 的 SPIFFE ID

创建这个 Entry 之后,SPIRE Server 会据此创建 SVID 下发给 Agent,Agent 只要根据 Selector 判断 Workload 身份,如果符合就可以发放 SVID 了。

接下来启动 Ghostunnel:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ ghostunnel server \
    --use-workload-api-addr unix:///$(pwd)/socks/server-side-agent.sock \
    --listen=0.0.0.0:9099 \
    --target=localhost:80 \
    --allow-uri=spiffe://spiffe.dom/curl

这里使用了一个参数 --use-workload-api-addr,要求使用 SPIFFE Workload API,对应 Agent Socket 为前面生成的 socks/server-side-agent.sock--listen--target 分别代表了监听端口和被代理端口(也就是 NGINX)。而 --allow-uri 参数则是一种访问控制手段,此处是允许 spiffe://spiffe.dom/curl 的 SPIFFE ID 访问本服务。除了这种死板的方式之外,Ghostunnel 还能对接 OPA 实现更加复杂的符合生产要求的策略管控能力。

如果此时用浏览器或者 CURL 访问该节点的 9099 端口,就会出现客户端证书不匹配的错误。

获取 CURL 客户端证书并测试连接

类似的,我们给 CURL 创建一个 SVID:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-server entry create \
    -selector unix:uid:1000 \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/curl \
    -ttl 600 \
    -parentID spiffe://spiffe.dom/client-node
Entry ID         : 50911ef7-f191-4917-adde-1bf4e6192002
SPIFFE ID        : spiffe://spiffe.dom/curl
Parent ID        : spiffe://spiffe.dom/client-node
Revision         : 0
TTL              : 600
Selector         : unix:uid:1000

因为我们用的是 CURL,并不具备直接访问 Workload API 的能力,所以这里用了比较特别的参数:

  • Selector 设置为当前用户的 ID,也就是说该用户执行的进程是可以匹配到这个 Entry 从而获取 SVID 的
  • 设置了 10 分钟的 TTL,满足我们后续手动操作的需要

然后用 spire-agent api fetch 的方式获取证书:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ spire-agent api fetch \
    --socketPath socks/client-side-agent.sock \
    -write certs

命令执行后,会在 certs 发现导出的证书文件,CURL 加上这个证书就能成功访问到 NGINX 了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ curl -kv https://127.0.0.1:9099 \
    --cert certs/svid.0.pem --key certs/svid.0.key
*   Trying 127.0.0.1:9099...
* Connected to 127.0.0.1 (127.0.0.1) port 9099 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
...
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...

然后

如果观察 logs 目录中的日志,会看到在两个 Agent 的目录中会频频出现 Node 和 Workload 的 SVID 轮转的信息。那么如果 Server 挂了呢?这里就会发现,SPIRE Server 是系统中的一个单点,各个 Node 会因为 SVID 无法更新而异常退出,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
level=error msg="Agent crashed" error="current SVID has already expired and rotation failed: failed to dial dns:///127.0.0.1:8081: connection error: desc = \"transport: error while dialing: dial tcp 127.0.0.1:8081: connect: connection refused\""

因此需要对 SPIRE Server 进行高可用部署。另外这个手工过程中我们也会看到,手工创建 Entry、传播 Bundle 以及获取证书、参数授权等,是不可能适应快速变更的云服务环境的,因此自动注册机制、策略执行机制以及相应的防篡改机制都是 SPIFFE 体系落地的必要条件。

后续还会根据这些问题进行进一步的尝试。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 伪架构师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Node.js服务端开发教程 (一):NestJS框架0到1
要做Node.js编程嘛,Node.js是必须安装的,大家可以到官网(https://nodejs.org)下载安装,推荐安装LTS版本。
一斤代码
2019/11/04
2.8K0
2024 版:Node.js+Express+Koa2+Nest.js 开发服务端(高の青)
在现代的 web 开发中,Node.js 是一种非常流行的服务器端 JavaScript 环境。我们来编写一个大致的框架和一些关键代码片段,以帮助你了解如何使用 Node.js、Express、Koa2 和 Nest.js 开发服务端应用。
百课优用户
2024/07/29
4470
使用NestJS搭建服务端应用
最近有个需求需要基于前端技术栈实现一套中间层API接口,用于处理由前端维护的一套JSON配置文件。
神奇的程序员
2022/04/10
2.2K0
使用NestJS搭建服务端应用
学完这篇 Nest.js 实战,还没入门的来锤我!(长文预警)
最近接到一个小需求,需要自己全干(前端+后端),看到群里大家对Nest.js热情都很高,自己也心痒痒,所以就走上了 Nest.js 的不归路~
玖柒的小窝
2021/11/21
11K0
学完这篇 Nest.js 实战,还没入门的来锤我!(长文预警)
面试官:说一下大文件分片下载
但是,这样文件是全部读取出来返回,如果文件大了,比如好几个 G,会占用很大的内存。
神说要有光zxg
2024/04/30
5550
面试官:说一下大文件分片下载
Node.js服务端开发教程 (四):依赖注入基础篇
现代的服务业真是越做越到位了,我们只要提供出我们的需求,就会有人主动来提供服务,针对性的解决我们的问题。就如上面的打车服务一样,我们不再需要像以前一样,在寒风凛冽的马路上、大雨瓢泼的黑夜里,哆哆嗦嗦的招手拦车,一辆辆的问司机走不走,司机大哥忙着要下班,不走;司机大哥还没吃饭,不走;司机大哥心情不好,不走;路程太近,不走;路程太远,不走......我们只需要在温暖的房间里向打车软件系统告知我们的行程信息、偏好信息等,就会有愿意服务我们的司机上门为我们服务。
一斤代码
2019/11/12
1.7K0
Node.js服务端开发教程 (七):模块系统
说到“模块”两字,我们脑海里肯定会浮现很多关于它好处的词汇:封装性、可复用、按需引入等等。当一个软件系统的代码规模上升到一定复杂度后,我们的确需要一些方式来条理更清晰的组织我们的代码,让代码更易阅读、团队分工协作更方便。
一斤代码
2019/11/29
1.6K0
Nest.js 从零到壹系列(一):项目创建&路由设置&模块
本系列将以前端的视角进行书写,分享自己的踩坑经历。教程主要面向前端或者毫无后端经验,但是又想尝试 Node.js 的读者,当然,也欢迎后端大佬斧正。
一只图雀
2020/04/07
5.6K0
Nest.js Controller 解析:探索路由和请求处理的强大功能
Controller  它 主要是负责 特定路由请求处理并将响应结果返回给客户端。
程序员海军
2023/11/03
6370
NestJS学习总结篇
完整版本,点击此处查看 http://blog.poetries.top/2022/05/25/nest-summary
前端进阶之旅
2022/05/27
2.5K0
Node.js学习笔记(三)——Node.js开发Web后台服务
Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。 使用 Express 可以快速地搭建一个完整功能的网站。使用Node.js作为AngularJS开发Web服务器的最佳方式是使用Express模块。
张果
2022/09/28
8.4K0
Node.js学习笔记(三)——Node.js开发Web后台服务
Nest系列教程之控制器
为了创建一个基本的控制器,我们必须将元数据附加到类中。Nest 知道如何映射我们的控制器到相应的路由。
阿宝哥
2019/11/06
1.9K0
Nest系列教程之控制器
NestJS中使用Guard实现路由保护
NestJS中Guard是一种用于保护路由的机制。它可以在路由处理之前执行一些逻辑,例如验证用户身份、检查权限等。
kongxx
2024/09/11
2320
NestJS中使用拦截器
在NestJS中,拦截器是一种强大的工具,可以用来在请求处理的不同阶段执行一些操作。拦截器可以用于日志记录、验证、转换请求和响应等任务。今天就看看如何在NestJS中使用拦截器。
kongxx
2024/09/11
1970
快速打开 Nestjs 的世界
src目录是主要的源码目录,主要由入口文件 main.ts 和 一组 module,service,controller构成。
前端小鑫同学
2023/12/04
8190
快速打开 Nestjs 的世界
学完这篇 Nest.js 实战,还没入门的来锤我!(长文预警)
最近接到一个小需求,需要自己全干(前端+后端),看到群里大家对Nest.js热情都很高,自己也心痒痒,所以就走上了Nest.js的不归路~
五月君
2021/11/30
15.1K2
学完这篇 Nest.js 实战,还没入门的来锤我!(长文预警)
NestJS 7.x 折腾记: (5) 管道,一个好玩的东西!比如入参校验!
意思就git的提交记录以单行显示的前三条,而且提交概要中有build这个词的 在nest里面的管道,主要是用来做入参数据类型的拦截转换; 跟ng提供的概念差不多,大同小异~~
CRPER
2022/03/08
1.2K0
NestJS 7.x 折腾记: (5) 管道,一个好玩的东西!比如入参校验!
10分钟上手nest.js+mongoDB
项目中我们会用到 Mongoose 来操作我们的数据库,Nest 官方为我们提供了一个 Mongoose 的封装,我们需要安装 mongoose 和 @nestjs/mongoose:
淼学派对
2024/04/10
5350
BFF与Nestjs实战
主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate
乐圣
2022/11/19
2.9K0
BFF与Nestjs实战
nestjs搭建HTTP与WebSocket服务
最近在做一款轻量级IM产品,后端技术栈框架使用了nodejs + nestjs作为服务端。同时,还需要满足一个服务同时支持HTTP服务调用以及WebSocket服务调用,此文主要记录本次搭建过程,以及基本的服务端设计。
w4ngzhen
2023/10/17
9000
nestjs搭建HTTP与WebSocket服务
推荐阅读
相关推荐
Node.js服务端开发教程 (一):NestJS框架0到1
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档