mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-07-05 03:26:26 +00:00
Allow to set custom trusted clone plugins (#4352)
Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Thomas Anderson <127358482+zc-devs@users.noreply.github.com> Co-authored-by: Anbraten <6918444+anbraten@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
bf1750a291
commit
5bb7cef08b
@ -5070,8 +5070,11 @@ const docTemplate = `{
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"netrc_only_trusted": {
|
"netrc_trusted": {
|
||||||
"type": "boolean"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"org_id": {
|
"org_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -5124,8 +5127,11 @@ const docTemplate = `{
|
|||||||
"description": "TODO: deprecated in favor of RequireApproval =\u003e Remove in next major release",
|
"description": "TODO: deprecated in favor of RequireApproval =\u003e Remove in next major release",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"netrc_only_trusted": {
|
"netrc_trusted": {
|
||||||
"type": "boolean"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"require_approval": {
|
"require_approval": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -39,16 +39,13 @@ Only server admins can set this option. If you are not a server admin this optio
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Only inject Git credentials into trusted clone plugins
|
## Custom trusted clone plugins
|
||||||
|
|
||||||
The clone step may require Git credentials (e.g. for private repos) which are injected via `netrc`.
|
The clone step may require Git credentials (e.g. for private repos) which are injected via `netrc`.
|
||||||
|
|
||||||
By default, they are only injected into trusted clone plugins listed in the env var `WOODPECKER_PLUGINS_TRUSTED_CLONE`.
|
They are only injected into trusted plugins listed in the env var `WOODPECKER_PLUGINS_TRUSTED_CLONE` or in this repo setting.
|
||||||
If this option is disabled, the Git credentials are injected into every clone plugin, regardless of whether it is trusted or not.
|
|
||||||
|
|
||||||
:::note
|
This allows you to use a trusted plugin for in the clone section or as a step to pull or push using your git credentials.
|
||||||
This option has no effect on steps other than the clone step.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Project visibility
|
## Project visibility
|
||||||
|
|
||||||
|
@ -98,7 +98,6 @@ type Compiler struct {
|
|||||||
defaultClonePlugin string
|
defaultClonePlugin string
|
||||||
trustedClonePlugins []string
|
trustedClonePlugins []string
|
||||||
securityTrustedPipeline bool
|
securityTrustedPipeline bool
|
||||||
netrcOnlyTrusted bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Compiler with options.
|
// New creates a new Compiler with options.
|
||||||
@ -196,7 +195,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only inject netrc if it's a trusted repo or a trusted plugin
|
// only inject netrc if it's a trusted repo or a trusted plugin
|
||||||
if !c.netrcOnlyTrusted || c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
||||||
for k, v := range c.cloneEnv {
|
for k, v := range c.cloneEnv {
|
||||||
step.Environment[k] = v
|
step.Environment[k] = v
|
||||||
}
|
}
|
||||||
@ -252,7 +251,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// inject netrc if it's a trusted repo or a trusted clone-plugin
|
// only inject netrc if it's a trusted repo or a trusted plugin
|
||||||
if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
||||||
for k, v := range c.cloneEnv {
|
for k, v := range c.cloneEnv {
|
||||||
step.Environment[k] = v
|
step.Environment[k] = v
|
||||||
|
@ -176,13 +176,6 @@ func WithTrustedSecurity(trusted bool) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithNetrcOnlyTrusted configures the compiler with the netrcOnlyTrusted repo option.
|
|
||||||
func WithNetrcOnlyTrusted(only bool) Option {
|
|
||||||
return func(compiler *Compiler) {
|
|
||||||
compiler.netrcOnlyTrusted = only
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyOptions struct {
|
type ProxyOptions struct {
|
||||||
NoProxy string
|
NoProxy string
|
||||||
HTTPProxy string
|
HTTPProxy string
|
||||||
|
@ -94,7 +94,6 @@ func PostRepo(c *gin.Context) {
|
|||||||
repo.RequireApproval = model.RequireApprovalForks
|
repo.RequireApproval = model.RequireApprovalForks
|
||||||
repo.AllowPull = true
|
repo.AllowPull = true
|
||||||
repo.AllowDeploy = false
|
repo.AllowDeploy = false
|
||||||
repo.NetrcOnlyTrusted = true
|
|
||||||
repo.CancelPreviousPipelineEvents = server.Config.Pipeline.DefaultCancelPreviousPipelineEvents
|
repo.CancelPreviousPipelineEvents = server.Config.Pipeline.DefaultCancelPreviousPipelineEvents
|
||||||
}
|
}
|
||||||
repo.IsActive = true
|
repo.IsActive = true
|
||||||
@ -275,8 +274,8 @@ func PatchRepo(c *gin.Context) {
|
|||||||
if in.CancelPreviousPipelineEvents != nil {
|
if in.CancelPreviousPipelineEvents != nil {
|
||||||
repo.CancelPreviousPipelineEvents = *in.CancelPreviousPipelineEvents
|
repo.CancelPreviousPipelineEvents = *in.CancelPreviousPipelineEvents
|
||||||
}
|
}
|
||||||
if in.NetrcOnlyTrusted != nil {
|
if in.NetrcTrusted != nil {
|
||||||
repo.NetrcOnlyTrusted = *in.NetrcOnlyTrusted
|
repo.NetrcTrustedPlugins = *in.NetrcTrusted
|
||||||
}
|
}
|
||||||
if in.Visibility != nil {
|
if in.Visibility != nil {
|
||||||
switch *in.Visibility {
|
switch *in.Visibility {
|
||||||
|
@ -71,7 +71,7 @@ type Repo struct {
|
|||||||
Hash string `json:"-" xorm:"varchar(500) 'hash'"`
|
Hash string `json:"-" xorm:"varchar(500) 'hash'"`
|
||||||
Perm *Perm `json:"-" xorm:"-"`
|
Perm *Perm `json:"-" xorm:"-"`
|
||||||
CancelPreviousPipelineEvents []WebhookEvent `json:"cancel_previous_pipeline_events" xorm:"json 'cancel_previous_pipeline_events'"`
|
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'"`
|
NetrcTrustedPlugins []string `json:"netrc_trusted" xorm:"json 'netrc_trusted'"`
|
||||||
} // @name Repo
|
} // @name Repo
|
||||||
|
|
||||||
// TableName return database table name for xorm.
|
// TableName return database table name for xorm.
|
||||||
@ -137,7 +137,7 @@ type RepoPatch struct {
|
|||||||
AllowPull *bool `json:"allow_pr,omitempty"`
|
AllowPull *bool `json:"allow_pr,omitempty"`
|
||||||
AllowDeploy *bool `json:"allow_deploy,omitempty"`
|
AllowDeploy *bool `json:"allow_deploy,omitempty"`
|
||||||
CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"`
|
CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"`
|
||||||
NetrcOnlyTrusted *bool `json:"netrc_only_trusted"`
|
NetrcTrusted *[]string `json:"netrc_trusted"`
|
||||||
Trusted *TrustedConfigurationPatch `json:"trusted"`
|
Trusted *TrustedConfigurationPatch `json:"trusted"`
|
||||||
} // @name RepoPatch
|
} // @name RepoPatch
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi
|
|||||||
b.Repo.IsSCMPrivate || server.Config.Pipeline.AuthenticatePublicRepos,
|
b.Repo.IsSCMPrivate || server.Config.Pipeline.AuthenticatePublicRepos,
|
||||||
),
|
),
|
||||||
compiler.WithDefaultClonePlugin(server.Config.Pipeline.DefaultClonePlugin),
|
compiler.WithDefaultClonePlugin(server.Config.Pipeline.DefaultClonePlugin),
|
||||||
compiler.WithTrustedClonePlugins(server.Config.Pipeline.TrustedClonePlugins),
|
compiler.WithTrustedClonePlugins(append(b.Repo.NetrcTrustedPlugins, server.Config.Pipeline.TrustedClonePlugins...)),
|
||||||
compiler.WithRegistry(registries...),
|
compiler.WithRegistry(registries...),
|
||||||
compiler.WithSecret(secrets...),
|
compiler.WithSecret(secrets...),
|
||||||
compiler.WithPrefix(
|
compiler.WithPrefix(
|
||||||
@ -304,7 +304,6 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi
|
|||||||
compiler.WithWorkspaceFromURL(compiler.DefaultWorkspaceBase, b.Repo.ForgeURL),
|
compiler.WithWorkspaceFromURL(compiler.DefaultWorkspaceBase, b.Repo.ForgeURL),
|
||||||
compiler.WithMetadata(metadata),
|
compiler.WithMetadata(metadata),
|
||||||
compiler.WithTrustedSecurity(b.Repo.Trusted.Security),
|
compiler.WithTrustedSecurity(b.Repo.Trusted.Security),
|
||||||
compiler.WithNetrcOnlyTrusted(b.Repo.NetrcOnlyTrusted),
|
|
||||||
).Compile(parsed)
|
).Compile(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2024 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 (
|
||||||
|
"src.techknowlogick.com/xormigrate"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var removeRepoNetrcOnlyTrusted = xormigrate.Migration{
|
||||||
|
ID: "remove-repo-netrc-only-trusted",
|
||||||
|
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||||
|
type repos struct {
|
||||||
|
NetrcOnlyTrusted string `xorm:"netrc_only_trusted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure columns to drop exist
|
||||||
|
if err := sess.Sync(new(repos)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dropTableColumns(sess, "repos", "netrc_only_trusted")
|
||||||
|
},
|
||||||
|
}
|
@ -48,6 +48,7 @@ var migrationTasks = []*xormigrate.Migration{
|
|||||||
&splitTrusted,
|
&splitTrusted,
|
||||||
&correctPotentialCorruptOrgsUsersRelation,
|
&correctPotentialCorruptOrgsUsersRelation,
|
||||||
&gatedToRequireApproval,
|
&gatedToRequireApproval,
|
||||||
|
&removeRepoNetrcOnlyTrusted,
|
||||||
}
|
}
|
||||||
|
|
||||||
var allBeans = []any{
|
var allBeans = []any{
|
||||||
|
@ -104,8 +104,8 @@
|
|||||||
"desc": "Permit 'deployment' runs for successful pipelines. All users with with push permissions can trigger these, so use with caution."
|
"desc": "Permit 'deployment' runs for successful pipelines. All users with with push permissions can trigger these, so use with caution."
|
||||||
},
|
},
|
||||||
"netrc_only_trusted": {
|
"netrc_only_trusted": {
|
||||||
"netrc_only_trusted": "Only inject git credentials into trusted clone plugins",
|
"netrc_only_trusted": "Custom trusted clone plugins",
|
||||||
"desc": "When enabled, git credentials are accessible only to trusted clone plugins specified in WOODPECKER_PLUGINS_TRUSTED_CLONE. Otherwise, custom clone plugins can use git credentials. This setting has no affect on non-clone steps."
|
"desc": "Plugins listed here will get access to netrc credentials that can be used to clone repositories from the forge or push to it."
|
||||||
},
|
},
|
||||||
"trusted": {
|
"trusted": {
|
||||||
"trusted": "Trusted",
|
"trusted": "Trusted",
|
||||||
|
@ -76,7 +76,7 @@ export interface Repo {
|
|||||||
// Events that will cancel running pipelines before starting a new one
|
// Events that will cancel running pipelines before starting a new one
|
||||||
cancel_previous_pipeline_events: string[];
|
cancel_previous_pipeline_events: string[];
|
||||||
|
|
||||||
netrc_only_trusted: boolean;
|
netrc_trusted: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
@ -104,7 +104,7 @@ export type RepoSettings = Pick<
|
|||||||
| 'allow_pr'
|
| 'allow_pr'
|
||||||
| 'allow_deploy'
|
| 'allow_deploy'
|
||||||
| 'cancel_previous_pipeline_events'
|
| 'cancel_previous_pipeline_events'
|
||||||
| 'netrc_only_trusted'
|
| 'netrc_trusted'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface RepoPermissions {
|
export interface RepoPermissions {
|
||||||
|
@ -15,11 +15,25 @@
|
|||||||
:label="$t('repo.settings.general.allow_deploy.allow')"
|
:label="$t('repo.settings.general.allow_deploy.allow')"
|
||||||
:description="$t('repo.settings.general.allow_deploy.desc')"
|
:description="$t('repo.settings.general.allow_deploy.desc')"
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
</InputField>
|
||||||
v-model="repoSettings.netrc_only_trusted"
|
|
||||||
|
<InputField
|
||||||
|
v-slot="{ id }"
|
||||||
:label="$t('repo.settings.general.netrc_only_trusted.netrc_only_trusted')"
|
:label="$t('repo.settings.general.netrc_only_trusted.netrc_only_trusted')"
|
||||||
:description="$t('repo.settings.general.netrc_only_trusted.desc')"
|
docs-url="docs/usage/project-settings#custom-trusted-clone-plugins"
|
||||||
/>
|
>
|
||||||
|
<span class="ml-1 mb-2 text-wp-text-alt-100">{{ $t('repo.settings.general.netrc_only_trusted.desc') }}</span>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div v-for="image in repoSettings.netrc_trusted" :key="image" class="flex gap-2">
|
||||||
|
<TextField :id="id" :model-value="image" disabled />
|
||||||
|
<Button type="button" color="gray" start-icon="trash" @click="removeImage(image)" />
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<TextField :id="id" v-model="newImage" @keydown.enter.prevent="addNewImage" />
|
||||||
|
<Button type="button" color="gray" start-icon="plus" @click="addNewImage" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<InputField
|
<InputField
|
||||||
@ -178,7 +192,7 @@ function loadRepoSettings() {
|
|||||||
allow_pr: repo.value.allow_pr,
|
allow_pr: repo.value.allow_pr,
|
||||||
allow_deploy: repo.value.allow_deploy,
|
allow_deploy: repo.value.allow_deploy,
|
||||||
cancel_previous_pipeline_events: repo.value.cancel_previous_pipeline_events || [],
|
cancel_previous_pipeline_events: repo.value.cancel_previous_pipeline_events || [],
|
||||||
netrc_only_trusted: repo.value.netrc_only_trusted,
|
netrc_trusted: repo.value.netrc_trusted || [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,4 +250,20 @@ const cancelPreviousPipelineEventsOptions: CheckboxOption[] = [
|
|||||||
},
|
},
|
||||||
{ value: WebhookEvents.Deploy, text: i18n.t('repo.pipeline.event.deploy') },
|
{ value: WebhookEvents.Deploy, text: i18n.t('repo.pipeline.event.deploy') },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const newImage = ref('');
|
||||||
|
function addNewImage() {
|
||||||
|
if (!newImage.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
repoSettings.value?.netrc_trusted.push(newImage.value);
|
||||||
|
newImage.value = '';
|
||||||
|
}
|
||||||
|
function removeImage(image: string) {
|
||||||
|
if (!repoSettings.value) {
|
||||||
|
throw new Error('Unexpected: repoSettings should be set');
|
||||||
|
}
|
||||||
|
|
||||||
|
repoSettings.value.netrc_trusted = repoSettings.value.netrc_trusted.filter((i) => i !== image);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user