Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
ab9f474
add repo plugins
anbraten Apr 5, 2022
f4c234f
use ed25519 for signing
anbraten May 14, 2022
180d767
revert unrelated
anbraten May 14, 2022
c4e8f2d
clean up code and fix tests
anbraten May 14, 2022
754a823
Merge remote-tracking branch 'upstream/master' into repo-plugins
anbraten May 14, 2022
0a479bb
clean up
anbraten May 14, 2022
ef13725
use async key pair for webhooks
anbraten May 14, 2022
ce7f026
fix tests
anbraten May 14, 2022
d0c8986
fix linter
anbraten May 14, 2022
a63433f
improve code
anbraten May 14, 2022
960bfac
add key pair to database
anbraten May 15, 2022
38d84e3
Merge remote-tracking branch 'upstream/master' into repo-plugins
anbraten May 17, 2022
bdb6887
Merge branch 'webhook-pubkey' into repo-plugins
anbraten May 17, 2022
5709122
add some extension docs
anbraten May 17, 2022
49396a9
Merge remote-tracking branch 'upstream/master' into repo-plugins
anbraten Jun 1, 2022
c1e1e4d
fix migration and undo some changes
anbraten Jun 1, 2022
c6257f3
Merge remote-tracking branch 'upstream/master' into repo-plugins
anbraten Jun 18, 2022
8bed39b
remove vendor folder
anbraten Jun 29, 2022
c603968
adjust names
anbraten Jun 29, 2022
1399009
fiy typo
anbraten Jun 29, 2022
889cc96
add extensions manager
anbraten Jul 2, 2022
554a2fc
Merge remote-tracking branch 'upstream/master' into repo-plugins
anbraten Jul 2, 2022
4bf532b
add extensions ui
anbraten Jul 2, 2022
dbd9d83
cleanup code
anbraten Jul 2, 2022
e497563
adjust spacings
anbraten Jul 2, 2022
84266ce
Merge branch 'master' into repo-plugins
anbraten Jul 2, 2022
12b8c54
fix colors
anbraten Jul 2, 2022
2539b6d
Merge branch 'repo-plugins' of github.com:anbraten/woodpecker into re…
anbraten Jul 2, 2022
2015d02
add docs links and fix types
anbraten Jul 2, 2022
bde2df0
Merge branch 'master' into repo-plugins
anbraten Jul 4, 2022
f37c392
fix tests
anbraten Jul 4, 2022
f43ff61
Merge remote-tracking branch 'upstream/main' into plugins
anbraten Feb 11, 2024
e384b7f
[pre-commit.ci] auto fixes from pre-commit.com hooks [CI SKIP]
pre-commit-ci[bot] Feb 11, 2024
9244c67
cleanup
anbraten Feb 11, 2024
381bcfb
Merge branch 'plugins' of github.com:anbraten/woodpecker into plugins
anbraten Feb 11, 2024
8af6ed5
use configured endpoints
anbraten Feb 11, 2024
0a600d9
improve
anbraten Feb 11, 2024
0a0a89a
Merge remote-tracking branch 'upstream/main' into plugins
anbraten Jun 5, 2024
bf73ef2
undo unrelated
anbraten Jun 5, 2024
15c0951
finish
anbraten Jun 5, 2024
b7fcc08
adjust urls and add debug loggin
anbraten Jun 5, 2024
4fc4608
Merge remote-tracking branch 'upstream/main' into plugins
anbraten Jun 5, 2024
072e1c2
update docs
anbraten Jun 5, 2024
17973b0
Merge branch 'main' into plugins
anbraten Jun 5, 2024
5ae18de
adjust docs
anbraten Jun 5, 2024
6b9bc26
Merge branch 'plugins' of github.com:anbraten/woodpecker into plugins
anbraten Jun 5, 2024
22ef60a
add endpoint check
anbraten Jun 5, 2024
98da4b4
prettier
anbraten Jun 5, 2024
c39f456
fix translations
anbraten Jun 5, 2024
d2b206c
update swagger
anbraten Jun 5, 2024
79cbe97
adjsut docs
anbraten Jun 5, 2024
826ea1f
Merge remote-tracking branch 'upstream/main' into plugins
anbraten Sep 30, 2024
93a8392
fix
anbraten Sep 30, 2024
d17793c
undo secret
anbraten Sep 30, 2024
399d877
review
anbraten Sep 30, 2024
9f61ad1
Update web/src/assets/locales/en.json
anbraten Sep 30, 2024
d760e18
fix
anbraten Sep 30, 2024
322b94f
Merge branch 'plugins' of github.com:anbraten/woodpecker into plugins
anbraten Sep 30, 2024
9afdde4
use const
anbraten Sep 30, 2024
4a4821c
fix
anbraten Sep 30, 2024
46f4c58
fix
anbraten Sep 30, 2024
5fc3096
Merge branch 'main' into plugins
anbraten Oct 9, 2024
171d554
Merge remote-tracking branch 'upstream/main' into plugins
anbraten Aug 21, 2025
688f68f
Merge branch 'plugins' of github.com:anbraten/woodpecker into plugins
anbraten Aug 21, 2025
59b84a4
cleanup
anbraten Aug 21, 2025
94980a0
update openapi
anbraten Aug 21, 2025
f784333
adjust docs
anbraten Aug 21, 2025
996ac94
adjust docs
anbraten Aug 21, 2025
4c7b9a4
adjust docs
anbraten Aug 21, 2025
f40542e
fix import
anbraten Aug 21, 2025
d5c2f5f
drop blocklist
anbraten Aug 21, 2025
83df7fd
move
anbraten Aug 21, 2025
1174441
cleanup
anbraten Aug 21, 2025
00b31f6
fix
anbraten Aug 21, 2025
71a580f
Merge remote-tracking branch 'upstream/main' into plugins
anbraten Aug 22, 2025
5c2a2fb
cleanup
anbraten Aug 22, 2025
bd4e2b3
fix spelling and docs
anbraten Aug 22, 2025
d92641f
fix http timeout
anbraten Aug 22, 2025
64e0f39
Merge branch 'main' into plugins
anbraten Aug 22, 2025
9096538
fix pre commit
anbraten Aug 22, 2025
1a711b2
Merge branch 'plugins' of github.com:anbraten/woodpecker into plugins
anbraten Aug 22, 2025
e02ae83
Update server/services/utils/http.go
anbraten Sep 22, 2025
a7afb53
Merge branch 'main' into plugins
anbraten Sep 22, 2025
d356e96
Update docs/docs/20-usage/72-extensions/_category_.yaml
anbraten Sep 22, 2025
963e953
Merge remote-tracking branch 'upstream/main' into plugins
anbraten Sep 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"Hetzner",
"HETZNERCLOUD",
"homelab",
"hostmatcher",
"HTMLURL",
"HTTPFS",
"httpsign",
Expand Down
7 changes: 7 additions & 0 deletions cmd/server/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/urfave/cli/v3"

