Skip to content

Commit 9a4f70a

Browse files
authored
feat(api): artifact managed in artifactory are signed by CDS API (#6595)
1 parent 933e43a commit 9a4f70a

File tree

9 files changed

+220
-5
lines changed

9 files changed

+220
-5
lines changed

engine/api/workflow/workflow_run_results.go

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import (
1010
"time"
1111

1212
"github.com/go-gorp/gorp"
13+
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
1314
"github.com/lib/pq"
1415
"github.com/rockbears/log"
1516

1617
art "github.com/ovh/cds/contrib/integrations/artifactory"
18+
"github.com/ovh/cds/engine/api/authentication"
1719
"github.com/ovh/cds/engine/api/database/gorpmapping"
1820
"github.com/ovh/cds/engine/cache"
1921
"github.com/ovh/cds/engine/gorpmapper"
@@ -468,6 +470,8 @@ func ResyncWorkflowRunResultsRoutine(ctx context.Context, DBFunc func() *gorp.Db
468470
}
469471
}
470472

473+
type ArtifactSignature map[string]string
474+
471475
func FindOldestWorkflowRunsWithResultToSync(ctx context.Context, dbmap *gorp.DbMap) ([]int64, error) {
472476
var results []int64
473477
_, err := dbmap.Select(&results, "select distinct workflow_run_id from workflow_run_result where sync is NULL order by workflow_run_id asc limit 100")
@@ -645,7 +649,6 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, db gorpmapper.SqlE
645649
return handleSyncError(sdk.Errorf("unable to prepare build info for artifact manager"))
646650
}
647651

648-
log.Debug(ctx, "artifact manager build info request: %+v", buildInfoRequest)
649652
log.Info(ctx, "Creating Artifactory Build %s %s on project %s...\n", buildInfoRequest.Name, buildInfoRequest.Number, artifactoryProjectKey)
650653

651654
// Instanciate artifactory client
@@ -680,6 +683,99 @@ func SyncRunResultArtifactManagerByRunID(ctx context.Context, db gorpmapper.SqlE
680683
}
681684
}
682685

