1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-03-27 21:49:15 +02:00

feat(cnbBuild): warn users when dockerConfigJSON is missing necessary credentials (#5007)

* feat(cnbBuild): warn users when dockerConfigJSON is missing necessary credentials

* Update cmd/cnbBuild.go

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>

* Update pkg/cnbutils/auth.go

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>

* fix linting

---------

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
This commit is contained in:
Pavel Busko 2024-08-15 10:20:01 +02:00 committed by GitHub
parent 61b9df54b1
commit 98e4e01635
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 154 additions and 85 deletions

View File

@ -2,10 +2,11 @@ package cmd
import (
"encoding/json"
"time"
"github.com/SAP/jenkins-library/pkg/ans"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"time"
)
func ansSendEvent(config ansSendEventOptions, telemetryData *telemetry.CustomData) {

View File

@ -148,7 +148,7 @@ func validateAppName(appName string) error {
}
message = append(message, fmt.Sprintf("Please change the name to fit this requirement(s). For more details please visit %s.", docuLink))
if fail {
return fmt.Errorf(strings.Join(message, " "))
return errors.New(strings.Join(message, " "))
}
return nil
}

View File

@ -168,18 +168,17 @@ func cleanDir(dir string, utils cnbutils.BuildUtils) error {
}
func extractZip(source, target string) error {
if isZip(source) {
log.Entry().Infof("Extracting archive '%s' to '%s'", source, target)
_, err := piperutils.Unzip(source, target)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "Extracting archive '%s' to '%s' failed", source, target)
}
} else {
if !isZip(source) {
log.SetErrorCategory(log.ErrorBuild)
return errors.New("application path must be a directory or zip")
}
log.Entry().Infof("Extracting archive '%s' to '%s'", source, target)
_, err := piperutils.Unzip(source, target)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "Extracting archive '%s' to '%s' failed", source, target)
}
return nil
}
@ -537,12 +536,6 @@ func runCnbBuild(config *cnbBuildOptions, telemetry *buildpacks.Telemetry, image
}
}
cnbRegistryAuth, err := cnbutils.GenerateCnbAuth(config.DockerConfigJSON, utils)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrap(err, "failed to generate CNB_REGISTRY_AUTH")
}
if len(config.CustomTLSCertificateLinks) > 0 {
caCertificates := "/tmp/ca-certificates.crt"
_, err := utils.Copy("/etc/ssl/certs/ca-certificates.crt", caCertificates)
@ -558,7 +551,17 @@ func runCnbBuild(config *cnbBuildOptions, telemetry *buildpacks.Telemetry, image
log.Entry().Info("skipping certificates update")
}
utils.AppendEnv([]string{fmt.Sprintf("CNB_REGISTRY_AUTH=%s", cnbRegistryAuth)})
dockerKeychain, err := cnbutils.ParseDockerConfig(config.DockerConfigJSON, utils)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrap(err, "failed to parse dockerConfigJSON")
}
cnbAuthString, err := dockerKeychain.ToCNBString()
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrap(err, "failed to generate CNB_REGISTRY_AUTH")
}
utils.AppendEnv([]string{fmt.Sprintf("CNB_REGISTRY_AUTH=%s", cnbAuthString)})
utils.AppendEnv([]string{fmt.Sprintf("CNB_PLATFORM_API=%s", platformAPIVersion)})
creatorArgs := []string{
@ -574,6 +577,10 @@ func runCnbBuild(config *cnbBuildOptions, telemetry *buildpacks.Telemetry, image
}
if config.RunImage != "" {
if !dockerKeychain.AuthExistsForImage(config.RunImage) {
log.Entry().Warnf("provided dockerConfigJSON does not contain credentials for the run-image %q, anonymous auth will be used", config.RunImage)
}
creatorArgs = append(creatorArgs, "-run-image", config.RunImage)
}
@ -582,6 +589,9 @@ func runCnbBuild(config *cnbBuildOptions, telemetry *buildpacks.Telemetry, image
}
containerImage := path.Join(targetImage.ContainerRegistry.Host, targetImage.ContainerImageName)
if !dockerKeychain.AuthExistsForImage(containerImage) {
log.Entry().Warnf("provided dockerConfigJSON does not contain credentials for the target image %q, anonymous auth will be used", containerImage)
}
for _, tag := range config.AdditionalTags {
target := fmt.Sprintf("%s:%s", containerImage, tag)
if !piperutils.ContainsString(creatorArgs, target) {

View File

@ -504,7 +504,7 @@ func TestRunCnbBuild(t *testing.T) {
addBuilderFiles(&utils)
err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
assert.EqualError(t, err, "failed to generate CNB_REGISTRY_AUTH: json: cannot unmarshal string into Go struct field ConfigFile.auths of type types.AuthConfig")
assert.EqualError(t, err, "failed to parse dockerConfigJSON: json: cannot unmarshal string into Go struct field ConfigFile.auths of type types.AuthConfig")
})
t.Run("error case: DockerConfigJSON file not there (config.json)", func(t *testing.T) {

View File

@ -10,6 +10,7 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
type contrastExecuteScanUtils interface {
@ -107,7 +108,7 @@ func runContrastExecuteScan(config *contrastExecuteScanOptions, telemetryData *t
if unaudited > config.VulnerabilityThresholdTotal {
msg := fmt.Sprintf("Your application %v in organization %v is not compliant. Total unaudited issues are %v which is greater than the VulnerabilityThresholdTotal count %v",
config.ApplicationID, config.OrganizationID, unaudited, config.VulnerabilityThresholdTotal)
return reports, fmt.Errorf(msg)
return reports, errors.New(msg)
}
}
}

View File

@ -845,7 +845,7 @@ func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOpt
}
if len(errorsOccured) > 0 {
return fmt.Errorf(strings.Join(errorsOccured, ": "))
return errors.New(strings.Join(errorsOccured, ": "))
}
return nil

View File

@ -3,10 +3,11 @@ package cmd
import (
"bytes"
"encoding/json"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/evanphx/json-patch"
jsonpatch "github.com/evanphx/json-patch"
)
func jsonApplyPatch(config jsonApplyPatchOptions, telemetryData *telemetry.CustomData) {

View File

@ -3,6 +3,11 @@ package cmd
import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
piperDocker "github.com/SAP/jenkins-library/pkg/docker"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
@ -11,10 +16,6 @@ import (
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/toolrecord"
"github.com/pkg/errors"
"io"
"os"
"strings"
"time"
)
type malwareScanUtils interface {

View File

@ -1,12 +1,13 @@
package cmd
import (
"os"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/SAP/jenkins-library/pkg/piperutils"
"os"
"github.com/SAP/jenkins-library/pkg/telemetry"
)

View File

@ -2,13 +2,14 @@ package cmd
import (
"fmt"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/SAP/jenkins-library/pkg/telemetry"
"path/filepath"
"strconv"
"strings"
"unicode"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/SAP/jenkins-library/pkg/telemetry"
)
func mavenExecuteIntegration(config mavenExecuteIntegrationOptions, _ *telemetry.CustomData) {

View File

@ -1,10 +1,11 @@
package cmd
import (
"strconv"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/SAP/jenkins-library/pkg/telemetry"
"strconv"
)
func mavenExecuteStaticCodeChecks(config mavenExecuteStaticCodeChecksOptions, telemetryData *telemetry.CustomData) {

View File

@ -2,14 +2,15 @@ package cmd
import (
"fmt"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/pkg/errors"
"io"
"net/http"
"os"
"path/filepath"
"strings"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/pkg/errors"
b64 "encoding/base64"
"github.com/SAP/jenkins-library/pkg/command"

View File

@ -8,13 +8,14 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"path"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/spf13/cobra"
"io"
"os"
"path"
)
// ReadPipelineEnv reads the commonPipelineEnvironment from disk and outputs it as JSON

View File

@ -1,9 +1,10 @@
package cmd
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCpeEncryption(t *testing.T) {

View File

@ -3,6 +3,7 @@ package cmd
import (
"bytes"
"fmt"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"

View File

@ -2,9 +2,10 @@ package cmd
import (
"fmt"
"os"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/spf13/cobra"
"os"
)
// GitCommit ...

View File

@ -308,7 +308,7 @@ func checkAndReportScanResults(ctx context.Context, config *ScanOptions, scan *w
}
if len(checkErrors) > 0 {
return reportPaths, fmt.Errorf(strings.Join(checkErrors, ": "))
return reportPaths, errors.New(strings.Join(checkErrors, ": "))
}
return reportPaths, nil
}
@ -674,7 +674,7 @@ func checkSecurityViolations(ctx context.Context, config *ScanOptions, scan *ws.
log.Entry().Debugf("Aggregated %v alerts for scanned projects", len(allAlerts))
}
reportPaths, errors := reportGitHubIssuesAndCreateReports(
reportPaths, e := reportGitHubIssuesAndCreateReports(
ctx,
config,
utils,
@ -686,13 +686,13 @@ func checkSecurityViolations(ctx context.Context, config *ScanOptions, scan *ws.
vulnerabilitiesCount,
)
allOccurredErrors = append(allOccurredErrors, errors...)
allOccurredErrors = append(allOccurredErrors, e...)
if len(allOccurredErrors) > 0 {
if vulnerabilitiesCount > 0 {
log.SetErrorCategory(log.ErrorCompliance)
}
return reportPaths, fmt.Errorf(strings.Join(allOccurredErrors, ": "))
return reportPaths, errors.New(strings.Join(allOccurredErrors, ": "))
}
return reportPaths, nil

View File

@ -8,11 +8,12 @@ import (
b64 "encoding/base64"
"encoding/json"
"fmt"
"github.com/SAP/jenkins-library/pkg/config"
"io"
"os"
"path/filepath"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperenv"
"github.com/spf13/cobra"

2
go.mod
View File

@ -22,6 +22,7 @@ require (
github.com/buildpacks/lifecycle v0.18.4
github.com/cloudevents/sdk-go/v2 v2.10.1
github.com/docker/cli v24.0.6+incompatible
github.com/docker/docker v24.0.7+incompatible
github.com/evanphx/json-patch v5.7.0+incompatible
github.com/getsentry/sentry-go v0.26.0
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
@ -159,7 +160,6 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect

View File

@ -7,50 +7,76 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
"github.com/docker/docker/registry"
)
func GenerateCnbAuth(config string, utils BuildUtils) (string, error) {
var err error
dockerConfig := &configfile.ConfigFile{}
type DockerKeychain struct {
dockerConfig *configfile.ConfigFile
}
func (dk *DockerKeychain) ToCNBString() (string, error) {
if dk.dockerConfig == nil || len(dk.dockerConfig.GetAuthConfigs()) == 0 {
return "{}", nil
}
cnbAuth := map[string]string{}
for reg, authConf := range dk.dockerConfig.GetAuthConfigs() {
registryHostname := registry.ConvertToHostname(reg)
log.Entry().Debugf("adding credentials for registry %q", registryHostname)
if authConf.RegistryToken != "" {
cnbAuth[registryHostname] = fmt.Sprintf("Bearer %s", authConf.RegistryToken)
continue
}
if authConf.Auth != "" {
cnbAuth[registryHostname] = fmt.Sprintf("Basic %s", authConf.Auth)
continue
}
if authConf.Username == "" && authConf.Password == "" {
log.Entry().Warnf("docker config.json contains empty credentials for registry %q. Either 'auth' or 'username' and 'password' have to be provided.", registryHostname)
continue
}
cnbAuth[registryHostname] = fmt.Sprintf("Basic %s", encodeAuth(authConf.Username, authConf.Password))
}
cnbAuthBytes, err := json.Marshal(&cnbAuth)
return string(cnbAuthBytes), err
}
func (dk *DockerKeychain) AuthExistsForImage(image string) bool {
var empty types.AuthConfig
conf, err := dk.dockerConfig.GetAuthConfig(registry.ConvertToHostname(image))
if err != nil {
log.Entry().Errorf("failed to get auth config for the image %q, error: %s", image, err.Error())
}
return conf != empty
}
func ParseDockerConfig(config string, utils BuildUtils) (*DockerKeychain, error) {
keychain := &DockerKeychain{
dockerConfig: &configfile.ConfigFile{},
}
if config != "" {
log.Entry().Debugf("using docker config file %q", config)
dockerConfigJSON, err := utils.FileRead(config)
if err != nil {
return "", err
return nil, err
}
err = json.Unmarshal(dockerConfigJSON, dockerConfig)
err = json.Unmarshal(dockerConfigJSON, keychain.dockerConfig)
if err != nil {
return "", err
return nil, err
}
}
auth := map[string]string{}
for registry, value := range dockerConfig.AuthConfigs {
if value.Auth == "" && value.Username == "" && value.Password == "" {
log.Entry().Warnf("docker config.json contains empty credentials for registry %q. Either 'auth' or 'username' and 'password' have to be provided.", registry)
continue
}
if value.Auth == "" {
value.Auth = encodeAuth(value.Username, value.Password)
}
log.Entry().Debugf("Adding credentials for: registry %q", registry)
auth[registry] = fmt.Sprintf("Basic %s", value.Auth)
}
if len(auth) == 0 {
log.Entry().Warn("docker config file is empty!")
}
cnbRegistryAuth, err := json.Marshal(auth)
if err != nil {
return "", err
}
return string(cnbRegistryAuth), nil
return keychain, nil
}
func encodeAuth(username, password string) string {

View File

@ -18,41 +18,61 @@ func TestGenerateCnbAuth(t *testing.T) {
}
t.Run("successfully generates cnb auth env variable", func(t *testing.T) {
mockUtils.AddFile("/test/valid_config.json", []byte("{\"auths\":{\"example.com\":{\"auth\":\"dXNlcm5hbWU6cGFzc3dvcmQ=\"}}}"))
auth, err := cnbutils.GenerateCnbAuth("/test/valid_config.json", mockUtils)
mockUtils.AddFile("/test/valid_config.json", []byte("{\"auths\":{\"https://example.com/\":{\"auth\":\"dXNlcm5hbWU6cGFzc3dvcmQ=\"}}}"))
auth, err := cnbutils.ParseDockerConfig("/test/valid_config.json", mockUtils)
assert.NoError(t, err)
assert.Equal(t, "{\"example.com\":\"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\"}", auth)
authString, err := auth.ToCNBString()
assert.NoError(t, err)
assert.Equal(t, "{\"example.com\":\"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\"}", authString)
assert.True(t, auth.AuthExistsForImage("example.com/foo/bar:123"))
assert.False(t, auth.AuthExistsForImage("docker.io/foo/bar"))
})
t.Run("successfully generates cnb auth env variable from username and password", func(t *testing.T) {
mockUtils.AddFile("/test/valid_config.json", []byte("{\"auths\":{\"example.com\":{\"username\":\"username\",\"password\":\"password\"}}}"))
auth, err := cnbutils.GenerateCnbAuth("/test/valid_config.json", mockUtils)
auth, err := cnbutils.ParseDockerConfig("/test/valid_config.json", mockUtils)
assert.NoError(t, err)
assert.Equal(t, "{\"example.com\":\"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\"}", auth)
authString, err := auth.ToCNBString()
assert.NoError(t, err)
assert.Equal(t, "{\"example.com\":\"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\"}", authString)
assert.True(t, auth.AuthExistsForImage("example.com/foo/bar:123"))
assert.False(t, auth.AuthExistsForImage("docker.io/foo/bar"))
})
t.Run("skips registry with empty credentials", func(t *testing.T) {
mockUtils.AddFile("/test/valid_config.json", []byte("{\"auths\":{\"example.com\":{}}}"))
auth, err := cnbutils.GenerateCnbAuth("/test/valid_config.json", mockUtils)
auth, err := cnbutils.ParseDockerConfig("/test/valid_config.json", mockUtils)
assert.NoError(t, err)
assert.Equal(t, "{}", auth)
authString, err := auth.ToCNBString()
assert.NoError(t, err)
assert.Equal(t, "{}", authString)
assert.False(t, auth.AuthExistsForImage("example.com/foo/bar:123"))
assert.False(t, auth.AuthExistsForImage("docker.io/foo/bar"))
})
t.Run("successfully generates cnb auth env variable if docker config is not present", func(t *testing.T) {
auth, err := cnbutils.GenerateCnbAuth("", mockUtils)
auth, err := cnbutils.ParseDockerConfig("", mockUtils)
assert.NoError(t, err)
assert.Equal(t, "{}", auth)
authString, err := auth.ToCNBString()
assert.NoError(t, err)
assert.Equal(t, "{}", authString)
assert.False(t, auth.AuthExistsForImage("example.com/foo/bar:123"))
assert.False(t, auth.AuthExistsForImage("docker.io/foo/bar"))
})
t.Run("fails if file not found", func(t *testing.T) {
_, err := cnbutils.GenerateCnbAuth("/not/found", mockUtils)
_, err := cnbutils.ParseDockerConfig("/not/found", mockUtils)
assert.Error(t, err)
assert.Equal(t, "could not read '/not/found'", err.Error())
})
t.Run("fails if file is invalid json", func(t *testing.T) {
mockUtils.AddFile("/test/invalid_config.json", []byte("not a json"))
_, err := cnbutils.GenerateCnbAuth("/test/invalid_config.json", mockUtils)
_, err := cnbutils.ParseDockerConfig("/test/invalid_config.json", mockUtils)
assert.Error(t, err)
assert.Equal(t, "invalid character 'o' in literal null (expecting 'u')", err.Error())
})