host_matcher "go.woodpecker-ci.org/woodpecker/v3/server/services/utils/hostmatcher"
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
"go.woodpecker-ci.org/woodpecker/v3/shared/logger"
)
Expand Down Expand Up @@ -277,6 +278,12 @@ var flags = append([]cli.Flag{
Name: "config-service-endpoint",
Usage: "url used for calling configuration service endpoint",
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_EXTENSIONS_ALLOWED_HOSTS"),
Name: "extensions-allowed-hosts",
Usage: "Hosts that are allowed to be contacted by extensions",
Value: host_matcher.MatchBuiltinExternal,
Comment thread
6543 marked this conversation as resolved.
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_DATABASE_DRIVER"),
Name: "db-driver",
Expand Down
9 changes: 9 additions & 0 deletions cmd/server/openapi/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5124,6 +5124,9 @@ const docTemplate = `{
"clone_url_ssh": {
"type": "string"
},
"config_extension_endpoint": {
"type": "string"
},
"config_file": {
"type": "string"
},
Expand Down Expand Up @@ -5214,6 +5217,9 @@ const docTemplate = `{
"clone_url_ssh": {
"type": "string"
},
"config_extension_endpoint": {
"type": "string"
},
"config_file": {
"type": "string"
},
Expand Down Expand Up @@ -5295,6 +5301,9 @@ const docTemplate = `{
"$ref": "#/definitions/WebhookEvent"
}
},
"config_extension_endpoint": {
"type": "string"
},
"config_file": {
"type": "string"
},
Expand Down
160 changes: 160 additions & 0 deletions docs/docs/20-usage/72-extensions/40-configuration-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Configuration extension

The configuration extension can be used to modify or generate Woodpeckers pipeline configurations. You can configure an HTTP endpoint in the repository settings in the extensions tab.

Using such an extension can be useful if you want to:

<!-- cSpell:words templating,Starlark,Jsonnet -->

- Preprocess the original configuration file with something like Go templating
- Convert custom attributes to Woodpecker attributes
- Add defaults to the configuration like default steps
- Convert configuration files from a totally different format like Gitlab CI config, Starlark, Jsonnet, ...
- Centralize configuration for multiple repositories in one place

## Security

:::warning
As Woodpecker will pass private information like tokens and will execute the returned configuration, it is extremely important to secure the external extension. Therefore Woodpecker signs every request. Read more about it in the [security section](./index.md#security).
:::

## Global configuration

In addition to the ability to configure the extension per repository, you can also configure a global endpoint in the Woodpecker server configuration. This can be useful if you want to use the extension for all repositories. Be careful if
you share your Woodpecker server with others as they will also use your configuration extension.

The global configuration will be called before the repository specific configuration extension if both are configured.
Comment thread
anbraten marked this conversation as resolved.

```ini title="Server"
WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig
```

## How it works

When a pipeline is triggered Woodpecker will fetch the pipeline configuration from the repository, then make a HTTP POST request to the configured extension with a JSON payload containing some data like the repository, pipeline information and the current config files retrieved from the repository. The extension can then send back modified or even new pipeline configurations following Woodpeckers official yaml format that should be used.

### Request

The extension receives an HTTP POST request with the following JSON payload:

```ts
class Request {
repo: Repo;
pipeline: Pipeline;
netrc: Netrc;
configuration: {
name: string; // filename of the configuration file
data: string; // content of the configuration file
}[];
}
```

Checkout the following models for more information:

- [repo model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/repo.go)
- [pipeline model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/pipeline.go)
- [netrc model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/netrc.go)

:::tip
The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to clone the repository or even use the forge (Github or Gitlab, ...) API to get more information about the repository.
:::

Example request:

```json
{
"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
},
"configs": [
{
"name": ".woodpecker.yaml",
"data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from Repo (.woodpecker.yaml)\"\n"
}
]
}
```

### Response

The extension should respond with a JSON payload containing the new configuration files in Woodpecker's official YAML format.
If the extension wants to keep the existing configuration files, it can respond with HTTP status `204 No Content`.

```ts
class Response {
configs: {
name: string; // filename of the configuration file
data: string; // content of the configuration file
}[];
}
```

Example response:

```json
{
"configs": [
{
"name": "central-override",
"data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from ConfigAPI\"\n"
}
]
}
```
7 changes: 7 additions & 0 deletions docs/docs/20-usage/72-extensions/_category_.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
label: 'Extensions'
# position: 3
collapsible: true
collapsed: true
link:
type: 'doc'
id: 'index'
34 changes: 34 additions & 0 deletions docs/docs/20-usage/72-extensions/index.md
Comment thread
qwerty287 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Extensions

Woodpecker allows you to replace internal logic with external extensions by using pre-defined http endpoints.

There is currently one type of extension available:

- [Configuration extension](./40-configuration-extension.md) to modify or generate pipeline configurations on the fly.

## Security

:::warning
You need to trust the extensions as they are receiving private information like secrets and tokens and might return harmful
data like malicious pipeline configurations that could be executed.
:::

To prevent your extensions from such attacks, Woodpecker is signing all HTTP requests using [HTTP signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). Woodpecker therefore uses a public-private ed25519 key pair.
To verify the requests your extension has to verify the signature of all request using the public key with some library like [httpsign](https://github.com/yaronf/httpsign).
You can get the public Woodpecker key by opening `http://my-woodpecker.tld/api/signature/public-key` or by visiting the Woodpecker UI, going to you repo settings and opening the extensions page.

## Example extensions

A simplistic service providing endpoints for a config and secrets extension can be found here: [https://github.com/woodpecker-ci/example-extensions](https://github.com/woodpecker-ci/example-extensions)

## Configuration

To prevent extensions from calling local services by default only external hosts / ip-addresses are allowed. You can change this behavior by setting the `WOODPECKER_EXTENSIONS_ALLOWED_HOSTS` environment variable. You can use a comma separated list of:

- Built-in networks:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add none ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the benefit? Disabling it completely?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's really needed we could add it in an upcoming PR. As external would not be a security issue IMO and there seems no concrete risk of having the possibility, I would skip it for this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the same atack vector as custom agents ... one could create an repo on a public forge+ci and then infite the victim to somehow to create a pull to it. now netrc/token extraction is possible.

we should address this in the long run to generate tokens from forges that are onlv valid for the repo it is currently in use ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#3349 (comment)

u might be right ... let me check how oauth2 tokens are used exactly again

anyway having the feat. to derive scope limited tokens for dayli usage and only let wp-server handle the original token would be a good hardening exercize ... is there already an open issue for that?

- `loopback`: 127.0.0.0/8 for IPv4 and ::1/128 for IPv6, localhost is included.
- `private`: RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and RFC 4193 (FC00::/7). Also called LAN/Intranet.
- `external`: A valid non-private unicast IP, you can access all hosts on public internet.
- `*`: All hosts are allowed.
- CIDR list: `1.2.3.0/8` for IPv4 and `2001:db8::/32` for IPv6
- (Wildcard) hosts: `example.com`, `*.example.com`, `192.168.100.*`
9 changes: 9 additions & 0 deletions docs/docs/30-administration/10-configuration/10-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,15 @@ Specify a configuration service endpoint, see [Configuration Extension](#externa

---

### EXTENSIONS_ALLOWED_HOSTS

- Name: `WOODPECKER_EXTENSIONS_ALLOWED_HOSTS`
- Default: `external`

Comma-separated list of hosts that are allowed to be contacted by extensions. Possible values are `loopback`, `private`, `external`, `*` or CIDR list.

---

### FORGE_TIMEOUT

- Name: `WOODPECKER_FORGE_TIMEOUT`
Expand Down
8 changes: 8 additions & 0 deletions docs/src/pages/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ To enhance the usability of Woodpecker and meet evolving security standards, occ

## `next`

### User-facing migrations

- (Kubernetes) Deprecated `step` label on pod in favor of new namespaced label `woodpecker-ci.org/step`. The `step` label will be removed in a future update.
- deprecated `CI_COMMIT_AUTHOR_AVATAR` and `CI_PREV_COMMIT_AUTHOR_AVATAR` env vars in favor of `CI_PIPELINE_AVATAR` and `CI_PREV_PIPELINE_AVATAR`

### Admin-facing migrations

#### Extensions

Extension HTTP calls (as of now the configuration extension) will by default only be allowed to contact external hosts. Set `WOODPECKER_EXTENSIONS_ALLOWED_HOSTS` accordingly to allow additional hosts as needed.

## 3.0.0

### User-facing migrations
Expand Down
3 changes: 3 additions & 0 deletions server/api/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ func PatchRepo(c *gin.Context) {
return
}
}
if in.ConfigExtensionEndpoint != nil {
repo.ConfigExtensionEndpoint = *in.ConfigExtensionEndpoint
}

err := _store.UpdateRepo(repo)
if err != nil {
Expand Down
23 changes: 23 additions & 0 deletions server/model/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

package model

import (
"fmt"
"strings"
)

type ListOptions struct {
All bool
Page int
Expand All @@ -32,3 +37,21 @@ func ApplyPagination[T any](d *ListOptions, slice []T) []T {
}
return slice[d.PerPage*(d.Page-1) : d.PerPage*(d.Page)]
}

func (d *ListOptions) Encode() string {
var query []string

if d.Page != 0 {
query = append(query, fmt.Sprintf("page=%d", d.Page))
}

if d.PerPage != 0 {
query = append(query, fmt.Sprintf("per_page=%d", d.PerPage))
}

if d.All {
query = append(query, "all=true")
}

return strings.Join(query, "&")
}
2 changes: 2 additions & 0 deletions server/model/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type Repo struct {
Perm *Perm `json:"-" xorm:"-"`
CancelPreviousPipelineEvents []WebhookEvent `json:"cancel_previous_pipeline_events" xorm:"json 'cancel_previous_pipeline_events'"`
NetrcTrustedPlugins []string `json:"netrc_trusted" xorm:"json 'netrc_trusted'"`
ConfigExtensionEndpoint string `json:"config_extension_endpoint" xorm:"varchar(500) 'config_extension_endpoint'"`
} // @name Repo

// TableName return database table name for xorm.
Expand Down Expand Up @@ -138,6 +139,7 @@ type RepoPatch struct {
CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"`
NetrcTrusted *[]string `json:"netrc_trusted"`
Trusted *TrustedConfigurationPatch `json:"trusted"`
ConfigExtensionEndpoint *string `json:"config_extension_endpoint,omitempty"`
} // @name RepoPatch

type ForgeRemoteID string
Expand Down
9 changes: 8 additions & 1 deletion server/services/config/combined_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v3/server/model"
"go.woodpecker-ci.org/woodpecker/v3/server/services/config"
"go.woodpecker-ci.org/woodpecker/v3/server/services/utils"
)

func TestFetchFromConfigService(t *testing.T) {
Expand Down Expand Up @@ -185,7 +186,13 @@ func TestFetchFromConfigService(t *testing.T) {

ts := httptest.NewServer(http.HandlerFunc(fixtureHandler))
defer ts.Close()
httpFetcher := config.NewHTTP(ts.URL+"/", privEd25519Key)

client, err := utils.NewHTTPClient(privEd25519Key, "loopback")
if !assert.NoError(t, err) {
return
}

httpFetcher := config.NewHTTP(ts.URL+"/", client)

for _, tt := range testTable {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading