Skip to content

Commit c6939ff

Browse files
committed
Improved Docker curation to use docker pull and add Accept headers for manifest requests
1 parent ff915c5 commit c6939ff

File tree

3 files changed

+31
-49
lines changed

3 files changed

+31
-49
lines changed

cli/docs/flags.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const (
141141
// Unique curation flags
142142
CurationOutput = "curation-format"
143143
DockerImageName = "image"
144-
SolutionPath = "solution-path"
144+
SolutionPath = "solution-path"
145145

146146
// Unique git flags
147147
InputFile = "input-file"
@@ -195,7 +195,7 @@ var commandFlags = map[string][]string{
195195
StaticSca, XrayLibPluginBinaryCustomPath, AnalyzerManagerCustomPath, AddSastRules,
196196
},
197197
CurationAudit: {
198-
CurationOutput, WorkingDirs, Threads, RequirementsFile, InsecureTls, useWrapperAudit, SolutionPath,DockerImageName,
198+
CurationOutput, WorkingDirs, Threads, RequirementsFile, InsecureTls, useWrapperAudit, SolutionPath, DockerImageName,
199199
},
200200
GitCountContributors: {
201201
InputFile, ScmType, ScmApiUrl, Token, Owner, RepoName, Months, DetailedSummary, InsecureTls,
@@ -316,7 +316,7 @@ var flagsMap = map[string]components.Flag{
316316
AddSastRules: components.NewStringFlag(AddSastRules, "Incorporate any additional SAST rules (in JSON format, with absolute path) into this local scan."),
317317

318318
// Docker flags
319-
DockerImageName: components.NewStringFlag(DockerImageName, "[Docker] Defines the Docker image name to audit. Format: 'repo/path/image:tag'. For example: 'curation-docker/dweomer/nginx-auth-ldap:1.13.5' or 'repo/image:tag'. If no tag is provided, 'latest' is used."),
319+
DockerImageName: components.NewStringFlag(DockerImageName, "[Docker] Defines the Docker image name to audit. Uses the same format as Docker client with Artifactory. Examples: 'acme.jfrog.io/docker-local/nginx:1.21' (repository path) or 'acme-docker-local.jfrog.io/nginx:1.21' (subdomain). Supports all Artifactory hosting methods."),
320320

321321
// Git flags
322322
InputFile: components.NewStringFlag(InputFile, "Path to an input file in YAML format contains multiple git providers. With this option, all other scm flags will be ignored and only git servers mentioned in the file will be examined.."),

commands/curation/curationaudit.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,9 @@ func (nc *treeAnalyzer) fetchNodeStatus(node xrayUtils.GraphNode, p *sync.Map) e
853853
if scope != "" {
854854
name = scope + "/" + name
855855
}
856+
if nc.tech == techutils.Docker {
857+
nc.httpClientDetails.Headers["Accept"] = "application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json"
858+
}
856859
for _, packageUrl := range packageUrls {
857860
resp, _, err := nc.rtManager.Client().SendHead(packageUrl, &nc.httpClientDetails)
858861
if err != nil {
@@ -1161,32 +1164,21 @@ func getDockerNameScopeAndVersion(id, artiUrl, repo string) (downloadUrls []stri
11611164
}
11621165

11631166
id = strings.TrimPrefix(id, "docker://")
1164-
var lastColonIndex int
1165-
if strings.Contains(id, ":sha256:") {
1166-
sha256Index := strings.Index(id, ":sha256:")
1167-
if sha256Index > 0 {
1168-
lastColonIndex = sha256Index
1169-
} else {
1170-
lastColonIndex = strings.LastIndex(id, ":")
1171-
}
1172-
} else {
1173-
lastColonIndex = strings.LastIndex(id, ":")
1174-
}
11751167

1176-
if lastColonIndex > 0 {
1177-
name = id[:lastColonIndex]
1178-
version = id[lastColonIndex+1:]
1168+
if idx := strings.Index(id, ":sha256:"); idx > 0 {
1169+
name, version = id[:idx], id[idx+1:]
1170+
} else if idx := strings.LastIndex(id, ":"); idx > 0 {
1171+
name, version = id[:idx], id[idx+1:]
11791172
} else {
1180-
name = id
1181-
version = "latest"
1173+
name, version = id, "latest"
11821174
}
11831175

11841176
if artiUrl != "" && repo != "" {
11851177
downloadUrls = []string{fmt.Sprintf("%s/api/docker/%s/v2/%s/manifests/%s",
11861178
strings.TrimSuffix(artiUrl, "/"), repo, name, version)}
11871179
}
11881180

1189-
return
1181+
return downloadUrls, name, scope, version
11901182
}
11911183

11921184
func GetCurationOutputFormat(formatFlagVal string) (format outFormat.OutputFormat, err error) {

sca/bom/buildinfo/technologies/docker/docker.go

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package docker
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"os/exec"
76
"regexp"
8-
"runtime"
97
"strings"
108

119
"github.com/jfrog/jfrog-cli-core/v2/common/project"
@@ -24,19 +22,10 @@ type DockerImageInfo struct {
2422
Tag string
2523
}
2624

27-
type dockerManifestList struct {
28-
Manifests []struct {
29-
Digest string `json:"digest"`
30-
Platform struct {
31-
Architecture string `json:"architecture"`
32-
OS string `json:"os"`
33-
} `json:"platform"`
34-
} `json:"manifests"`
35-
}
36-
3725
var (
3826
jfrogSubdomainPattern = regexp.MustCompile(`^([a-zA-Z0-9]+)-([a-zA-Z0-9-]+)\.jfrog\.io$`)
3927
ipAddressPattern = regexp.MustCompile(`^\d+\.`)
28+
hexDigestPattern = regexp.MustCompile(`[a-fA-F0-9]{64}`)
4029
)
4130

4231
func ParseDockerImage(imageName string) (*DockerImageInfo, error) {
@@ -141,33 +130,34 @@ func BuildDependencyTree(params technologies.BuildInfoBomGeneratorParams) ([]*xr
141130
}
142131

143132
func getArchDigestUsingDocker(fullImageName string) (string, error) {
144-
cmd := exec.Command("docker", "buildx", "imagetools", "inspect", "--raw", fullImageName)
145-
output, err := cmd.CombinedOutput()
133+
log.Debug(fmt.Sprintf("Pulling Docker image: %s", fullImageName))
134+
pullCmd := exec.Command("docker", "pull", fullImageName)
135+
output, err := pullCmd.CombinedOutput()
146136
if err != nil {
147137
outputStr := string(output)
148-
if strings.Contains(outputStr, "403") || strings.Contains(outputStr, "Forbidden") {
149-
return "", nil
138+
if strings.Contains(outputStr, "curation service") {
139+
return extractDigestFromBlockedMessage(outputStr), nil
150140
}
151-
return "", fmt.Errorf("%s", strings.TrimSpace(outputStr))
141+
return "", fmt.Errorf("docker pull failed: %s", strings.TrimSpace(outputStr))
152142
}
153143

154-
var manifestList dockerManifestList
155-
if err := json.Unmarshal(output, &manifestList); err != nil {
144+
inspectCmd := exec.Command("docker", "inspect", fullImageName, "--format", "{{index .RepoDigests 0}}")
145+
output, err = inspectCmd.CombinedOutput()
146+
if err != nil {
156147
return "", nil
157148
}
158-
159-
if len(manifestList.Manifests) == 0 {
160-
return "", nil
149+
repoDigest := strings.TrimSpace(string(output))
150+
if idx := strings.Index(repoDigest, "@"); idx > 0 {
151+
return repoDigest[idx+1:], nil
161152
}
153+
return "", nil
154+
}
162155

163-
currentArch := runtime.GOARCH
164-
for _, manifest := range manifestList.Manifests {
165-
if manifest.Platform.Architecture == currentArch && manifest.Digest != "" {
166-
return manifest.Digest, nil
167-
}
156+
func extractDigestFromBlockedMessage(output string) string {
157+
if match := hexDigestPattern.FindString(output); match != "" {
158+
return "sha256:" + match
168159
}
169-
170-
return "", nil
160+
return ""
171161
}
172162

173163
func GetDockerRepositoryConfig(serverDetails *config.ServerDetails, imageName string) (*project.RepositoryConfig, error) {

0 commit comments

Comments
 (0)