Skip to content

Commit

Permalink
Merge branch 'main' into local-backend/fix-windows-cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
6543 authored Oct 24, 2023
2 parents 771213a + f44aa8a commit 0f48c4c
Show file tree
Hide file tree
Showing 19 changed files with 113 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .woodpecker/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ steps:

codecov:
pull: true
image: woodpeckerci/plugin-codecov:2.1.0
image: woodpeckerci/plugin-codecov:2.1.2
settings:
files:
- agent-coverage.out
Expand Down
17 changes: 6 additions & 11 deletions cli/secret/secret_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,13 @@ var secretCreateCmd = &cli.Command{
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Name: "events",
Usage: "secret limited to these events",
},
&cli.StringSliceFlag{
Name: "image",
Name: "images",
Usage: "secret limited to these images",
},
&cli.BoolFlag{
Name: "plugins-only",
Usage: "secret limited to plugins",
},
),
}

Expand All @@ -67,11 +63,10 @@ func secretCreate(c *cli.Context) error {
}

secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
PluginsOnly: c.Bool("plugins-only"),
Events: c.StringSlice("event"),
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("images"),
Events: c.StringSlice("events"),
}
if len(secret.Events) == 0 {
secret.Events = defaultSecretEvents
Expand Down
17 changes: 6 additions & 11 deletions cli/secret/secret_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,13 @@ var secretUpdateCmd = &cli.Command{
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Name: "events",
Usage: "secret limited to these events",
},
&cli.StringSliceFlag{
Name: "image",
Name: "images",
Usage: "secret limited to these images",
},
&cli.BoolFlag{
Name: "plugins-only",
Usage: "secret limited to plugins",
},
),
}

Expand All @@ -67,11 +63,10 @@ func secretUpdate(c *cli.Context) error {
}

secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
PluginsOnly: c.Bool("plugins-only"),
Events: c.StringSlice("event"),
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("images"),
Events: c.StringSlice("events"),
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
Expand Down
7 changes: 2 additions & 5 deletions cmd/server/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4212,7 +4212,7 @@ const docTemplate = `{
"Secret": {
"type": "object",
"properties": {
"event": {
"events": {
"type": "array",
"items": {
"$ref": "#/definitions/WebhookEvent"
Expand All @@ -4221,7 +4221,7 @@ const docTemplate = `{
"id": {
"type": "integer"
},
"image": {
"images": {
"type": "array",
"items": {
"type": "string"
Expand All @@ -4230,9 +4230,6 @@ const docTemplate = `{
"name": {
"type": "string"
},
"plugins_only": {
"type": "boolean"
},
"value": {
"type": "string"
}
Expand Down
7 changes: 1 addition & 6 deletions docs/docs/20-usage/40-secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,7 @@ Please be careful when exposing secrets to pull requests. If your repository is

## Image filter

To prevent abusing your secrets with malicious pull requests, you can limit a secret to a list of images. They are not available to any other container. In addition, you can make the secret available only for plugins (steps without user-defined commands).

:::warning
If you enable the option "Only available for plugins", always set an image filter too. Otherwise, the secret can be accessed by a very simple self-developed plugin and is thus _not_ safe.
If you only set an image filter, you could still access the secret using the same image and by specifying a command that prints it.
:::
To prevent abusing your secrets from malicious usage, you can limit a secret to a list of images. If enabled they are not available to any other plugin (steps without user-defined commands). If you or an attacker defines explicit commands, the secrets will not be available to the container to prevent leaking them.

## CLI Examples

Expand Down
2 changes: 2 additions & 0 deletions docs/docs/91-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Some versions need some changes to the server configuration or the pipeline conf
- Dropped deprecated `pipeline:` keyword in favor of `steps:` in pipeline config
- Dropped deprecated `branches:` filter in favor of global [`when.branch`](./20-usage/20-workflow-syntax.md#branch-1) filter
- Deprecated `platform:` filter in favor of `labels:`, [read more](./20-usage/20-workflow-syntax.md#filter-by-platform)
- Secrets `event` property was renamed to `events` and `image` to `images` as both are lists. The new property `events` / `images` has to be used in the api and as cli argument. The old properties `event` and `image` were removed.
- The secrets `plugin_only` option was removed. Secrets with images are now always only available for plugins using listed by the `images` property. Existing secrets with a list of `images` will now only be available to the listed images if they are used as a plugin.
- Removed `build` alias for `pipeline` command in CLI
- Removed `ssh` backend. Use an agent directly on the SSH machine using the `local` backend.
- Removed `/hook` and `/stream` API paths in favor of `/api/(hook|stream)`. You may need to use the "Repair repository" button in the repo settings or "Repair all" in the admin settings to recreate the forge hook.
Expand Down
9 changes: 4 additions & 5 deletions pipeline/frontend/yaml/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,13 @@ type Registry struct {
}

type Secret struct {
Name string
Value string
Match []string
PluginOnly bool
Name string
Value string
AllowedPlugins []string
}

func (s *Secret) Available(container *yaml_types.Container) bool {
return (len(s.Match) == 0 || utils.MatchImage(container.Image, s.Match...)) && (!s.PluginOnly || container.IsPlugin())
return (len(s.AllowedPlugins) == 0 || utils.MatchImage(container.Image, s.AllowedPlugins...)) && (len(s.AllowedPlugins) == 0 || container.IsPlugin())
}

type secretMap map[string]Secret
Expand Down
17 changes: 6 additions & 11 deletions pipeline/frontend/yaml/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,28 @@ import (

func TestSecretAvailable(t *testing.T) {
secret := Secret{
Match: []string{"golang"},
PluginOnly: false,
AllowedPlugins: []string{},
}
assert.True(t, secret.Available(&yaml_types.Container{
Image: "golang",
Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"},
}))
assert.False(t, secret.Available(&yaml_types.Container{
Image: "not-golang",
Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"},
}))

// secret only available for "golang" plugin
secret = Secret{
Match: []string{"golang"},
PluginOnly: true,
AllowedPlugins: []string{"golang"},
}
assert.True(t, secret.Available(&yaml_types.Container{
Image: "golang",
Commands: yaml_base_types.StringOrSlice{},
}))
assert.False(t, secret.Available(&yaml_types.Container{
Image: "not-golang",
Commands: yaml_base_types.StringOrSlice{},
Image: "golang",
Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"},
}))
assert.False(t, secret.Available(&yaml_types.Container{
Image: "not-golang",
Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"},
Commands: yaml_base_types.StringOrSlice{},
}))
}

Expand Down
7 changes: 3 additions & 4 deletions pipeline/stepBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,9 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi
continue
}
secrets = append(secrets, compiler.Secret{
Name: sec.Name,
Value: sec.Value,
Match: sec.Images,
PluginOnly: sec.PluginsOnly,
Name: sec.Name,
Value: sec.Value,
AllowedPlugins: sec.Images,
})
}

Expand Down
10 changes: 4 additions & 6 deletions server/api/global_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,10 @@ func PostGlobalSecret(c *gin.Context) {
return
}
secret := &model.Secret{
Name: in.Name,
Value: in.Value,
Events: in.Events,
Images: in.Images,
PluginsOnly: in.PluginsOnly,
Name: in.Name,
Value: in.Value,
Events: in.Events,
Images: in.Images,
}
if err := secret.Validate(); err != nil {
c.String(http.StatusBadRequest, "Error inserting global secret. %s", err)
Expand Down Expand Up @@ -135,7 +134,6 @@ func PatchGlobalSecret(c *gin.Context) {
if in.Images != nil {
secret.Images = in.Images
}
secret.PluginsOnly = in.PluginsOnly

if err := secret.Validate(); err != nil {
c.String(http.StatusBadRequest, "Error updating global secret. %s", err)
Expand Down
12 changes: 5 additions & 7 deletions server/api/org_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,11 @@ func PostOrgSecret(c *gin.Context) {
return
}
secret := &model.Secret{
OrgID: orgID,
Name: in.Name,
Value: in.Value,
Events: in.Events,
Images: in.Images,
PluginsOnly: in.PluginsOnly,
OrgID: orgID,
Name: in.Name,
Value: in.Value,
Events: in.Events,
Images: in.Images,
}
if err := secret.Validate(); err != nil {
c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", orgID, err)
Expand Down Expand Up @@ -165,7 +164,6 @@ func PatchOrgSecret(c *gin.Context) {
if in.Images != nil {
secret.Images = in.Images
}
secret.PluginsOnly = in.PluginsOnly

if err := secret.Validate(); err != nil {
c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", orgID, err)
Expand Down
12 changes: 5 additions & 7 deletions server/api/repo_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,11 @@ func PostSecret(c *gin.Context) {
return
}
secret := &model.Secret{
RepoID: repo.ID,
Name: strings.ToLower(in.Name),
Value: in.Value,
Events: in.Events,
Images: in.Images,
PluginsOnly: in.PluginsOnly,
RepoID: repo.ID,
Name: strings.ToLower(in.Name),
Value: in.Value,
Events: in.Events,
Images: in.Images,
}
if err := secret.Validate(); err != nil {
c.String(http.StatusUnprocessableEntity, "Error inserting secret. %s", err)
Expand Down Expand Up @@ -123,7 +122,6 @@ func PatchSecret(c *gin.Context) {
if in.Images != nil {
secret.Images = in.Images
}
secret.PluginsOnly = in.PluginsOnly

if err := secret.Validate(); err != nil {
c.String(http.StatusUnprocessableEntity, "Error updating secret. %s", err)
Expand Down
30 changes: 13 additions & 17 deletions server/model/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,13 @@ type SecretStore interface {

// Secret represents a secret variable, such as a password or token.
type Secret struct {
ID int64 `json:"id" xorm:"pk autoincr 'secret_id'"`
OrgID int64 `json:"-" xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_org_id'"`
RepoID int64 `json:"-" xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_repo_id'"`
Name string `json:"name" xorm:"NOT NULL UNIQUE(s) INDEX 'secret_name'"`
Value string `json:"value,omitempty" xorm:"TEXT 'secret_value'"`
Images []string `json:"image" xorm:"json 'secret_images'"`
PluginsOnly bool `json:"plugins_only" xorm:"secret_plugins_only"`
Events []WebhookEvent `json:"event" xorm:"json 'secret_events'"`
SkipVerify bool `json:"-" xorm:"secret_skip_verify"`
Conceal bool `json:"-" xorm:"secret_conceal"`
ID int64 `json:"id" xorm:"pk autoincr 'secret_id'"`
OrgID int64 `json:"-" xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_org_id'"`
RepoID int64 `json:"-" xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_repo_id'"`
Name string `json:"name" xorm:"NOT NULL UNIQUE(s) INDEX 'secret_name'"`
Value string `json:"value,omitempty" xorm:"TEXT 'secret_value'"`
Images []string `json:"images" xorm:"json 'secret_images'"`
Events []WebhookEvent `json:"events" xorm:"json 'secret_events'"`
} // @name Secret

// TableName return database table name for xorm
Expand Down Expand Up @@ -154,13 +151,12 @@ func (s *Secret) Validate() error {
// Copy makes a copy of the secret without the value.
func (s *Secret) Copy() *Secret {
return &Secret{
ID: s.ID,
OrgID: s.OrgID,
RepoID: s.RepoID,
Name: s.Name,
Images: s.Images,
PluginsOnly: s.PluginsOnly,
Events: sortEvents(s.Events),
ID: s.ID,
OrgID: s.OrgID,
RepoID: s.RepoID,
Name: s.Name,
Images: s.Images,
Events: sortEvents(s.Events),
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package migration

import (
"xorm.io/xorm"
)

type oldSecret025 struct {
ID int64 `json:"id" xorm:"pk autoincr 'secret_id'"`
PluginsOnly bool `json:"plugins_only" xorm:"secret_plugins_only"`
SkipVerify bool `json:"-" xorm:"secret_skip_verify"`
Conceal bool `json:"-" xorm:"secret_conceal"`
Images []string `json:"images" xorm:"json 'secret_images'"`
}

func (oldSecret025) TableName() string {
return "secrets"
}

var removePluginOnlyOptionFromSecretsTable = task{
name: "remove-plugin-only-option-from-secrets-table",
fn: func(sess *xorm.Session) (err error) {
// make sure plugin_only column exists
if err := sess.Sync(new(oldSecret025)); err != nil {
return err
}

return dropTableColumns(sess, "secrets", "secret_plugins_only", "secret_skip_verify", "secret_conceal")
},
}
1 change: 1 addition & 0 deletions server/store/datastore/migration/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var migrationTasks = []*task{
&addOrgID,
&alterTableTasksUpdateColumnTaskDataType,
&alterTableConfigUpdateColumnConfigDataType,
&removePluginOnlyOptionFromSecretsTable,
}

var allBeans = []interface{}{
Expand Down
Loading

0 comments on commit 0f48c4c

Please sign in to comment.