-
-
Notifications
You must be signed in to change notification settings - Fork 374
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
Allow to configure config extensions for repos #3349
base: main
Are you sure you want to change the base?
Changes from 51 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
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 |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Secrets extensions (alpha state) | ||
|
||
:::warning | ||
The secret extension is still very expirimental and might change at any time. If you have any feedback or ideas please let us know. | ||
::: | ||
|
||
Woodpecker allows you to replace the internal secrets service with an external HTTP extension. This allows you to store secrets in a different system like Hashicorp Vault or AWS Secrets Manager. | ||
|
||
## 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](./10-extensions.md#security). | ||
::: | ||
|
||
## How it works | ||
|
||
For example if you open the secrets list in the repository settings, the following will happen: | ||
|
||
- Woodpecker will send a HTTP GET request to the `<this-server-url>/repo/<repo-id>/secrets` endpoint of your extension | ||
- The extension will first verify the request is coming from the actual Woodpecker instance and wasn't modified in transit | ||
using a http signature. This is pretty important so no one can just send a request to this server and get all your | ||
secrets for example. | ||
- The extension server will then return a list of secrets that are available for this repository. | ||
- The secrets will be displayed in the UI. | ||
|
||
### Endpoints | ||
|
||
The secrets extension needs the following endpoints to work. | ||
All data is sent as JSON in the request body and the response should also be JSON. | ||
A successful request should always return a 2xx status code. | ||
|
||
The secret json object should look like this: | ||
|
||
```ts | ||
class Secret { | ||
id: number; | ||
org_id: number; // has to be 0 for repository secrets | ||
repo_id: number; | ||
name: string; | ||
value: string; | ||
images: string[]; // a list of docker images this secret can be used with | ||
events: string[]; // a list of pipeline events this secret can be used with | ||
} | ||
``` | ||
|
||
Check out the [secret model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/secret.go) for more information. | ||
|
||
#### `GET /repo/:repoId/secrets` | ||
|
||
Should return an array of secrets for the repository: | ||
|
||
#### `GET /repo/:repoId/secrets/:secretName` | ||
|
||
Should return a single secret for the repository. | ||
|
||
#### `POST /repo/:repoId/secrets` | ||
|
||
Create a new secret for the repository. | ||
|
||
#### `PUT /repo/:repoId/secrets/:secretName` | ||
|
||
Update an existing secret for the repository. | ||
|
||
#### `DELETE /repo/:repoId/secrets/:secretName` | ||
|
||
Delete a secret from the repository. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,163 @@ | ||||||
# Configuration extension | ||||||
|
||||||
The configuration extension can be used to modify or generate Woodpeckers pipeline configurations. You can configure a HTTP | ||||||
endpoint in the repository settings in the extensions tab. | ||||||
|
||||||
Using such an extension can be useful if you want to: | ||||||
|
||||||
- Preprocess the original configuration file with something like go templating | ||||||
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.
Suggested change
|
||||||
- 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](./10-extensions.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. | ||||||
|
||||||
```ini title="Server" | ||||||
WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig | ||||||
``` | ||||||
|
||||||
## How it works | ||||||
|
||||||
When a pipline 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 offical yaml format that should be used. | ||||||
|
||||||
:::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. | ||||||
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. Duplicate? |
||||||
::: | ||||||
|
||||||
### 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. | ||||||
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.
Suggested change
|
||||||
::: | ||||||
|
||||||
Example request: | ||||||
|
||||||
```json | ||||||
{ | ||||||
"repo": { | ||||||
"id": 100, | ||||||
"uid": "", | ||||||
"user_id": 0, | ||||||
"namespace": "", | ||||||
"name": "woodpecker-testpipe", | ||||||
"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": ["somefilename.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 Woodpeckers official yaml format. | ||||||
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.
Suggested change
|
||||||
If the extension wants to keep the existing configuration files, it can respond with **HTTP 204**. | ||||||
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.
Suggested change
|
||||||
|
||||||
```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,33 @@ | ||||||
# Extensions | ||||||
|
||||||
Woodpecker allows you to replace internal logic with external extensions by using pre-defined http endpoints. | ||||||
|
||||||
There are currently two types of extensions available: | ||||||
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 is the second one? Is it possible to configure it globally like config extension? |
||||||
|
||||||
- [Configuration extension](./40-configuration-extension.md) to modify or generate Woodpeckers pipeline configurations. | ||||||
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.
Suggested change
|
||||||
- [Secrets extension (alpha state)](./20-secrets-extension.md) to receive and update secrets from an external system like Hashicorp Vault or AWS Secrets Manager. | ||||||
|
||||||
## 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 attaks, 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 [httpsig](https://github.com/go-fed/httpsig). 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 serivce 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_ALLOWED_EXTENSIONS_HOSTS` environment variable. You can use a comma separated list of: | ||||||
anbraten marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
- Built-in networks: | ||||||
- `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: `mydomain.com`, `*.mydomain.com`, `192.168.100.*` |
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: 'extensions' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.