- CI/CD
- DevOps
【免费下载链接】woodpecker
Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.
Woodpecker 允许通过一组预定义的 HTTP 端点,将内部逻辑(如流水线配置解析、镜像仓库凭证获取、密钥注入)替换为外部扩展服务,从而在不改动 CI/CD 引擎本身的前提下,实现配置的动态生成、凭证的集中管理。读完本文,你将掌握三类扩展(Configuration / Registry / Secret Extension)的全局与仓库级配置方式、请求/响应协议格式、HTTP 签名安全机制,以及扩展网络访问控制(WOODPECKER_EXTENSIONS_ALLOWED_HOSTS)的完整规则。
扩展机制总览:用 HTTP 端点替换内部逻辑
扩展(Extensions)是 Woodpecker 提供的一种插件化能力:它允许你通过配置一个 HTTP 端点,让外部服务接管引擎内部的特定职责。目前共提供三种类型的扩展,详见本仓库的 扩展目录:
- 配置扩展(Configuration extension):在流水线触发时即时修改或生成流水线配置。
- 注册表扩展(Registry extension):从扩展服务获取镜像仓库(Registry)的登录凭证。
- 密钥扩展(Secret extension):从外部服务(如 HashiCorp Vault、AWS Secrets Manager)获取密钥。
权限边界提示:Woodpecker 的权限处理与 forge(代码托管平台)绑定。如果某个用户在 forge 上对仓库拥有管理员权限,那么在 Woodpecker 中他也拥有该仓库的管理员权限,可以修改已配置的扩展端点,这可能被用于窃取 forge 用户的凭证。因此在启用扩展前,请确保你信任所有能够登录 Woodpecker 的仓库管理员。
安全基础:HTTP 签名(HTTP Signatures)
扩展会接收令牌、密钥等私密信息,也可能返回会被执行器执行的恶意流水线配置,因此你必须在信任扩展的前提下使用它。为了防御伪造请求等攻击,Woodpecker 使用 HTTP Signatures(draft-cavage-http-signatures 草案)对所有发往扩展的 HTTP 请求进行签名,签名算法基于 ed25519 公私钥对。
如何获取并校验公钥
扩展服务端必须使用 Woodpecker 的公钥验证每个请求的签名。公钥可以通过以下两种方式获取:
- 直接访问 HTTP 接口:
http://my-woodpecker.tld/api/signature/public-key; - 打开 Woodpecker UI,进入仓库设置的 Extensions 页面查看。
该接口在本仓库中的实现位于 server/api/signature_public_key.go,它使用x509.MarshalPKIXPublicKey将服务器的签名公钥序列化为 PEM 格式的PUBLIC KEY块后返回。扩展端可使用httpsign之类的库完成签名校验。
签名实现细节(源码佐证)
在 server/services/utils/http.go 中可以确认签名机制的具体实现:
- 使用
httpsign.NewEd25519Signer创建签名器,签名覆盖@request-target与content-digest两个头部(Content-Digest由库自动生成),签名名称(keyId)为woodpecker-ci-extensions; - 请求默认超时时间为 10 秒;
- 客户端带有
server-extensions的 User-Agent; Send方法内置重试逻辑:最大重试 3 次,采用指数退避(backoff);5xx 状态码与连接重置、超时等瞬时错误可重试,4xx 客户端错误直接终止。
配置扩展(Configuration Extension):动态生成与改写流水线配置
配置扩展用于修改或生成 Woodpecker 的流水线配置。在仓库设置的 Extensions 页签中可以配置一个 HTTP 端点。典型的应用场景包括:
- 用 Go template 等模板引擎预处理原始配置文件;
- 将自定义属性转换为 Woodpecker 属性;
- 为配置补充默认值(例如默认步骤);
- 将 GitLab CI、Starlark、Jsonnet 等完全不同的格式转换为 Woodpecker 配置;
- 在单一位置集中管理多个仓库的配置。
⚠️ 安全警告:Woodpecker 会把令牌等私密信息传给扩展,并执行扩展返回的配置,因此必须严格保护外部扩展。详见上文的安全签名机制。
全局配置
除了按仓库配置外,也可以在服务器配置中设置一个全局端点,适用于所有仓库。注意:如果与别人共享 Woodpecker 服务器,对方也会使用你的配置扩展。
WOODPECKER_CONFIG_EXTENSION_ENDPOINT=https://example.com/ciconfig对应 CLI 标志为--config-extension-endpoint与--config-extension-netrc,定义于 cmd/server/flags.go。
工作流程
流水线被触发后,Woodpecker 先从仓库拉取流水线配置,然后向配置的扩展发送一个 HTTP POST 请求,JSON 载荷中包含仓库信息、流水线信息和从仓库获取到的当前配置文件。扩展随后可以返回修改后的、甚至是全新的流水线配置(必须遵循 Woodpecker 官方的 YAML 格式)。
如果启用了exclusive(独占)设置(全局和仓库级均可),Woodpecker 将只调用你的扩展,不再做任何其他事情,从而可以完全跳过 forge;此时发送给扩展的请求中不会附带配置文件。
从源码上看,这一逻辑体现在 server/services/manager.go 的ConfigServiceFromRepo:当仓库配置了扩展端点且启用了ConfigExtensionExclusive时,直接使用config.NewHTTP(仅扩展);否则使用config.NewCombined(先本地配置服务、再合并扩展结果)。仓库级字段定义在 server/model/repo.go:ConfigExtensionEndpoint、ConfigExtensionExclusive、ConfigExtensionNetrc。
请求载荷
扩展收到一个 HTTP POST 请求,载荷结构如下:
class Request { repo: Repo; pipeline: Pipeline; netrc?: Netrc; // 仅当启用了 netrc 发送时才包含(见下方说明) configuration?: { // 配置文件列表,没有配置时不发送 name: string; // 配置文件名 data: string; // 配置文件内容 }[]; }ℹ️
netrc字段仅在全局WOODPECKER_CONFIG_EXTENSION_NETRC=true(默认false)或仓库级勾选 "Send netrc credentials" 时才会包含在请求中。
Repo、Pipeline、Netrc的结构可参考 server/model/repo.go、server/model/pipeline.go、server/model/netrc.go。其中netrc数据非常强大,包含访问仓库的凭证,你可以用它克隆仓库,甚至调用 forge(GitHub、GitLab 等)的 API 获取仓库的更多信息。
请求示例:
{ "repo": { "id": 100, "uid": "", "user_id": 0, "namespace": "", "name": "woodpecker-test-pipeline", "slug": "", "scm": "git", "git_http_url": "", "git_ssh_url": "", "link": "", "default_branch": "", "private": true, "visibility": "private", "active": true, "config": "", "trusted": false, "protected": false, "ignore_forks": false, "ignore_pulls": false, "cancel_pulls": false, "timeout": 60, "counter": 0, "synced": 0, "created": 0, "updated": 0, "version": 0 }, "pipeline": { "author": "myUser", "author_avatar": "https://myforge.com/avatars/d6b3f7787a685fcdf2a44e2c685c7e03", "author_email": "my@email.com", "branch": "main", "changed_files": ["some-filename.txt"], "commit": "2fff90f8d288a4640e90f05049fe30e61a14fd50", "created_at": 0, "deploy_to": "", "enqueued_at": 0, "error": "", "event": "push", "finished_at": 0, "id": 0, "link_url": "https://myforge.com/myUser/woodpecker-testpipe/commit/2fff90f8d288a4640e90f05049fe30e61a14fd50", "message": "test old config\n", "number": 0, "parent": 0, "ref": "refs/heads/main", "refspec": "", "clone_url": "", "reviewed_at": 0, "reviewed_by": "", "sender": "myUser", "signed": false, "started_at": 0, "status": "", "timestamp": 1645962783, "title": "", "updated_at": 0, "verified": false }, "configuration": [ { "name": ".woodpecker.yaml", "data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from Repo (.woodpecker.yaml)\"\n" } ], "netrc": { "machine": "myforge.com", "login": "myUser", "password": "forge-access-token" } }响应格式
扩展应返回一个 JSON 载荷,包含 Woodpecker 官方 YAML 格式的新配置文件;如果希望保留原有配置,可以直接返回 HTTP 状态码204 No Content。
class Response { configs: { name: string; // 配置文件名 data: string; // 配置文件内容 }[]; }响应示例:
{ "configs": [ { "name": "central-override", "data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from ConfigAPI\"\n" } ] }注册表扩展(Registry Extension):集中管理镜像仓库凭证
注册表扩展用于获取镜像仓库(Registry)的登录凭证,在仓库设置的 Extensions 页签中配置 HTTP 端点。适用场景:
- 集中管理注册表凭证;
- 使用外部存储保存凭证;
- 动态决定 Woodpecker 应该使用哪组凭证。
⚠️ 同样地,由于扩展会接收令牌等私密信息,且返回的配置会被执行,务必严格保护扩展,请求均带签名。
全局配置
WOODPECKER_REGISTRY_EXTENSION_ENDPOINT=https://example.com/ciconfig对应 CLI 标志为--registry-extension-endpoint与--registry-extension-netrc(见 cmd/server/flags.go)。仓库级字段为RegistryExtensionEndpoint、RegistryExtensionNetrc(见 server/model/repo.go)。
优先级规则:如果全局扩展与仓库级扩展同时为某个注册表返回凭证,Woodpecker 将使用仓库级扩展返回的凭证。从 server/services/manager.go 的RegistryServiceFromRepo可以看到,仓库级扩展通过registry.NewWithExtension组合:扩展凭证优先,本地配置作为 fallback。
工作流程
流水线触发时,Woodpecker 会向你的服务请求凭证;作为兜底,它会使用直接在 Woodpecker 中配置的凭证。
请求载荷
class Request { repo: Repo; pipeline: Pipeline; netrc?: Netrc; // 仅当启用了 netrc 发送时才包含(见下方说明) }netrc字段同样受WOODPECKER_REGISTRY_EXTENSION_NETRC(默认false)或仓库级 "Send netrc credentials" 控制。请求 JSON 示例与配置扩展相同(repo、pipeline、可选netrc三个字段),具体结构请以 server/model/repo.go、server/model/pipeline.go、server/model/netrc.go 中的最新定义为准。
响应格式
class Response { registries: { address: string; // Docker 注册表地址 username: string; // 注册表用户名 password: string; // 注册表密码 }[]; }响应示例:
{ "registries": [ { "address": "docker.io", "username": "woodpecker-bot", "password": "your-pass-word-123" } ] }密钥扩展(Secret Extension):从外部服务获取密钥
密钥扩展用于从外部服务获取密钥,同样在仓库设置的 Extensions 页签中配置 HTTP 端点。适用场景:
- 集中管理密钥(例如 HashiCorp Vault、AWS Secrets Manager);
- 按流水线动态生成密钥。
⚠️ 安全警告与前面两类扩展一致:扩展接收令牌等私密信息,必须严格保护,所有请求均带签名。
全局配置
WOODPECKER_SECRET_EXTENSION_ENDPOINT=https://example.com/secrets WOODPECKER_SECRET_EXTENSION_NETRC=false对应 CLI 标志为--secret-extension-endpoint与--secret-extension-netrc(见 cmd/server/flags.go)。仓库级字段为SecretExtensionEndpoint、SecretExtensionNetrc(见 server/model/repo.go)。
优先级规则:如果全局扩展与仓库级扩展返回了同名的密钥,将使用仓库级扩展的密钥。从 server/services/manager.go 的SecretServiceFromRepo可以看到,仓库级扩展通过secret.NewCombined与本地密钥服务组合:扩展密钥按名称优先,本地配置的密钥作为兜底。
工作流程
流水线触发时,Woodpecker 会从你的服务获取密钥,并将其与 Woodpecker 中直接配置的密钥合并(扩展密钥按名称优先)。如果扩展不可用,则回退到本地配置的密钥。
请求载荷
class Request { repo: Repo; pipeline: Pipeline; netrc?: Netrc; // 仅当启用了 netrc 发送时才包含(见下方说明) }netrc字段受WOODPECKER_SECRET_EXTENSION_NETRC(默认false)或仓库级 "Send netrc credentials" 控制。请求 JSON 示例与注册表扩展相同,其中未启用 netrc 时netrc字段会被省略。
响应格式
class Response { secrets: { name: string; // 密钥名称,与流水线配置中的 from_secret 匹配 value: string; // 密钥值 images?: string[]; // 可选:限制仅用于指定插件 events?: string[]; // 可选:限制仅用于指定流水线事件 }[]; }如果扩展不想新增任何密钥、希望保留现有密钥,可以直接返回 HTTP 状态码204 No Content。
响应示例:
{ "secrets": [ { "name": "docker_password", "value": "your-secret-password-123" }, { "name": "deploy_token", "value": "super-secret-token", "events": ["push", "tag"] } ] }第三方扩展
🚨 危险提示:以下第三方扩展未经 Woodpecker CI 开发或验证,使用前务必确认你信任它们。官方文档在 55-secret-extension.md 末尾为第三方扩展保留了清单位置,社区可在确认安全后自行补充。
扩展的网络访问控制:WOODPECKER_EXTENSIONS_ALLOWED_HOSTS
出于安全考虑,默认情况下扩展只能访问外部主机/IP 地址,以防止扩展被用来调用本地服务(例如元数据服务、内网端口扫描)。可以通过环境变量WOODPECKER_EXTENSIONS_ALLOWED_HOSTS改变这一行为,值为逗号分隔的列表,支持以下三种形式:
- 内置网络(Built-in networks):
loopback:IPv4 的 127.0.0.0/8 与 IPv6 的 ::1/128,包含 localhost;private:RFC 1918(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)与 RFC 4193(FC00::/7),即 LAN/内网;external:合法的非私有单播 IP,可访问公网上的所有主机;*:允许所有主机。
- CIDR 列表:IPv4 如
1.2.3.0/8,IPv6 如2001:db8::/32。 - (通配)主机名:
example.com、*.example.com、192.168.100.*。
默认值与底层实现
从源码看,当该变量为空时,默认值被设置为external(见 server/services/utils/http.go 中的getHTTPClient),并通过hostmatcher.NewDialContext将主机匹配器注入 HTTP 传输层,从网络连接层面拦截不合规的目标。
匹配规则实现在 server/services/utils/hostmatcher/hostmatcher.go:
- 每个列表项依次尝试按 CIDR(
net.ParseCIDR)、内置网络名、通配主机名模式解析; - 内置网络的判定:
external要求ip.IsGlobalUnicast() && !ip.IsPrivate(),private要求ip.IsPrivate(),loopback要求ip.IsLoopback(); - 通配主机名使用
filepath.Match进行匹配,因此*、192.168.100.*、*.example.com这类模式均有效; - 具体匹配入口为
MatchHostName/MatchIPAddr/MatchHostOrIP。
对应 CLI 标志为--extensions-allowed-hosts(见 cmd/server/flags.go)。
实战建议与小结
综合官方文档与仓库源码,接入扩展的正确姿势可以归纳为以下几点:
- 信任先行:扩展会收到令牌、netrc 凭证等敏感信息,且配置扩展的返回值会被直接执行,务必只使用可信服务,并留意仓库管理员可修改扩展端点这一权限边界;
- 验证签名:扩展端用
httpsign等库校验每个请求的 HTTP 签名,公钥通过/api/signature/public-key接口或 UI 的 Extensions 页获取;签名实现细节可参考 server/services/utils/http.go; - 按需收敛网络:默认仅允许访问
external公网主机;若扩展需要访问内网 Vault、自建服务,再按loopback/private/ CIDR / 通配主机名精确放开,而不是直接使用*; - 理解优先级与独占语义:仓库级扩展优先于全局扩展(注册表、密钥按名称覆盖);配置扩展的
exclusive设置可完全跳过 forge 与本地配置服务,实现"配置完全由扩展驱动"; - 协议对齐:三类扩展均以 HTTP POST + JSON 交互,注意区分各自的响应结构(
configs/registries/secrets),"保持不变"的场景统一返回204 No Content。
一个同时提供配置扩展与密钥扩展端点的简易参考实现,可参见 Woodpecker CI 官方维护的 example-extensions 示例项目。在将扩展投入生产前,建议先在测试仓库中验证签名校验、网络白名单与优先级行为,再逐步推广到全部仓库。
- CI/CD
- DevOps
【免费下载链接】woodpecker
Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.
相关推荐
Woodpecker 扩展机制完全指南:用 HTTP 端点替换内部逻辑(配置、注册表与密钥扩展)
Woodpecker 扩展机制完全指南:用 HTTP 端点替换内部逻辑(配置、注册表与密钥扩展) Woodpecker 允许通过预定义的 HTTP 端点将内部逻
CI/CDDevOpsMCP Everything Server 扩展指南:Tools、Prompts、Resources 三类扩展点的注册机制与实战方法
MCP Everything Server 扩展指南:Tools、Prompts、Resources 三类扩展点的注册机制与实战方法 Everything Se
MCP 服务AI 应用后端G6 扩展机制完全指南:注册、使用与获取自定义扩展
G6 扩展机制完全指南:注册、使用与获取自定义扩展 本文基于 G6 官方文档 extension.zh.md https://link.gitcode.com/
数据可视化前端图表库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考