686+
// Push git info as properties
687+
for _, result := range runResults {
688+
if result.Type != sdk.WorkflowRunResultTypeArtifactManager {
689+
continue
690+
}
691+
692+
artifact, err := result.GetArtifactManager()
693+
if err != nil {
694+
ctx := log.ContextWithStackTrace(ctx, err)
695+
log.Error(ctx, "unable to get artifact from result %s: %v", result.ID, err)
696+
continue
697+
}
698+
699+
maturity := lowMaturitySuffixFromConfig
700+
if result.DataSync != nil && result.DataSync.LatestPromotionOrRelease() != nil {
701+
maturity = result.DataSync.LatestPromotionOrRelease().ToMaturity
702+
}
703+
localRepository := fmt.Sprintf("%s-%s", artifact.RepoName, maturity)
704+
705+
fi, err := artifactClient.GetFileInfo(artifact.RepoName, artifact.Path)
706+
if err != nil {
707+
ctx := log.ContextWithStackTrace(ctx, err)
708+
log.Error(ctx, "unable to get artifact info from result %s: %v", result.ID, err)
709+
continue
710+
}
711+
712+
existingProperties, err := artifactClient.GetProperties(localRepository, artifact.Path)
713+
if err != nil {
714+
ctx := log.ContextWithStackTrace(ctx, err)
715+
log.Error(ctx, "unable to get artifact properties from result %s: %v", result.ID, err)
716+
continue
717+
}
718+
719+
if sdk.MapHasKeys(existingProperties, "cds.signature") {
720+
log.Debug(ctx, "artifact is already signed by cds")
721+
continue
722+
}
723+
724+
// Push git properties as artifact properties
725+
props := utils.NewProperties()
726+
signedProps := make(ArtifactSignature)
727+
728+
props.AddProperty("cds.project", wr.Workflow.ProjectKey)
729+
signedProps["cds.project"] = wr.Workflow.ProjectKey
730+
props.AddProperty("cds.workflow", wr.Workflow.Name)
731+
signedProps["cds.workflow"] = wr.Workflow.Name
732+
if wr.Version != nil {
733+
props.AddProperty("cds.version", *wr.Version)
734+
signedProps["cds.version"] = *wr.Version
735+
}
736+
props.AddProperty("cds.run", strconv.FormatInt(wr.Number, 10))
737+
signedProps["cds.run"] = strconv.FormatInt(wr.Number, 10)
738+
739+
if gitUrl != "" {
740+
props.AddProperty("git.url", gitUrl)
741+
}
742+
signedProps["git.url"] = gitUrl
743+
if gitHash != "" {
744+
props.AddProperty("git.hash", gitHash)
745+
}
746+
signedProps["git.hash"] = gitHash
747+
if gitBranch != "" {
748+
props.AddProperty("git.branch", gitBranch)
749+
signedProps["git.branch"] = gitBranch
750+
}
751+
752+
// Prepare artifact signature
753+
signedProps["repository"] = artifact.RepoName
754+
signedProps["type"] = artifact.RepoType
755+
signedProps["path"] = artifact.Path
756+
signedProps["name"] = artifact.Name
757+
signedProps["md5"] = fi.Checksums.Md5
758+
signedProps["sha1"] = fi.Checksums.Sha1
759+
signedProps["sha256"] = fi.Checksums.Sha256
760+
761+
// Sign the properties with main CDS authentication key pair
762+
signature, err := authentication.SignJWS(signedProps, time.Now(), 0)
763+
if err != nil {
764+
ctx := log.ContextWithStackTrace(ctx, err)
765+
log.Error(ctx, "unable to get artifact properties from result %s: %v", result.ID, err)
766+
continue
767+
}
768+
769+
log.Info(ctx, "artifact %s%s signature: %s", localRepository, artifact.Path, signature)
770+
771+
props.AddProperty("cds.signature", signature)
772+
if err := artifactClient.SetProperties(localRepository, artifact.Path, props); err != nil {
773+
ctx := log.ContextWithStackTrace(ctx, err)
774+
log.Error(ctx, "unable to set artifact properties from result %s: %v", result.ID, err)
775+
continue
776+
}
777+
}
778+
683779
for _, result := range runResults {
684780
if result.DataSync == nil {
685781
result.DataSync = new(sdk.WorkflowRunResultSync)

sdk/artifact_manager/artifactory/client.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/jfrog/jfrog-client-go/artifactory"
1515
"github.com/jfrog/jfrog-client-go/artifactory/services"
1616
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
17+
"github.com/jfrog/jfrog-client-go/utils/errorutils"
1718
"github.com/pkg/errors"
1819

1920
"github.com/ovh/cds/sdk"
@@ -89,6 +90,33 @@ func (c *Client) SetProperties(repoName string, filePath string, props *utils.Pr
8990
return nil
9091
}
9192

93+
func (c *Client) GetProperties(repoName string, filePath string) (map[string][]string, error) {
94+
type PropertiesResponse struct {
95+
Properties map[string][]string `json:"properties"`
96+
}
97+
if !strings.HasPrefix(filePath, "/") {
98+
filePath = "/" + filePath
99+
}
100+
var rp = services.NewCreateReplicationService(c.Asm.Client())
101+
rp.ArtDetails = c.Asm.GetConfig().GetServiceDetails()
102+
var httpDetails = rp.ArtDetails.CreateHttpClientDetails()
103+
propUrl := fmt.Sprintf("%sapi/storage/%s%s?properties", c.Asm.GetConfig().GetServiceDetails().GetUrl(), repoName, filePath)
104+
105+
resp, body, _, err := c.Asm.Client().SendGet(propUrl, true, &httpDetails)
106+
if err != nil {
107+
return nil, errors.Errorf("unable to get properties on %s%s: %v", repoName, filePath, err)
108+
}
109+
if resp.StatusCode >= http.StatusBadRequest {
110+
return nil, errors.WithStack(errorutils.CheckError(errors.New(fmt.Sprintf("GET Properties Artifactory on %s%s response: [%d] %v\n", repoName, filePath, resp.StatusCode, string(body)))))
111+
}
112+
113+
var props PropertiesResponse
114+
if err := json.Unmarshal(body, &props); err != nil {
115+
return nil, errors.Errorf("unable get properties, unable to read response: %s: %v", string(body), err)
116+
}
117+
return props.Properties, nil
118+
}
119+
92120
type DeleteBuildRequest struct {
93121
Project string `json:"project"`
94122
BuildName string `json:"buildName"`

sdk/artifact_manager/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package artifact_manager
22

33
import (
44
"context"
5+
56
buildinfo "github.com/jfrog/build-info-go/entities"
67
"github.com/jfrog/jfrog-client-go/artifactory/services"
78
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
@@ -15,6 +16,7 @@ type ArtifactManager interface {
1516
GetFileInfo(repoName string, filePath string) (sdk.FileInfo, error)
1617
GetRepository(repoName string) (*services.RepositoryDetails, error)
1718
GetFolderInfo(repoName string, folderPath string) (*utils.FolderInfo, error)
19+
GetProperties(repoName string, filePath string) (map[string][]string, error)
1820
SetProperties(repoName string, filePath string, values *utils.Properties) error
1921
DeleteBuild(project string, buildName string, buildVersion string) error
2022
PublishBuildInfo(project string, request *buildinfo.BuildInfo) error

sdk/artifact_manager/mock_artifact_manager/interface_mock.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/common.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,3 +382,24 @@ func NoPath(path string) string {
382382
}
383383
return filepath.Base(CleanPath(path))
384384
}
385+
386+
func MapHasKeys(i interface{}, expectedKeys ...interface{}) bool {
387+
valueOf := reflect.ValueOf(i)
388+
if valueOf.Kind() != reflect.Map {
389+
return false
390+
}
391+
actualKeyValues := valueOf.MapKeys()
392+
for _, expectedKey := range expectedKeys {
393+
var found = false
394+
for _, actualKey := range actualKeyValues {
395+
if actualKey.Equal(reflect.ValueOf(expectedKey)) {
396+
found = true
397+
break
398+
}
399+
}
400+
if !found {
401+
return false
402+
}
403+
}
404+
return true
405+
}

sdk/common_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,55 @@ func TestNoPath(t *testing.T) {
126126
})
127127
}
128128
}
129+
130+
func TestMapHasKeys(t *testing.T) {
131+
type args struct {
132+
i interface{}
133+
expectedKeys []interface{}
134+
}
135+
tests := []struct {
136+
name string
137+
args args
138+
want bool
139+
}{
140+
{
141+
name: "should return true",
142+
args: args{
143+
i: map[string]string{"a": "a", "b": "b"},
144+
expectedKeys: []interface{}{"a", "b"},
145+
},
146+
want: true,
147+
},
148+
{
149+
name: "should return false (one key is missing)",
150+
args: args{
151+
i: map[string]string{"a": "a", "b": "b"},
152+
expectedKeys: []interface{}{"a", "b", "c"},
153+
},
154+
want: false,
155+
},
156+
{
157+
name: "should return false (wrong key type)",
158+
args: args{
159+
i: map[string]string{"a": "a", "b": "b"},
160+
expectedKeys: []interface{}{1, 2},
161+
},
162+
want: false,
163+
},
164+
{
165+
name: "should return false (wrong type)",
166+
args: args{
167+
i: "foo",
168+
expectedKeys: []interface{}{1, 2},
169+
},
170+
want: false,
171+
},
172+
}
173+
for _, tt := range tests {
174+
t.Run(tt.name, func(t *testing.T) {
175+
if got := MapHasKeys(tt.args.i, tt.args.expectedKeys...); got != tt.want {
176+
t.Errorf("MapHasKeys() = %v, want %v", got, tt.want)
177+
}
178+
})
179+
}
180+
}

tests/fixtures/04SCWorkflowRunSimplePlugin/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ build-plugin:
1616
--rm \
1717
-e "GOOS=$(OS)" \
1818
-e "GOARCH=$(ARCH)" \
19-
golang:1.18 \
19+
golang:1.20 \
2020
/bin/bash -c \
2121
"cd /go/src/github.com/ovh/cds/tests/fixtures/04SCWorkflowRunSimplePlugin && go version && CGO_ENABLED=0 go build -installsuffix cgo -ldflags '-extldflags "-static"' -o plugin-simple-$(OS)-$(ARCH) ."

tests/fixtures/ITSCWRKFLW15/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ plugin-simple-darwin-amd64:
1111
$(MAKE) build-plugin OS=darwin ARCH=amd64
1212

1313
build-plugin:
14-
@docker run \
14+
docker run \
1515
--mount type=bind,source=$$(pwd)/../../../,dst=/go/src/github.com/ovh/cds \
1616
--rm \
1717
-e "GOOS=$(OS)" \
1818
-e "GOARCH=$(ARCH)" \
19-
golang:1.18 \
19+
golang:1.20 \
2020
/bin/bash -c \
2121
"cd /go/src/github.com/ovh/cds/tests/fixtures/ITSCWRKFLW15 && go version && CGO_ENABLED=0 go build -installsuffix cgo -ldflags '-extldflags "-static"' -o plugin-simple-integ-$(OS)-$(ARCH) ."

tests/fixtures/ITSCWRKFLW15/SimplePlugin.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package main
33
import (
44
"context"
55
"fmt"
6-
"github.com/ovh/cds/sdk/grpcplugin/integrationplugin"
76
"strconv"
87

8+
"github.com/ovh/cds/sdk/grpcplugin/integrationplugin"
9+
910
"github.com/golang/protobuf/ptypes/empty"
1011

1112
"github.com/ovh/cds/sdk"

0 commit comments

Comments
 (0)