-
-
Notifications
You must be signed in to change notification settings - Fork 586
Allow to configure a config extension per repo #3349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ab9f474
f4c234f
180d767
c4e8f2d
754a823
0a479bb
ef13725
ce7f026
d0c8986
a63433f
960bfac
38d84e3
bdb6887
5709122
49396a9
c1e1e4d
c6257f3
8bed39b
c603968
1399009
889cc96
554a2fc
4bf532b
dbd9d83
e497563
84266ce
12b8c54
2539b6d
2015d02
bde2df0
f37c392
f43ff61
e384b7f
9244c67
381bcfb
8af6ed5
0a600d9
0a0a89a
bf73ef2
15c0951
b7fcc08
4fc4608
072e1c2
17973b0
5ae18de
6b9bc26
22ef60a
98da4b4
c39f456
d2b206c
79cbe97
826ea1f
93a8392
d17793c
399d877
9f61ad1
d760e18
322b94f
9afdde4
4a4821c
46f4c58
5fc3096
171d554
688f68f
59b84a4
94980a0
f784333
996ac94
4c7b9a4
f40542e
d5c2f5f
83df7fd
1174441
00b31f6
71a580f
5c2a2fb
bd4e2b3
d92641f
64e0f39
9096538
1a711b2
e02ae83
a7afb53
d356e96
963e953
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,6 +87,7 @@ | |
| "Hetzner", | ||
| "HETZNERCLOUD", | ||
| "homelab", | ||
| "hostmatcher", | ||
| "HTMLURL", | ||
| "HTTPFS", | ||
| "httpsign", | ||
|
|
||
| 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. | ||
|
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" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
| 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' |
|
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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the benefit? Disabling it completely?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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.*` | ||
Uh oh!
There was an error while loading. Please reload this page.