Skip to content
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

Pass netrc data to external config service request #2310

Merged
merged 9 commits into from
Aug 21, 2023
Merged
4 changes: 4 additions & 0 deletions docs/docs/30-administration/100-external-configuration-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Every request sent by Woodpecker is signed using a [http-signature](https://data

A simplistic example configuration service can be found here: [https://github.com/woodpecker-ci/example-config-service](https://github.com/woodpecker-ci/example-config-service)

:::warning
You need to trust the external config service as it is getting secret information about the repository and pipeline and has the ability to change pipeline configs that could run malicious tasks.
:::

## Config

```shell
Expand Down
7 changes: 6 additions & 1 deletion server/api/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,12 @@ func PostPipeline(c *gin.Context) {
}
}

newpipeline, err := pipeline.Restart(c, _store, pl, user, repo, envs)
netrc, err := server.Config.Services.Forge.Netrc(user, repo)
if err != nil {
handlePipelineErr(c, err)
}
6543 marked this conversation as resolved.
Show resolved Hide resolved

newpipeline, err := pipeline.Restart(c, _store, pl, user, repo, envs, netrc)
if err != nil {
handlePipelineErr(c, err)
} else {
Expand Down
13 changes: 9 additions & 4 deletions server/forge/configFetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,15 @@ func (cf *configFetcher) Fetch(ctx context.Context) (files []*types.FileMeta, er
defer cancel() // ok here as we only try http fetching once, returning on fail and success

log.Trace().Msgf("ConfigFetch[%s]: getting config from external http service", cf.repo.FullName)
newConfigs, useOld, err := cf.configExtension.FetchConfig(fetchCtx, cf.repo, cf.pipeline, files)
netrc, err := cf.forge.Netrc(cf.user, cf.repo)
if err != nil {
log.Error().Msg("Got error " + err.Error())
return nil, fmt.Errorf("On Fetching config via http : %w", err)
return nil, fmt.Errorf("could not get Netrc data from forge: %w", err)
}

newConfigs, useOld, err := cf.configExtension.FetchConfig(fetchCtx, cf.repo, cf.pipeline, files, netrc)
if err != nil {
log.Error().Err(err).Msg("could not fetch config via http")
return nil, fmt.Errorf("could not fetch config via http: %w", err)
}

if !useOld {
Expand Down Expand Up @@ -109,7 +114,7 @@ func (cf *configFetcher) fetch(c context.Context, timeout time.Duration, config
return nil, fmt.Errorf("user defined config '%s' not found: %w", config, err)
}

log.Trace().Msgf("ConfigFetch[%s]: user did not defined own config, following default procedure", cf.repo.FullName)
log.Trace().Msgf("ConfigFetch[%s]: user did not define own config, following default procedure", cf.repo.FullName)
// for the order see shared/constants/constants.go
fileMeta, err := cf.getFirstAvailableConfig(ctx, constant.DefaultConfigOrder[:], false)
if err == nil {
Expand Down
2 changes: 2 additions & 0 deletions server/forge/configFetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ func TestFetchFromConfigService(t *testing.T) {
f.On("File", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("File not found"))
f.On("Dir", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("Directory not found"))

f.On("Netrc", mock.Anything, mock.Anything).Return(&model.Netrc{Machine: "mock", Login: "mock", Password: "mock"}, nil)

configFetcher := forge.NewConfigFetcher(
f,
time.Second*3,
Expand Down
4 changes: 2 additions & 2 deletions server/pipeline/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

// Restart a pipeline by creating a new one out of the old and start it
func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipeline, user *model.User, repo *model.Repo, envs map[string]string) (*model.Pipeline, error) {
func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipeline, user *model.User, repo *model.Repo, envs map[string]string, netrc *model.Netrc) (*model.Pipeline, error) {
switch lastPipeline.Status {
case model.StatusDeclined,
model.StatusBlocked:
Expand Down Expand Up @@ -58,7 +58,7 @@ func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipelin
currentFileMeta[i] = &forge_types.FileMeta{Name: cfg.Name, Data: cfg.Data}
}

newConfig, useOld, err := server.Config.Services.ConfigService.FetchConfig(ctx, repo, lastPipeline, currentFileMeta)
newConfig, useOld, err := server.Config.Services.ConfigService.FetchConfig(ctx, repo, lastPipeline, currentFileMeta, netrc)
if err != nil {
return nil, &ErrBadRequest{
Msg: fmt.Sprintf("On fetching external pipeline config: %s", err),
Expand Down
2 changes: 1 addition & 1 deletion server/plugins/config/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ import (

type Extension interface {
IsConfigured() bool
FetchConfig(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline, currentFileMeta []*forge_types.FileMeta) (configData []*forge_types.FileMeta, useOld bool, err error)
FetchConfig(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline, currentFileMeta []*forge_types.FileMeta, netrc *model.Netrc) (configData []*forge_types.FileMeta, useOld bool, err error)
}
11 changes: 9 additions & 2 deletions server/plugins/config/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type requestStructure struct {
Repo *model.Repo `json:"repo"`
Pipeline *model.Pipeline `json:"pipeline"`
Configuration []*config `json:"configs"`
Netrc *model.Netrc `json:"netrc"`
}

type responseStructure struct {
Expand All @@ -53,14 +54,20 @@ func (cp *http) IsConfigured() bool {
return cp.endpoint != ""
}

func (cp *http) FetchConfig(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline, currentFileMeta []*forge_types.FileMeta) (configData []*forge_types.FileMeta, useOld bool, err error) {
func (cp *http) FetchConfig(ctx context.Context, repo *model.Repo, pipeline *model.Pipeline, currentFileMeta []*forge_types.FileMeta, netrc *model.Netrc) (configData []*forge_types.FileMeta, useOld bool, err error) {
currentConfigs := make([]*config, len(currentFileMeta))
for i, pipe := range currentFileMeta {
currentConfigs[i] = &config{Name: pipe.Name, Data: string(pipe.Data)}
}

response := new(responseStructure)
body := requestStructure{Repo: repo, Pipeline: pipeline, Configuration: currentConfigs}
body := requestStructure{
Repo: repo,
Pipeline: pipeline,
Configuration: currentConfigs,
Netrc: netrc,
}

status, err := utils.Send(ctx, "POST", cp.endpoint, cp.privateKey, body, response)
if err != nil && status != 204 {
return nil, false, fmt.Errorf("Failed to fetch config via http (%d) %w", status, err)
Expand Down