mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-03 06:08:42 +00:00
Split repo trusted setting (#4025)
This commit is contained in:
parent
383bfbb6de
commit
29474fc7d9
26 changed files with 373 additions and 193 deletions
|
@ -207,7 +207,11 @@ func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, ax
|
|||
|
||||
// lint the yaml file
|
||||
err = linter.New(
|
||||
linter.WithTrusted(true),
|
||||
linter.WithTrusted(linter.TrustedConfiguration{
|
||||
Security: c.Bool("repo-trusted-security"),
|
||||
Network: c.Bool("repo-trusted-network"),
|
||||
Volumes: c.Bool("repo-trusted-volumes"),
|
||||
}),
|
||||
linter.PrivilegedPlugins(privilegedPlugins),
|
||||
linter.WithTrustedClonePlugins(constant.TrustedClonePlugins),
|
||||
).Lint([]*linter.WorkflowConfig{{
|
||||
|
|
|
@ -185,9 +185,19 @@ var flags = []cli.Flag{
|
|||
Usage: "Set the metadata environment variable \"CI_REPO_PRIVATE\".",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Sources: cli.EnvVars("CI_REPO_TRUSTED"),
|
||||
Name: "repo-trusted",
|
||||
Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED\".",
|
||||
Sources: cli.EnvVars("CI_REPO_TRUSTED_NETWORK"),
|
||||
Name: "repo-trusted-network",
|
||||
Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_NETWORK\".",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Sources: cli.EnvVars("CI_REPO_TRUSTED_VOLUMES"),
|
||||
Name: "repo-trusted-volumes",
|
||||
Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_VOLUMES\".",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Sources: cli.EnvVars("CI_REPO_TRUSTED_SECURITY"),
|
||||
Name: "repo-trusted-security",
|
||||
Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_SECURITY\".",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("CI_PIPELINE_NUMBER"),
|
||||
|
|
|
@ -83,7 +83,9 @@ func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w
|
|||
metadataFileAndOverrideOrDefault(c, "repo-clone-url", func(s string) { m.Repo.CloneURL = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-clone-ssh-url", func(s string) { m.Repo.CloneSSHURL = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-private", func(b bool) { m.Repo.Private = b }, c.Bool)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-trusted", func(b bool) { m.Repo.Trusted = b }, c.Bool)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-trusted-network", func(b bool) { m.Repo.Trusted.Network = b }, c.Bool)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-trusted-security", func(b bool) { m.Repo.Trusted.Security = b }, c.Bool)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-trusted-volumes", func(b bool) { m.Repo.Trusted.Volumes = b }, c.Bool)
|
||||
|
||||
// Current Pipeline
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-number", func(i int64) { m.Curr.Number = i }, c.Int)
|
||||
|
|
|
@ -110,7 +110,11 @@ func lintFile(_ context.Context, c *cli.Command, file string) error {
|
|||
|
||||
// TODO: lint multiple files at once to allow checks for sth like "depends_on" to work
|
||||
err = linter.New(
|
||||
linter.WithTrusted(true),
|
||||
linter.WithTrusted(linter.TrustedConfiguration{
|
||||
Network: true,
|
||||
Volumes: true,
|
||||
Security: true,
|
||||
}),
|
||||
linter.PrivilegedPlugins(c.StringSlice("plugins-privileged")),
|
||||
linter.WithTrustedClonePlugins(c.StringSlice("plugins-trusted-clone")),
|
||||
).Lint([]*linter.WorkflowConfig{config})
|
||||
|
|
|
@ -5099,7 +5099,7 @@ const docTemplate = `{
|
|||
"type": "integer"
|
||||
},
|
||||
"trusted": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/model.TrustedConfiguration"
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/definitions/RepoVisibility"
|
||||
|
@ -5134,7 +5134,7 @@ const docTemplate = `{
|
|||
"type": "integer"
|
||||
},
|
||||
"trusted": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/model.TrustedConfigurationPatch"
|
||||
},
|
||||
"visibility": {
|
||||
"type": "string"
|
||||
|
@ -5555,7 +5555,7 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"trusted": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/metadata.TrustedConfiguration"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5590,6 +5590,20 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"metadata.TrustedConfiguration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"network": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"security": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"volumes": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Workflow": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -5628,6 +5642,34 @@ const docTemplate = `{
|
|||
"ForgeTypeAddon"
|
||||
]
|
||||
},
|
||||
"model.TrustedConfiguration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"network": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"security": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"volumes": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.TrustedConfigurationPatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"network": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"security": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"volumes": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.Workflow": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -62,7 +62,9 @@ This is the reference list of all environment variables available to your pipeli
|
|||
| `CI_REPO_CLONE_SSH_URL` | repository SSH clone URL | `git@git.example.com:john-doe/my-repo.git` |
|
||||
| `CI_REPO_DEFAULT_BRANCH` | repository default branch | `main` |
|
||||
| `CI_REPO_PRIVATE` | repository is private | `true` |
|
||||
| `CI_REPO_TRUSTED` | repository is trusted | `false` |
|
||||
| `CI_REPO_TRUSTED_NETWORK` | repository has trusted network access | `false` |
|
||||
| `CI_REPO_TRUSTED_VOLUMES` | repository has trusted volumes access | `false` |
|
||||
| `CI_REPO_TRUSTED_SECURITY` | repository has trusted security access | `false` |
|
||||
| | **Current Commit** | |
|
||||
| `CI_COMMIT_SHA` | commit SHA | `eba09b46064473a1d345da7abf28b477468e8dbd` |
|
||||
| `CI_COMMIT_REF` | commit ref | `refs/heads/main` |
|
||||
|
|
|
@ -32,6 +32,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
- Removed `WOODPECKER_WEBHOOK_HOST` in favor of `WOODPECKER_EXPERT_WEBHOOK_HOST`
|
||||
- Migrated to rfc9421 for webhook signatures
|
||||
- Renamed `start_time`, `end_time`, `created_at`, `started_at`, `finished_at` and `reviewed_at` JSON fields to `started`, `finished`, `created`, `started`, `finished`, `reviewed`
|
||||
- JSON field `trusted` on repo model was changed from boolean to object
|
||||
- Update all webhooks by pressing the "Repair all" button in the admin settings as the webhook token claims have changed
|
||||
- Crons now use standard Linux syntax without seconds
|
||||
- Replaced `configs` object by `netrc` in external configuration APIs
|
||||
|
|
|
@ -166,6 +166,9 @@ CI_REPO_PRIVATE=false
|
|||
CI_REPO_REMOTE_ID=4
|
||||
CI_REPO_SCM=git
|
||||
CI_REPO_TRUSTED=false
|
||||
CI_REPO_TRUSTED_NETWORK=false
|
||||
CI_REPO_TRUSTED_VOLUMES=false
|
||||
CI_REPO_TRUSTED_SECURITY=false
|
||||
CI_REPO_URL=http://1.2.3.4:3000/test/woodpecker-test
|
||||
CI_STEP_NAME=
|
||||
CI_STEP_NUMBER=0
|
||||
|
|
|
@ -62,7 +62,11 @@ func (m *Metadata) Environ() map[string]string {
|
|||
"CI_REPO_CLONE_SSH_URL": m.Repo.CloneSSHURL,
|
||||
"CI_REPO_DEFAULT_BRANCH": m.Repo.Branch,
|
||||
"CI_REPO_PRIVATE": strconv.FormatBool(m.Repo.Private),
|
||||
"CI_REPO_TRUSTED": strconv.FormatBool(m.Repo.Trusted),
|
||||
"CI_REPO_TRUSTED_NETWORK": strconv.FormatBool(m.Repo.Trusted.Network),
|
||||
"CI_REPO_TRUSTED_VOLUMES": strconv.FormatBool(m.Repo.Trusted.Volumes),
|
||||
"CI_REPO_TRUSTED_SECURITY": strconv.FormatBool(m.Repo.Trusted.Security),
|
||||
// Deprecated remove in 4.x
|
||||
"CI_REPO_TRUSTED": strconv.FormatBool(m.Repo.Trusted.Security && m.Repo.Trusted.Network && m.Repo.Trusted.Volumes),
|
||||
|
||||
"CI_COMMIT_SHA": m.Curr.Commit.Sha,
|
||||
"CI_COMMIT_REF": m.Curr.Commit.Ref,
|
||||
|
|
|
@ -39,7 +39,7 @@ type (
|
|||
CloneSSHURL string `json:"clone_url_ssh,omitempty"`
|
||||
Private bool `json:"private,omitempty"`
|
||||
Branch string `json:"default_branch,omitempty"`
|
||||
Trusted bool `json:"trusted,omitempty"`
|
||||
Trusted TrustedConfiguration `json:"trusted,omitempty"`
|
||||
}
|
||||
|
||||
// Pipeline defines runtime metadata for a pipeline.
|
||||
|
@ -113,4 +113,10 @@ type (
|
|||
// URL returns the root url of a configured forge
|
||||
URL() string
|
||||
}
|
||||
|
||||
TrustedConfiguration struct {
|
||||
Network bool `json:"network,omitempty"`
|
||||
Volumes bool `json:"volumes,omitempty"`
|
||||
Security bool `json:"security,omitempty"`
|
||||
}
|
||||
)
|
||||
|
|
|
@ -97,7 +97,7 @@ type Compiler struct {
|
|||
secrets map[string]Secret
|
||||
defaultClonePlugin string
|
||||
trustedClonePlugins []string
|
||||
trustedPipeline bool
|
||||
securityTrustedPipeline bool
|
||||
netrcOnlyTrusted bool
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,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
|
||||
if !c.netrcOnlyTrusted || c.trustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
||||
if !c.netrcOnlyTrusted || c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
||||
for k, v := range c.cloneEnv {
|
||||
step.Environment[k] = v
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
|||
}
|
||||
|
||||
// inject netrc if it's a trusted repo or a trusted clone-plugin
|
||||
if c.trustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
||||
if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
|
||||
for k, v := range c.cloneEnv {
|
||||
step.Environment[k] = v
|
||||
}
|
||||
|
|
|
@ -169,10 +169,10 @@ func WithTrustedClonePlugins(images []string) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithTrusted configures the compiler with the trusted repo option.
|
||||
func WithTrusted(trusted bool) Option {
|
||||
// WithTrustedSecurity configures the compiler with the trusted repo option.
|
||||
func WithTrustedSecurity(trusted bool) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.trustedPipeline = trusted
|
||||
compiler.securityTrustedPipeline = trusted
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,11 +30,17 @@ import (
|
|||
|
||||
// A Linter lints a pipeline configuration.
|
||||
type Linter struct {
|
||||
trusted bool
|
||||
trusted TrustedConfiguration
|
||||
privilegedPlugins *[]string
|
||||
trustedClonePlugins *[]string
|
||||
}
|
||||
|
||||
type TrustedConfiguration struct {
|
||||
Network bool
|
||||
Volumes bool
|
||||
Security bool
|
||||
}
|
||||
|
||||
// New creates a new Linter with options.
|
||||
func New(opts ...Option) *Linter {
|
||||
linter := new(Linter)
|
||||
|
@ -143,11 +149,9 @@ func (l *Linter) lintContainers(config *WorkflowConfig, area string) error {
|
|||
if err := l.lintImage(config, container, area); err != nil {
|
||||
linterErr = multierr.Append(linterErr, err)
|
||||
}
|
||||
if !l.trusted {
|
||||
if err := l.lintTrusted(config, container, area); err != nil {
|
||||
linterErr = multierr.Append(linterErr, err)
|
||||
}
|
||||
}
|
||||
if err := l.lintSettings(config, container, area); err != nil {
|
||||
linterErr = multierr.Append(linterErr, err)
|
||||
}
|
||||
|
@ -204,30 +208,36 @@ func (l *Linter) lintSettings(config *WorkflowConfig, c *types.Container, field
|
|||
func (l *Linter) lintTrusted(config *WorkflowConfig, c *types.Container, area string) error {
|
||||
yamlPath := fmt.Sprintf("%s.%s", area, c.Name)
|
||||
errors := []string{}
|
||||
if !l.trusted.Security {
|
||||
if c.Privileged {
|
||||
errors = append(errors, "Insufficient privileges to use privileged mode")
|
||||
}
|
||||
}
|
||||
if !l.trusted.Network {
|
||||
if len(c.DNS) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use custom dns")
|
||||
}
|
||||
if len(c.DNSSearch) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use dns_search")
|
||||
}
|
||||
if len(c.Devices) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use devices")
|
||||
}
|
||||
if len(c.ExtraHosts) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use extra_hosts")
|
||||
}
|
||||
if len(c.NetworkMode) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use network_mode")
|
||||
}
|
||||
}
|
||||
if !l.trusted.Volumes {
|
||||
if len(c.Devices) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use devices")
|
||||
}
|
||||
if len(c.Volumes.Volumes) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use volumes")
|
||||
}
|
||||
if len(c.Tmpfs) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use tmpfs")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
var err error
|
||||
|
|
|
@ -94,7 +94,11 @@ steps:
|
|||
conf, err := yaml.ParseString(testd.Data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{{
|
||||
assert.NoError(t, linter.New(linter.WithTrusted(linter.TrustedConfiguration{
|
||||
Network: true,
|
||||
Volumes: true,
|
||||
Security: true,
|
||||
})).Lint([]*linter.WorkflowConfig{{
|
||||
File: testd.Title,
|
||||
RawConfig: testd.Data,
|
||||
Workflow: conf,
|
||||
|
|
|
@ -18,7 +18,7 @@ package linter
|
|||
type Option func(*Linter)
|
||||
|
||||
// WithTrusted adds the trusted option to the linter.
|
||||
func WithTrusted(trusted bool) Option {
|
||||
func WithTrusted(trusted TrustedConfiguration) Option {
|
||||
return func(linter *Linter) {
|
||||
linter.trusted = trusted
|
||||
}
|
||||
|
|
|
@ -224,12 +224,25 @@ func PatchRepo(c *gin.Context) {
|
|||
c.String(http.StatusForbidden, fmt.Sprintf("Timeout is not allowed to be higher than max timeout (%d min)", server.Config.Pipeline.MaxTimeout))
|
||||
return
|
||||
}
|
||||
if in.IsTrusted != nil && *in.IsTrusted != repo.IsTrusted && !user.Admin {
|
||||
log.Trace().Msgf("user '%s' wants to make repo trusted without being an instance admin", user.Login)
|
||||
|
||||
if in.Trusted != nil {
|
||||
if (*in.Trusted.Network != repo.Trusted.Network || *in.Trusted.Volumes != repo.Trusted.Volumes || *in.Trusted.Security != repo.Trusted.Security) && !user.Admin {
|
||||
log.Trace().Msgf("user '%s' wants to change trusted without being an instance admin", user.Login)
|
||||
c.String(http.StatusForbidden, "Insufficient privileges")
|
||||
return
|
||||
}
|
||||
|
||||
if in.Trusted.Network != nil {
|
||||
repo.Trusted.Network = *in.Trusted.Network
|
||||
}
|
||||
if in.Trusted.Security != nil {
|
||||
repo.Trusted.Security = *in.Trusted.Security
|
||||
}
|
||||
if in.Trusted.Volumes != nil {
|
||||
repo.Trusted.Volumes = *in.Trusted.Volumes
|
||||
}
|
||||
}
|
||||
|
||||
if in.AllowPull != nil {
|
||||
repo.AllowPull = *in.AllowPull
|
||||
}
|
||||
|
@ -239,9 +252,6 @@ func PatchRepo(c *gin.Context) {
|
|||
if in.IsGated != nil {
|
||||
repo.IsGated = *in.IsGated
|
||||
}
|
||||
if in.IsTrusted != nil {
|
||||
repo.IsTrusted = *in.IsTrusted
|
||||
}
|
||||
if in.Timeout != nil {
|
||||
repo.Timeout = *in.Timeout
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ type Repo struct {
|
|||
Timeout int64 `json:"timeout,omitempty" xorm:"timeout"`
|
||||
Visibility RepoVisibility `json:"visibility" xorm:"varchar(10) 'visibility'"`
|
||||
IsSCMPrivate bool `json:"private" xorm:"private"`
|
||||
IsTrusted bool `json:"trusted" xorm:"trusted"`
|
||||
Trusted TrustedConfiguration `json:"trusted" xorm:"json 'trusted'"`
|
||||
IsGated bool `json:"gated" xorm:"gated"`
|
||||
IsActive bool `json:"active" xorm:"active"`
|
||||
AllowPull bool `json:"allow_pr" xorm:"allow_pr"`
|
||||
|
@ -109,7 +109,6 @@ func (r *Repo) Update(from *Repo) {
|
|||
// RepoPatch represents a repository patch object.
|
||||
type RepoPatch struct {
|
||||
Config *string `json:"config_file,omitempty"`
|
||||
IsTrusted *bool `json:"trusted,omitempty"`
|
||||
IsGated *bool `json:"gated,omitempty"`
|
||||
Timeout *int64 `json:"timeout,omitempty"`
|
||||
Visibility *string `json:"visibility,omitempty"`
|
||||
|
@ -117,6 +116,7 @@ type RepoPatch struct {
|
|||
AllowDeploy *bool `json:"allow_deploy,omitempty"`
|
||||
CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"`
|
||||
NetrcOnlyTrusted *bool `json:"netrc_only_trusted"`
|
||||
Trusted *TrustedConfigurationPatch `json:"trusted"`
|
||||
} // @name RepoPatch
|
||||
|
||||
type ForgeRemoteID string
|
||||
|
@ -124,3 +124,15 @@ type ForgeRemoteID string
|
|||
func (r ForgeRemoteID) IsValid() bool {
|
||||
return r != "" && r != "0"
|
||||
}
|
||||
|
||||
type TrustedConfiguration struct {
|
||||
Network bool `json:"network"`
|
||||
Volumes bool `json:"volumes"`
|
||||
Security bool `json:"security"`
|
||||
}
|
||||
|
||||
type TrustedConfigurationPatch struct {
|
||||
Network *bool `json:"network"`
|
||||
Volumes *bool `json:"volumes"`
|
||||
Security *bool `json:"security"`
|
||||
}
|
||||
|
|
|
@ -53,7 +53,11 @@ func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline,
|
|||
CloneSSHURL: repo.CloneSSH,
|
||||
Private: repo.IsSCMPrivate,
|
||||
Branch: repo.Branch,
|
||||
Trusted: repo.IsTrusted,
|
||||
Trusted: metadata.TrustedConfiguration{
|
||||
Network: repo.Trusted.Network,
|
||||
Volumes: repo.Trusted.Volumes,
|
||||
Security: repo.Trusted.Security,
|
||||
},
|
||||
}
|
||||
|
||||
if idx := strings.LastIndex(repo.FullName, "/"); idx != -1 {
|
||||
|
|
|
@ -53,7 +53,8 @@ func TestMetadataFromStruct(t *testing.T) {
|
|||
"CI_PREV_COMMIT_MESSAGE": "", "CI_PREV_COMMIT_REF": "", "CI_PREV_COMMIT_REFSPEC": "", "CI_PREV_COMMIT_SHA": "", "CI_PREV_COMMIT_URL": "", "CI_PREV_PIPELINE_CREATED": "0",
|
||||
"CI_PREV_PIPELINE_DEPLOY_TARGET": "", "CI_PREV_PIPELINE_DEPLOY_TASK": "", "CI_PREV_PIPELINE_EVENT": "", "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "0", "CI_PREV_PIPELINE_PARENT": "0",
|
||||
"CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "/repos/0/pipeline/0", "CI_PREV_PIPELINE_FORGE_URL": "", "CI_REPO": "", "CI_REPO_CLONE_URL": "", "CI_REPO_CLONE_SSH_URL": "", "CI_REPO_DEFAULT_BRANCH": "", "CI_REPO_REMOTE_ID": "",
|
||||
"CI_REPO_NAME": "", "CI_REPO_OWNER": "", "CI_REPO_PRIVATE": "false", "CI_REPO_SCM": "", "CI_REPO_TRUSTED": "false", "CI_REPO_URL": "",
|
||||
"CI_REPO_NAME": "", "CI_REPO_OWNER": "", "CI_REPO_PRIVATE": "false", "CI_REPO_SCM": "", "CI_REPO_TRUSTED": "false", "CI_REPO_TRUSTED_NETWORK": "false",
|
||||
"CI_REPO_TRUSTED_VOLUMES": "false", "CI_REPO_TRUSTED_SECURITY": "false", "CI_REPO_URL": "",
|
||||
"CI_STEP_NAME": "", "CI_STEP_NUMBER": "0", "CI_STEP_STARTED": "", "CI_STEP_URL": "/repos/0/pipeline/0", "CI_SYSTEM_HOST": "", "CI_SYSTEM_NAME": "woodpecker",
|
||||
"CI_SYSTEM_PLATFORM": "", "CI_SYSTEM_URL": "", "CI_SYSTEM_VERSION": "", "CI_WORKFLOW_NAME": "", "CI_WORKFLOW_NUMBER": "0",
|
||||
},
|
||||
|
@ -89,7 +90,9 @@ func TestMetadataFromStruct(t *testing.T) {
|
|||
"CI_PREV_PIPELINE_DEPLOY_TARGET": "", "CI_PREV_PIPELINE_DEPLOY_TASK": "", "CI_PREV_PIPELINE_EVENT": "", "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "2", "CI_PREV_PIPELINE_PARENT": "0",
|
||||
"CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "https://example.com/repos/0/pipeline/2", "CI_PREV_PIPELINE_FORGE_URL": "", "CI_REPO": "testUser/testRepo", "CI_REPO_CLONE_URL": "https://gitea.com/testUser/testRepo.git", "CI_REPO_CLONE_SSH_URL": "git@gitea.com:testUser/testRepo.git",
|
||||
"CI_REPO_DEFAULT_BRANCH": "main", "CI_REPO_NAME": "testRepo", "CI_REPO_OWNER": "testUser", "CI_REPO_PRIVATE": "true", "CI_REPO_REMOTE_ID": "",
|
||||
"CI_REPO_SCM": "git", "CI_REPO_TRUSTED": "false", "CI_REPO_URL": "https://gitea.com/testUser/testRepo",
|
||||
"CI_REPO_SCM": "git", "CI_REPO_TRUSTED": "false", "CI_REPO_TRUSTED_NETWORK": "false",
|
||||
"CI_REPO_TRUSTED_VOLUMES": "false", "CI_REPO_TRUSTED_SECURITY": "false",
|
||||
"CI_REPO_URL": "https://gitea.com/testUser/testRepo",
|
||||
"CI_STEP_NAME": "", "CI_STEP_NUMBER": "0", "CI_STEP_STARTED": "", "CI_STEP_URL": "https://example.com/repos/0/pipeline/3", "CI_SYSTEM_HOST": "example.com",
|
||||
"CI_SYSTEM_NAME": "woodpecker", "CI_SYSTEM_PLATFORM": "", "CI_SYSTEM_URL": "https://example.com", "CI_SYSTEM_VERSION": "", "CI_WORKFLOW_NAME": "hello", "CI_WORKFLOW_NUMBER": "0",
|
||||
},
|
||||
|
|
|
@ -141,7 +141,11 @@ func (b *StepBuilder) genItemForWorkflow(workflow *model.Workflow, axis matrix.A
|
|||
|
||||
// lint pipeline
|
||||
errorsAndWarnings = multierr.Append(errorsAndWarnings, linter.New(
|
||||
linter.WithTrusted(b.Repo.IsTrusted),
|
||||
linter.WithTrusted(linter.TrustedConfiguration{
|
||||
Network: b.Repo.Trusted.Network,
|
||||
Volumes: b.Repo.Trusted.Volumes,
|
||||
Security: b.Repo.Trusted.Security,
|
||||
}),
|
||||
linter.PrivilegedPlugins(server.Config.Pipeline.PrivilegedPlugins),
|
||||
linter.WithTrustedClonePlugins(server.Config.Pipeline.TrustedClonePlugins),
|
||||
).Lint([]*linter.WorkflowConfig{{
|
||||
|
@ -295,7 +299,7 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi
|
|||
compiler.WithProxy(b.ProxyOpts),
|
||||
compiler.WithWorkspaceFromURL(compiler.DefaultWorkspaceBase, b.Repo.ForgeURL),
|
||||
compiler.WithMetadata(metadata),
|
||||
compiler.WithTrusted(b.Repo.IsTrusted),
|
||||
compiler.WithTrustedSecurity(b.Repo.Trusted.Security),
|
||||
compiler.WithNetrcOnlyTrusted(b.Repo.NetrcOnlyTrusted),
|
||||
).Compile(parsed)
|
||||
}
|
||||
|
|
73
server/store/datastore/migration/017_split_trusted.go
Normal file
73
server/store/datastore/migration/017_split_trusted.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
type repoV035 struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
IsTrusted bool `xorm:"'trusted'"`
|
||||
Trusted model.TrustedConfiguration `xorm:"json 'trusted_conf'"`
|
||||
}
|
||||
|
||||
func (repoV035) TableName() string {
|
||||
return "repos"
|
||||
}
|
||||
|
||||
var splitTrusted = xormigrate.Migration{
|
||||
ID: "split-trusted",
|
||||
MigrateSession: func(sess *xorm.Session) error {
|
||||
if err := sess.Sync(new(repoV035)); err != nil {
|
||||
return fmt.Errorf("sync new models failed: %w", err)
|
||||
}
|
||||
|
||||
if _, err := sess.Where("trusted = ?", false).Cols("trusted_conf").Update(&repoV035{
|
||||
Trusted: model.TrustedConfiguration{
|
||||
Network: false,
|
||||
Security: false,
|
||||
Volumes: false,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Where("trusted = ?", true).Cols("trusted_conf").Update(&repoV035{
|
||||
Trusted: model.TrustedConfiguration{
|
||||
Network: true,
|
||||
Security: true,
|
||||
Volumes: true,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dropTableColumns(sess, "repos", "trusted"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return renameColumn(sess, "repos", "trusted_conf", "trusted")
|
||||
},
|
||||
}
|
|
@ -76,66 +76,14 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
}
|
||||
}
|
||||
|
||||
// Here we need to get the columns from the original table
|
||||
sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName)
|
||||
res, err := sess.Query(sql)
|
||||
// Now drop the columns
|
||||
for _, columnName := range columnNames {
|
||||
_, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN `%s`;", tableName, columnName))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("table `%s`, drop column %v: %w", tableName, columnName, err)
|
||||
}
|
||||
tableSQL := normalizeSQLiteTableSchema(string(res[0]["sql"]))
|
||||
|
||||
// Separate out the column definitions
|
||||
sqlIndex := strings.Index(tableSQL, "(")
|
||||
if sqlIndex < 0 {
|
||||
return fmt.Errorf("could not separate column definitions")
|
||||
}
|
||||
tableSQL = tableSQL[sqlIndex:]
|
||||
|
||||
// Remove the required columnNames
|
||||
tableSQL = removeColumnFromSQLITETableSchema(tableSQL, columnNames...)
|
||||
|
||||
// Ensure the query is ended properly
|
||||
tableSQL = strings.TrimSpace(tableSQL)
|
||||
if tableSQL[len(tableSQL)-1] != ')' {
|
||||
if tableSQL[len(tableSQL)-1] == ',' {
|
||||
tableSQL = tableSQL[:len(tableSQL)-1]
|
||||
}
|
||||
tableSQL += ")"
|
||||
}
|
||||
|
||||
// Find all the columns in the table
|
||||
var columns []string
|
||||
for _, rawColumn := range strings.Split(strings.ReplaceAll(tableSQL[1:len(tableSQL)-1], ", ", ",\n"), "\n") {
|
||||
if strings.ContainsAny(rawColumn, "()") {
|
||||
continue
|
||||
}
|
||||
rawColumn = strings.TrimSpace(rawColumn)
|
||||
columns = append(columns,
|
||||
strings.ReplaceAll(rawColumn[0:strings.Index(rawColumn, " ")], "`", ""),
|
||||
)
|
||||
}
|
||||
|
||||
tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL
|
||||
if _, err := sess.Exec(tableSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now restore the data
|
||||
columnsSeparated := strings.Join(columns, ",")
|
||||
insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName)
|
||||
if _, err := sess.Exec(insertSQL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now drop the old table
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename the table
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil {
|
||||
return err
|
||||
}
|
||||
case schemas.POSTGRES:
|
||||
cols := ""
|
||||
for _, col := range columnNames {
|
||||
|
@ -145,7 +93,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
cols += "DROP COLUMN `" + col + "` CASCADE"
|
||||
}
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
|
||||
return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err)
|
||||
return fmt.Errorf("table `%s`, drop columns %v: %w", tableName, columnNames, err)
|
||||
}
|
||||
case schemas.MYSQL:
|
||||
// Drop indexes on columns first
|
||||
|
@ -173,7 +121,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
cols += "DROP COLUMN `" + col + "`"
|
||||
}
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
|
||||
return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err)
|
||||
return fmt.Errorf("table `%s`, drop columns %v: %w", tableName, columnNames, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("dialect '%s' not supported", dialect)
|
||||
|
|
|
@ -45,6 +45,7 @@ var migrationTasks = []*xormigrate.Migration{
|
|||
&removeOldMigrationsOfV1,
|
||||
&addOrgAgents,
|
||||
&addCustomLabelsToAgent,
|
||||
&splitTrusted,
|
||||
}
|
||||
|
||||
var allBeans = []any{
|
||||
|
|
|
@ -98,7 +98,18 @@
|
|||
},
|
||||
"trusted": {
|
||||
"trusted": "Trusted",
|
||||
"desc": "Underlying pipeline containers get access to escalated capabilities (like mounting volumes)."
|
||||
"network": {
|
||||
"network": "Network",
|
||||
"desc": "Underlying pipeline containers get access to network privileges like changing DNS."
|
||||
},
|
||||
"volumes": {
|
||||
"volumes": "Volumes",
|
||||
"desc": "Underlying pipeline containers get access to volume privileges."
|
||||
},
|
||||
"security": {
|
||||
"security": "Security",
|
||||
"desc": "Underlying pipeline containers get access to security privileges."
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"visibility": "Project visibility",
|
||||
|
|
|
@ -45,11 +45,27 @@
|
|||
:label="$t('repo.settings.general.netrc_only_trusted.netrc_only_trusted')"
|
||||
:description="$t('repo.settings.general.netrc_only_trusted.desc')"
|
||||
/>
|
||||
<Checkbox
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
v-if="user?.admin"
|
||||
v-model="repoSettings.trusted"
|
||||
docs-url="docs/usage/project-settings#project-settings-1"
|
||||
:label="$t('repo.settings.general.trusted.trusted')"
|
||||
:description="$t('repo.settings.general.trusted.desc')"
|
||||
>
|
||||
<Checkbox
|
||||
v-model="repoSettings.trusted.network"
|
||||
:label="$t('repo.settings.general.trusted.network.network')"
|
||||
:description="$t('repo.settings.general.trusted.network.desc')"
|
||||
/>
|
||||
<Checkbox
|
||||
v-model="repoSettings.trusted.volumes"
|
||||
:label="$t('repo.settings.general.trusted.volumes.volumes')"
|
||||
:description="$t('repo.settings.general.trusted.volumes.desc')"
|
||||
/>
|
||||
<Checkbox
|
||||
v-model="repoSettings.trusted.security"
|
||||
:label="$t('repo.settings.general.trusted.security.security')"
|
||||
:description="$t('repo.settings.general.trusted.security.desc')"
|
||||
/>
|
||||
</InputField>
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ export interface Repo {
|
|||
// Whether the repository has trusted access for pipelines.
|
||||
// If the repository is trusted then the host network can be used and
|
||||
// volumes can be created.
|
||||
trusted: boolean;
|
||||
trusted: RepoTrusted;
|
||||
|
||||
// x-dart-type: Duration
|
||||
// The amount of time in minutes before the pipeline is killed.
|
||||
|
@ -102,3 +102,9 @@ export interface RepoPermissions {
|
|||
admin: boolean;
|
||||
synced: number;
|
||||
}
|
||||
|
||||
export interface RepoTrusted {
|
||||
network: boolean;
|
||||
volumes: boolean;
|
||||
security: boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue