Only inject netrc to trusted clone plugins (#1352)

Co-authored-by: qwerty287 <ndev@web.de>
Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
6543 2023-03-20 21:17:49 +01:00 committed by GitHub
parent 7870c29f5f
commit e28b43ab19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 68 additions and 8 deletions

View file

@ -48,6 +48,7 @@ import (
"github.com/woodpecker-ci/woodpecker/server/router/middleware"
"github.com/woodpecker-ci/woodpecker/server/store"
"github.com/woodpecker-ci/woodpecker/server/web"
"github.com/woodpecker-ci/woodpecker/shared/constant"
"github.com/woodpecker-ci/woodpecker/version"
// "github.com/woodpecker-ci/woodpecker/server/plugins/encryption"
// encryptedStore "github.com/woodpecker-ci/woodpecker/server/plugins/encryption/wrapper/store"
@ -313,6 +314,7 @@ func setupEvilGlobals(c *cli.Context, v store.Store, f forge.Forge) {
// Cloning
server.Config.Pipeline.DefaultCloneImage = c.String("default-clone-image")
constant.TrustedCloneImages = append(constant.TrustedCloneImages, server.Config.Pipeline.DefaultCloneImage)
// Execution
_events := c.StringSlice("default-cancel-previous-pipeline-events")

View file

@ -81,6 +81,8 @@ type Compiler struct {
cacher Cacher
reslimit ResourceLimit
defaultCloneImage string
trustedPipeline bool
netrcOnlyTrusted bool
}
// New creates a new Compiler with options.
@ -146,12 +148,13 @@ func (c *Compiler) Compile(conf *yaml.Config) (*backend.Config, error) {
c.path = conf.Workspace.Path
}
cloneImage := constant.DefaultCloneImage
if len(c.defaultCloneImage) > 0 {
cloneImage = c.defaultCloneImage
}
// add default clone step
if !c.local && len(conf.Clone.Containers) == 0 && !conf.SkipClone {
cloneImage := constant.DefaultCloneImage
if len(c.defaultCloneImage) > 0 {
cloneImage = c.defaultCloneImage
}
cloneSettings := map[string]interface{}{"depth": "0"}
if c.metadata.Curr.Event == frontend.EventTag {
cloneSettings["tags"] = "true"
@ -185,9 +188,14 @@ func (c *Compiler) Compile(conf *yaml.Config) (*backend.Config, error) {
name := fmt.Sprintf("%s_clone_%d", c.prefix, i)
step := c.createProcess(name, container, defaultCloneName)
for k, v := range c.cloneEnv {
step.Environment[k] = v
// only inject netrc if it's a trusted repo or a trusted plugin
if !c.netrcOnlyTrusted || c.trustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage()) {
for k, v := range c.cloneEnv {
step.Environment[k] = v
}
}
stage.Steps = append(stage.Steps, step)
config.Stages = append(config.Stages, stage)

View file

@ -219,6 +219,20 @@ func WithDefaultCloneImage(cloneImage string) Option {
}
}
// WithTrusted configures the compiler with the trusted repo option
func WithTrusted(trusted bool) Option {
return func(compiler *Compiler) {
compiler.trustedPipeline = trusted
}
}
// WithNetrcOnlyTrusted configures the compiler with the netrcOnlyTrusted repo option
func WithNetrcOnlyTrusted(only bool) Option {
return func(compiler *Compiler) {
compiler.netrcOnlyTrusted = only
}
}
// TODO(bradrydzewski) consider an alternate approach to
// WithProxy where the proxy strings are passed directly
// to the function as named parameters.

View file

@ -3,10 +3,12 @@ package yaml
import (
"fmt"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml/constraint"
"github.com/woodpecker-ci/woodpecker/pipeline/frontend/yaml/types"
"github.com/woodpecker-ci/woodpecker/shared/constant"
)
type (
@ -114,3 +116,7 @@ func (c *Containers) UnmarshalYAML(value *yaml.Node) error {
func (c *Container) IsPlugin() bool {
return len(c.Commands) == 0
}
func (c *Container) IsTrustedCloneImage() bool {
return c.IsPlugin() && slices.Contains(constant.TrustedCloneImages, c.Image)
}

View file

@ -287,6 +287,8 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml.Config, environ map[
compiler.WithProxy(),
compiler.WithWorkspaceFromURL("/woodpecker", b.Repo.Link),
compiler.WithMetadata(metadata),
compiler.WithTrusted(b.Repo.IsTrusted),
compiler.WithNetrcOnlyTrusted(b.Repo.NetrcOnlyTrusted),
).Compile(parsed)
}

View file

@ -46,6 +46,7 @@ func PostRepo(c *gin.Context) {
repo.IsActive = true
repo.UserID = user.ID
repo.AllowPull = true
repo.NetrcOnlyTrusted = true
repo.CancelPreviousPipelineEvents = server.Config.Pipeline.DefaultCancelPreviousPipelineEvents
if repo.Visibility == "" {
@ -147,6 +148,9 @@ func PatchRepo(c *gin.Context) {
if in.CancelPreviousPipelineEvents != nil {
repo.CancelPreviousPipelineEvents = *in.CancelPreviousPipelineEvents
}
if in.NetrcOnlyTrusted != nil {
repo.NetrcOnlyTrusted = *in.NetrcOnlyTrusted
}
if in.Visibility != nil {
switch *in.Visibility {
case string(model.VisibilityInternal), string(model.VisibilityPrivate), string(model.VisibilityPublic):

View file

@ -48,6 +48,7 @@ type Repo struct {
Hash string `json:"-" xorm:"varchar(500) 'repo_hash'"`
Perm *Perm `json:"-" xorm:"-"`
CancelPreviousPipelineEvents []WebhookEvent `json:"cancel_previous_pipeline_events" xorm:"json 'cancel_previous_pipeline_events'"`
NetrcOnlyTrusted bool `json:"netrc_only_trusted" xorm:"NOT NULL DEFAULT true 'netrc_only_trusted'"`
}
// TableName return database table name for xorm
@ -108,6 +109,7 @@ type RepoPatch struct {
Visibility *string `json:"visibility,omitempty"`
AllowPull *bool `json:"allow_pr,omitempty"`
CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"`
NetrcOnlyTrusted *bool `json:"netrc_only_trusted"`
}
type ForgeRemoteID string

View file

@ -35,3 +35,7 @@ const (
// DefaultCloneImage can be changed by 'WOODPECKER_DEFAULT_CLONE_IMAGE' at runtime
DefaultCloneImage = "docker.io/woodpeckerci/plugin-git:2.0.3"
)
var TrustedCloneImages = []string{
DefaultCloneImage,
}

View file

@ -92,7 +92,11 @@
"protected": "Protected",
"desc": "Every pipeline needs to be approved before being executed."
},
"trusted": {
"netrc_only_trusted": {
"netrc_only_trusted": "Only inject netrc credentials into trusted containers",
"desc": "Only inject netrc credentials into trusted containers (recommended)."
},
"trusted": {
"trusted": "Trusted",
"desc": "Underlying pipeline containers get access to escalated capabilities like mounting volumes."
},

View file

@ -34,6 +34,11 @@
:label="$t('repo.settings.general.protected.protected')"
:description="$t('repo.settings.general.protected.desc')"
/>
<Checkbox
v-model="repoSettings.netrc_only_trusted"
:label="$t('repo.settings.general.netrc_only_trusted.netrc_only_trusted')"
:description="$t('repo.settings.general.netrc_only_trusted.desc')"
/>
<Checkbox
v-if="user?.admin"
v-model="repoSettings.trusted"
@ -130,6 +135,7 @@ export default defineComponent({
trusted: repo.value.trusted,
allow_pr: repo.value.allow_pr,
cancel_previous_pipeline_events: repo.value.cancel_previous_pipeline_events || [],
netrc_only_trusted: repo.value.netrc_only_trusted,
};
}

View file

@ -57,6 +57,7 @@ export type Repo = {
// Events that will cancel running pipelines before starting a new one
cancel_previous_pipeline_events: string[];
netrc_only_trusted: boolean;
};
export enum RepoVisibility {
@ -67,7 +68,14 @@ export enum RepoVisibility {
export type RepoSettings = Pick<
Repo,
'config_file' | 'timeout' | 'visibility' | 'trusted' | 'gated' | 'allow_pr' | 'cancel_previous_pipeline_events'
| 'config_file'
| 'timeout'
| 'visibility'
| 'trusted'
| 'gated'
| 'allow_pr'
| 'cancel_previous_pipeline_events'
| 'netrc_only_trusted'
>;
export type RepoPermissions = {