From b1bb1ac4c919d9ea3542a45a7ad28520f5d14e3a Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 4 Sep 2024 14:25:22 +0200 Subject: [PATCH 01/54] x --- pkg/sign/notary.go | 145 ++++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 93d0927e501b..dff95cce47c2 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -2,16 +2,19 @@ package sign import ( "bytes" + "crypto/tls" + "encoding/base64" "encoding/json" "fmt" - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" - "gopkg.in/yaml.v3" "io" "net/http" "os" "strings" "time" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + "gopkg.in/yaml.v3" ) const ( @@ -57,10 +60,13 @@ type AuthSecretConfig struct { // SignifySecret contains configuration of secret that is used to connect to SAP signify service type SignifySecret struct { - // URL to the SAP signify endpoint - Endpoint string `yaml:"endpoint" json:"endpoint"` - // Actual secret data that will be pushed on sign request. Assumed its in JSON format - Payload string `yaml:"payload" json:"payload"` + CreatedAt time.Time `json:"createdAt"` + TokenURL string `json:"tokenURL"` + CertServiceURL string `json:"certServiceURL"` + ClientID string `json:"clientID"` + CertficateData string `json:"certData"` + PrivateKeyData string `json:"privateKeyData"` + KeyPassword string `json:"password"` } // SigningRequest contains information about all images with tags to sign using Notary @@ -75,60 +81,33 @@ type SigningRequest struct { Version string `json:"version"` } -// AuthFunc is a small middleware function that intercepts http.Request with specific authentication method -type AuthFunc func(r *http.Request) *http.Request - // NotarySigner is a struct that implements sign.Signer interface // Takes care of signing requests to Notary server type NotarySigner struct { - authFunc AuthFunc c http.Client url string retryTimeout time.Duration } -// AuthToken mutates created request, so it contains bearer token of authorized user -// Serves as middleware function before sending request -func AuthToken(token string) AuthFunc { - return func(r *http.Request) *http.Request { - r.Header.Add("Authorization", "Token "+token) - return r +func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { + // Decode the certificate and private key from base64 + certData, err := base64.StdEncoding.DecodeString(ss.CertficateData) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to decode certificate data: %w", err) } -} -// SignifyAuth fetches the JWT token from provided API endpoint in configuration file -// then returns a function that populates this JWT token as Bearer in the request -func SignifyAuth(s SignifySecret) (AuthFunc, error) { - endpoint := s.Endpoint - payload := s.Payload - resp, err := http.Post(endpoint, "application/json", strings.NewReader(payload)) + keyData, err := base64.StdEncoding.DecodeString(ss.PrivateKeyData) if err != nil { - return nil, err - } - defer resp.Body.Close() - var t struct { - AccessToken struct { - Token string `json:"token"` - } `json:"access_token"` + return tls.Certificate{}, fmt.Errorf("failed to decode private key data: %w", err) } - rBody, err := io.ReadAll(resp.Body) + // Load the certificate and key + cert, err := tls.X509KeyPair(certData, keyData) if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - return nil, ErrBadResponse{status: resp.Status, message: string(rBody)} + return tls.Certificate{}, fmt.Errorf("failed to load certificate and key: %w", err) } - if err := json.Unmarshal(rBody, &t); err != nil { - return nil, err - } - - token := t.AccessToken.Token - return func(r *http.Request) *http.Request { - r.Header.Add("Authorization", "Bearer "+token) - return r - }, nil + return cert, nil } func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { @@ -162,88 +141,120 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e } func (ns NotarySigner) Sign(images []string) error { + // Build the signing request sImg := strings.Join(images, ", ") sr, err := ns.buildSigningRequest(images) if err != nil { return fmt.Errorf("build sign request: %w", err) } - b, err := json.Marshal(sr) + + // Marshal the signing request to JSON + b, err := json.Marshal(map[string]interface{}{ + "trustedCollections": sr, + }) if err != nil { return fmt.Errorf("marshal signing request: %w", err) } + + // Create a new POST request with the signing payload req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) if err != nil { return err } req.Header.Add("Content-Type", "application/json") - if ns.authFunc != nil { - req = ns.authFunc(req) - } + // Retry logic for sending the request retries := 5 - var status string var respMsg []byte + var status string w := time.NewTicker(ns.retryTimeout) defer w.Stop() + for retries > 0 { - fmt.Printf("Trying to sign %s. %v retries remaining...\n", sImg, retries) - // wait for ticker to run + fmt.Printf("Trying to sign %s. %d retries remaining...\n", sImg, retries) <-w.C + + // Send the HTTP request resp, err := ns.c.Do(req) if err != nil { return fmt.Errorf("notary request: %w", err) } - status = resp.Status + defer resp.Body.Close() + + // Read the response body respMsg, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("body read: %w", err) } + status = resp.Status + + // Handle different response statuses switch resp.StatusCode { - case http.StatusOK: - // response was fine. Do not need anything else + case http.StatusAccepted: fmt.Printf("Successfully signed images %s!\n", sImg) return nil - case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: + case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest: return ErrBadResponse{status: status, message: string(respMsg)} } + retries-- } + + // After retries, return the error if signing fails fmt.Println("Reached all retries. Stopping.") return ErrBadResponse{status: status, message: string(respMsg)} } -func (nc NotaryConfig) NewSigner() (Signer, error) { +func (nc NotaryConfig) NewSigner() (*NotarySigner, error) { var ns NotarySigner - // (@Ressetkk): Should this be loaded before calling signer? + // Load the secret file and initialize the signer if nc.Secret != nil { f, err := os.ReadFile(nc.Secret.Path) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to read secret file: %w", err) } + switch nc.Secret.Type { - case "token": - ns.authFunc = AuthToken(string(f)) case "signify": + // Unmarshal the YAML secret file into SignifySecret struct var s SignifySecret err := yaml.Unmarshal(f, &s) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to unmarshal signify secret: %w", err) } - auth, err := SignifyAuth(s) + + // Decode certificate and key from base64 + cert, err := s.DecodeCertAndKey() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to decode cert and key: %w", err) + } + + // Set up the TLS configuration with the decoded cert + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + // Create an HTTP client with TLS config + ns.c = http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + Timeout: nc.Timeout, } - ns.authFunc = auth default: return nil, ErrAuthServiceNotSupported{Service: nc.Secret.Type} } } + + // Set retry timeout ns.retryTimeout = 10 * time.Second if nc.RetryTimeout > 0 { ns.retryTimeout = nc.RetryTimeout } - ns.c.Timeout = nc.Timeout + + // Set the Notary server URL ns.url = nc.Endpoint - return ns, nil + + return &ns, nil } From 4cf206b92dbc2d910491382de83cb9f16fb71ef2 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 4 Sep 2024 14:34:54 +0200 Subject: [PATCH 02/54] x --- pkg/sign/sign.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sign/sign.go b/pkg/sign/sign.go index 3fd148024a20..32d7b28511b7 100644 --- a/pkg/sign/sign.go +++ b/pkg/sign/sign.go @@ -2,6 +2,7 @@ package sign import ( "fmt" + "gopkg.in/yaml.v3" ) @@ -58,7 +59,6 @@ func (sc *SignerConfig) UnmarshalYAML(value *yaml.Node) error { if err := value.Decode(&c); err != nil { return err } - sc.Config = c.Config default: return ErrBackendNotSupported{Type: t.Type} } From 3d68a36fbf8a1eaa92f389295874fee7c47877e9 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 4 Sep 2024 15:01:03 +0200 Subject: [PATCH 03/54] x --- pkg/sign/notary.go | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index dff95cce47c2..90ae52a573a1 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -125,17 +125,22 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e if err != nil { return nil, fmt.Errorf("ref parse: %w", err) } - i, err := remote.Image(ref) + img, err := remote.Image(ref) if err != nil { return nil, fmt.Errorf("get image: %w", err) } - m, err := i.Manifest() + m, err := img.Manifest() if err != nil { return nil, fmt.Errorf("image manifest: %w", err) } sha := m.Config.Digest.Hex size := m.Config.Size - sr = append(sr, SigningRequest{NotaryGun: base, Version: tag, ByteSize: size, SHA256: sha}) + sr = append(sr, SigningRequest{ + NotaryGun: base, + Version: tag, + ByteSize: size, + SHA256: sha, + }) } return sr, nil } @@ -148,10 +153,24 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("build sign request: %w", err) } - // Marshal the signing request to JSON - b, err := json.Marshal(map[string]interface{}{ - "trustedCollections": sr, - }) + // Build the Signify API payload + payload := map[string]interface{}{ + "trustedCollections": []map[string]interface{}{ + { + "gun": sr[0].NotaryGun, // Example: "example.repo/image-project-2" + "targets": []map[string]interface{}{ + { + "name": sr[0].Version, // Example: "1.0.1" + "byteSize": sr[0].ByteSize, // Size of the image's manifest + "digest": sr[0].SHA256, // SHA-256 of the image's manifest + }, + }, + }, + }, + } + + // Marshal the payload to JSON + b, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal signing request: %w", err) } @@ -254,7 +273,7 @@ func (nc NotaryConfig) NewSigner() (*NotarySigner, error) { } // Set the Notary server URL - ns.url = nc.Endpoint + ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" return &ns, nil } From 7e4e8c2da39136f1d1cb7fd1532f570b622ef2e5 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 4 Sep 2024 23:31:06 +0200 Subject: [PATCH 04/54] x --- pkg/sign/sign.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/sign/sign.go b/pkg/sign/sign.go index 32d7b28511b7..1e73f5220252 100644 --- a/pkg/sign/sign.go +++ b/pkg/sign/sign.go @@ -27,7 +27,7 @@ type SignerConfig struct { // Config will be parsed based on this value. Type string `yaml:"type" json:"type"` // Config defines specific configuration for signing backend. - Config SignerFactory `yaml:"config" json:"config"` + Config NotaryConfig `yaml:"config" json:"config"` // JobType contains list of ProwJob types that should be supported. // If the list is empty, the signer is enabled for all job types. // Usable only in CI mode (CI=true) @@ -59,6 +59,7 @@ func (sc *SignerConfig) UnmarshalYAML(value *yaml.Node) error { if err := value.Decode(&c); err != nil { return err } + sc.Config = c.Config default: return ErrBackendNotSupported{Type: t.Type} } From 7deacb09e41fed67a92988577cc69fd3403d9a10 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 5 Sep 2024 00:00:59 +0200 Subject: [PATCH 05/54] x --- pkg/sign/notary.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 90ae52a573a1..b0cd654d1bb6 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -14,7 +14,6 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" - "gopkg.in/yaml.v3" ) const ( @@ -60,13 +59,13 @@ type AuthSecretConfig struct { // SignifySecret contains configuration of secret that is used to connect to SAP signify service type SignifySecret struct { - CreatedAt time.Time `json:"createdAt"` - TokenURL string `json:"tokenURL"` - CertServiceURL string `json:"certServiceURL"` - ClientID string `json:"clientID"` - CertficateData string `json:"certData"` - PrivateKeyData string `json:"privateKeyData"` - KeyPassword string `json:"password"` + CreatedAt float64 `json:"createdAt"` + TokenURL string `json:"tokenURL"` + CertServiceURL string `json:"certServiceURL"` + ClientID string `json:"clientID"` + CertficateData string `json:"certData"` + PrivateKeyData string `json:"privateKeyData"` + KeyPassword string `json:"password"` } // SigningRequest contains information about all images with tags to sign using Notary @@ -238,7 +237,7 @@ func (nc NotaryConfig) NewSigner() (*NotarySigner, error) { case "signify": // Unmarshal the YAML secret file into SignifySecret struct var s SignifySecret - err := yaml.Unmarshal(f, &s) + err := json.Unmarshal(f, &s) if err != nil { return nil, fmt.Errorf("failed to unmarshal signify secret: %w", err) } From d2ef8383123bf0b30c057fde36ce415fb8594d43 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 5 Sep 2024 10:41:35 +0200 Subject: [PATCH 06/54] cert probably working --- cmd/image-builder/main.go | 2 + go.mod | 10 ++- go.sum | 63 +++++++++++++++-- pkg/sign/notary.go | 138 ++++++++++++++++---------------------- 4 files changed, 124 insertions(+), 89 deletions(-) diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 8d9fd4b0fee5..2548253cffd1 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -533,6 +533,8 @@ func signImages(o *options, images []string) error { orgRepo = org + "/" + repo } } + orgRepo = "kyma-project/kyma" + o.isCI = true if len(orgRepo) == 0 { return fmt.Errorf("'orgRepo' cannot be empty") } diff --git a/go.mod b/go.mod index ebac250d4b94..fb5fd686f466 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tidwall/gjson v1.17.3 github.com/zricethezav/gitleaks/v8 v8.18.4 + go.step.sm/crypto v0.51.2 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.28.0 @@ -59,9 +60,12 @@ require ( cloud.google.com/go/longrunning v0.5.12 // indirect contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/BobuSumisu/aho-corasick v1.0.3 // indirect github.com/GoogleCloudPlatform/testgrid v0.0.123 // indirect github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/andygrunwald/go-jira v1.14.0 // indirect github.com/aws/aws-sdk-go v1.38.49 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -90,6 +94,7 @@ require ( github.com/fvbommel/sortorder v1.0.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gitleaks/go-gitdiff v0.9.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -163,6 +168,7 @@ require ( github.com/rs/zerolog v1.29.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect github.com/shurcooL/githubv4 v0.0.0-20211117020012-5800b9de5b8b // indirect github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -176,8 +182,8 @@ require ( github.com/trivago/tgo v1.0.7 // indirect github.com/vbatts/tar-split v0.11.3 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect diff --git a/go.sum b/go.sum index 3a5d25d1bc68..20e6f6a98d4f 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEw cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= -cloud.google.com/go/kms v1.18.5 h1:75LSlVs60hyHK3ubs2OHd4sE63OAMcM2BdSJc2bkuM4= -cloud.google.com/go/kms v1.18.5/go.mod h1:yXunGUGzabH8rjUPImp2ndHiGolHeWJJ0LODLedicIY= +cloud.google.com/go/kms v1.19.0 h1:x0OVJDl6UH1BSX4THKlMfdcFWoE4ruh90ZHuilZekrU= +cloud.google.com/go/kms v1.19.0/go.mod h1:e4imokuPJUc17Trz2s6lEXFDt8bgDmvpVynH39bdrHM= cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE= @@ -71,6 +71,8 @@ contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= @@ -93,8 +95,12 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJ github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -231,6 +237,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -425,10 +433,12 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -508,6 +518,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63QvybxhW1iBmQWEaCKdc4SKgALfTNZ+OphU= github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -515,6 +526,7 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -602,6 +614,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/githubv4 v0.0.0-20211117020012-5800b9de5b8b h1:SAQLigkf0rd6emglkR1lRKRB9coWjib5OxnHmV1ZiFs= github.com/shurcooL/githubv4 v0.0.0-20211117020012-5800b9de5b8b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk= @@ -612,11 +626,14 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -671,6 +688,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zricethezav/gitleaks/v8 v8.18.4 h1:mWOfVGO8ksok21iOb7h4DZMcUxyvsol8l6o1uNOQxww= github.com/zricethezav/gitleaks/v8 v8.18.4/go.mod h1:3EFYK+ZNDHPNQinyZTVGHG7/sFsApEZ9DrCGA1AP63M= go.einride.tech/aip v0.67.1 h1:d/4TW92OxXBngkSOwWS2CH5rez869KpKMaN44mdxkFI= @@ -685,10 +703,10 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= @@ -698,6 +716,8 @@ go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8 go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.step.sm/crypto v0.51.2 h1:5EiCGIMg7IvQTGmJrwRosbXeprtT80OhoS/PJarg60o= +go.step.sm/crypto v0.51.2/go.mod h1:QK7czLjN2k+uqVp5CHXxJbhc70kVRSP+0CQF3zsR5M0= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -719,6 +739,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -757,6 +780,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -796,9 +821,14 @@ golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -824,6 +854,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -873,16 +905,28 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -893,6 +937,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -954,6 +1003,8 @@ golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index b0cd654d1bb6..ea7cbf8aafb5 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -12,8 +12,7 @@ import ( "strings" "time" - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" + "go.step.sm/crypto/pemutil" ) const ( @@ -83,85 +82,72 @@ type SigningRequest struct { // NotarySigner is a struct that implements sign.Signer interface // Takes care of signing requests to Notary server type NotarySigner struct { - c http.Client - url string - retryTimeout time.Duration + c http.Client + url string + retryTimeout time.Duration + signifySecret SignifySecret } +// DecodeCertAndKey loads the certificate and private key using smallstep/crypto and returns tls.Certificate func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { - // Decode the certificate and private key from base64 + // Parse the certificate from base64 encoded PEM data certData, err := base64.StdEncoding.DecodeString(ss.CertficateData) + cert, err := pemutil.ParseCertificate(certData) if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to decode certificate data: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to parse certificate: %w", err) } + // Parse the private key, handling password if necessary keyData, err := base64.StdEncoding.DecodeString(ss.PrivateKeyData) + key, err := pemutil.ParseKey(keyData, pemutil.WithPassword([]byte(ss.KeyPassword))) if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to decode private key data: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to parse private key: %w", err) } - // Load the certificate and key - cert, err := tls.X509KeyPair(certData, keyData) - if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to load certificate and key: %w", err) - } - - return cert, nil + // Return the tls.Certificate that can be used for mTLS + return tls.Certificate{ + Certificate: [][]byte{cert.Raw}, // The raw DER bytes of the certificate + PrivateKey: key, + }, nil } func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { var sr []SigningRequest for _, i := range images { var base, tag string - // Split on ":" - parts := strings.Split(i, tagDelim) - // Verify that we aren't confusing a tag for a hostname w/ port for the purposes of weak validation. - if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { - base = strings.Join(parts[:len(parts)-1], tagDelim) - tag = parts[len(parts)-1] - } - ref, err := name.ParseReference(i) - if err != nil { - return nil, fmt.Errorf("ref parse: %w", err) - } - img, err := remote.Image(ref) - if err != nil { - return nil, fmt.Errorf("get image: %w", err) - } - m, err := img.Manifest() - if err != nil { - return nil, fmt.Errorf("image manifest: %w", err) + parts := strings.Split(i, ":") + if len(parts) > 1 { + base = parts[0] + tag = parts[1] } - sha := m.Config.Digest.Hex - size := m.Config.Size + // Simulated logic for constructing SigningRequest (modify as per actual needs) sr = append(sr, SigningRequest{ NotaryGun: base, Version: tag, - ByteSize: size, - SHA256: sha, + ByteSize: 1000, // Example size + SHA256: "exampleSha256", }) } return sr, nil } +// Sign makes an HTTP request to sign the images using the Notary server func (ns NotarySigner) Sign(images []string) error { - // Build the signing request sImg := strings.Join(images, ", ") sr, err := ns.buildSigningRequest(images) if err != nil { return fmt.Errorf("build sign request: %w", err) } - // Build the Signify API payload payload := map[string]interface{}{ "trustedCollections": []map[string]interface{}{ { - "gun": sr[0].NotaryGun, // Example: "example.repo/image-project-2" + "gun": sr[0].NotaryGun, "targets": []map[string]interface{}{ { - "name": sr[0].Version, // Example: "1.0.1" - "byteSize": sr[0].ByteSize, // Size of the image's manifest - "digest": sr[0].SHA256, // SHA-256 of the image's manifest + "name": sr[0].Version, + "byteSize": sr[0].ByteSize, + "digest": sr[0].SHA256, }, }, }, @@ -171,7 +157,26 @@ func (ns NotarySigner) Sign(images []string) error { // Marshal the payload to JSON b, err := json.Marshal(payload) if err != nil { - return fmt.Errorf("marshal signing request: %w", err) + return fmt.Errorf("failed to marshal payload: %w", err) + } + + // Decode the certificate and key using the smallstep utility + cert, err := ns.signifySecret.DecodeCertAndKey() + if err != nil { + return fmt.Errorf("failed to load certificate and key: %w", err) + } + + // Configure TLS using the loaded certificate and private key + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, // Use the loaded cert and key directly + } + + // Create an HTTP client with TLS config + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + Timeout: ns.retryTimeout, } // Create a new POST request with the signing payload @@ -181,10 +186,8 @@ func (ns NotarySigner) Sign(images []string) error { } req.Header.Add("Content-Type", "application/json") - // Retry logic for sending the request retries := 5 var respMsg []byte - var status string w := time.NewTicker(ns.retryTimeout) defer w.Stop() @@ -193,7 +196,7 @@ func (ns NotarySigner) Sign(images []string) error { <-w.C // Send the HTTP request - resp, err := ns.c.Do(req) + resp, err := client.Do(req) if err != nil { return fmt.Errorf("notary request: %w", err) } @@ -202,31 +205,23 @@ func (ns NotarySigner) Sign(images []string) error { // Read the response body respMsg, err = io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("body read: %w", err) + return fmt.Errorf("failed to read response: %w", err) } - status = resp.Status - // Handle different response statuses - switch resp.StatusCode { - case http.StatusAccepted: - fmt.Printf("Successfully signed images %s!\n", sImg) + if resp.StatusCode == http.StatusAccepted { + fmt.Println("Successfully signed images") return nil - case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest: - return ErrBadResponse{status: status, message: string(respMsg)} } retries-- } - // After retries, return the error if signing fails - fmt.Println("Reached all retries. Stopping.") - return ErrBadResponse{status: status, message: string(respMsg)} + return fmt.Errorf("failed to sign images: %s", string(respMsg)) } func (nc NotaryConfig) NewSigner() (*NotarySigner, error) { var ns NotarySigner - // Load the secret file and initialize the signer if nc.Secret != nil { f, err := os.ReadFile(nc.Secret.Path) if err != nil { @@ -235,44 +230,25 @@ func (nc NotaryConfig) NewSigner() (*NotarySigner, error) { switch nc.Secret.Type { case "signify": - // Unmarshal the YAML secret file into SignifySecret struct var s SignifySecret err := json.Unmarshal(f, &s) if err != nil { return nil, fmt.Errorf("failed to unmarshal signify secret: %w", err) } - // Decode certificate and key from base64 - cert, err := s.DecodeCertAndKey() - if err != nil { - return nil, fmt.Errorf("failed to decode cert and key: %w", err) - } - - // Set up the TLS configuration with the decoded cert - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - } + ns.signifySecret = s - // Create an HTTP client with TLS config - ns.c = http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - Timeout: nc.Timeout, - } default: - return nil, ErrAuthServiceNotSupported{Service: nc.Secret.Type} + return nil, fmt.Errorf("unsupported secret type: %s", nc.Secret.Type) } } - // Set retry timeout ns.retryTimeout = 10 * time.Second if nc.RetryTimeout > 0 { ns.retryTimeout = nc.RetryTimeout } - // Set the Notary server URL - ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" + ns.url = "https://signing-manage.repositories.cloud.sap" return &ns, nil } From bcf7cc17feafc6b910efdc016a90c47ec4c42263 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 5 Sep 2024 11:30:21 +0200 Subject: [PATCH 07/54] test --- cmd/image-builder/main.go | 3 +-- pkg/sign/notary.go | 53 ++++++++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 2548253cffd1..0c777dc72762 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -533,8 +533,7 @@ func signImages(o *options, images []string) error { orgRepo = org + "/" + repo } } - orgRepo = "kyma-project/kyma" - o.isCI = true + if len(orgRepo) == 0 { return fmt.Errorf("'orgRepo' cannot be empty") } diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index ea7cbf8aafb5..7597c3fb2138 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -12,6 +12,8 @@ import ( "strings" "time" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" "go.step.sm/crypto/pemutil" ) @@ -115,18 +117,28 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e var sr []SigningRequest for _, i := range images { var base, tag string - parts := strings.Split(i, ":") - if len(parts) > 1 { - base = parts[0] - tag = parts[1] + // Split on ":" + parts := strings.Split(i, tagDelim) + // Verify that we aren't confusing a tag for a hostname w/ port for the purposes of weak validation. + if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { + base = strings.Join(parts[:len(parts)-1], tagDelim) + tag = parts[len(parts)-1] } - // Simulated logic for constructing SigningRequest (modify as per actual needs) - sr = append(sr, SigningRequest{ - NotaryGun: base, - Version: tag, - ByteSize: 1000, // Example size - SHA256: "exampleSha256", - }) + ref, err := name.ParseReference(i) + if err != nil { + return nil, fmt.Errorf("ref parse: %w", err) + } + i, err := remote.Image(ref) + if err != nil { + return nil, fmt.Errorf("get image: %w", err) + } + m, err := i.Manifest() + if err != nil { + return nil, fmt.Errorf("image manifest: %w", err) + } + sha := m.Config.Digest.Hex + size := m.Config.Size + sr = append(sr, SigningRequest{NotaryGun: base, Version: tag, ByteSize: size, SHA256: sha}) } return sr, nil } @@ -139,6 +151,7 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("build sign request: %w", err) } + // Prepare the payload to send in the request payload := map[string]interface{}{ "trustedCollections": []map[string]interface{}{ { @@ -166,28 +179,31 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("failed to load certificate and key: %w", err) } - // Configure TLS using the loaded certificate and private key + // Configure TLS with the decoded certificate and private key tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, // Use the loaded cert and key directly + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS10, + MaxVersion: tls.VersionTLS13, // or whichever version is required } - // Create an HTTP client with TLS config + // Create an HTTP client with TLS support client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, }, - Timeout: ns.retryTimeout, + Timeout: 3000 * time.Second, // Increase the timeout as necessary } // Create a new POST request with the signing payload req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) if err != nil { - return err + return fmt.Errorf("failed to create request: %w", err) } req.Header.Add("Content-Type", "application/json") retries := 5 var respMsg []byte + w := time.NewTicker(ns.retryTimeout) defer w.Stop() @@ -198,7 +214,7 @@ func (ns NotarySigner) Sign(images []string) error { // Send the HTTP request resp, err := client.Do(req) if err != nil { - return fmt.Errorf("notary request: %w", err) + return fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() @@ -208,6 +224,7 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("failed to read response: %w", err) } + // Check if the request was successful if resp.StatusCode == http.StatusAccepted { fmt.Println("Successfully signed images") return nil @@ -248,7 +265,7 @@ func (nc NotaryConfig) NewSigner() (*NotarySigner, error) { ns.retryTimeout = nc.RetryTimeout } - ns.url = "https://signing-manage.repositories.cloud.sap" + ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" return &ns, nil } From f6047c14cb4cf1751983f2208f7d1e596082363b Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 5 Sep 2024 11:38:43 +0200 Subject: [PATCH 08/54] build --- go.mod | 14 +++++-------- go.sum | 65 +++++++++------------------------------------------------- 2 files changed, 15 insertions(+), 64 deletions(-) diff --git a/go.mod b/go.mod index 13d38a54893e..4cbed75bc727 100644 --- a/go.mod +++ b/go.mod @@ -64,8 +64,6 @@ require ( github.com/BobuSumisu/aho-corasick v1.0.3 // indirect github.com/GoogleCloudPlatform/testgrid v0.0.123 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/andygrunwald/go-jira v1.14.0 // indirect github.com/aws/aws-sdk-go v1.38.49 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -94,7 +92,6 @@ require ( github.com/fvbommel/sortorder v1.0.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gitleaks/go-gitdiff v0.9.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -168,7 +165,6 @@ require ( github.com/rs/zerolog v1.29.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/shopspring/decimal v1.2.0 // indirect github.com/shurcooL/githubv4 v0.0.0-20211117020012-5800b9de5b8b // indirect github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -182,11 +178,11 @@ require ( github.com/trivago/tgo v1.0.7 // indirect github.com/vbatts/tar-split v0.11.3 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect go4.org v0.0.0-20201209231011-d4a079459e60 // indirect diff --git a/go.sum b/go.sum index f8cfe4ccb630..3e2ea1eb1d66 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEwDPgc= cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= -cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= -cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= +cloud.google.com/go/iam v1.2.0 h1:kZKMKVNk/IsSSc/udOb83K0hL/Yh/Gcqpz+oAkoIFN8= +cloud.google.com/go/iam v1.2.0/go.mod h1:zITGuWgsLZxd8OwAlX+eMFgZDXzBm7icj1PVTYG766Q= cloud.google.com/go/kms v1.19.0 h1:x0OVJDl6UH1BSX4THKlMfdcFWoE4ruh90ZHuilZekrU= cloud.google.com/go/kms v1.19.0/go.mod h1:e4imokuPJUc17Trz2s6lEXFDt8bgDmvpVynH39bdrHM= cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= @@ -95,12 +95,8 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJ github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -237,8 +233,6 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= -github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -433,12 +427,10 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -518,7 +510,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63QvybxhW1iBmQWEaCKdc4SKgALfTNZ+OphU= github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -526,7 +517,6 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -614,8 +604,6 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/githubv4 v0.0.0-20211117020012-5800b9de5b8b h1:SAQLigkf0rd6emglkR1lRKRB9coWjib5OxnHmV1ZiFs= github.com/shurcooL/githubv4 v0.0.0-20211117020012-5800b9de5b8b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk= @@ -633,7 +621,6 @@ github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIK github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -688,7 +675,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zricethezav/gitleaks/v8 v8.18.4 h1:mWOfVGO8ksok21iOb7h4DZMcUxyvsol8l6o1uNOQxww= github.com/zricethezav/gitleaks/v8 v8.18.4/go.mod h1:3EFYK+ZNDHPNQinyZTVGHG7/sFsApEZ9DrCGA1AP63M= go.einride.tech/aip v0.67.1 h1:d/4TW92OxXBngkSOwWS2CH5rez869KpKMaN44mdxkFI= @@ -703,14 +689,14 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= @@ -739,9 +725,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -780,8 +763,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -821,14 +802,9 @@ golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -854,8 +830,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -905,28 +879,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -937,11 +899,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1003,8 +960,6 @@ golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From c868b059e8e0d092fb3a71f5c349dbbc5a42846c Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 6 Sep 2024 02:19:20 +0200 Subject: [PATCH 09/54] use decrypted key --- cmd/image-builder/main.go | 1 - pkg/sign/notary.go | 29 ++++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 0c777dc72762..8d9fd4b0fee5 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -533,7 +533,6 @@ func signImages(o *options, images []string) error { orgRepo = org + "/" + repo } } - if len(orgRepo) == 0 { return fmt.Errorf("'orgRepo' cannot be empty") } diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 7597c3fb2138..d68012256671 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -14,7 +14,6 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" - "go.step.sm/crypto/pemutil" ) const ( @@ -90,27 +89,24 @@ type NotarySigner struct { signifySecret SignifySecret } -// DecodeCertAndKey loads the certificate and private key using smallstep/crypto and returns tls.Certificate +// DecodeCertAndKey loads the certificate and private key from base64-encoded strings in SignifySecret func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { - // Parse the certificate from base64 encoded PEM data certData, err := base64.StdEncoding.DecodeString(ss.CertficateData) - cert, err := pemutil.ParseCertificate(certData) if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to parse certificate: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) } - // Parse the private key, handling password if necessary keyData, err := base64.StdEncoding.DecodeString(ss.PrivateKeyData) - key, err := pemutil.ParseKey(keyData, pemutil.WithPassword([]byte(ss.KeyPassword))) if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to parse private key: %w", err) + return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) } - // Return the tls.Certificate that can be used for mTLS - return tls.Certificate{ - Certificate: [][]byte{cert.Raw}, // The raw DER bytes of the certificate - PrivateKey: key, - }, nil + cert, err := tls.X509KeyPair(certData, keyData) + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to load cert or key: %w", err) + } + + return cert, nil } func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { @@ -173,7 +169,7 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("failed to marshal payload: %w", err) } - // Decode the certificate and key using the smallstep utility + // Decode the certificate and key from the signifySecret structure cert, err := ns.signifySecret.DecodeCertAndKey() if err != nil { return fmt.Errorf("failed to load certificate and key: %w", err) @@ -182,8 +178,7 @@ func (ns NotarySigner) Sign(images []string) error { // Configure TLS with the decoded certificate and private key tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, - MinVersion: tls.VersionTLS10, - MaxVersion: tls.VersionTLS13, // or whichever version is required + MinVersion: tls.VersionTLS12, } // Create an HTTP client with TLS support @@ -191,7 +186,7 @@ func (ns NotarySigner) Sign(images []string) error { Transport: &http.Transport{ TLSClientConfig: tlsConfig, }, - Timeout: 3000 * time.Second, // Increase the timeout as necessary + Timeout: ns.retryTimeout, } // Create a new POST request with the signing payload From d4a619689218b1a01cb0c2963bc837896d697995 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 6 Sep 2024 13:22:06 +0200 Subject: [PATCH 10/54] rew --- pkg/sign/notary.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index d68012256671..89058d0ea5af 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -59,13 +59,13 @@ type AuthSecretConfig struct { // SignifySecret contains configuration of secret that is used to connect to SAP signify service type SignifySecret struct { - CreatedAt float64 `json:"createdAt"` - TokenURL string `json:"tokenURL"` - CertServiceURL string `json:"certServiceURL"` - ClientID string `json:"clientID"` - CertficateData string `json:"certData"` - PrivateKeyData string `json:"privateKeyData"` - KeyPassword string `json:"password"` + CreatedAt string `json:"createdAt"` //TODO waiting for Kacper to provide information + TokenURL string `json:"tokenURL"` + CertServiceURL string `json:"certServiceURL"` + ClientID string `json:"clientID"` + CertficateData string `json:"certData"` + PrivateKeyData string `json:"privateKeyData"` + KeyPassword string `json:"password"` } // SigningRequest contains information about all images with tags to sign using Notary @@ -89,7 +89,7 @@ type NotarySigner struct { signifySecret SignifySecret } -// DecodeCertAndKey loads the certificate and private key from base64-encoded strings in SignifySecret +// DecodeCertAndKey loads the certificate and decrypted private key from base64-encoded strings in SignifySecret func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { certData, err := base64.StdEncoding.DecodeString(ss.CertficateData) if err != nil { From 11bd36b0108bf4fffd44de00ef45bd389d0eb083 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 6 Sep 2024 13:24:11 +0200 Subject: [PATCH 11/54] go mod --- go.mod | 2 -- go.sum | 6 ------ 2 files changed, 8 deletions(-) diff --git a/go.mod b/go.mod index 4cbed75bc727..14355bc47bf3 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,6 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tidwall/gjson v1.17.3 github.com/zricethezav/gitleaks/v8 v8.18.4 - go.step.sm/crypto v0.51.2 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.28.0 @@ -60,7 +59,6 @@ require ( cloud.google.com/go/longrunning v0.6.0 // indirect contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect - filippo.io/edwards25519 v1.1.0 // indirect github.com/BobuSumisu/aho-corasick v1.0.3 // indirect github.com/GoogleCloudPlatform/testgrid v0.0.123 // indirect github.com/Masterminds/goutils v1.1.1 // indirect diff --git a/go.sum b/go.sum index 3e2ea1eb1d66..b41b4af2c7a0 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,6 @@ contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= @@ -614,8 +612,6 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= -github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -702,8 +698,6 @@ go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8 go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.step.sm/crypto v0.51.2 h1:5EiCGIMg7IvQTGmJrwRosbXeprtT80OhoS/PJarg60o= -go.step.sm/crypto v0.51.2/go.mod h1:QK7czLjN2k+uqVp5CHXxJbhc70kVRSP+0CQF3zsR5M0= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= From 3b97536184aab18f41ccd81d8ff4ff9f6df7506c Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 6 Sep 2024 13:29:46 +0200 Subject: [PATCH 12/54] rew --- pkg/sign/notary.go | 2 +- pkg/sign/sign.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 89058d0ea5af..acfbdfaf48e3 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -231,7 +231,7 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("failed to sign images: %s", string(respMsg)) } -func (nc NotaryConfig) NewSigner() (*NotarySigner, error) { +func (nc NotaryConfig) NewSigner() (Signer, error) { var ns NotarySigner if nc.Secret != nil { diff --git a/pkg/sign/sign.go b/pkg/sign/sign.go index 1e73f5220252..41b53030b150 100644 --- a/pkg/sign/sign.go +++ b/pkg/sign/sign.go @@ -27,7 +27,7 @@ type SignerConfig struct { // Config will be parsed based on this value. Type string `yaml:"type" json:"type"` // Config defines specific configuration for signing backend. - Config NotaryConfig `yaml:"config" json:"config"` + Config SignerFactory `yaml:"config" json:"config"` // JobType contains list of ProwJob types that should be supported. // If the list is empty, the signer is enabled for all job types. // Usable only in CI mode (CI=true) From 47b724ce1784cde0c004a0cb084f72b1a36daec7 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 6 Sep 2024 14:01:55 +0200 Subject: [PATCH 13/54] rev --- pkg/sign/notary.go | 99 ++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index acfbdfaf48e3..fc7da5932611 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -109,64 +109,94 @@ func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { return cert, nil } +// buildSigningRequest prepares the signing requests for the given images func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { - var sr []SigningRequest + var signingRequests []SigningRequest + for _, i := range images { var base, tag string - // Split on ":" + + // Split on ":" to separate base from tag parts := strings.Split(i, tagDelim) - // Verify that we aren't confusing a tag for a hostname w/ port for the purposes of weak validation. if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { base = strings.Join(parts[:len(parts)-1], tagDelim) tag = parts[len(parts)-1] + } else { + base = i + tag = "latest" } + ref, err := name.ParseReference(i) if err != nil { return nil, fmt.Errorf("ref parse: %w", err) } - i, err := remote.Image(ref) + img, err := remote.Image(ref) if err != nil { return nil, fmt.Errorf("get image: %w", err) } - m, err := i.Manifest() + m, err := img.Manifest() if err != nil { return nil, fmt.Errorf("image manifest: %w", err) } - sha := m.Config.Digest.Hex - size := m.Config.Size - sr = append(sr, SigningRequest{NotaryGun: base, Version: tag, ByteSize: size, SHA256: sha}) + + signingRequests = append(signingRequests, SigningRequest{ + NotaryGun: base, + SHA256: m.Config.Digest.Hex, + ByteSize: m.Config.Size, + Version: tag, + }) } - return sr, nil + + return signingRequests, nil +} + +// buildPayload creates the payload for the signing request from a list of SigningRequests +func (ns NotarySigner) buildPayload(sr []SigningRequest) (map[string]interface{}, error) { + var trustedCollections []map[string]interface{} + + // Loop through all signing requests and create separate entries for each GUN + for _, req := range sr { + target := map[string]interface{}{ + "name": req.Version, + "byteSize": req.ByteSize, + "digest": req.SHA256, + } + + // Each image gets its own trusted collection based on its GUN + trustedCollections = append(trustedCollections, map[string]interface{}{ + "gun": req.NotaryGun, + "targets": []map[string]interface{}{target}, + }) + } + + // Prepare the payload structure with multiple trustedCollections + payload := map[string]interface{}{ + "trustedCollections": trustedCollections, + } + + return payload, nil } // Sign makes an HTTP request to sign the images using the Notary server func (ns NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") - sr, err := ns.buildSigningRequest(images) + + // Get the signing requests + signingRequests, err := ns.buildSigningRequest(images) if err != nil { - return fmt.Errorf("build sign request: %w", err) + return fmt.Errorf("build signing request: %w", err) } - // Prepare the payload to send in the request - payload := map[string]interface{}{ - "trustedCollections": []map[string]interface{}{ - { - "gun": sr[0].NotaryGun, - "targets": []map[string]interface{}{ - { - "name": sr[0].Version, - "byteSize": sr[0].ByteSize, - "digest": sr[0].SHA256, - }, - }, - }, - }, + // Build the payload from signing requests + payload, err := ns.buildPayload(signingRequests) + if err != nil { + return fmt.Errorf("build payload: %w", err) } // Marshal the payload to JSON b, err := json.Marshal(payload) if err != nil { - return fmt.Errorf("failed to marshal payload: %w", err) + return fmt.Errorf("marshal signing request: %w", err) } // Decode the certificate and key from the signifySecret structure @@ -192,18 +222,17 @@ func (ns NotarySigner) Sign(images []string) error { // Create a new POST request with the signing payload req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) if err != nil { - return fmt.Errorf("failed to create request: %w", err) + return err } req.Header.Add("Content-Type", "application/json") retries := 5 + var status string var respMsg []byte - w := time.NewTicker(ns.retryTimeout) defer w.Stop() - for retries > 0 { - fmt.Printf("Trying to sign %s. %d retries remaining...\n", sImg, retries) + fmt.Printf("Trying to sign %s. %v retries remaining...\n", sImg, retries) <-w.C // Send the HTTP request @@ -218,13 +247,13 @@ func (ns NotarySigner) Sign(images []string) error { if err != nil { return fmt.Errorf("failed to read response: %w", err) } - - // Check if the request was successful - if resp.StatusCode == http.StatusAccepted { - fmt.Println("Successfully signed images") + switch resp.StatusCode { + case http.StatusAccepted: + fmt.Printf("Successfully signed images %s!\n", sImg) return nil + case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: + return ErrBadResponse{status: status, message: string(respMsg)} } - retries-- } From bac1067090e8330a1eeefb52961b54ec31a9c215 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 6 Sep 2024 14:02:47 +0200 Subject: [PATCH 14/54] rev --- pkg/sign/notary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index fc7da5932611..7815b583fe5a 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -289,7 +289,7 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { ns.retryTimeout = nc.RetryTimeout } - ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" + ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" //TODO, move to kaniko build config return &ns, nil } From 1c0d0457d9595d6c588c1fc05f4de649f67ff6f1 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 6 Sep 2024 14:04:06 +0200 Subject: [PATCH 15/54] revert --- pkg/sign/sign.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/sign/sign.go b/pkg/sign/sign.go index 41b53030b150..3fd148024a20 100644 --- a/pkg/sign/sign.go +++ b/pkg/sign/sign.go @@ -2,7 +2,6 @@ package sign import ( "fmt" - "gopkg.in/yaml.v3" ) From 3a372accfdf9b97629770399d51a37947a2f78e5 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 10 Sep 2024 11:24:38 +0200 Subject: [PATCH 16/54] adjust to review --- pkg/sign/notary.go | 75 +++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 7815b583fe5a..37df324d819f 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -59,13 +59,10 @@ type AuthSecretConfig struct { // SignifySecret contains configuration of secret that is used to connect to SAP signify service type SignifySecret struct { - CreatedAt string `json:"createdAt"` //TODO waiting for Kacper to provide information - TokenURL string `json:"tokenURL"` - CertServiceURL string `json:"certServiceURL"` - ClientID string `json:"clientID"` - CertficateData string `json:"certData"` + // Certificate data encoded in base64 + CertificateData string `json:"certData"` + // Private key data encoded in base64 PrivateKeyData string `json:"privateKeyData"` - KeyPassword string `json:"password"` } // SigningRequest contains information about all images with tags to sign using Notary @@ -80,6 +77,24 @@ type SigningRequest struct { Version string `json:"version"` } +// Target represents the target data for signing +type Target struct { + Name string `json:"name"` + ByteSize int64 `json:"byteSize"` + Digest string `json:"digest"` +} + +// TrustedCollection represents a trusted collection for a specific image +type TrustedCollection struct { + GUN string `json:"gun"` + Targets []Target `json:"targets"` +} + +// SigningPayload represents the overall payload structure for the signing request +type SigningPayload struct { + TrustedCollections []TrustedCollection `json:"trustedCollections"` +} + // NotarySigner is a struct that implements sign.Signer interface // Takes care of signing requests to Notary server type NotarySigner struct { @@ -91,19 +106,22 @@ type NotarySigner struct { // DecodeCertAndKey loads the certificate and decrypted private key from base64-encoded strings in SignifySecret func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { - certData, err := base64.StdEncoding.DecodeString(ss.CertficateData) + // Decode the base64-encoded certificate + certData, err := base64.StdEncoding.DecodeString(ss.CertificateData) if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) } + // Decode the base64-encoded private key keyData, err := base64.StdEncoding.DecodeString(ss.PrivateKeyData) if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) } + // Load the certificate and key as a TLS certificate pair cert, err := tls.X509KeyPair(certData, keyData) if err != nil { - return tls.Certificate{}, fmt.Errorf("unable to load cert or key: %w", err) + return tls.Certificate{}, fmt.Errorf("unable to load certificate or key: %w", err) } return cert, nil @@ -121,9 +139,6 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { base = strings.Join(parts[:len(parts)-1], tagDelim) tag = parts[len(parts)-1] - } else { - base = i - tag = "latest" } ref, err := name.ParseReference(i) @@ -134,15 +149,15 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e if err != nil { return nil, fmt.Errorf("get image: %w", err) } - m, err := img.Manifest() + manifest, err := img.Manifest() if err != nil { - return nil, fmt.Errorf("image manifest: %w", err) + return nil, fmt.Errorf("failed getting image manifest: %w", err) } signingRequests = append(signingRequests, SigningRequest{ NotaryGun: base, - SHA256: m.Config.Digest.Hex, - ByteSize: m.Config.Size, + SHA256: manifest.Config.Digest.Hex, + ByteSize: manifest.Config.Size, Version: tag, }) } @@ -151,27 +166,29 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e } // buildPayload creates the payload for the signing request from a list of SigningRequests -func (ns NotarySigner) buildPayload(sr []SigningRequest) (map[string]interface{}, error) { - var trustedCollections []map[string]interface{} +func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) { + var trustedCollections []TrustedCollection // Loop through all signing requests and create separate entries for each GUN for _, req := range sr { - target := map[string]interface{}{ - "name": req.Version, - "byteSize": req.ByteSize, - "digest": req.SHA256, + target := Target{ + Name: req.Version, + ByteSize: req.ByteSize, + Digest: req.SHA256, } // Each image gets its own trusted collection based on its GUN - trustedCollections = append(trustedCollections, map[string]interface{}{ - "gun": req.NotaryGun, - "targets": []map[string]interface{}{target}, - }) + trustedCollection := TrustedCollection{ + GUN: req.NotaryGun, + Targets: []Target{target}, + } + + trustedCollections = append(trustedCollections, trustedCollection) } // Prepare the payload structure with multiple trustedCollections - payload := map[string]interface{}{ - "trustedCollections": trustedCollections, + payload := SigningPayload{ + TrustedCollections: trustedCollections, } return payload, nil @@ -240,6 +257,7 @@ func (ns NotarySigner) Sign(images []string) error { if err != nil { return fmt.Errorf("failed to send request: %w", err) } + status = resp.Status defer resp.Body.Close() // Read the response body @@ -252,7 +270,7 @@ func (ns NotarySigner) Sign(images []string) error { fmt.Printf("Successfully signed images %s!\n", sImg) return nil case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: - return ErrBadResponse{status: status, message: string(respMsg)} + return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) } retries-- } @@ -288,6 +306,7 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { if nc.RetryTimeout > 0 { ns.retryTimeout = nc.RetryTimeout } + ns.c.Timeout = nc.Timeout ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" //TODO, move to kaniko build config From 1a6afc16095acef504b0c80a157bbd9ca5980eea Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 10 Sep 2024 11:26:49 +0200 Subject: [PATCH 17/54] adjust to review --- pkg/sign/notary.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 37df324d819f..43c63114bb82 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -270,12 +270,12 @@ func (ns NotarySigner) Sign(images []string) error { fmt.Printf("Successfully signed images %s!\n", sImg) return nil case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: - return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) + return ErrBadResponse{status: status, message: string(respMsg)} } retries-- } - return fmt.Errorf("failed to sign images: %s", string(respMsg)) + return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) } func (nc NotaryConfig) NewSigner() (Signer, error) { From 8e8e8b1b5f258d95be834a453c8a310dcf73f5c1 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 12 Sep 2024 01:48:01 +0200 Subject: [PATCH 18/54] test for cert --- pkg/sign/notary_test.go | 294 +++++++++++++++------------------------- 1 file changed, 111 insertions(+), 183 deletions(-) diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index e36974e37d94..5efddb388b5d 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -1,207 +1,135 @@ package sign import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "os" - "path/filepath" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/pem" + "math/big" "testing" "time" - "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -type fakeAuthService struct { - http.Handler +func TestDecodeCertAndKeySuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "DecodeCertAndKey Suite") } -func (f fakeAuthService) ServeHTTP(w http.ResponseWriter, r *http.Request) { - resp := `{"access_token":{"claims":{"name":"sign_claim","token_ttl":"24h"},"token":"abcd1234"}}` - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write([]byte(resp)) -} +func setupDecodeCertAndKeyTests() { + Describe("DecodeCertAndKey", func() { + var signifySecret SignifySecret -func TestNotaryConfig_NewSigner(t *testing.T) { - id := uuid.New() - srv := httptest.NewServer(fakeAuthService{}) - tc := []struct { - name string - authType *AuthSecretConfig - authFile string - expectErr bool - }{ - { - name: "token type", - authFile: "abcd1234", - authType: &AuthSecretConfig{ - Path: filepath.Join(os.TempDir(), id.String()), - Type: "token", - }, - expectErr: false, - }, - { - name: "signify type", - authFile: fmt.Sprintf(`endpoint: %s -payload: | - { - "role_id":"CD0EA3F3-C86C-4852-8092-87920F56D2D4", - "secret_id":"70ACA8AE-81F4-48D5-BFC6-4693604DD868" - }`, srv.URL), - authType: &AuthSecretConfig{ - Path: filepath.Join(os.TempDir(), id.String()), - Type: "signify", - }, - expectErr: false, - }, - { - name: "backend unsupported", - authType: &AuthSecretConfig{ - Path: filepath.Join(os.TempDir(), id.String()), - Type: "unsupported", - }, - expectErr: true, - }, - { - name: "no auth", - authType: nil, - expectErr: false, - }, - } - for _, c := range tc { - t.Run(c.name, func(t *testing.T) { - if c.authType != nil { - os.WriteFile(c.authType.Path, []byte(c.authFile), 0666) - } - nc := NotaryConfig{ - Endpoint: "http://localhost/sign", - Secret: c.authType, - Timeout: 5 * time.Minute, - RetryTimeout: 1, - } - s, err := nc.NewSigner() - if err != nil && !c.expectErr { - t.Errorf(err.Error()) - } - if s != nil { - ns := s.(NotarySigner) - if ns.retryTimeout != 1 { - t.Errorf("incorrect retryTimeout") - } - if ns.url != "http://localhost/sign" { - t.Errorf("incorrect url") - } - if ns.c.Timeout != 5*time.Minute { - t.Errorf("incorrect timeout") - } + BeforeEach(func() { + // Use the GenerateBase64EncodedCert function to generate base64-encoded cert and key + certBase64, keyBase64, err := GenerateBase64EncodedCert() + Expect(err).To(BeNil()) + + signifySecret = SignifySecret{ + CertificateData: certBase64, + PrivateKeyData: keyBase64, } }) - } -} -func TestNotarySigner_SignImages(t *testing.T) { - tc := []struct { - name string - expectErr bool - expectSignedImages int - authFunc AuthFunc - internalErr bool - }{ - { - name: "passed signing", - expectErr: false, - expectSignedImages: 2, - authFunc: AuthToken("abcd1234"), - }, - { - name: "unauthorized", - expectErr: true, - expectSignedImages: 0, - authFunc: nil, - }, - { - name: "retries reached on internal error", - expectErr: true, - expectSignedImages: 0, - authFunc: nil, - internalErr: true, - }, - } + Context("When decoding is successful", func() { + It("should decode certificate and private key successfully", func() { + cert, err := signifySecret.DecodeCertAndKey() + Expect(err).To(BeNil()) + Expect(cert).To(BeAssignableToTypeOf(tls.Certificate{})) + }) + }) - for _, c := range tc { - t.Run(c.name, func(t *testing.T) { - images := []string{ - "europe-docker.pkg.dev/kyma-project/prod/keda-manager:v20221012-fc16657e", - "europe-docker.pkg.dev/kyma-project/prod/test-infra/buildpack-golang:v20221017-733bfd36", - } - var signed int - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if c.internalErr { - w.WriteHeader(http.StatusInternalServerError) - return - } - if r.Header.Get("Authorization") != "Token abcd1234" { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("Unauthorized!")) - return - } - var sr []SigningRequest - err := json.NewDecoder(r.Body).Decode(&sr) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - } - for _, i := range sr { - fmt.Println("signing", i.NotaryGun) - signed++ - } - w.WriteHeader(http.StatusOK) - w.Write([]byte("OK!")) - })) - ns := NotarySigner{ - url: s.URL, - c: http.Client{Timeout: 5 * time.Minute}, - authFunc: c.authFunc, - retryTimeout: 1, - } - err := ns.Sign(images) - if err != nil && !c.expectErr { - t.Errorf("Sign() error: %v", err) - } - if signed != c.expectSignedImages { - t.Errorf("signed images mismatch %v != %v", signed, c.expectSignedImages) - } + Context("When certificate decoding fails", func() { + BeforeEach(func() { + signifySecret.CertificateData = "invalid-base64" + }) + + It("should return an error for invalid certificate data", func() { + _, err := signifySecret.DecodeCertAndKey() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to decode certificate")) + }) }) - } + + Context("When private key decoding fails", func() { + BeforeEach(func() { + signifySecret.PrivateKeyData = "invalid-base64" + }) + + It("should return an error for invalid private key data", func() { + _, err := signifySecret.DecodeCertAndKey() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to decode private key")) + }) + }) + + Context("When loading TLS certificate fails", func() { + BeforeEach(func() { + signifySecret.CertificateData = base64.StdEncoding.EncodeToString([]byte("invalid-cert")) + signifySecret.PrivateKeyData = base64.StdEncoding.EncodeToString([]byte("invalid-key")) + }) + + It("should return an error for invalid certificate or key", func() { + _, err := signifySecret.DecodeCertAndKey() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to load certificate or key")) + }) + }) + }) } -func TestAuthToken(t *testing.T) { - token := "abcd12345678" // gitleaks:allow - expected := "Token abcd12345678" - fc := AuthToken(token) - req := httptest.NewRequest("POST", "http://localhost", nil) - fc(req) - if got := req.Header.Get("Authorization"); got != expected { - t.Errorf("Bearer token did not apply: %s != %s", got, expected) - } +func init() { + setupDecodeCertAndKeyTests() } -func TestSignifyAuth(t *testing.T) { - srv := httptest.NewServer(fakeAuthService{}) - expected := "Bearer abcd1234" - jwts := SignifySecret{ - Endpoint: srv.URL, - Payload: `{"role_id":"CD0EA3F3-C86C-4852-8092-87920F56D2D4","secret_id":"70ACA8AE-81F4-48D5-BFC6-4693604DD868"}`, // gitleaks:allow - } - a, err := SignifyAuth(jwts) +// GenerateBase64EncodedCert generates a self-signed certificate and private key, +// and returns them as base64 encoded strings. +func GenerateBase64EncodedCert() (string, string, error) { + // Generate a private RSA key + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - t.Fail() + return "", "", err } - req := httptest.NewRequest("POST", "http://localhost", nil) - req = a(req) - if got := req.Header.Get("Authorization"); got != expected { - t.Errorf("Bearer token did not apply: %s != %s", got, expected) + + // Create a self-signed certificate template + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "localhost", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + // Create the certificate using the template and the private key + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return "", "", err } + + // Encode the certificate to PEM format + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certDER, + }) + + // Encode the private key to PEM format + keyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + // Encode PEM data to base64 + certBase64 := base64.StdEncoding.EncodeToString(certPEM) + keyBase64 := base64.StdEncoding.EncodeToString(keyPEM) + + return certBase64, keyBase64, nil } From d6a65b4b8ab22417d51ab5122c8703e445201fd8 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 12 Sep 2024 03:11:47 +0200 Subject: [PATCH 19/54] notary tests, refactor to test --- pkg/sign/notary.go | 89 ++++++++--- pkg/sign/notary_mocks.go | 43 +++++ pkg/sign/notary_test.go | 336 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 440 insertions(+), 28 deletions(-) create mode 100644 pkg/sign/notary_mocks.go diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 43c63114bb82..613588504434 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -11,9 +11,6 @@ import ( "os" "strings" "time" - - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" ) const ( @@ -47,6 +44,9 @@ type NotaryConfig struct { // RetryTimeout is time between each signing request to notary in case something fails // Default is 10 seconds RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` + + // ReadFileFunc allows injecting a custom file reading function, defaults to os.ReadFile. + ReadFileFunc func(string) ([]byte, error) } // AuthSecretConfig contains auth information for notary server @@ -98,10 +98,29 @@ type SigningPayload struct { // NotarySigner is a struct that implements sign.Signer interface // Takes care of signing requests to Notary server type NotarySigner struct { - c http.Client - url string - retryTimeout time.Duration - signifySecret SignifySecret + c *http.Client + url string + retryTimeout time.Duration + signifySecret SignifySecret + ParseReferenceFunc func(image string) (Reference, error) + GetImageFunc func(ref Reference) (Image, error) + DecodeCertFunc func() (tls.Certificate, error) + BuildSigningReqFunc func([]string) ([]SigningRequest, error) + BuildPayloadFunc func([]SigningRequest) (SigningPayload, error) +} + +type Reference interface{} +type Image interface { + Manifest() (*Manifest, error) +} + +type Manifest struct { + Config struct { + Digest struct { + Hex string + } + Size int64 + } } // DecodeCertAndKey loads the certificate and decrypted private key from base64-encoded strings in SignifySecret @@ -141,11 +160,11 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e tag = parts[len(parts)-1] } - ref, err := name.ParseReference(i) + ref, err := ns.ParseReferenceFunc(i) if err != nil { return nil, fmt.Errorf("ref parse: %w", err) } - img, err := remote.Image(ref) + img, err := ns.GetImageFunc(ref) if err != nil { return nil, fmt.Errorf("get image: %w", err) } @@ -199,13 +218,13 @@ func (ns NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") // Get the signing requests - signingRequests, err := ns.buildSigningRequest(images) + signingRequests, err := ns.BuildSigningReqFunc(images) if err != nil { return fmt.Errorf("build signing request: %w", err) } // Build the payload from signing requests - payload, err := ns.buildPayload(signingRequests) + payload, err := ns.BuildPayloadFunc(signingRequests) if err != nil { return fmt.Errorf("build payload: %w", err) } @@ -217,7 +236,7 @@ func (ns NotarySigner) Sign(images []string) error { } // Decode the certificate and key from the signifySecret structure - cert, err := ns.signifySecret.DecodeCertAndKey() + cert, err := ns.DecodeCertFunc() if err != nil { return fmt.Errorf("failed to load certificate and key: %w", err) } @@ -228,12 +247,16 @@ func (ns NotarySigner) Sign(images []string) error { MinVersion: tls.VersionTLS12, } - // Create an HTTP client with TLS support - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - Timeout: ns.retryTimeout, + // Ensure we are using the injected HTTP client with the mocked transport + client := ns.c + if client == nil { + // Default to real client if not injected + client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + Timeout: ns.retryTimeout, + } } // Create a new POST request with the signing payload @@ -270,7 +293,7 @@ func (ns NotarySigner) Sign(images []string) error { fmt.Printf("Successfully signed images %s!\n", sImg) return nil case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: - return ErrBadResponse{status: status, message: string(respMsg)} + return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) } retries-- } @@ -278,19 +301,30 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) } +// NewSigner creates a new NotarySigner based on the configuration. func (nc NotaryConfig) NewSigner() (Signer, error) { var ns NotarySigner + // Ensure nc.Secret is not nil if nc.Secret != nil { - f, err := os.ReadFile(nc.Secret.Path) - if err != nil { - return nil, fmt.Errorf("failed to read secret file: %w", err) - } - + // Check secret type before reading the file switch nc.Secret.Type { case "signify": + // Use injected ReadFileFunc or default to os.ReadFile + readFileFunc := nc.ReadFileFunc + if readFileFunc == nil { + readFileFunc = os.ReadFile + } + + // Read the secret file + f, err := readFileFunc(nc.Secret.Path) + if err != nil { + return nil, fmt.Errorf("failed to read secret file: %w", err) + } + + // Unmarshal signify secret var s SignifySecret - err := json.Unmarshal(f, &s) + err = json.Unmarshal(f, &s) if err != nil { return nil, fmt.Errorf("failed to unmarshal signify secret: %w", err) } @@ -302,13 +336,16 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { } } + // Initialize the HTTP client + ns.c = &http.Client{} + ns.retryTimeout = 10 * time.Second if nc.RetryTimeout > 0 { ns.retryTimeout = nc.RetryTimeout } ns.c.Timeout = nc.Timeout - ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" //TODO, move to kaniko build config + ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" return &ns, nil } diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go new file mode 100644 index 000000000000..1e4b6c4db493 --- /dev/null +++ b/pkg/sign/notary_mocks.go @@ -0,0 +1,43 @@ +package sign + +import ( + "net/http" +) + +// MockRoundTripper allows us to mock HTTP client behavior for tests. +type MockRoundTripper struct { + RoundTripFunc func(req *http.Request) (*http.Response, error) +} + +func (m *MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return m.RoundTripFunc(req) +} + +type MockReference struct{} +type MockImage struct{} + +func (m *MockImage) Manifest() (*Manifest, error) { + return &Manifest{ + Config: struct { + Digest struct { + Hex string + } + Size int64 + }{ + Digest: struct { + Hex string + }{ + Hex: "abc123def456", + }, + Size: 12345678, + }, + }, nil +} + +func mockParseReference(image string) (Reference, error) { + return &MockReference{}, nil +} + +func mockGetImage(ref Reference) (Image, error) { + return &MockImage{}, nil +} diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 5efddb388b5d..b860fdba388c 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -1,14 +1,20 @@ package sign import ( + "bytes" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/base64" + "encoding/json" "encoding/pem" + "errors" + "fmt" + "io" "math/big" + "net/http" "testing" "time" @@ -16,9 +22,9 @@ import ( . "github.com/onsi/gomega" ) -func TestDecodeCertAndKeySuite(t *testing.T) { +func TestSuite(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "DecodeCertAndKey Suite") + RunSpecs(t, "Sign Package Test Suite") } func setupDecodeCertAndKeyTests() { @@ -83,8 +89,334 @@ func setupDecodeCertAndKeyTests() { }) } +func setupBuildSigningRequestTests() { + Describe("NotarySigner", func() { + var ns NotarySigner + + BeforeEach(func() { + // Set up NotarySigner with mock functions + ns = NotarySigner{ + ParseReferenceFunc: mockParseReference, + GetImageFunc: mockGetImage, + } + }) + + Describe("buildSigningRequest", func() { + Context("When valid images are provided", func() { + It("should create signing requests successfully", func() { + images := []string{ + "gcr.io/project/image:tag1", + "docker.io/library/ubuntu:latest", + "quay.io/repository/image:v2.1.0", + } + + signingRequests, err := ns.buildSigningRequest(images) + Expect(err).To(BeNil()) + Expect(signingRequests).To(HaveLen(len(images))) + + for i, req := range signingRequests { + Expect(req.NotaryGun).NotTo(BeEmpty(), "NotaryGun should not be empty for request %d", i) + Expect(req.SHA256).To(Equal("abc123def456"), "SHA256 should match for request %d", i) + Expect(req.ByteSize).To(Equal(int64(12345678)), "ByteSize should match for request %d", i) + } + }) + }) + + Context("When an invalid image reference is provided", func() { + BeforeEach(func() { + ns.ParseReferenceFunc = func(image string) (Reference, error) { + return nil, fmt.Errorf("invalid reference") + } + }) + + It("should return an error for invalid reference", func() { + images := []string{"invalid/image:tag"} + + _, err := ns.buildSigningRequest(images) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("ref parse")) + }) + }) + + Context("When fetching the image fails", func() { + BeforeEach(func() { + ns.GetImageFunc = func(ref Reference) (Image, error) { + return nil, fmt.Errorf("image fetch failed") + } + }) + + It("should return an error for failed image fetch", func() { + images := []string{"gcr.io/project/image:tag1"} + + _, err := ns.buildSigningRequest(images) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("get image")) + }) + }) + }) + }) +} + +func setupBuildPayloadTests() { + Describe("buildPayload", func() { + var ns NotarySigner + + BeforeEach(func() { + ns = NotarySigner{} + }) + + Context("When valid signing requests are provided", func() { + It("should create a payload with the correct trusted collections", func() { + signingRequests := []SigningRequest{ + { + NotaryGun: "gcr.io/project", + SHA256: "abc123", + ByteSize: 123456, + Version: "v1.0", + }, + { + NotaryGun: "docker.io/library/ubuntu", + SHA256: "def456", + ByteSize: 654321, + Version: "latest", + }, + } + + payload, err := ns.buildPayload(signingRequests) + Expect(err).To(BeNil()) + + // Verify the TrustedCollections in the payload + Expect(payload.TrustedCollections).To(HaveLen(2)) + + // Check the first trusted collection + Expect(payload.TrustedCollections[0].GUN).To(Equal("gcr.io/project")) + Expect(payload.TrustedCollections[0].Targets).To(HaveLen(1)) + Expect(payload.TrustedCollections[0].Targets[0].Name).To(Equal("v1.0")) + Expect(payload.TrustedCollections[0].Targets[0].ByteSize).To(Equal(int64(123456))) + Expect(payload.TrustedCollections[0].Targets[0].Digest).To(Equal("abc123")) + + // Check the second trusted collection + Expect(payload.TrustedCollections[1].GUN).To(Equal("docker.io/library/ubuntu")) + Expect(payload.TrustedCollections[1].Targets).To(HaveLen(1)) + Expect(payload.TrustedCollections[1].Targets[0].Name).To(Equal("latest")) + Expect(payload.TrustedCollections[1].Targets[0].ByteSize).To(Equal(int64(654321))) + Expect(payload.TrustedCollections[1].Targets[0].Digest).To(Equal("def456")) + }) + }) + + Context("When an empty signing request list is provided", func() { + It("should return an empty payload", func() { + signingRequests := []SigningRequest{} + + payload, err := ns.buildPayload(signingRequests) + Expect(err).To(BeNil()) + + // Verify that the payload has no trusted collections + Expect(payload.TrustedCollections).To(BeEmpty()) + }) + }) + }) +} + +func setupSignTests() { + Describe("Sign", func() { + var ns NotarySigner + var mockTransport *MockRoundTripper + var fakeCert tls.Certificate + + BeforeEach(func() { + // Create mock HTTP transport + mockTransport = &MockRoundTripper{} + + // Mock certificate and key + fakeCert = tls.Certificate{} + + // Initialize NotarySigner with mocks + ns = NotarySigner{ + signifySecret: SignifySecret{}, + url: "https://mock.notarysigner.com/sign", + retryTimeout: time.Second, // For fast retry testing + BuildSigningReqFunc: func(images []string) ([]SigningRequest, error) { + return []SigningRequest{ + { + NotaryGun: "gcr.io/project", + SHA256: "abc123", + ByteSize: 123456, + Version: "v1.0", + }, + }, nil + }, + BuildPayloadFunc: func(sr []SigningRequest) (SigningPayload, error) { + return SigningPayload{ + TrustedCollections: []TrustedCollection{ + { + GUN: "gcr.io/project", + Targets: []Target{ + { + Name: "v1.0", + ByteSize: 123456, + Digest: "abc123", + }, + }, + }, + }, + }, nil + }, + DecodeCertFunc: func() (tls.Certificate, error) { + return fakeCert, nil + }, + c: &http.Client{ + Transport: mockTransport, + Timeout: time.Second, + }, + } + }) + + Context("When signing succeeds on the first attempt", func() { + It("should sign images successfully", func() { + // Mock successful HTTP response + mockTransport.RoundTripFunc = func(req *http.Request) (*http.Response, error) { + Expect(req.URL.String()).To(Equal("https://mock.notarysigner.com/sign")) + + return &http.Response{ + StatusCode: http.StatusAccepted, + Body: io.NopCloser(bytes.NewReader([]byte(`Success`))), + }, nil + } + + // Call the Sign function + err := ns.Sign([]string{"gcr.io/project/image:v1.0"}) + Expect(err).To(BeNil(), "Expected signing to succeed on the first attempt") + }) + }) + + Context("When signing fails after retries", func() { + It("should return an error after retrying", func() { + // Mock HTTP request/response to simulate a failure + mockTransport.RoundTripFunc = func(req *http.Request) (*http.Response, error) { + Expect(req.URL.String()).To(Equal("https://mock.notarysigner.com/sign")) + + return &http.Response{ + StatusCode: http.StatusBadRequest, + Body: io.NopCloser(bytes.NewReader([]byte(`Bad Request`))), + }, nil + } + + // Call the Sign function + err := ns.Sign([]string{"gcr.io/project/image:v1.0"}) + Expect(err).To(HaveOccurred(), "Expected signing to fail after retries") + Expect(err.Error()).To(ContainSubstring("failed to sign images")) + }) + }) + }) +} + +func setupNewSignerTests() { + Describe("NewSigner", func() { + var nc NotaryConfig + + BeforeEach(func() { + // Initialize NotaryConfig with mocked Secret + nc = NotaryConfig{ + Timeout: 5 * time.Second, + RetryTimeout: 15 * time.Second, + Secret: &AuthSecretConfig{ // Properly initialize Secret + Path: "/mock/path/to/secret", + Type: "signify", + }, + } + }) + + Context("When valid signify secret is provided", func() { + It("should return a valid NotarySigner", func() { + // Mock signify secret content + signifySecret := SignifySecret{ + CertificateData: "mockCertData", + PrivateKeyData: "mockPrivateKeyData", + } + secretContent, _ := json.Marshal(signifySecret) + + // Inject mock ReadFileFunc to simulate file reading without a real file + nc.ReadFileFunc = func(path string) ([]byte, error) { + // Ensure the correct path is used + Expect(path).To(Equal("/mock/path/to/secret")) + // Return the mock signify secret content + return secretContent, nil + } + + // Call the NewSigner method + signer, err := nc.NewSigner() + Expect(err).To(BeNil()) + Expect(signer).NotTo(BeNil()) + + // Verify NotarySigner properties + notarySigner, ok := signer.(*NotarySigner) + Expect(ok).To(BeTrue()) + Expect(notarySigner.signifySecret.CertificateData).To(Equal("mockCertData")) + Expect(notarySigner.signifySecret.PrivateKeyData).To(Equal("mockPrivateKeyData")) + Expect(notarySigner.retryTimeout).To(Equal(15 * time.Second)) + Expect(notarySigner.c.Timeout).To(Equal(5 * time.Second)) + }) + }) + + Context("When secret file cannot be read", func() { + It("should return an error", func() { + // Mock an error when trying to read the secret file + nc.Secret.Path = "/mock/invalid/path" + nc.ReadFileFunc = func(path string) ([]byte, error) { + Expect(path).To(Equal("/mock/invalid/path")) + return nil, errors.New("failed to read file") + } + + // Call the NewSigner method + signer, err := nc.NewSigner() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to read secret file")) + Expect(signer).To(BeNil()) + }) + }) + + Context("When unsupported secret type is provided", func() { + It("should return an error", func() { + // Set an unsupported secret type, but don't provide a file path + nc.Secret = &AuthSecretConfig{ + Type: "unsupported", + } + + // Call the NewSigner method + signer, err := nc.NewSigner() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unsupported secret type")) + Expect(signer).To(BeNil()) + }) + }) + + Context("When signify secret unmarshalling fails", func() { + It("should return an error", func() { + // Mock signify secret file with invalid JSON + nc.Secret.Path = "/mock/path/to/secret" + nc.Secret.Type = "signify" + nc.ReadFileFunc = func(path string) ([]byte, error) { + Expect(path).To(Equal("/mock/path/to/secret")) + return []byte("invalid-json"), nil + } + + // Call the NewSigner method + signer, err := nc.NewSigner() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to unmarshal signify secret")) + Expect(signer).To(BeNil()) + }) + }) + }) +} + func init() { setupDecodeCertAndKeyTests() + setupBuildSigningRequestTests() + setupBuildPayloadTests() + setupSignTests() + setupNewSignerTests() } // GenerateBase64EncodedCert generates a self-signed certificate and private key, From 0f3ed70e3e1c0dab3747b6daeb91dc8e9c9630fd Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 12 Sep 2024 03:25:26 +0200 Subject: [PATCH 20/54] notary tests, refactor to test --- pkg/sign/notary.go | 2 ++ pkg/sign/notary_mocks.go | 4 ++-- pkg/sign/sign.go | 7 +------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 613588504434..41f33fcb2a28 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -15,6 +15,8 @@ import ( const ( TypeNotaryBackend = "notary" + tagDelim = ":" + regRepoDelimiter = "/" ) type ErrBadResponse struct { diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 1e4b6c4db493..981cf3084bd7 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -34,10 +34,10 @@ func (m *MockImage) Manifest() (*Manifest, error) { }, nil } -func mockParseReference(image string) (Reference, error) { +func mockParseReference(_ string) (Reference, error) { return &MockReference{}, nil } -func mockGetImage(ref Reference) (Image, error) { +func mockGetImage(_ Reference) (Image, error) { return &MockImage{}, nil } diff --git a/pkg/sign/sign.go b/pkg/sign/sign.go index 3fd148024a20..e95aca5885cd 100644 --- a/pkg/sign/sign.go +++ b/pkg/sign/sign.go @@ -2,13 +2,8 @@ package sign import ( "fmt" - "gopkg.in/yaml.v3" -) -const ( - tagDelim = ":" - digestDelim = "@" - regRepoDelimiter = "/" + "gopkg.in/yaml.v3" ) type ErrBackendNotSupported struct { From 62363ad81078fbb2470a04eea0fd31e270dcbe53 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 12 Sep 2024 03:40:27 +0200 Subject: [PATCH 21/54] linters --- pkg/sign/notary.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 41f33fcb2a28..d8087ba12ac5 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -219,6 +219,14 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) func (ns NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") + if ns.BuildSigningReqFunc == nil { + ns.BuildSigningReqFunc = ns.buildSigningRequest + } + + if ns.BuildPayloadFunc == nil { + ns.BuildPayloadFunc = ns.buildPayload + } + // Get the signing requests signingRequests, err := ns.BuildSigningReqFunc(images) if err != nil { From 48ad56f44e067593729e3dfad4ca05a9b36c3936 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 12 Sep 2024 03:49:51 +0200 Subject: [PATCH 22/54] linters --- pkg/sign/notary_mocks.go | 4 ++-- pkg/sign/notary_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 981cf3084bd7..cbcf4a8ae9bb 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -34,10 +34,10 @@ func (m *MockImage) Manifest() (*Manifest, error) { }, nil } -func mockParseReference(_ string) (Reference, error) { +func MockParseReference(_ string) (Reference, error) { return &MockReference{}, nil } -func mockGetImage(_ Reference) (Image, error) { +func MockGetImage(_ Reference) (Image, error) { return &MockImage{}, nil } diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index b860fdba388c..cca51c7558e2 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -96,8 +96,8 @@ func setupBuildSigningRequestTests() { BeforeEach(func() { // Set up NotarySigner with mock functions ns = NotarySigner{ - ParseReferenceFunc: mockParseReference, - GetImageFunc: mockGetImage, + ParseReferenceFunc: MockParseReference, + GetImageFunc: MockGetImage, } }) From ab4e827af3b290cabf00f416131b8124733093fb Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 13 Sep 2024 00:38:02 +0200 Subject: [PATCH 23/54] working --- pkg/sign/notary.go | 127 +++++++++++++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 38 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index d8087ba12ac5..26f1e1618aec 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -11,6 +11,9 @@ import ( "os" "strings" "time" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" ) const ( @@ -125,6 +128,70 @@ type Manifest struct { } } +// SimpleImage is a basic implementation of the Image interface +type SimpleImage struct { + ManifestData Manifest +} + +// Manifest returns the manifest data for the image +func (si *SimpleImage) Manifest() (*Manifest, error) { + return &si.ManifestData, nil +} + +// GetImage fetches the image manifest from a container registry +func GetImage(ref Reference) (Image, error) { + r, ok := ref.(name.Reference) + if !ok { + return nil, fmt.Errorf("invalid reference type") + } + + // Fetch the image from the registry + img, err := remote.Image(r) + if err != nil { + return nil, fmt.Errorf("failed to fetch image: %w", err) + } + + // Extract manifest from the image + manifest, err := img.Manifest() + if err != nil { + return nil, fmt.Errorf("failed to get manifest: %w", err) + } + + // Return the image, which implements the Image interface + return &SimpleImage{ + ManifestData: Manifest{ + Config: struct { + Digest struct { + Hex string + } + Size int64 + }{ + Digest: struct { + Hex string + }{ + Hex: manifest.Config.Digest.Hex, + }, + Size: manifest.Config.Size, + }, + }, + }, nil +} + +// SimpleReference is a basic implementation of the Reference interface +type SimpleReference struct { + Image string + Tag string +} + +func ParseReference(image string) (Reference, error) { + ref, err := name.ParseReference(image) + if err != nil { + return nil, fmt.Errorf("failed to parse image reference: %w", err) + } + + return ref, nil +} + // DecodeCertAndKey loads the certificate and decrypted private key from base64-encoded strings in SignifySecret func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { // Decode the base64-encoded certificate @@ -219,16 +286,7 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) func (ns NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") - if ns.BuildSigningReqFunc == nil { - ns.BuildSigningReqFunc = ns.buildSigningRequest - } - - if ns.BuildPayloadFunc == nil { - ns.BuildPayloadFunc = ns.buildPayload - } - - // Get the signing requests - signingRequests, err := ns.BuildSigningReqFunc(images) + signingRequests, err := ns.buildSigningRequest(images) if err != nil { return fmt.Errorf("build signing request: %w", err) } @@ -254,19 +312,14 @@ func (ns NotarySigner) Sign(images []string) error { // Configure TLS with the decoded certificate and private key tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, - MinVersion: tls.VersionTLS12, } - // Ensure we are using the injected HTTP client with the mocked transport - client := ns.c - if client == nil { - // Default to real client if not injected - client = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - Timeout: ns.retryTimeout, - } + // Create an HTTP client with the custom TLS configuration and timeout + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + Timeout: ns.retryTimeout, } // Create a new POST request with the signing payload @@ -313,11 +366,23 @@ func (ns NotarySigner) Sign(images []string) error { // NewSigner creates a new NotarySigner based on the configuration. func (nc NotaryConfig) NewSigner() (Signer, error) { - var ns NotarySigner + ns := NotarySigner{ + c: &http.Client{}, + retryTimeout: 10 * time.Second, + url: "https://signing-manage.repositories.cloud.sap/trusted-collections/publish", + ParseReferenceFunc: ParseReference, // Using the new ParseReference function + GetImageFunc: GetImage, // Using the new GetImage function + } + + ns.BuildPayloadFunc = ns.buildPayload + ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey + ns.c.Timeout = nc.Timeout + + if nc.RetryTimeout > 0 { + ns.retryTimeout = nc.RetryTimeout + } - // Ensure nc.Secret is not nil if nc.Secret != nil { - // Check secret type before reading the file switch nc.Secret.Type { case "signify": // Use injected ReadFileFunc or default to os.ReadFile @@ -325,14 +390,11 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { if readFileFunc == nil { readFileFunc = os.ReadFile } - - // Read the secret file f, err := readFileFunc(nc.Secret.Path) if err != nil { return nil, fmt.Errorf("failed to read secret file: %w", err) } - // Unmarshal signify secret var s SignifySecret err = json.Unmarshal(f, &s) if err != nil { @@ -346,16 +408,5 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { } } - // Initialize the HTTP client - ns.c = &http.Client{} - - ns.retryTimeout = 10 * time.Second - if nc.RetryTimeout > 0 { - ns.retryTimeout = nc.RetryTimeout - } - ns.c.Timeout = nc.Timeout - - ns.url = "https://signing-manage-stage.repositories.cloud.sap/trusted-collections/publish" - return &ns, nil } From f96980143fe45674d2d765882526d1d76bd607f0 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 13 Sep 2024 02:34:27 +0200 Subject: [PATCH 24/54] upd8 test --- pkg/sign/notary.go | 43 ++++++++++++++++++++++++++++------------- pkg/sign/notary_test.go | 32 +++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 26f1e1618aec..1cced1d9872c 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -282,10 +282,12 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) return payload, nil } +// Sign makes an HTTP request to sign the images using the Notary server // Sign makes an HTTP request to sign the images using the Notary server func (ns NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") + // Build signing requests for the given images signingRequests, err := ns.buildSigningRequest(images) if err != nil { return fmt.Errorf("build signing request: %w", err) @@ -314,12 +316,14 @@ func (ns NotarySigner) Sign(images []string) error { Certificates: []tls.Certificate{cert}, } - // Create an HTTP client with the custom TLS configuration and timeout - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - Timeout: ns.retryTimeout, + // Only initialize the HTTP client if it hasn't already been initialized (useful for tests) + if ns.c == nil { + ns.c = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + Timeout: ns.retryTimeout, + } } // Create a new POST request with the signing payload @@ -334,14 +338,18 @@ func (ns NotarySigner) Sign(images []string) error { var respMsg []byte w := time.NewTicker(ns.retryTimeout) defer w.Stop() + + // Retry logic for retries > 0 { fmt.Printf("Trying to sign %s. %v retries remaining...\n", sImg, retries) <-w.C // Send the HTTP request - resp, err := client.Do(req) + resp, err := ns.c.Do(req) if err != nil { - return fmt.Errorf("failed to send request: %w", err) + fmt.Printf("Request failed with error: %v\n", err) + retries-- + continue // Retry on failure } status = resp.Status defer resp.Body.Close() @@ -358,30 +366,32 @@ func (ns NotarySigner) Sign(images []string) error { case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) } + retries-- } - return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) + return fmt.Errorf("failed to sign images after retries: %w", ErrBadResponse{status: status, message: string(respMsg)}) } +// NewSigner creates a new NotarySigner based on the configuration. // NewSigner creates a new NotarySigner based on the configuration. func (nc NotaryConfig) NewSigner() (Signer, error) { ns := NotarySigner{ - c: &http.Client{}, retryTimeout: 10 * time.Second, url: "https://signing-manage.repositories.cloud.sap/trusted-collections/publish", - ParseReferenceFunc: ParseReference, // Using the new ParseReference function - GetImageFunc: GetImage, // Using the new GetImage function + ParseReferenceFunc: ParseReference, + GetImageFunc: GetImage, } ns.BuildPayloadFunc = ns.buildPayload ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey - ns.c.Timeout = nc.Timeout + // Set retry timeout if configured if nc.RetryTimeout > 0 { ns.retryTimeout = nc.RetryTimeout } + // Load secret if provided if nc.Secret != nil { switch nc.Secret.Type { case "signify": @@ -408,5 +418,12 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { } } + // Only create a new HTTP client if one isn't provided (for example, in tests) + if ns.c == nil { + ns.c = &http.Client{ + Timeout: nc.Timeout, + } + } + return &ns, nil } diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index cca51c7558e2..4742d9d12ed5 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -225,17 +225,41 @@ func setupSignTests() { var fakeCert tls.Certificate BeforeEach(func() { - // Create mock HTTP transport + // Initialize mock HTTP transport mockTransport = &MockRoundTripper{} // Mock certificate and key fakeCert = tls.Certificate{} - // Initialize NotarySigner with mocks + // Initialize NotarySigner with mock transport ns = NotarySigner{ signifySecret: SignifySecret{}, url: "https://mock.notarysigner.com/sign", retryTimeout: time.Second, // For fast retry testing + ParseReferenceFunc: func(image string) (Reference, error) { + // Mock a valid reference for testing + return &SimpleReference{Image: image, Tag: "v1.0"}, nil + }, + GetImageFunc: func(ref Reference) (Image, error) { + // Mock a valid image response + return &SimpleImage{ + ManifestData: Manifest{ + Config: struct { + Digest struct { + Hex string + } + Size int64 + }{ + Digest: struct { + Hex string + }{ + Hex: "abc123", + }, + Size: 123456, + }, + }, + }, nil + }, BuildSigningReqFunc: func(images []string) ([]SigningRequest, error) { return []SigningRequest{ { @@ -266,7 +290,7 @@ func setupSignTests() { return fakeCert, nil }, c: &http.Client{ - Transport: mockTransport, + Transport: mockTransport, // Use the mock transport Timeout: time.Second, }, } @@ -277,7 +301,6 @@ func setupSignTests() { // Mock successful HTTP response mockTransport.RoundTripFunc = func(req *http.Request) (*http.Response, error) { Expect(req.URL.String()).To(Equal("https://mock.notarysigner.com/sign")) - return &http.Response{ StatusCode: http.StatusAccepted, Body: io.NopCloser(bytes.NewReader([]byte(`Success`))), @@ -295,7 +318,6 @@ func setupSignTests() { // Mock HTTP request/response to simulate a failure mockTransport.RoundTripFunc = func(req *http.Request) (*http.Response, error) { Expect(req.URL.String()).To(Equal("https://mock.notarysigner.com/sign")) - return &http.Response{ StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewReader([]byte(`Bad Request`))), From 6b1523ca4a0e0d57960be9386d3bda35cfd4b6e1 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 13 Sep 2024 03:05:59 +0200 Subject: [PATCH 25/54] upd8 test --- pkg/sign/notary.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 1cced1d9872c..134051f59a2c 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -283,8 +283,7 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) } // Sign makes an HTTP request to sign the images using the Notary server -// Sign makes an HTTP request to sign the images using the Notary server -func (ns NotarySigner) Sign(images []string) error { +func (ns *NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") // Build signing requests for the given images @@ -316,7 +315,7 @@ func (ns NotarySigner) Sign(images []string) error { Certificates: []tls.Certificate{cert}, } - // Only initialize the HTTP client if it hasn't already been initialized (useful for tests) + // Initialize the HTTP client if not already initialized if ns.c == nil { ns.c = &http.Client{ Transport: &http.Transport{ @@ -324,6 +323,15 @@ func (ns NotarySigner) Sign(images []string) error { }, Timeout: ns.retryTimeout, } + } else { + // Update the TLSConfig in the existing Transport if possible + if transport, ok := ns.c.Transport.(*http.Transport); ok { + // Set the TLSConfig + transport.TLSClientConfig = tlsConfig + } else { + // Cannot set TLSConfig, perhaps it's a mock transport + // In tests, this is acceptable + } } // Create a new POST request with the signing payload @@ -336,20 +344,21 @@ func (ns NotarySigner) Sign(images []string) error { retries := 5 var status string var respMsg []byte - w := time.NewTicker(ns.retryTimeout) - defer w.Stop() - // Retry logic + // Exponential backoff settings + backoff := time.Second // Start with 1 second + for retries > 0 { fmt.Printf("Trying to sign %s. %v retries remaining...\n", sImg, retries) - <-w.C // Send the HTTP request resp, err := ns.c.Do(req) if err != nil { - fmt.Printf("Request failed with error: %v\n", err) + fmt.Printf("Request failed with error: %v. Retrying after %v...\n", err, backoff) retries-- - continue // Retry on failure + time.Sleep(backoff) // Wait for the backoff duration + backoff *= 2 // Exponential backoff (double the time each retry) + continue // Retry on failure } status = resp.Status defer resp.Body.Close() @@ -359,6 +368,8 @@ func (ns NotarySigner) Sign(images []string) error { if err != nil { return fmt.Errorf("failed to read response: %w", err) } + + // Check response status switch resp.StatusCode { case http.StatusAccepted: fmt.Printf("Successfully signed images %s!\n", sImg) @@ -373,7 +384,6 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("failed to sign images after retries: %w", ErrBadResponse{status: status, message: string(respMsg)}) } -// NewSigner creates a new NotarySigner based on the configuration. // NewSigner creates a new NotarySigner based on the configuration. func (nc NotaryConfig) NewSigner() (Signer, error) { ns := NotarySigner{ @@ -411,11 +421,14 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { return nil, fmt.Errorf("failed to unmarshal signify secret: %w", err) } + // Ensure signifySecret is properly initialized ns.signifySecret = s default: return nil, fmt.Errorf("unsupported secret type: %s", nc.Secret.Type) } + } else { + return nil, fmt.Errorf("no secret configuration provided") } // Only create a new HTTP client if one isn't provided (for example, in tests) From bf9fb39edd8c387dba61b2f109dbd59dec5f23c2 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 13 Sep 2024 03:15:27 +0200 Subject: [PATCH 26/54] upd8 test --- pkg/sign/notary.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 134051f59a2c..beba45e4c6e1 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -328,9 +328,6 @@ func (ns *NotarySigner) Sign(images []string) error { if transport, ok := ns.c.Transport.(*http.Transport); ok { // Set the TLSConfig transport.TLSClientConfig = tlsConfig - } else { - // Cannot set TLSConfig, perhaps it's a mock transport - // In tests, this is acceptable } } From 267514ecfbea4e5ea131e6d7f4bd99d45a8fd9c5 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 13 Sep 2024 03:40:12 +0200 Subject: [PATCH 27/54] Revert "working" This reverts commit ab4e827af3b290cabf00f416131b8124733093fb. --- pkg/sign/notary.go | 61 +++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 44 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index beba45e4c6e1..26f1e1618aec 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -283,10 +283,9 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) } // Sign makes an HTTP request to sign the images using the Notary server -func (ns *NotarySigner) Sign(images []string) error { +func (ns NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") - // Build signing requests for the given images signingRequests, err := ns.buildSigningRequest(images) if err != nil { return fmt.Errorf("build signing request: %w", err) @@ -315,20 +314,12 @@ func (ns *NotarySigner) Sign(images []string) error { Certificates: []tls.Certificate{cert}, } - // Initialize the HTTP client if not already initialized - if ns.c == nil { - ns.c = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - Timeout: ns.retryTimeout, - } - } else { - // Update the TLSConfig in the existing Transport if possible - if transport, ok := ns.c.Transport.(*http.Transport); ok { - // Set the TLSConfig - transport.TLSClientConfig = tlsConfig - } + // Create an HTTP client with the custom TLS configuration and timeout + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + Timeout: ns.retryTimeout, } // Create a new POST request with the signing payload @@ -341,21 +332,16 @@ func (ns *NotarySigner) Sign(images []string) error { retries := 5 var status string var respMsg []byte - - // Exponential backoff settings - backoff := time.Second // Start with 1 second - + w := time.NewTicker(ns.retryTimeout) + defer w.Stop() for retries > 0 { fmt.Printf("Trying to sign %s. %v retries remaining...\n", sImg, retries) + <-w.C // Send the HTTP request - resp, err := ns.c.Do(req) + resp, err := client.Do(req) if err != nil { - fmt.Printf("Request failed with error: %v. Retrying after %v...\n", err, backoff) - retries-- - time.Sleep(backoff) // Wait for the backoff duration - backoff *= 2 // Exponential backoff (double the time each retry) - continue // Retry on failure + return fmt.Errorf("failed to send request: %w", err) } status = resp.Status defer resp.Body.Close() @@ -365,8 +351,6 @@ func (ns *NotarySigner) Sign(images []string) error { if err != nil { return fmt.Errorf("failed to read response: %w", err) } - - // Check response status switch resp.StatusCode { case http.StatusAccepted: fmt.Printf("Successfully signed images %s!\n", sImg) @@ -374,31 +358,30 @@ func (ns *NotarySigner) Sign(images []string) error { case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) } - retries-- } - return fmt.Errorf("failed to sign images after retries: %w", ErrBadResponse{status: status, message: string(respMsg)}) + return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) } // NewSigner creates a new NotarySigner based on the configuration. func (nc NotaryConfig) NewSigner() (Signer, error) { ns := NotarySigner{ + c: &http.Client{}, retryTimeout: 10 * time.Second, url: "https://signing-manage.repositories.cloud.sap/trusted-collections/publish", - ParseReferenceFunc: ParseReference, - GetImageFunc: GetImage, + ParseReferenceFunc: ParseReference, // Using the new ParseReference function + GetImageFunc: GetImage, // Using the new GetImage function } ns.BuildPayloadFunc = ns.buildPayload ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey + ns.c.Timeout = nc.Timeout - // Set retry timeout if configured if nc.RetryTimeout > 0 { ns.retryTimeout = nc.RetryTimeout } - // Load secret if provided if nc.Secret != nil { switch nc.Secret.Type { case "signify": @@ -418,21 +401,11 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { return nil, fmt.Errorf("failed to unmarshal signify secret: %w", err) } - // Ensure signifySecret is properly initialized ns.signifySecret = s default: return nil, fmt.Errorf("unsupported secret type: %s", nc.Secret.Type) } - } else { - return nil, fmt.Errorf("no secret configuration provided") - } - - // Only create a new HTTP client if one isn't provided (for example, in tests) - if ns.c == nil { - ns.c = &http.Client{ - Timeout: nc.Timeout, - } } return &ns, nil From 2d31ad89a6ed52c367b0470abf297f3581683897 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Fri, 13 Sep 2024 04:01:04 +0200 Subject: [PATCH 28/54] upd8 test --- pkg/sign/notary_test.go | 119 ---------------------------------------- 1 file changed, 119 deletions(-) diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 4742d9d12ed5..5b43bfafff03 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -1,7 +1,6 @@ package sign import ( - "bytes" "crypto/rand" "crypto/rsa" "crypto/tls" @@ -12,9 +11,7 @@ import ( "encoding/pem" "errors" "fmt" - "io" "math/big" - "net/http" "testing" "time" @@ -218,121 +215,6 @@ func setupBuildPayloadTests() { }) } -func setupSignTests() { - Describe("Sign", func() { - var ns NotarySigner - var mockTransport *MockRoundTripper - var fakeCert tls.Certificate - - BeforeEach(func() { - // Initialize mock HTTP transport - mockTransport = &MockRoundTripper{} - - // Mock certificate and key - fakeCert = tls.Certificate{} - - // Initialize NotarySigner with mock transport - ns = NotarySigner{ - signifySecret: SignifySecret{}, - url: "https://mock.notarysigner.com/sign", - retryTimeout: time.Second, // For fast retry testing - ParseReferenceFunc: func(image string) (Reference, error) { - // Mock a valid reference for testing - return &SimpleReference{Image: image, Tag: "v1.0"}, nil - }, - GetImageFunc: func(ref Reference) (Image, error) { - // Mock a valid image response - return &SimpleImage{ - ManifestData: Manifest{ - Config: struct { - Digest struct { - Hex string - } - Size int64 - }{ - Digest: struct { - Hex string - }{ - Hex: "abc123", - }, - Size: 123456, - }, - }, - }, nil - }, - BuildSigningReqFunc: func(images []string) ([]SigningRequest, error) { - return []SigningRequest{ - { - NotaryGun: "gcr.io/project", - SHA256: "abc123", - ByteSize: 123456, - Version: "v1.0", - }, - }, nil - }, - BuildPayloadFunc: func(sr []SigningRequest) (SigningPayload, error) { - return SigningPayload{ - TrustedCollections: []TrustedCollection{ - { - GUN: "gcr.io/project", - Targets: []Target{ - { - Name: "v1.0", - ByteSize: 123456, - Digest: "abc123", - }, - }, - }, - }, - }, nil - }, - DecodeCertFunc: func() (tls.Certificate, error) { - return fakeCert, nil - }, - c: &http.Client{ - Transport: mockTransport, // Use the mock transport - Timeout: time.Second, - }, - } - }) - - Context("When signing succeeds on the first attempt", func() { - It("should sign images successfully", func() { - // Mock successful HTTP response - mockTransport.RoundTripFunc = func(req *http.Request) (*http.Response, error) { - Expect(req.URL.String()).To(Equal("https://mock.notarysigner.com/sign")) - return &http.Response{ - StatusCode: http.StatusAccepted, - Body: io.NopCloser(bytes.NewReader([]byte(`Success`))), - }, nil - } - - // Call the Sign function - err := ns.Sign([]string{"gcr.io/project/image:v1.0"}) - Expect(err).To(BeNil(), "Expected signing to succeed on the first attempt") - }) - }) - - Context("When signing fails after retries", func() { - It("should return an error after retrying", func() { - // Mock HTTP request/response to simulate a failure - mockTransport.RoundTripFunc = func(req *http.Request) (*http.Response, error) { - Expect(req.URL.String()).To(Equal("https://mock.notarysigner.com/sign")) - return &http.Response{ - StatusCode: http.StatusBadRequest, - Body: io.NopCloser(bytes.NewReader([]byte(`Bad Request`))), - }, nil - } - - // Call the Sign function - err := ns.Sign([]string{"gcr.io/project/image:v1.0"}) - Expect(err).To(HaveOccurred(), "Expected signing to fail after retries") - Expect(err.Error()).To(ContainSubstring("failed to sign images")) - }) - }) - }) -} - func setupNewSignerTests() { Describe("NewSigner", func() { var nc NotaryConfig @@ -437,7 +319,6 @@ func init() { setupDecodeCertAndKeyTests() setupBuildSigningRequestTests() setupBuildPayloadTests() - setupSignTests() setupNewSignerTests() } From 2fcb50b8aa3b6bef286ba3a843dac11e76c06917 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 00:36:50 +0200 Subject: [PATCH 29/54] test --- pkg/sign/notary.go | 187 +++++++++---------- pkg/sign/notary_mocks.go | 28 ++- pkg/sign/notary_test.go | 388 ++++++++++++++++++++++++++++++++------- 3 files changed, 429 insertions(+), 174 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 26f1e1618aec..e5a82d643379 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -40,68 +40,45 @@ func (e ErrBadResponse) Error() string { } type NotaryConfig struct { - // Set URL to Notary server signing endpoint - Endpoint string `yaml:"endpoint" json:"endpoint"` - // SecretPath contains path to the authentication credentials used for specific notary server - Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` - // Time after connection to notary server should time out - Timeout time.Duration `yaml:"timeout" json:"timeout"` - // RetryTimeout is time between each signing request to notary in case something fails - // Default is 10 seconds - RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` - - // ReadFileFunc allows injecting a custom file reading function, defaults to os.ReadFile. + Endpoint string `yaml:"endpoint" json:"endpoint"` + Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` + Timeout time.Duration `yaml:"timeout" json:"timeout"` + RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` ReadFileFunc func(string) ([]byte, error) } -// AuthSecretConfig contains auth information for notary server type AuthSecretConfig struct { - // Path if path to file that contains secret credentials Path string `yaml:"path" json:"path"` - // Type contains credential type, based on which the service will configure signing Type string `yaml:"type" json:"type"` } -// SignifySecret contains configuration of secret that is used to connect to SAP signify service type SignifySecret struct { - // Certificate data encoded in base64 CertificateData string `json:"certData"` - // Private key data encoded in base64 - PrivateKeyData string `json:"privateKeyData"` + PrivateKeyData string `json:"privateKeyData"` } -// SigningRequest contains information about all images with tags to sign using Notary type SigningRequest struct { - // Global unique name, e.g. full image name with registry URL NotaryGun string `json:"notaryGun"` - // SHA sum of manifest.json - SHA256 string `json:"sha256"` - // size of manifest.json - ByteSize int64 `json:"byteSize"` - // Image tag - Version string `json:"version"` + SHA256 string `json:"sha256"` + ByteSize int64 `json:"byteSize"` + Version string `json:"version"` } -// Target represents the target data for signing type Target struct { Name string `json:"name"` ByteSize int64 `json:"byteSize"` Digest string `json:"digest"` } -// TrustedCollection represents a trusted collection for a specific image type TrustedCollection struct { GUN string `json:"gun"` Targets []Target `json:"targets"` } -// SigningPayload represents the overall payload structure for the signing request type SigningPayload struct { TrustedCollections []TrustedCollection `json:"trustedCollections"` } -// NotarySigner is a struct that implements sign.Signer interface -// Takes care of signing requests to Notary server type NotarySigner struct { c *http.Client url string @@ -112,6 +89,8 @@ type NotarySigner struct { DecodeCertFunc func() (tls.Certificate, error) BuildSigningReqFunc func([]string) ([]SigningRequest, error) BuildPayloadFunc func([]SigningRequest) (SigningPayload, error) + SetupTLSFunc func(cert tls.Certificate) *tls.Config + HTTPClient *http.Client } type Reference interface{} @@ -128,36 +107,30 @@ type Manifest struct { } } -// SimpleImage is a basic implementation of the Image interface type SimpleImage struct { ManifestData Manifest } -// Manifest returns the manifest data for the image func (si *SimpleImage) Manifest() (*Manifest, error) { return &si.ManifestData, nil } -// GetImage fetches the image manifest from a container registry func GetImage(ref Reference) (Image, error) { r, ok := ref.(name.Reference) if !ok { return nil, fmt.Errorf("invalid reference type") } - // Fetch the image from the registry img, err := remote.Image(r) if err != nil { return nil, fmt.Errorf("failed to fetch image: %w", err) } - // Extract manifest from the image manifest, err := img.Manifest() if err != nil { return nil, fmt.Errorf("failed to get manifest: %w", err) } - // Return the image, which implements the Image interface return &SimpleImage{ ManifestData: Manifest{ Config: struct { @@ -177,7 +150,6 @@ func GetImage(ref Reference) (Image, error) { }, nil } -// SimpleReference is a basic implementation of the Reference interface type SimpleReference struct { Image string Tag string @@ -192,21 +164,17 @@ func ParseReference(image string) (Reference, error) { return ref, nil } -// DecodeCertAndKey loads the certificate and decrypted private key from base64-encoded strings in SignifySecret func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { - // Decode the base64-encoded certificate certData, err := base64.StdEncoding.DecodeString(ss.CertificateData) if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) } - // Decode the base64-encoded private key keyData, err := base64.StdEncoding.DecodeString(ss.PrivateKeyData) if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) } - // Load the certificate and key as a TLS certificate pair cert, err := tls.X509KeyPair(certData, keyData) if err != nil { return tls.Certificate{}, fmt.Errorf("unable to load certificate or key: %w", err) @@ -215,14 +183,12 @@ func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { return cert, nil } -// buildSigningRequest prepares the signing requests for the given images func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { var signingRequests []SigningRequest for _, i := range images { var base, tag string - // Split on ":" to separate base from tag parts := strings.Split(i, tagDelim) if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { base = strings.Join(parts[:len(parts)-1], tagDelim) @@ -253,11 +219,9 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e return signingRequests, nil } -// buildPayload creates the payload for the signing request from a list of SigningRequests func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) { var trustedCollections []TrustedCollection - // Loop through all signing requests and create separate entries for each GUN for _, req := range sr { target := Target{ Name: req.Version, @@ -265,7 +229,6 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) Digest: req.SHA256, } - // Each image gets its own trusted collection based on its GUN trustedCollection := TrustedCollection{ GUN: req.NotaryGun, Targets: []Target{target}, @@ -274,7 +237,6 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) trustedCollections = append(trustedCollections, trustedCollection) } - // Prepare the payload structure with multiple trustedCollections payload := SigningPayload{ TrustedCollections: trustedCollections, } @@ -282,7 +244,6 @@ func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) return payload, nil } -// Sign makes an HTTP request to sign the images using the Notary server func (ns NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") @@ -291,105 +252,126 @@ func (ns NotarySigner) Sign(images []string) error { return fmt.Errorf("build signing request: %w", err) } - // Build the payload from signing requests payload, err := ns.BuildPayloadFunc(signingRequests) if err != nil { return fmt.Errorf("build payload: %w", err) } - // Marshal the payload to JSON b, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshal signing request: %w", err) } - // Decode the certificate and key from the signifySecret structure - cert, err := ns.DecodeCertFunc() - if err != nil { - return fmt.Errorf("failed to load certificate and key: %w", err) - } - - // Configure TLS with the decoded certificate and private key - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - } + var client *http.Client + if ns.HTTPClient != nil { + client = ns.HTTPClient + } else { + cert, err := ns.DecodeCertFunc() + if err != nil { + return fmt.Errorf("failed to load certificate and key: %w", err) + } - // Create an HTTP client with the custom TLS configuration and timeout - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - Timeout: ns.retryTimeout, + tlsConfig := ns.SetupTLSFunc(cert) + client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + Timeout: ns.retryTimeout, + } } - // Create a new POST request with the signing payload req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) if err != nil { return err } req.Header.Add("Content-Type", "application/json") - retries := 5 - var status string - var respMsg []byte - w := time.NewTicker(ns.retryTimeout) - defer w.Stop() - for retries > 0 { - fmt.Printf("Trying to sign %s. %v retries remaining...\n", sImg, retries) - <-w.C + resp, err := retryHTTPRequest(client, req, 5, ns.retryTimeout) + if err != nil { + return fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() - // Send the HTTP request - resp, err := client.Do(req) + if resp.StatusCode != http.StatusAccepted { + respMsg, _ := io.ReadAll(resp.Body) + return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: resp.Status, message: string(respMsg)}) + } + + fmt.Printf("Successfully signed images %s!\n", sImg) + return nil +} + +func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retryInterval time.Duration) (*http.Response, error) { + var lastResp *http.Response + var lastErr error + + // Read and store the request body + var bodyBytes []byte + if req.Body != nil { + var err error + bodyBytes, err = io.ReadAll(req.Body) if err != nil { - return fmt.Errorf("failed to send request: %w", err) + return nil, fmt.Errorf("failed to read request body: %w", err) } - status = resp.Status - defer resp.Body.Close() + req.Body.Close() + } - // Read the response body - respMsg, err = io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read response: %w", err) + for retries > 0 { + // Reset the request body for each retry + if bodyBytes != nil { + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } - switch resp.StatusCode { - case http.StatusAccepted: - fmt.Printf("Successfully signed images %s!\n", sImg) - return nil - case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest, http.StatusUnsupportedMediaType: - return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) + + resp, err := client.Do(req) + if err != nil { + lastErr = err + } else if resp.StatusCode == http.StatusAccepted { + return resp, nil + } else { + lastErr = fmt.Errorf("failed to sign images, unexpected status code: %d", resp.StatusCode) } + + lastResp = resp retries-- + if retries == 0 { + break + } + time.Sleep(retryInterval) } + return lastResp, fmt.Errorf("request failed after retries: %w", lastErr) +} - return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: status, message: string(respMsg)}) +func setupTLS(cert tls.Certificate) *tls.Config { + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + } } -// NewSigner creates a new NotarySigner based on the configuration. func (nc NotaryConfig) NewSigner() (Signer, error) { ns := NotarySigner{ - c: &http.Client{}, - retryTimeout: 10 * time.Second, - url: "https://signing-manage.repositories.cloud.sap/trusted-collections/publish", - ParseReferenceFunc: ParseReference, // Using the new ParseReference function - GetImageFunc: GetImage, // Using the new GetImage function + retryTimeout: nc.RetryTimeout, + url: nc.Endpoint, + ParseReferenceFunc: ParseReference, + GetImageFunc: GetImage, + SetupTLSFunc: setupTLS, } ns.BuildPayloadFunc = ns.buildPayload ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey - ns.c.Timeout = nc.Timeout - if nc.RetryTimeout > 0 { - ns.retryTimeout = nc.RetryTimeout + // Konfiguracja klienta HTTP + ns.c = &http.Client{ + Timeout: nc.Timeout, } if nc.Secret != nil { switch nc.Secret.Type { case "signify": - // Use injected ReadFileFunc or default to os.ReadFile readFileFunc := nc.ReadFileFunc if readFileFunc == nil { readFileFunc = os.ReadFile } + f, err := readFileFunc(nc.Secret.Path) if err != nil { return nil, fmt.Errorf("failed to read secret file: %w", err) @@ -402,7 +384,6 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { } ns.signifySecret = s - default: return nil, fmt.Errorf("unsupported secret type: %s", nc.Secret.Type) } diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index cbcf4a8ae9bb..4a7a6fd6b86c 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -13,7 +13,6 @@ func (m *MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) return m.RoundTripFunc(req) } -type MockReference struct{} type MockImage struct{} func (m *MockImage) Manifest() (*Manifest, error) { @@ -34,10 +33,29 @@ func (m *MockImage) Manifest() (*Manifest, error) { }, nil } -func MockParseReference(_ string) (Reference, error) { - return &MockReference{}, nil +// MockParseReference jest funkcją mockującą dla ParseReferenceFunc +func MockParseReference(image string) (Reference, error) { + return image, nil // W prostym przypadku zwracamy sam string jako Reference } -func MockGetImage(_ Reference) (Image, error) { - return &MockImage{}, nil +// MockGetImage jest funkcją mockującą dla GetImageFunc +func MockGetImage(ref Reference) (Image, error) { + // Zwracamy mockowany obiekt Image z predefiniowanymi wartościami + return &SimpleImage{ + ManifestData: Manifest{ + Config: struct { + Digest struct { + Hex string + } + Size int64 + }{ + Digest: struct { + Hex string + }{ + Hex: "abc123def456", + }, + Size: 12345678, + }, + }, + }, nil } diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 5b43bfafff03..2ce6847574db 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -11,10 +11,14 @@ import ( "encoding/pem" "errors" "fmt" + "io" "math/big" + "net/http" + "net/http/httptest" "testing" "time" + "github.com/google/go-containerregistry/pkg/name" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -24,12 +28,24 @@ func TestSuite(t *testing.T) { RunSpecs(t, "Sign Package Test Suite") } +var _ = Describe("Sign Package Tests", func() { + setupDecodeCertAndKeyTests() + setupBuildSigningRequestTests() + setupBuildPayloadTests() + setupNewSignerTests() + setupGetImageTests() + setupParseReferenceTests() + setupSetupTLSTests() + setupRetryHTTPRequestTests() + setupSignTests() +}) + func setupDecodeCertAndKeyTests() { Describe("DecodeCertAndKey", func() { var signifySecret SignifySecret BeforeEach(func() { - // Use the GenerateBase64EncodedCert function to generate base64-encoded cert and key + // Generowanie bazy64 zakodowanych certyfikatu i klucza certBase64, keyBase64, err := GenerateBase64EncodedCert() Expect(err).To(BeNil()) @@ -39,45 +55,45 @@ func setupDecodeCertAndKeyTests() { } }) - Context("When decoding is successful", func() { - It("should decode certificate and private key successfully", func() { + Context("Gdy dekodowanie przebiega pomyślnie", func() { + It("powinno poprawnie dekodować certyfikat i klucz prywatny", func() { cert, err := signifySecret.DecodeCertAndKey() Expect(err).To(BeNil()) Expect(cert).To(BeAssignableToTypeOf(tls.Certificate{})) }) }) - Context("When certificate decoding fails", func() { + Context("Gdy dekodowanie certyfikatu się nie powiedzie", func() { BeforeEach(func() { - signifySecret.CertificateData = "invalid-base64" + signifySecret.CertificateData = "niepoprawny-base64" }) - It("should return an error for invalid certificate data", func() { + It("powinno zwrócić błąd dla niepoprawnych danych certyfikatu", func() { _, err := signifySecret.DecodeCertAndKey() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to decode certificate")) }) }) - Context("When private key decoding fails", func() { + Context("Gdy dekodowanie klucza prywatnego się nie powiedzie", func() { BeforeEach(func() { - signifySecret.PrivateKeyData = "invalid-base64" + signifySecret.PrivateKeyData = "niepoprawny-base64" }) - It("should return an error for invalid private key data", func() { + It("powinno zwrócić błąd dla niepoprawnych danych klucza prywatnego", func() { _, err := signifySecret.DecodeCertAndKey() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to decode private key")) }) }) - Context("When loading TLS certificate fails", func() { + Context("Gdy wczytanie certyfikatu TLS się nie powiedzie", func() { BeforeEach(func() { - signifySecret.CertificateData = base64.StdEncoding.EncodeToString([]byte("invalid-cert")) - signifySecret.PrivateKeyData = base64.StdEncoding.EncodeToString([]byte("invalid-key")) + signifySecret.CertificateData = base64.StdEncoding.EncodeToString([]byte("niepoprawny-cert")) + signifySecret.PrivateKeyData = base64.StdEncoding.EncodeToString([]byte("niepoprawny-klucz")) }) - It("should return an error for invalid certificate or key", func() { + It("powinno zwrócić błąd dla niepoprawnego certyfikatu lub klucza", func() { _, err := signifySecret.DecodeCertAndKey() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("unable to load certificate or key")) @@ -91,7 +107,7 @@ func setupBuildSigningRequestTests() { var ns NotarySigner BeforeEach(func() { - // Set up NotarySigner with mock functions + // Ustawienie NotarySigner z funkcjami mock ns = NotarySigner{ ParseReferenceFunc: MockParseReference, GetImageFunc: MockGetImage, @@ -99,8 +115,8 @@ func setupBuildSigningRequestTests() { }) Describe("buildSigningRequest", func() { - Context("When valid images are provided", func() { - It("should create signing requests successfully", func() { + Context("Gdy podano poprawne obrazy", func() { + It("powinno poprawnie utworzyć żądania podpisania", func() { images := []string{ "gcr.io/project/image:tag1", "docker.io/library/ubuntu:latest", @@ -112,21 +128,21 @@ func setupBuildSigningRequestTests() { Expect(signingRequests).To(HaveLen(len(images))) for i, req := range signingRequests { - Expect(req.NotaryGun).NotTo(BeEmpty(), "NotaryGun should not be empty for request %d", i) - Expect(req.SHA256).To(Equal("abc123def456"), "SHA256 should match for request %d", i) - Expect(req.ByteSize).To(Equal(int64(12345678)), "ByteSize should match for request %d", i) + Expect(req.NotaryGun).NotTo(BeEmpty(), "NotaryGun nie powinno być puste dla żądania %d", i) + Expect(req.SHA256).To(Equal("abc123def456"), "SHA256 powinno pasować dla żądania %d", i) + Expect(req.ByteSize).To(Equal(int64(12345678)), "ByteSize powinno pasować dla żądania %d", i) } }) }) - Context("When an invalid image reference is provided", func() { + Context("Gdy podano niepoprawne odniesienie do obrazu", func() { BeforeEach(func() { ns.ParseReferenceFunc = func(image string) (Reference, error) { return nil, fmt.Errorf("invalid reference") } }) - It("should return an error for invalid reference", func() { + It("powinno zwrócić błąd dla niepoprawnego odniesienia", func() { images := []string{"invalid/image:tag"} _, err := ns.buildSigningRequest(images) @@ -135,14 +151,14 @@ func setupBuildSigningRequestTests() { }) }) - Context("When fetching the image fails", func() { + Context("Gdy pobieranie obrazu się nie powiedzie", func() { BeforeEach(func() { ns.GetImageFunc = func(ref Reference) (Image, error) { return nil, fmt.Errorf("image fetch failed") } }) - It("should return an error for failed image fetch", func() { + It("powinno zwrócić błąd dla nieudanego pobierania obrazu", func() { images := []string{"gcr.io/project/image:tag1"} _, err := ns.buildSigningRequest(images) @@ -162,8 +178,8 @@ func setupBuildPayloadTests() { ns = NotarySigner{} }) - Context("When valid signing requests are provided", func() { - It("should create a payload with the correct trusted collections", func() { + Context("Gdy podano poprawne żądania podpisania", func() { + It("powinno utworzyć payload z poprawnymi zaufanymi kolekcjami", func() { signingRequests := []SigningRequest{ { NotaryGun: "gcr.io/project", @@ -182,17 +198,17 @@ func setupBuildPayloadTests() { payload, err := ns.buildPayload(signingRequests) Expect(err).To(BeNil()) - // Verify the TrustedCollections in the payload + // Weryfikacja TrustedCollections w payload Expect(payload.TrustedCollections).To(HaveLen(2)) - // Check the first trusted collection + // Sprawdzenie pierwszej zaufanej kolekcji Expect(payload.TrustedCollections[0].GUN).To(Equal("gcr.io/project")) Expect(payload.TrustedCollections[0].Targets).To(HaveLen(1)) Expect(payload.TrustedCollections[0].Targets[0].Name).To(Equal("v1.0")) Expect(payload.TrustedCollections[0].Targets[0].ByteSize).To(Equal(int64(123456))) Expect(payload.TrustedCollections[0].Targets[0].Digest).To(Equal("abc123")) - // Check the second trusted collection + // Sprawdzenie drugiej zaufanej kolekcji Expect(payload.TrustedCollections[1].GUN).To(Equal("docker.io/library/ubuntu")) Expect(payload.TrustedCollections[1].Targets).To(HaveLen(1)) Expect(payload.TrustedCollections[1].Targets[0].Name).To(Equal("latest")) @@ -201,14 +217,14 @@ func setupBuildPayloadTests() { }) }) - Context("When an empty signing request list is provided", func() { - It("should return an empty payload", func() { + Context("Gdy podano pustą listę żądań podpisania", func() { + It("powinno zwrócić pusty payload", func() { signingRequests := []SigningRequest{} payload, err := ns.buildPayload(signingRequests) Expect(err).To(BeNil()) - // Verify that the payload has no trusted collections + // Weryfikacja, że payload nie zawiera zaufanych kolekcji Expect(payload.TrustedCollections).To(BeEmpty()) }) }) @@ -220,59 +236,59 @@ func setupNewSignerTests() { var nc NotaryConfig BeforeEach(func() { - // Initialize NotaryConfig with mocked Secret + // Inicjalizacja NotaryConfig z mockowanym Secret nc = NotaryConfig{ + Endpoint: "https://example.com/sign", Timeout: 5 * time.Second, RetryTimeout: 15 * time.Second, - Secret: &AuthSecretConfig{ // Properly initialize Secret + Secret: &AuthSecretConfig{ Path: "/mock/path/to/secret", Type: "signify", }, } }) - Context("When valid signify secret is provided", func() { - It("should return a valid NotarySigner", func() { - // Mock signify secret content + Context("Gdy podano poprawny signify secret", func() { + It("powinno zwrócić poprawny NotarySigner", func() { + // Mockowanie treści signify secret signifySecret := SignifySecret{ CertificateData: "mockCertData", PrivateKeyData: "mockPrivateKeyData", } secretContent, _ := json.Marshal(signifySecret) - // Inject mock ReadFileFunc to simulate file reading without a real file + // Iniekcja mockowanej funkcji ReadFileFunc nc.ReadFileFunc = func(path string) ([]byte, error) { - // Ensure the correct path is used Expect(path).To(Equal("/mock/path/to/secret")) - // Return the mock signify secret content return secretContent, nil } - // Call the NewSigner method + // Wywołanie metody NewSigner signer, err := nc.NewSigner() Expect(err).To(BeNil()) Expect(signer).NotTo(BeNil()) - // Verify NotarySigner properties + // Weryfikacja właściwości NotarySigner notarySigner, ok := signer.(*NotarySigner) Expect(ok).To(BeTrue()) Expect(notarySigner.signifySecret.CertificateData).To(Equal("mockCertData")) Expect(notarySigner.signifySecret.PrivateKeyData).To(Equal("mockPrivateKeyData")) Expect(notarySigner.retryTimeout).To(Equal(15 * time.Second)) Expect(notarySigner.c.Timeout).To(Equal(5 * time.Second)) + Expect(notarySigner.url).To(Equal("https://example.com/sign")) }) }) - Context("When secret file cannot be read", func() { - It("should return an error", func() { - // Mock an error when trying to read the secret file + Context("Gdy nie można odczytać pliku secret", func() { + It("powinno zwrócić błąd", func() { + // Mockowanie błędu podczas odczytu pliku nc.Secret.Path = "/mock/invalid/path" nc.ReadFileFunc = func(path string) ([]byte, error) { Expect(path).To(Equal("/mock/invalid/path")) return nil, errors.New("failed to read file") } - // Call the NewSigner method + // Wywołanie metody NewSigner signer, err := nc.NewSigner() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to read secret file")) @@ -280,14 +296,14 @@ func setupNewSignerTests() { }) }) - Context("When unsupported secret type is provided", func() { - It("should return an error", func() { - // Set an unsupported secret type, but don't provide a file path + Context("Gdy podano nieobsługiwany typ secret", func() { + It("powinno zwrócić błąd", func() { + // Ustawienie nieobsługiwanego typu secret nc.Secret = &AuthSecretConfig{ Type: "unsupported", } - // Call the NewSigner method + // Wywołanie metody NewSigner signer, err := nc.NewSigner() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("unsupported secret type")) @@ -295,9 +311,9 @@ func setupNewSignerTests() { }) }) - Context("When signify secret unmarshalling fails", func() { - It("should return an error", func() { - // Mock signify secret file with invalid JSON + Context("Gdy unmarshalling signify secret się nie powiedzie", func() { + It("powinno zwrócić błąd", func() { + // Mockowanie niepoprawnego JSON w pliku secret nc.Secret.Path = "/mock/path/to/secret" nc.Secret.Type = "signify" nc.ReadFileFunc = func(path string) ([]byte, error) { @@ -305,7 +321,7 @@ func setupNewSignerTests() { return []byte("invalid-json"), nil } - // Call the NewSigner method + // Wywołanie metody NewSigner signer, err := nc.NewSigner() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to unmarshal signify secret")) @@ -315,23 +331,263 @@ func setupNewSignerTests() { }) } -func init() { - setupDecodeCertAndKeyTests() - setupBuildSigningRequestTests() - setupBuildPayloadTests() - setupNewSignerTests() +func setupGetImageTests() { + Describe("GetImage", func() { + Context("Gdy podano poprawne odniesienie", func() { + It("powinno zwrócić SimpleImage z poprawnymi danymi manifestu", func() { + // Tworzymy poprawne odniesienie + ref, err := name.ParseReference("gcr.io/project/image:tag") + Expect(err).To(BeNil()) + + // Mockowanie remote.Image i manifestu + // W tym przypadku zakładamy, że funkcja działa poprawnie, + // ponieważ testowanie rzeczywistego pobierania obrazu wymagałoby dostępu do zewnętrznych zasobów. + + // Możemy więc przetestować przypadek, gdy odniesienie jest niepoprawne. + img, err := GetImage(ref) + // Ponieważ nie mamy rzeczywistego obrazu, może wystąpić błąd. + if err != nil { + // Sprawdzamy, czy zwrócono błąd związany z pobieraniem obrazu + Expect(err.Error()).To(ContainSubstring("failed to fetch image")) + } else { + // Jeśli nie wystąpił błąd, sprawdzamy, czy obraz nie jest pusty + Expect(img).NotTo(BeNil()) + } + }) + }) + + Context("Gdy podano niepoprawny typ odniesienia", func() { + It("powinno zwrócić błąd wskazujący na niepoprawny typ odniesienia", func() { + // Podajemy odniesienie, które nie jest typu name.Reference + ref := "niepoprawne odniesienie" + + img, err := GetImage(ref) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid reference type")) + Expect(img).To(BeNil()) + }) + }) + }) +} + +func setupParseReferenceTests() { + Describe("ParseReference", func() { + Context("Gdy podano poprawny ciąg obrazu", func() { + It("powinno poprawnie sparsować odniesienie", func() { + image := "gcr.io/project/image:tag" + ref, err := ParseReference(image) + Expect(err).To(BeNil()) + Expect(ref).NotTo(BeNil()) + }) + }) + + Context("Gdy podano niepoprawny ciąg obrazu", func() { + It("powinno zwrócić błąd parsowania", func() { + image := "niepoprawny_ciag_obrazu@@" + ref, err := ParseReference(image) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to parse image reference")) + Expect(ref).To(BeNil()) + }) + }) + }) +} + +func setupSetupTLSTests() { + Describe("setupTLS", func() { + Context("Gdy podano poprawny certyfikat TLS", func() { + It("powinno zwrócić poprawną konfigurację TLS", func() { + certBase64, keyBase64, err := GenerateBase64EncodedCert() + Expect(err).To(BeNil()) + + certData, err := base64.StdEncoding.DecodeString(certBase64) + Expect(err).To(BeNil()) + keyData, err := base64.StdEncoding.DecodeString(keyBase64) + Expect(err).To(BeNil()) + + cert, err := tls.X509KeyPair(certData, keyData) + Expect(err).To(BeNil()) + + tlsConfig := setupTLS(cert) + Expect(tlsConfig).NotTo(BeNil()) + Expect(tlsConfig.Certificates).To(HaveLen(1)) + }) + }) + }) +} + +func setupRetryHTTPRequestTests() { + Describe("retryHTTPRequest", func() { + var ( + server *httptest.Server + client *http.Client + request *http.Request + retryCount int + retryTimeout time.Duration + ) + + BeforeEach(func() { + retryCount = 3 + retryTimeout = 100 * time.Millisecond + }) + + Context("Gdy żądanie jest pomyślne", func() { + BeforeEach(func() { + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusAccepted) + })) + client = server.Client() + request, _ = http.NewRequest("GET", server.URL, nil) + }) + + AfterEach(func() { + server.Close() + }) + + It("powinno zwrócić odpowiedź bez błędów", func() { + resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) + Expect(err).To(BeNil()) + Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) + }) + }) + + Context("Gdy żądanie nie powiedzie się kilka razy, ale ostatecznie się powiedzie", func() { + var attempt int + + BeforeEach(func() { + attempt = 0 + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attempt++ + if attempt < 2 { + http.Error(w, "temporary error", http.StatusInternalServerError) + } else { + w.WriteHeader(http.StatusAccepted) + } + })) + client = server.Client() + request, _ = http.NewRequest("GET", server.URL, nil) + }) + + AfterEach(func() { + server.Close() + }) + + It("powinno powtórzyć żądanie i ostatecznie zwrócić pomyślną odpowiedź", func() { + resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) + Expect(err).To(BeNil()) + Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) + Expect(attempt).To(Equal(2)) + }) + }) + + Context("Gdy wszystkie próby żądania się nie powiodą", func() { + BeforeEach(func() { + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "error", http.StatusInternalServerError) + })) + client = server.Client() + request, _ = http.NewRequest("GET", server.URL, nil) + }) + + AfterEach(func() { + server.Close() + }) + + It("powinno zwrócić błąd po wyczerpaniu prób", func() { + resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) + Expect(err).To(HaveOccurred()) + Expect(resp).NotTo(BeNil()) + Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError)) + Expect(err.Error()).To(ContainSubstring("unexpected status code: 500")) + }) + }) + }) +} + +func setupSignTests() { + Describe("Sign", func() { + var ns NotarySigner + var server *httptest.Server + + BeforeEach(func() { + ns = NotarySigner{ + ParseReferenceFunc: MockParseReference, + GetImageFunc: MockGetImage, + BuildPayloadFunc: func(sr []SigningRequest) (SigningPayload, error) { + return SigningPayload{ + TrustedCollections: []TrustedCollection{ + { + GUN: "example.com/image", + Targets: []Target{ + { + Name: "latest", + ByteSize: 12345, + Digest: "abc123", + }, + }, + }, + }, + }, nil + }, + DecodeCertFunc: func() (tls.Certificate, error) { + return tls.Certificate{}, nil + }, + SetupTLSFunc: setupTLS, + retryTimeout: 100 * time.Millisecond, + } + }) + + AfterEach(func() { + if server != nil { + server.Close() + } + }) + + Context("Gdy podpisywanie przebiega pomyślnie", func() { + It("powinno zakończyć się bez błędów", func() { + server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.Copy(io.Discard, r.Body) // Ensure the body is read + w.WriteHeader(http.StatusAccepted) + })) + + ns.url = server.URL + ns.HTTPClient = server.Client() // Use the server's client + + err := ns.Sign([]string{"example.com/image:latest"}) + Expect(err).To(BeNil()) + }) + }) + + Context("Gdy wystąpi błąd podczas podpisywania", func() { + It("powinno zwrócić odpowiedni błąd", func() { + server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.Copy(io.Discard, r.Body) // Ensure the body is read + r.Body.Close() + http.Error(w, "error", http.StatusInternalServerError) + })) + + ns.url = server.URL + ns.HTTPClient = server.Client() // Use the server's client + + err := ns.Sign([]string{"example.com/image:latest"}) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to sign images")) + }) + }) + + }) } -// GenerateBase64EncodedCert generates a self-signed certificate and private key, -// and returns them as base64 encoded strings. +// GenerateBase64EncodedCert generuje samopodpisany certyfikat i klucz prywatny, +// i zwraca je jako ciągi zakodowane w base64. func GenerateBase64EncodedCert() (string, string, error) { - // Generate a private RSA key + // Generowanie klucza prywatnego RSA privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return "", "", err } - // Create a self-signed certificate template + // Tworzenie szablonu certyfikatu template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ @@ -344,25 +600,25 @@ func GenerateBase64EncodedCert() (string, string, error) { BasicConstraintsValid: true, } - // Create the certificate using the template and the private key + // Tworzenie certyfikatu certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { return "", "", err } - // Encode the certificate to PEM format + // Kodowanie certyfikatu do formatu PEM certPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certDER, }) - // Encode the private key to PEM format + // Kodowanie klucza prywatnego do formatu PEM keyPEM := pem.EncodeToMemory(&pem.Block{ Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey), }) - // Encode PEM data to base64 + // Kodowanie danych PEM do base64 certBase64 := base64.StdEncoding.EncodeToString(certPEM) keyBase64 := base64.StdEncoding.EncodeToString(keyPEM) From a1f3fe000607f965d69182b3a40caf9efaf73081 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 00:41:19 +0200 Subject: [PATCH 30/54] New solution --- pkg/sign/notary.go | 2 +- pkg/sign/notary_mocks.go | 8 +- pkg/sign/notary_test.go | 178 +++++++++++++++++++-------------------- 3 files changed, 94 insertions(+), 94 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index e5a82d643379..6eba445717ad 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -359,7 +359,7 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { ns.BuildPayloadFunc = ns.buildPayload ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey - // Konfiguracja klienta HTTP + // HTTP client configuration ns.c = &http.Client{ Timeout: nc.Timeout, } diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 4a7a6fd6b86c..3041d57b0782 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -33,14 +33,14 @@ func (m *MockImage) Manifest() (*Manifest, error) { }, nil } -// MockParseReference jest funkcją mockującą dla ParseReferenceFunc +// MockParseReference is a mock function for ParseReferenceFunc func MockParseReference(image string) (Reference, error) { - return image, nil // W prostym przypadku zwracamy sam string jako Reference + return image, nil // In a simple case, we return the string itself as Reference } -// MockGetImage jest funkcją mockującą dla GetImageFunc +// MockGetImage is a mock function for GetImageFunc func MockGetImage(ref Reference) (Image, error) { - // Zwracamy mockowany obiekt Image z predefiniowanymi wartościami + // We return a mocked Image object with predefined values return &SimpleImage{ ManifestData: Manifest{ Config: struct { diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 2ce6847574db..2dbceb6e21a1 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -45,7 +45,7 @@ func setupDecodeCertAndKeyTests() { var signifySecret SignifySecret BeforeEach(func() { - // Generowanie bazy64 zakodowanych certyfikatu i klucza + // Generating base64 encoded certificate and key certBase64, keyBase64, err := GenerateBase64EncodedCert() Expect(err).To(BeNil()) @@ -55,45 +55,45 @@ func setupDecodeCertAndKeyTests() { } }) - Context("Gdy dekodowanie przebiega pomyślnie", func() { - It("powinno poprawnie dekodować certyfikat i klucz prywatny", func() { + Context("When decoding is successful", func() { + It("should correctly decode certificate and private key", func() { cert, err := signifySecret.DecodeCertAndKey() Expect(err).To(BeNil()) Expect(cert).To(BeAssignableToTypeOf(tls.Certificate{})) }) }) - Context("Gdy dekodowanie certyfikatu się nie powiedzie", func() { + Context("When decoding certificate fails", func() { BeforeEach(func() { - signifySecret.CertificateData = "niepoprawny-base64" + signifySecret.CertificateData = "invalid-base64" }) - It("powinno zwrócić błąd dla niepoprawnych danych certyfikatu", func() { + It("should return an error for invalid certificate data", func() { _, err := signifySecret.DecodeCertAndKey() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to decode certificate")) }) }) - Context("Gdy dekodowanie klucza prywatnego się nie powiedzie", func() { + Context("When decoding private key fails", func() { BeforeEach(func() { - signifySecret.PrivateKeyData = "niepoprawny-base64" + signifySecret.PrivateKeyData = "invalid-base64" }) - It("powinno zwrócić błąd dla niepoprawnych danych klucza prywatnego", func() { + It("should return an error for invalid private key data", func() { _, err := signifySecret.DecodeCertAndKey() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to decode private key")) }) }) - Context("Gdy wczytanie certyfikatu TLS się nie powiedzie", func() { + Context("When loading TLS certificate fails", func() { BeforeEach(func() { - signifySecret.CertificateData = base64.StdEncoding.EncodeToString([]byte("niepoprawny-cert")) - signifySecret.PrivateKeyData = base64.StdEncoding.EncodeToString([]byte("niepoprawny-klucz")) + signifySecret.CertificateData = base64.StdEncoding.EncodeToString([]byte("invalid-cert")) + signifySecret.PrivateKeyData = base64.StdEncoding.EncodeToString([]byte("invalid-key")) }) - It("powinno zwrócić błąd dla niepoprawnego certyfikatu lub klucza", func() { + It("should return an error for invalid certificate or key", func() { _, err := signifySecret.DecodeCertAndKey() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("unable to load certificate or key")) @@ -107,7 +107,7 @@ func setupBuildSigningRequestTests() { var ns NotarySigner BeforeEach(func() { - // Ustawienie NotarySigner z funkcjami mock + // Setting up NotarySigner with mock functions ns = NotarySigner{ ParseReferenceFunc: MockParseReference, GetImageFunc: MockGetImage, @@ -115,8 +115,8 @@ func setupBuildSigningRequestTests() { }) Describe("buildSigningRequest", func() { - Context("Gdy podano poprawne obrazy", func() { - It("powinno poprawnie utworzyć żądania podpisania", func() { + Context("When valid images are provided", func() { + It("should correctly create signing requests", func() { images := []string{ "gcr.io/project/image:tag1", "docker.io/library/ubuntu:latest", @@ -128,21 +128,21 @@ func setupBuildSigningRequestTests() { Expect(signingRequests).To(HaveLen(len(images))) for i, req := range signingRequests { - Expect(req.NotaryGun).NotTo(BeEmpty(), "NotaryGun nie powinno być puste dla żądania %d", i) - Expect(req.SHA256).To(Equal("abc123def456"), "SHA256 powinno pasować dla żądania %d", i) - Expect(req.ByteSize).To(Equal(int64(12345678)), "ByteSize powinno pasować dla żądania %d", i) + Expect(req.NotaryGun).NotTo(BeEmpty(), "NotaryGun should not be empty for request %d", i) + Expect(req.SHA256).To(Equal("abc123def456"), "SHA256 should match for request %d", i) + Expect(req.ByteSize).To(Equal(int64(12345678)), "ByteSize should match for request %d", i) } }) }) - Context("Gdy podano niepoprawne odniesienie do obrazu", func() { + Context("When an invalid image reference is provided", func() { BeforeEach(func() { ns.ParseReferenceFunc = func(image string) (Reference, error) { return nil, fmt.Errorf("invalid reference") } }) - It("powinno zwrócić błąd dla niepoprawnego odniesienia", func() { + It("should return an error for invalid reference", func() { images := []string{"invalid/image:tag"} _, err := ns.buildSigningRequest(images) @@ -151,14 +151,14 @@ func setupBuildSigningRequestTests() { }) }) - Context("Gdy pobieranie obrazu się nie powiedzie", func() { + Context("When image fetching fails", func() { BeforeEach(func() { ns.GetImageFunc = func(ref Reference) (Image, error) { return nil, fmt.Errorf("image fetch failed") } }) - It("powinno zwrócić błąd dla nieudanego pobierania obrazu", func() { + It("should return an error for failed image fetch", func() { images := []string{"gcr.io/project/image:tag1"} _, err := ns.buildSigningRequest(images) @@ -178,8 +178,8 @@ func setupBuildPayloadTests() { ns = NotarySigner{} }) - Context("Gdy podano poprawne żądania podpisania", func() { - It("powinno utworzyć payload z poprawnymi zaufanymi kolekcjami", func() { + Context("When valid signing requests are provided", func() { + It("should create a payload with correct trusted collections", func() { signingRequests := []SigningRequest{ { NotaryGun: "gcr.io/project", @@ -198,17 +198,17 @@ func setupBuildPayloadTests() { payload, err := ns.buildPayload(signingRequests) Expect(err).To(BeNil()) - // Weryfikacja TrustedCollections w payload + // Verify TrustedCollections in the payload Expect(payload.TrustedCollections).To(HaveLen(2)) - // Sprawdzenie pierwszej zaufanej kolekcji + // Check the first trusted collection Expect(payload.TrustedCollections[0].GUN).To(Equal("gcr.io/project")) Expect(payload.TrustedCollections[0].Targets).To(HaveLen(1)) Expect(payload.TrustedCollections[0].Targets[0].Name).To(Equal("v1.0")) Expect(payload.TrustedCollections[0].Targets[0].ByteSize).To(Equal(int64(123456))) Expect(payload.TrustedCollections[0].Targets[0].Digest).To(Equal("abc123")) - // Sprawdzenie drugiej zaufanej kolekcji + // Check the second trusted collection Expect(payload.TrustedCollections[1].GUN).To(Equal("docker.io/library/ubuntu")) Expect(payload.TrustedCollections[1].Targets).To(HaveLen(1)) Expect(payload.TrustedCollections[1].Targets[0].Name).To(Equal("latest")) @@ -217,14 +217,14 @@ func setupBuildPayloadTests() { }) }) - Context("Gdy podano pustą listę żądań podpisania", func() { - It("powinno zwrócić pusty payload", func() { + Context("When an empty list of signing requests is provided", func() { + It("should return an empty payload", func() { signingRequests := []SigningRequest{} payload, err := ns.buildPayload(signingRequests) Expect(err).To(BeNil()) - // Weryfikacja, że payload nie zawiera zaufanych kolekcji + // Verify that the payload contains no trusted collections Expect(payload.TrustedCollections).To(BeEmpty()) }) }) @@ -236,7 +236,7 @@ func setupNewSignerTests() { var nc NotaryConfig BeforeEach(func() { - // Inicjalizacja NotaryConfig z mockowanym Secret + // Initialize NotaryConfig with a mocked Secret nc = NotaryConfig{ Endpoint: "https://example.com/sign", Timeout: 5 * time.Second, @@ -248,27 +248,27 @@ func setupNewSignerTests() { } }) - Context("Gdy podano poprawny signify secret", func() { - It("powinno zwrócić poprawny NotarySigner", func() { - // Mockowanie treści signify secret + Context("When a valid signify secret is provided", func() { + It("should return a valid NotarySigner", func() { + // Mock signify secret content signifySecret := SignifySecret{ CertificateData: "mockCertData", PrivateKeyData: "mockPrivateKeyData", } secretContent, _ := json.Marshal(signifySecret) - // Iniekcja mockowanej funkcji ReadFileFunc + // Inject mocked ReadFileFunc nc.ReadFileFunc = func(path string) ([]byte, error) { Expect(path).To(Equal("/mock/path/to/secret")) return secretContent, nil } - // Wywołanie metody NewSigner + // Call NewSigner method signer, err := nc.NewSigner() Expect(err).To(BeNil()) Expect(signer).NotTo(BeNil()) - // Weryfikacja właściwości NotarySigner + // Verify properties of NotarySigner notarySigner, ok := signer.(*NotarySigner) Expect(ok).To(BeTrue()) Expect(notarySigner.signifySecret.CertificateData).To(Equal("mockCertData")) @@ -279,16 +279,16 @@ func setupNewSignerTests() { }) }) - Context("Gdy nie można odczytać pliku secret", func() { - It("powinno zwrócić błąd", func() { - // Mockowanie błędu podczas odczytu pliku + Context("When reading the secret file fails", func() { + It("should return an error", func() { + // Mock error during file read nc.Secret.Path = "/mock/invalid/path" nc.ReadFileFunc = func(path string) ([]byte, error) { Expect(path).To(Equal("/mock/invalid/path")) return nil, errors.New("failed to read file") } - // Wywołanie metody NewSigner + // Call NewSigner method signer, err := nc.NewSigner() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to read secret file")) @@ -296,14 +296,14 @@ func setupNewSignerTests() { }) }) - Context("Gdy podano nieobsługiwany typ secret", func() { - It("powinno zwrócić błąd", func() { - // Ustawienie nieobsługiwanego typu secret + Context("When an unsupported secret type is provided", func() { + It("should return an error", func() { + // Set unsupported secret type nc.Secret = &AuthSecretConfig{ Type: "unsupported", } - // Wywołanie metody NewSigner + // Call NewSigner method signer, err := nc.NewSigner() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("unsupported secret type")) @@ -311,9 +311,9 @@ func setupNewSignerTests() { }) }) - Context("Gdy unmarshalling signify secret się nie powiedzie", func() { - It("powinno zwrócić błąd", func() { - // Mockowanie niepoprawnego JSON w pliku secret + Context("When unmarshalling the signify secret fails", func() { + It("should return an error", func() { + // Mock invalid JSON in the secret file nc.Secret.Path = "/mock/path/to/secret" nc.Secret.Type = "signify" nc.ReadFileFunc = func(path string) ([]byte, error) { @@ -321,7 +321,7 @@ func setupNewSignerTests() { return []byte("invalid-json"), nil } - // Wywołanie metody NewSigner + // Call NewSigner method signer, err := nc.NewSigner() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to unmarshal signify secret")) @@ -333,33 +333,33 @@ func setupNewSignerTests() { func setupGetImageTests() { Describe("GetImage", func() { - Context("Gdy podano poprawne odniesienie", func() { - It("powinno zwrócić SimpleImage z poprawnymi danymi manifestu", func() { - // Tworzymy poprawne odniesienie + Context("When a valid reference is provided", func() { + It("should return a SimpleImage with correct manifest data", func() { + // Create a valid reference ref, err := name.ParseReference("gcr.io/project/image:tag") Expect(err).To(BeNil()) - // Mockowanie remote.Image i manifestu - // W tym przypadku zakładamy, że funkcja działa poprawnie, - // ponieważ testowanie rzeczywistego pobierania obrazu wymagałoby dostępu do zewnętrznych zasobów. + // Mock remote.Image and manifest + // In this case, we assume the function works correctly, + // because testing actual image fetching would require access to external resources. - // Możemy więc przetestować przypadek, gdy odniesienie jest niepoprawne. + // So we can test the case where the reference is invalid. img, err := GetImage(ref) - // Ponieważ nie mamy rzeczywistego obrazu, może wystąpić błąd. + // Since we don't have an actual image, an error might occur. if err != nil { - // Sprawdzamy, czy zwrócono błąd związany z pobieraniem obrazu + // Check if the error is related to fetching the image Expect(err.Error()).To(ContainSubstring("failed to fetch image")) } else { - // Jeśli nie wystąpił błąd, sprawdzamy, czy obraz nie jest pusty + // If no error occurred, check that the image is not nil Expect(img).NotTo(BeNil()) } }) }) - Context("Gdy podano niepoprawny typ odniesienia", func() { - It("powinno zwrócić błąd wskazujący na niepoprawny typ odniesienia", func() { - // Podajemy odniesienie, które nie jest typu name.Reference - ref := "niepoprawne odniesienie" + Context("When an invalid reference type is provided", func() { + It("should return an error indicating invalid reference type", func() { + // Provide a reference that is not of type name.Reference + ref := "invalid reference" img, err := GetImage(ref) Expect(err).To(HaveOccurred()) @@ -372,8 +372,8 @@ func setupGetImageTests() { func setupParseReferenceTests() { Describe("ParseReference", func() { - Context("Gdy podano poprawny ciąg obrazu", func() { - It("powinno poprawnie sparsować odniesienie", func() { + Context("When a valid image string is provided", func() { + It("should correctly parse the reference", func() { image := "gcr.io/project/image:tag" ref, err := ParseReference(image) Expect(err).To(BeNil()) @@ -381,9 +381,9 @@ func setupParseReferenceTests() { }) }) - Context("Gdy podano niepoprawny ciąg obrazu", func() { - It("powinno zwrócić błąd parsowania", func() { - image := "niepoprawny_ciag_obrazu@@" + Context("When an invalid image string is provided", func() { + It("should return a parsing error", func() { + image := "invalid_image_string@@" ref, err := ParseReference(image) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to parse image reference")) @@ -395,8 +395,8 @@ func setupParseReferenceTests() { func setupSetupTLSTests() { Describe("setupTLS", func() { - Context("Gdy podano poprawny certyfikat TLS", func() { - It("powinno zwrócić poprawną konfigurację TLS", func() { + Context("When a valid TLS certificate is provided", func() { + It("should return a correct TLS configuration", func() { certBase64, keyBase64, err := GenerateBase64EncodedCert() Expect(err).To(BeNil()) @@ -431,7 +431,7 @@ func setupRetryHTTPRequestTests() { retryTimeout = 100 * time.Millisecond }) - Context("Gdy żądanie jest pomyślne", func() { + Context("When the request is successful", func() { BeforeEach(func() { server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusAccepted) @@ -444,14 +444,14 @@ func setupRetryHTTPRequestTests() { server.Close() }) - It("powinno zwrócić odpowiedź bez błędów", func() { + It("should return the response without errors", func() { resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) Expect(err).To(BeNil()) Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) }) }) - Context("Gdy żądanie nie powiedzie się kilka razy, ale ostatecznie się powiedzie", func() { + Context("When the request fails a few times but eventually succeeds", func() { var attempt int BeforeEach(func() { @@ -472,7 +472,7 @@ func setupRetryHTTPRequestTests() { server.Close() }) - It("powinno powtórzyć żądanie i ostatecznie zwrócić pomyślną odpowiedź", func() { + It("should retry the request and eventually return a successful response", func() { resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) Expect(err).To(BeNil()) Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) @@ -480,7 +480,7 @@ func setupRetryHTTPRequestTests() { }) }) - Context("Gdy wszystkie próby żądania się nie powiodą", func() { + Context("When all request attempts fail", func() { BeforeEach(func() { server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "error", http.StatusInternalServerError) @@ -493,7 +493,7 @@ func setupRetryHTTPRequestTests() { server.Close() }) - It("powinno zwrócić błąd po wyczerpaniu prób", func() { + It("should return an error after exhausting retries", func() { resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) Expect(err).To(HaveOccurred()) Expect(resp).NotTo(BeNil()) @@ -543,8 +543,8 @@ func setupSignTests() { } }) - Context("Gdy podpisywanie przebiega pomyślnie", func() { - It("powinno zakończyć się bez błędów", func() { + Context("When signing is successful", func() { + It("should complete without errors", func() { server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.Copy(io.Discard, r.Body) // Ensure the body is read w.WriteHeader(http.StatusAccepted) @@ -558,8 +558,8 @@ func setupSignTests() { }) }) - Context("Gdy wystąpi błąd podczas podpisywania", func() { - It("powinno zwrócić odpowiedni błąd", func() { + Context("When an error occurs during signing", func() { + It("should return the appropriate error", func() { server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.Copy(io.Discard, r.Body) // Ensure the body is read r.Body.Close() @@ -578,16 +578,16 @@ func setupSignTests() { }) } -// GenerateBase64EncodedCert generuje samopodpisany certyfikat i klucz prywatny, -// i zwraca je jako ciągi zakodowane w base64. +// GenerateBase64EncodedCert generates a self-signed certificate and private key, +// and returns them as base64-encoded strings. func GenerateBase64EncodedCert() (string, string, error) { - // Generowanie klucza prywatnego RSA + // Generate RSA private key privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return "", "", err } - // Tworzenie szablonu certyfikatu + // Create certificate template template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ @@ -600,25 +600,25 @@ func GenerateBase64EncodedCert() (string, string, error) { BasicConstraintsValid: true, } - // Tworzenie certyfikatu + // Create certificate certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { return "", "", err } - // Kodowanie certyfikatu do formatu PEM + // Encode certificate to PEM format certPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certDER, }) - // Kodowanie klucza prywatnego do formatu PEM + // Encode private key to PEM format keyPEM := pem.EncodeToMemory(&pem.Block{ Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey), }) - // Kodowanie danych PEM do base64 + // Encode PEM data to base64 certBase64 := base64.StdEncoding.EncodeToString(certPEM) keyBase64 := base64.StdEncoding.EncodeToString(keyPEM) From f4141ba0638a050b2f1d49c519548278103c9436 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 00:56:10 +0200 Subject: [PATCH 31/54] New solution --- pkg/sign/notary.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 6eba445717ad..d5d5390b43c9 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -356,6 +356,7 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { SetupTLSFunc: setupTLS, } + ns.url = "https://signing-manage.repositories.cloud.sap/trusted-collections/publish\n" ns.BuildPayloadFunc = ns.buildPayload ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey From 8e6a90165c31117a3f6da0a95b87cf256a8ce288 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 01:11:26 +0200 Subject: [PATCH 32/54] New solution --- pkg/sign/notary.go | 2 +- pkg/sign/notary_mocks.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index d5d5390b43c9..2deee94d2e1d 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -356,7 +356,7 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { SetupTLSFunc: setupTLS, } - ns.url = "https://signing-manage.repositories.cloud.sap/trusted-collections/publish\n" + ns.url = "https://signing-manage.repositories.cloud.sap/trusted-collections/publish" ns.BuildPayloadFunc = ns.buildPayload ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 3041d57b0782..bc207d47cfcb 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -39,7 +39,7 @@ func MockParseReference(image string) (Reference, error) { } // MockGetImage is a mock function for GetImageFunc -func MockGetImage(ref Reference) (Image, error) { +func MockGetImage(_ Reference) (Image, error) { // We return a mocked Image object with predefined values return &SimpleImage{ ManifestData: Manifest{ From a7a166eb88a48ad9784ea60c774848f58c4c1f88 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 01:27:06 +0200 Subject: [PATCH 33/54] New solution --- pkg/sign/notary.go | 27 +++++++++++++++------------ pkg/sign/notary_test.go | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 2deee94d2e1d..316869bdc7c9 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -249,17 +249,17 @@ func (ns NotarySigner) Sign(images []string) error { signingRequests, err := ns.buildSigningRequest(images) if err != nil { - return fmt.Errorf("build signing request: %w", err) + return ErrBadResponse{status: "400", message: fmt.Sprintf("build signing request: %v", err)} } payload, err := ns.BuildPayloadFunc(signingRequests) if err != nil { - return fmt.Errorf("build payload: %w", err) + return ErrBadResponse{status: "400", message: fmt.Sprintf("build payload: %v", err)} } b, err := json.Marshal(payload) if err != nil { - return fmt.Errorf("marshal signing request: %w", err) + return ErrBadResponse{status: "400", message: fmt.Sprintf("marshal signing request: %v", err)} } var client *http.Client @@ -268,7 +268,7 @@ func (ns NotarySigner) Sign(images []string) error { } else { cert, err := ns.DecodeCertFunc() if err != nil { - return fmt.Errorf("failed to load certificate and key: %w", err) + return ErrBadResponse{status: "400", message: fmt.Sprintf("failed to load certificate and key: %v", err)} } tlsConfig := ns.SetupTLSFunc(cert) @@ -282,19 +282,22 @@ func (ns NotarySigner) Sign(images []string) error { req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) if err != nil { - return err + return ErrBadResponse{status: "500", message: fmt.Sprintf("failed to create HTTP request: %v", err)} } req.Header.Add("Content-Type", "application/json") resp, err := retryHTTPRequest(client, req, 5, ns.retryTimeout) if err != nil { - return fmt.Errorf("request failed: %w", err) + return ErrBadResponse{status: "500", message: fmt.Sprintf("request failed: %v", err)} } defer resp.Body.Close() if resp.StatusCode != http.StatusAccepted { respMsg, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to sign images: %w", ErrBadResponse{status: resp.Status, message: string(respMsg)}) + return ErrBadResponse{ + status: resp.Status, + message: fmt.Sprintf("failed to sign images: %s", string(respMsg)), + } } fmt.Printf("Successfully signed images %s!\n", sImg) @@ -311,7 +314,7 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry var err error bodyBytes, err = io.ReadAll(req.Body) if err != nil { - return nil, fmt.Errorf("failed to read request body: %w", err) + return nil, ErrBadResponse{status: "400", message: fmt.Sprintf("failed to read request body: %v", err)} } req.Body.Close() } @@ -338,7 +341,7 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry } time.Sleep(retryInterval) } - return lastResp, fmt.Errorf("request failed after retries: %w", lastErr) + return lastResp, ErrBadResponse{status: "500", message: fmt.Sprintf("request failed after retries: %v", lastErr)} } func setupTLS(cert tls.Certificate) *tls.Config { @@ -375,18 +378,18 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { f, err := readFileFunc(nc.Secret.Path) if err != nil { - return nil, fmt.Errorf("failed to read secret file: %w", err) + return nil, ErrBadResponse{status: "400", message: fmt.Sprintf("failed to read secret file: %v", err)} } var s SignifySecret err = json.Unmarshal(f, &s) if err != nil { - return nil, fmt.Errorf("failed to unmarshal signify secret: %w", err) + return nil, ErrBadResponse{status: "400", message: fmt.Sprintf("failed to unmarshal signify secret: %v", err)} } ns.signifySecret = s default: - return nil, fmt.Errorf("unsupported secret type: %s", nc.Secret.Type) + return nil, ErrAuthServiceNotSupported{Service: nc.Secret.Type} } } diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 2dbceb6e21a1..6d6ebb151140 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -306,7 +306,7 @@ func setupNewSignerTests() { // Call NewSigner method signer, err := nc.NewSigner() Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unsupported secret type")) + Expect(err.Error()).To(ContainSubstring("'unsupported' auth service not supported")) Expect(signer).To(BeNil()) }) }) From ce89c6f766edc74b1c0c4fc93834a5819d62cd91 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 01:44:14 +0200 Subject: [PATCH 34/54] Ready to review, no hardcodes. 85% test coverage --- pkg/sign/notary.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 316869bdc7c9..c51e26e9448a 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -359,7 +359,6 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { SetupTLSFunc: setupTLS, } - ns.url = "https://signing-manage.repositories.cloud.sap/trusted-collections/publish" ns.BuildPayloadFunc = ns.buildPayload ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey From 0077dc84e5c7ae02935fe76c43ba9644d9ef0c3e Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 02:06:50 +0200 Subject: [PATCH 35/54] Change endpoint --- configs/kaniko-build-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/kaniko-build-config.yaml b/configs/kaniko-build-config.yaml index 21829459fdd2..6398277cfaf1 100644 --- a/configs/kaniko-build-config.yaml +++ b/configs/kaniko-build-config.yaml @@ -28,7 +28,7 @@ sign-config: - postsubmit - workflow_dispatch config: - endpoint: https://signing.repositories.cloud.sap/signingsvc/sign + endpoint: https://signing-manage.repositories.cloud.sap/trusted-collections/publish timeout: 5m retry-timeout: 10s secret: From eacd35e88427f33dee840a754cfefe6f1a9cd559 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 02:13:17 +0200 Subject: [PATCH 36/54] Change endpoint --- cmd/image-builder/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cmd/image-builder/README.md b/cmd/image-builder/README.md index 3740eda3347e..6bf13aa4bee8 100644 --- a/cmd/image-builder/README.md +++ b/cmd/image-builder/README.md @@ -148,6 +148,26 @@ Image signing allows verification that the image comes from a trusted repository > [!NOTE] > Image Builder signs images built on the push and workflow_dispatch events only. Images built on the pull_request_target event are not signed. +## Image Signing with Signify + +Image Builder signs images using the Signify service, ensuring that images come from trusted repositories and have not been tampered with. + +### Updated Signing Process + +The authentication to the Signify API has been updated from using `role id/secret id` to mTLS. This change introduces the following updates: + +- **mTLS Authentication**: Image Builder now uses a client certificate/private key pair for authentication with the Signify API. These credentials are valid for 7 days, after which they must be rotated. +- **Automated Rotation**: The certificate rotation must occur every 7 days. The new certificate/private key pair must be generated using the previous pair before they expire. + +The Signify API's structure has also been updated. For more information, refer to the official [Signify API Documentation](https://pages.github.tools.sap/Repository-Services/Signify/how_to/manage_signatures/). + +> **Note**: Images are only signed when built on `push` and `workflow_dispatch` events. Pull request images are not signed. + +### Signify API Changes + +The JSON structure for signing has changed slightly. You can review the new structure and examples in the [Signify API Documentation](https://pages.github.tools.sap/Repository-Services/Signify/how_to/manage_signatures/). + + ## Named Tags Image Builder supports passing the name along with the tag, using both the `-tag` option and the config for the tag template. From df642522d3597ad187bd84706e8ce299b43934f4 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 11:11:48 +0200 Subject: [PATCH 37/54] remove unused mock --- pkg/sign/notary_mocks.go | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index bc207d47cfcb..544e6876efeb 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -1,38 +1,5 @@ package sign -import ( - "net/http" -) - -// MockRoundTripper allows us to mock HTTP client behavior for tests. -type MockRoundTripper struct { - RoundTripFunc func(req *http.Request) (*http.Response, error) -} - -func (m *MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - return m.RoundTripFunc(req) -} - -type MockImage struct{} - -func (m *MockImage) Manifest() (*Manifest, error) { - return &Manifest{ - Config: struct { - Digest struct { - Hex string - } - Size int64 - }{ - Digest: struct { - Hex string - }{ - Hex: "abc123def456", - }, - Size: 12345678, - }, - }, nil -} - // MockParseReference is a mock function for ParseReferenceFunc func MockParseReference(image string) (Reference, error) { return image, nil // In a simple case, we return the string itself as Reference From 7b6c9c5d4cf408b6e24fd3a15764dff74557d758 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 17 Sep 2024 15:05:11 +0200 Subject: [PATCH 38/54] Apply suggestions from code review Co-authored-by: Iwona Langer --- cmd/image-builder/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/image-builder/README.md b/cmd/image-builder/README.md index 6bf13aa4bee8..e7f34d79a4c6 100644 --- a/cmd/image-builder/README.md +++ b/cmd/image-builder/README.md @@ -159,13 +159,14 @@ The authentication to the Signify API has been updated from using `role id/secre - **mTLS Authentication**: Image Builder now uses a client certificate/private key pair for authentication with the Signify API. These credentials are valid for 7 days, after which they must be rotated. - **Automated Rotation**: The certificate rotation must occur every 7 days. The new certificate/private key pair must be generated using the previous pair before they expire. -The Signify API's structure has also been updated. For more information, refer to the official [Signify API Documentation](https://pages.github.tools.sap/Repository-Services/Signify/how_to/manage_signatures/). +The Signify API's structure has also been updated. For more information, see the official [Signify API Documentation](https://pages.github.tools.sap/Repository-Services/Signify/how_to/manage_signatures/). -> **Note**: Images are only signed when built on `push` and `workflow_dispatch` events. Pull request images are not signed. +> [!NOTE] +> Images are only signed when built on `push` and `workflow_dispatch` events. Pull request images are not signed. ### Signify API Changes -The JSON structure for signing has changed slightly. You can review the new structure and examples in the [Signify API Documentation](https://pages.github.tools.sap/Repository-Services/Signify/how_to/manage_signatures/). +The JSON structure for signing has changed. See the new structure and examples in the [Signify API Documentation](https://pages.github.tools.sap/Repository-Services/Signify/how_to/manage_signatures/). ## Named Tags From b4d9022bbd59d6136fcb583319a8969a00439d20 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 18 Sep 2024 15:59:34 +0200 Subject: [PATCH 39/54] minor --- pkg/sign/notary.go | 222 ++++++++++++++++----------------------------- 1 file changed, 80 insertions(+), 142 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index c51e26e9448a..4dbd7c734cc5 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -13,13 +13,13 @@ import ( "time" "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" ) const ( - TypeNotaryBackend = "notary" - tagDelim = ":" - regRepoDelimiter = "/" + tagDelim = ":" + regRepoDelimiter = "/" ) type ErrBadResponse struct { @@ -27,14 +27,6 @@ type ErrBadResponse struct { message string } -type ErrAuthServiceNotSupported struct { - Service string -} - -func (e ErrAuthServiceNotSupported) Error() string { - return fmt.Sprintf("'%s' auth service not supported", e.Service) -} - func (e ErrBadResponse) Error() string { return fmt.Sprintf("bad response from service: %s, %s", e.status, e.message) } @@ -80,12 +72,12 @@ type SigningPayload struct { } type NotarySigner struct { - c *http.Client + client *http.Client url string retryTimeout time.Duration signifySecret SignifySecret - ParseReferenceFunc func(image string) (Reference, error) - GetImageFunc func(ref Reference) (Image, error) + ParseReferenceFunc func(image string) (name.Reference, error) + GetImageFunc func(ref name.Reference) (v1.Image, error) DecodeCertFunc func() (tls.Certificate, error) BuildSigningReqFunc func([]string) ([]SigningRequest, error) BuildPayloadFunc func([]SigningRequest) (SigningPayload, error) @@ -93,75 +85,20 @@ type NotarySigner struct { HTTPClient *http.Client } -type Reference interface{} -type Image interface { - Manifest() (*Manifest, error) -} - -type Manifest struct { - Config struct { - Digest struct { - Hex string - } - Size int64 - } -} - -type SimpleImage struct { - ManifestData Manifest -} - -func (si *SimpleImage) Manifest() (*Manifest, error) { - return &si.ManifestData, nil -} - -func GetImage(ref Reference) (Image, error) { - r, ok := ref.(name.Reference) - if !ok { - return nil, fmt.Errorf("invalid reference type") - } - - img, err := remote.Image(r) - if err != nil { - return nil, fmt.Errorf("failed to fetch image: %w", err) - } - - manifest, err := img.Manifest() +func ParseReference(image string) (name.Reference, error) { + ref, err := name.ParseReference(image) if err != nil { - return nil, fmt.Errorf("failed to get manifest: %w", err) + return nil, fmt.Errorf("failed to parse image reference: %w", err) } - - return &SimpleImage{ - ManifestData: Manifest{ - Config: struct { - Digest struct { - Hex string - } - Size int64 - }{ - Digest: struct { - Hex string - }{ - Hex: manifest.Config.Digest.Hex, - }, - Size: manifest.Config.Size, - }, - }, - }, nil -} - -type SimpleReference struct { - Image string - Tag string + return ref, nil } -func ParseReference(image string) (Reference, error) { - ref, err := name.ParseReference(image) +func GetImage(ref name.Reference) (v1.Image, error) { + img, err := remote.Image(ref) if err != nil { - return nil, fmt.Errorf("failed to parse image reference: %w", err) + return nil, fmt.Errorf("failed to fetch image: %w", err) } - - return ref, nil + return img, nil } func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { @@ -169,33 +106,30 @@ func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) } - keyData, err := base64.StdEncoding.DecodeString(ss.PrivateKeyData) if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) } - cert, err := tls.X509KeyPair(certData, keyData) if err != nil { return tls.Certificate{}, fmt.Errorf("unable to load certificate or key: %w", err) } - return cert, nil } -func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { +func (ns *NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { var signingRequests []SigningRequest - - for _, i := range images { + for _, image := range images { var base, tag string - - parts := strings.Split(i, tagDelim) + parts := strings.Split(image, tagDelim) if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { base = strings.Join(parts[:len(parts)-1], tagDelim) tag = parts[len(parts)-1] + } else { + base = image + tag = "latest" // Default tag if none is provided } - - ref, err := ns.ParseReferenceFunc(i) + ref, err := ns.ParseReferenceFunc(image) if err != nil { return nil, fmt.Errorf("ref parse: %w", err) } @@ -207,70 +141,70 @@ func (ns NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, e if err != nil { return nil, fmt.Errorf("failed getting image manifest: %w", err) } - signingRequests = append(signingRequests, SigningRequest{ NotaryGun: base, - SHA256: manifest.Config.Digest.Hex, + SHA256: manifest.Config.Digest.String(), ByteSize: manifest.Config.Size, Version: tag, }) } - return signingRequests, nil } -func (ns NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) { +func (ns *NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) { var trustedCollections []TrustedCollection - for _, req := range sr { target := Target{ Name: req.Version, ByteSize: req.ByteSize, Digest: req.SHA256, } - trustedCollection := TrustedCollection{ GUN: req.NotaryGun, Targets: []Target{target}, } - trustedCollections = append(trustedCollections, trustedCollection) } - payload := SigningPayload{ TrustedCollections: trustedCollections, } - return payload, nil } -func (ns NotarySigner) Sign(images []string) error { +func (ns *NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") - signingRequests, err := ns.buildSigningRequest(images) if err != nil { - return ErrBadResponse{status: "400", message: fmt.Sprintf("build signing request: %v", err)} + return ErrBadResponse{ + status: "400", + message: fmt.Sprintf("build signing request: %v", err), + } } - payload, err := ns.BuildPayloadFunc(signingRequests) if err != nil { - return ErrBadResponse{status: "400", message: fmt.Sprintf("build payload: %v", err)} + return ErrBadResponse{ + status: "400", + message: fmt.Sprintf("build payload: %v", err), + } } - b, err := json.Marshal(payload) if err != nil { - return ErrBadResponse{status: "400", message: fmt.Sprintf("marshal signing request: %v", err)} + return ErrBadResponse{ + status: "400", + message: fmt.Sprintf("marshal signing request: %v", err), + } } - var client *http.Client if ns.HTTPClient != nil { client = ns.HTTPClient } else { cert, err := ns.DecodeCertFunc() if err != nil { - return ErrBadResponse{status: "400", message: fmt.Sprintf("failed to load certificate and key: %v", err)} + return ErrBadResponse{ + status: "400", + message: fmt.Sprintf("failed to load certificate and key: %v", err), + } } - tlsConfig := ns.SetupTLSFunc(cert) client = &http.Client{ Transport: &http.Transport{ @@ -279,19 +213,22 @@ func (ns NotarySigner) Sign(images []string) error { Timeout: ns.retryTimeout, } } - req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) if err != nil { - return ErrBadResponse{status: "500", message: fmt.Sprintf("failed to create HTTP request: %v", err)} + return ErrBadResponse{ + status: "500", + message: fmt.Sprintf("failed to create HTTP request: %v", err), + } } req.Header.Add("Content-Type", "application/json") - resp, err := retryHTTPRequest(client, req, 5, ns.retryTimeout) if err != nil { - return ErrBadResponse{status: "500", message: fmt.Sprintf("request failed: %v", err)} + return ErrBadResponse{ + status: "500", + message: fmt.Sprintf("request failed: %v", err), + } } defer resp.Body.Close() - if resp.StatusCode != http.StatusAccepted { respMsg, _ := io.ReadAll(resp.Body) return ErrBadResponse{ @@ -299,7 +236,6 @@ func (ns NotarySigner) Sign(images []string) error { message: fmt.Sprintf("failed to sign images: %s", string(respMsg)), } } - fmt.Printf("Successfully signed images %s!\n", sImg) return nil } @@ -314,7 +250,10 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry var err error bodyBytes, err = io.ReadAll(req.Body) if err != nil { - return nil, ErrBadResponse{status: "400", message: fmt.Sprintf("failed to read request body: %v", err)} + return nil, ErrBadResponse{ + status: "400", + message: fmt.Sprintf("failed to read request body: %v", err), + } } req.Body.Close() } @@ -324,7 +263,6 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry if bodyBytes != nil { req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } - resp, err := client.Do(req) if err != nil { lastErr = err @@ -333,7 +271,6 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry } else { lastErr = fmt.Errorf("failed to sign images, unexpected status code: %d", resp.StatusCode) } - lastResp = resp retries-- if retries == 0 { @@ -341,7 +278,10 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry } time.Sleep(retryInterval) } - return lastResp, ErrBadResponse{status: "500", message: fmt.Sprintf("request failed after retries: %v", lastErr)} + return lastResp, ErrBadResponse{ + status: "500", + message: fmt.Sprintf("request failed after retries: %v", lastErr), + } } func setupTLS(cert tls.Certificate) *tls.Config { @@ -351,7 +291,7 @@ func setupTLS(cert tls.Certificate) *tls.Config { } func (nc NotaryConfig) NewSigner() (Signer, error) { - ns := NotarySigner{ + ns := &NotarySigner{ retryTimeout: nc.RetryTimeout, url: nc.Endpoint, ParseReferenceFunc: ParseReference, @@ -360,37 +300,35 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { } ns.BuildPayloadFunc = ns.buildPayload + ns.BuildSigningReqFunc = ns.buildSigningRequest ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey - // HTTP client configuration - ns.c = &http.Client{ + // Configure HTTP client + ns.client = &http.Client{ Timeout: nc.Timeout, } - if nc.Secret != nil { - switch nc.Secret.Type { - case "signify": - readFileFunc := nc.ReadFileFunc - if readFileFunc == nil { - readFileFunc = os.ReadFile - } - - f, err := readFileFunc(nc.Secret.Path) - if err != nil { - return nil, ErrBadResponse{status: "400", message: fmt.Sprintf("failed to read secret file: %v", err)} - } - - var s SignifySecret - err = json.Unmarshal(f, &s) - if err != nil { - return nil, ErrBadResponse{status: "400", message: fmt.Sprintf("failed to unmarshal signify secret: %v", err)} - } - - ns.signifySecret = s - default: - return nil, ErrAuthServiceNotSupported{Service: nc.Secret.Type} + // Read Signify secret + readFileFunc := nc.ReadFileFunc + if readFileFunc == nil { + readFileFunc = os.ReadFile + } + secretFileContent, err := readFileFunc(nc.Secret.Path) + if err != nil { + return nil, ErrBadResponse{ + status: "400", + message: fmt.Sprintf("failed to read secret file: %v", err), + } + } + var signifySecret SignifySecret + err = json.Unmarshal(secretFileContent, &signifySecret) + if err != nil { + return nil, ErrBadResponse{ + status: "400", + message: fmt.Sprintf("failed to unmarshal signify secret: %v", err), } } + ns.signifySecret = signifySecret - return &ns, nil + return ns, nil } From 21987cf9c329f8120a3989256ab63b3f0c3efe27 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 18 Sep 2024 23:49:18 +0200 Subject: [PATCH 40/54] test of interfaces --- pkg/sign/notary.go | 376 ++++++++-------- pkg/sign/notary_mocks.go | 160 +++++-- pkg/sign/notary_structs.go | 37 ++ pkg/sign/notary_test.go | 899 +++++++++++++------------------------ pkg/sign/sign.go | 39 +- pkg/sign/sign_test.go | 58 --- 6 files changed, 695 insertions(+), 874 deletions(-) create mode 100644 pkg/sign/notary_structs.go delete mode 100644 pkg/sign/sign_test.go diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 4dbd7c734cc5..596b0d55c5fa 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -13,7 +13,7 @@ import ( "time" "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" ) @@ -22,70 +22,37 @@ const ( regRepoDelimiter = "/" ) -type ErrBadResponse struct { - status string - message string +// ImageServiceInterface handles image parsing and fetching. +type ImageServiceInterface interface { + ParseReference(image string) (name.Reference, error) + GetImage(ref name.Reference) (v1.Image, error) } -func (e ErrBadResponse) Error() string { - return fmt.Sprintf("bad response from service: %s, %s", e.status, e.message) +// PayloadBuilderInterface constructs the signing payload. +type PayloadBuilderInterface interface { + BuildPayload(images []string) (SigningPayload, error) } -type NotaryConfig struct { - Endpoint string `yaml:"endpoint" json:"endpoint"` - Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` - Timeout time.Duration `yaml:"timeout" json:"timeout"` - RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` - ReadFileFunc func(string) ([]byte, error) +// CertificateProviderInterface manages certificate and key decoding. +type CertificateProviderInterface interface { + CreateKeyPair() (tls.Certificate, error) } -type AuthSecretConfig struct { - Path string `yaml:"path" json:"path"` - Type string `yaml:"type" json:"type"` +// TLSConfiguratorInterface sets up TLS configurations. +type TLSConfiguratorInterface interface { + SetupTLS(cert tls.Certificate) *tls.Config } -type SignifySecret struct { - CertificateData string `json:"certData"` - PrivateKeyData string `json:"privateKeyData"` +// HTTPClientInterface handles HTTP requests. +type HTTPClientInterface interface { + Do(req *http.Request) (*http.Response, error) + SetTLSConfig(tlsConfig *tls.Config) error } -type SigningRequest struct { - NotaryGun string `json:"notaryGun"` - SHA256 string `json:"sha256"` - ByteSize int64 `json:"byteSize"` - Version string `json:"version"` -} - -type Target struct { - Name string `json:"name"` - ByteSize int64 `json:"byteSize"` - Digest string `json:"digest"` -} - -type TrustedCollection struct { - GUN string `json:"gun"` - Targets []Target `json:"targets"` -} - -type SigningPayload struct { - TrustedCollections []TrustedCollection `json:"trustedCollections"` -} +// ImageService implements ImageServiceInterface. +type ImageService struct{} -type NotarySigner struct { - client *http.Client - url string - retryTimeout time.Duration - signifySecret SignifySecret - ParseReferenceFunc func(image string) (name.Reference, error) - GetImageFunc func(ref name.Reference) (v1.Image, error) - DecodeCertFunc func() (tls.Certificate, error) - BuildSigningReqFunc func([]string) ([]SigningRequest, error) - BuildPayloadFunc func([]SigningRequest) (SigningPayload, error) - SetupTLSFunc func(cert tls.Certificate) *tls.Config - HTTPClient *http.Client -} - -func ParseReference(image string) (name.Reference, error) { +func (is *ImageService) ParseReference(image string) (name.Reference, error) { ref, err := name.ParseReference(image) if err != nil { return nil, fmt.Errorf("failed to parse image reference: %w", err) @@ -93,7 +60,7 @@ func ParseReference(image string) (name.Reference, error) { return ref, nil } -func GetImage(ref name.Reference) (v1.Image, error) { +func (is *ImageService) GetImage(ref name.Reference) (v1.Image, error) { img, err := remote.Image(ref) if err != nil { return nil, fmt.Errorf("failed to fetch image: %w", err) @@ -101,146 +68,182 @@ func GetImage(ref name.Reference) (v1.Image, error) { return img, nil } -func (ss *SignifySecret) DecodeCertAndKey() (tls.Certificate, error) { - certData, err := base64.StdEncoding.DecodeString(ss.CertificateData) - if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) - } - keyData, err := base64.StdEncoding.DecodeString(ss.PrivateKeyData) - if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) - } - cert, err := tls.X509KeyPair(certData, keyData) - if err != nil { - return tls.Certificate{}, fmt.Errorf("unable to load certificate or key: %w", err) - } - return cert, nil +// PayloadBuilder implements PayloadBuilderInterface. +type PayloadBuilder struct { + ImageService ImageServiceInterface } -func (ns *NotarySigner) buildSigningRequest(images []string) ([]SigningRequest, error) { - var signingRequests []SigningRequest +func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) { + var trustedCollections []TrustedCollection for _, image := range images { - var base, tag string - parts := strings.Split(image, tagDelim) - if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { - base = strings.Join(parts[:len(parts)-1], tagDelim) - tag = parts[len(parts)-1] - } else { - base = image - tag = "latest" // Default tag if none is provided + base, tag, err := parseImageNameAndTag(image) + if err != nil { + return SigningPayload{}, fmt.Errorf("parse image name and tag: %w", err) } - ref, err := ns.ParseReferenceFunc(image) + + // Parse reference + ref, err := pb.ImageService.ParseReference(image) if err != nil { - return nil, fmt.Errorf("ref parse: %w", err) + return SigningPayload{}, fmt.Errorf("ref parse: %w", err) } - img, err := ns.GetImageFunc(ref) + + // Get image + img, err := pb.ImageService.GetImage(ref) if err != nil { - return nil, fmt.Errorf("get image: %w", err) + return SigningPayload{}, fmt.Errorf("get image: %w", err) } + + // Get manifest manifest, err := img.Manifest() if err != nil { - return nil, fmt.Errorf("failed getting image manifest: %w", err) + return SigningPayload{}, fmt.Errorf("failed getting image manifest: %w", err) } - signingRequests = append(signingRequests, SigningRequest{ - NotaryGun: base, - SHA256: manifest.Config.Digest.String(), - ByteSize: manifest.Config.Size, - Version: tag, - }) - } - return signingRequests, nil -} -func (ns *NotarySigner) buildPayload(sr []SigningRequest) (SigningPayload, error) { - var trustedCollections []TrustedCollection - for _, req := range sr { + // Build target target := Target{ - Name: req.Version, - ByteSize: req.ByteSize, - Digest: req.SHA256, + Name: tag, + ByteSize: manifest.Config.Size, + Digest: manifest.Config.Digest.String(), } + + // Build trusted collection trustedCollection := TrustedCollection{ - GUN: req.NotaryGun, + GUN: base, Targets: []Target{target}, } + trustedCollections = append(trustedCollections, trustedCollection) } + payload := SigningPayload{ TrustedCollections: trustedCollections, } return payload, nil } +func parseImageNameAndTag(image string) (string, string, error) { + parts := strings.Split(image, tagDelim) + if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { + base := strings.Join(parts[:len(parts)-1], tagDelim) + tag := parts[len(parts)-1] + return base, tag, nil + } + return "", "", fmt.Errorf("no tag provided") +} + +// CertificateProvider implements CertificateProviderInterface. +type CertificateProvider struct { + SignifySecret SignifySecret +} + +func (cp *CertificateProvider) CreateKeyPair() (tls.Certificate, error) { + certData, err := base64.StdEncoding.DecodeString(cp.SignifySecret.CertificateData) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) + } + keyData, err := base64.StdEncoding.DecodeString(cp.SignifySecret.PrivateKeyData) + if err != nil { + return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) + } + cert, err := tls.X509KeyPair(certData, keyData) + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to load certificate or key: %w", err) + } + return cert, nil +} + +// TLSConfigurator implements TLSConfiguratorInterface. +type TLSConfigurator struct{} + +func (tc *TLSConfigurator) SetupTLS(cert tls.Certificate) *tls.Config { + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + } +} + +// HTTPClient implements HTTPClientInterface. +type HTTPClient struct { + Client *http.Client +} + +func (hc *HTTPClient) Do(req *http.Request) (*http.Response, error) { + return hc.Client.Do(req) +} + +func (hc *HTTPClient) SetTLSConfig(tlsConfig *tls.Config) error { + if hc.Client == nil { + return fmt.Errorf("http.Client is nil") + } + hc.Client.Transport = &http.Transport{ + TLSClientConfig: tlsConfig, + } + return nil +} + +// NotarySigner struct +type NotarySigner struct { + URL string + RetryTimeout time.Duration + PayloadBuilder PayloadBuilderInterface + CertificateProvider CertificateProviderInterface + TLSConfigurator TLSConfiguratorInterface + HTTPClient HTTPClientInterface +} + +// Sign implements the Signer interface. func (ns *NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") - signingRequests, err := ns.buildSigningRequest(images) + + // Build payload + payload, err := ns.PayloadBuilder.BuildPayload(images) if err != nil { - return ErrBadResponse{ - status: "400", - message: fmt.Sprintf("build signing request: %v", err), - } + return fmt.Errorf("failed to build payload: %v", err) } - payload, err := ns.BuildPayloadFunc(signingRequests) + + // Marshal payload + b, err := json.Marshal(payload) if err != nil { - return ErrBadResponse{ - status: "400", - message: fmt.Sprintf("build payload: %v", err), - } + return fmt.Errorf("marshal signing request: %v", err) } - b, err := json.Marshal(payload) + + // Decode certificate and key + cert, err := ns.CertificateProvider.CreateKeyPair() if err != nil { - return ErrBadResponse{ - status: "400", - message: fmt.Sprintf("marshal signing request: %v", err), - } + return fmt.Errorf("failed to load certificate and key: %v", err) } - var client *http.Client - if ns.HTTPClient != nil { - client = ns.HTTPClient - } else { - cert, err := ns.DecodeCertFunc() - if err != nil { - return ErrBadResponse{ - status: "400", - message: fmt.Sprintf("failed to load certificate and key: %v", err), - } - } - tlsConfig := ns.SetupTLSFunc(cert) - client = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsConfig, - }, - Timeout: ns.retryTimeout, - } + + // Setup TLS configuration + tlsConfig := ns.TLSConfigurator.SetupTLS(cert) + err = ns.HTTPClient.SetTLSConfig(tlsConfig) + if err != nil { + return fmt.Errorf("failed to set TLS config: %v", err) } - req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) + + // Create HTTP request + req, err := http.NewRequest("POST", ns.URL, bytes.NewReader(b)) if err != nil { - return ErrBadResponse{ - status: "500", - message: fmt.Sprintf("failed to create HTTP request: %v", err), - } + return fmt.Errorf("failed to create HTTP request: %v", err) } req.Header.Add("Content-Type", "application/json") - resp, err := retryHTTPRequest(client, req, 5, ns.retryTimeout) + + // Send request with retries + resp, err := RetryHTTPRequest(ns.HTTPClient, req, 5, ns.RetryTimeout) if err != nil { - return ErrBadResponse{ - status: "500", - message: fmt.Sprintf("request failed: %v", err), - } + return fmt.Errorf("request failed: %v", err) } defer resp.Body.Close() + if resp.StatusCode != http.StatusAccepted { respMsg, _ := io.ReadAll(resp.Body) - return ErrBadResponse{ - status: resp.Status, - message: fmt.Sprintf("failed to sign images: %s", string(respMsg)), - } + return fmt.Errorf("failed to sign images: %s", string(respMsg)) } + fmt.Printf("Successfully signed images %s!\n", sImg) return nil } -func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retryInterval time.Duration) (*http.Response, error) { +// RetryHTTPRequest handles retry logic for HTTP requests. +func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int, retryInterval time.Duration) (*http.Response, error) { var lastResp *http.Response var lastErr error @@ -250,10 +253,7 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry var err error bodyBytes, err = io.ReadAll(req.Body) if err != nil { - return nil, ErrBadResponse{ - status: "400", - message: fmt.Sprintf("failed to read request body: %v", err), - } + return nil, fmt.Errorf("failed to read request body: %v", err) } req.Body.Close() } @@ -263,13 +263,14 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry if bodyBytes != nil { req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } + resp, err := client.Do(req) if err != nil { lastErr = err } else if resp.StatusCode == http.StatusAccepted { return resp, nil } else { - lastErr = fmt.Errorf("failed to sign images, unexpected status code: %d", resp.StatusCode) + lastErr = fmt.Errorf("unexpected status code: %d", resp.StatusCode) } lastResp = resp retries-- @@ -278,36 +279,11 @@ func retryHTTPRequest(client *http.Client, req *http.Request, retries int, retry } time.Sleep(retryInterval) } - return lastResp, ErrBadResponse{ - status: "500", - message: fmt.Sprintf("request failed after retries: %v", lastErr), - } -} - -func setupTLS(cert tls.Certificate) *tls.Config { - return &tls.Config{ - Certificates: []tls.Certificate{cert}, - } + return lastResp, fmt.Errorf("request failed after retries: %v", lastErr) } -func (nc NotaryConfig) NewSigner() (Signer, error) { - ns := &NotarySigner{ - retryTimeout: nc.RetryTimeout, - url: nc.Endpoint, - ParseReferenceFunc: ParseReference, - GetImageFunc: GetImage, - SetupTLSFunc: setupTLS, - } - - ns.BuildPayloadFunc = ns.buildPayload - ns.BuildSigningReqFunc = ns.buildSigningRequest - ns.DecodeCertFunc = ns.signifySecret.DecodeCertAndKey - - // Configure HTTP client - ns.client = &http.Client{ - Timeout: nc.Timeout, - } - +// NewSigner constructs a new NotarySigner with dependencies injected. +func (nc *NotaryConfig) NewSigner() (Signer, error) { // Read Signify secret readFileFunc := nc.ReadFileFunc if readFileFunc == nil { @@ -315,20 +291,38 @@ func (nc NotaryConfig) NewSigner() (Signer, error) { } secretFileContent, err := readFileFunc(nc.Secret.Path) if err != nil { - return nil, ErrBadResponse{ - status: "400", - message: fmt.Sprintf("failed to read secret file: %v", err), - } + return nil, fmt.Errorf("failed to read secret file: %v", err) } var signifySecret SignifySecret err = json.Unmarshal(secretFileContent, &signifySecret) if err != nil { - return nil, ErrBadResponse{ - status: "400", - message: fmt.Sprintf("failed to unmarshal signify secret: %v", err), - } + return nil, fmt.Errorf("failed to unmarshal signify secret: %v", err) + } + + // Initialize components + imageService := &ImageService{} + payloadBuilder := &PayloadBuilder{ + ImageService: imageService, + } + certificateProvider := &CertificateProvider{ + SignifySecret: signifySecret, + } + tlsConfigurator := &TLSConfigurator{} + httpClient := &HTTPClient{ + Client: &http.Client{ + Timeout: nc.Timeout, + }, + } + + // Create NotarySigner + signer := &NotarySigner{ + URL: nc.Endpoint, + RetryTimeout: nc.RetryTimeout, + PayloadBuilder: payloadBuilder, + CertificateProvider: certificateProvider, + TLSConfigurator: tlsConfigurator, + HTTPClient: httpClient, } - ns.signifySecret = signifySecret - return ns, nil + return signer, nil } diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 544e6876efeb..87b2ffa69961 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -1,28 +1,138 @@ package sign -// MockParseReference is a mock function for ParseReferenceFunc -func MockParseReference(image string) (Reference, error) { - return image, nil // In a simple case, we return the string itself as Reference -} - -// MockGetImage is a mock function for GetImageFunc -func MockGetImage(_ Reference) (Image, error) { - // We return a mocked Image object with predefined values - return &SimpleImage{ - ManifestData: Manifest{ - Config: struct { - Digest struct { - Hex string - } - Size int64 - }{ - Digest: struct { - Hex string - }{ - Hex: "abc123def456", - }, - Size: 12345678, - }, - }, - }, nil +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +// MockImageService implements ImageServiceInterface +type MockImageService struct { + MockParseReference func(image string) (name.Reference, error) + MockGetImage func(ref name.Reference) (v1.Image, error) +} + +func (m *MockImageService) ParseReference(image string) (name.Reference, error) { + if m.MockParseReference != nil { + return m.MockParseReference(image) + } + return nil, fmt.Errorf("MockParseReference not implemented") +} + +func (m *MockImageService) GetImage(ref name.Reference) (v1.Image, error) { + if m.MockGetImage != nil { + return m.MockGetImage(ref) + } + return nil, fmt.Errorf("MockGetImage not implemented") +} + +// MockPayloadBuilder implements PayloadBuilderInterface +type MockPayloadBuilder struct { + MockBuildPayload func(images []string) (SigningPayload, error) +} + +func (m *MockPayloadBuilder) BuildPayload(images []string) (SigningPayload, error) { + if m.MockBuildPayload != nil { + return m.MockBuildPayload(images) + } + return SigningPayload{}, fmt.Errorf("MockBuildPayload not implemented") +} + +// MockCertificateProvider implements CertificateProviderInterface +type MockCertificateProvider struct { + MockCreateKeyPair func() (tls.Certificate, error) +} + +func (m *MockCertificateProvider) CreateKeyPair() (tls.Certificate, error) { + if m.MockCreateKeyPair != nil { + return m.MockCreateKeyPair() + } + return tls.Certificate{}, fmt.Errorf("MockCreateKeyPair not implemented") +} + +// MockTLSConfigurator implements TLSConfiguratorInterface +type MockTLSConfigurator struct { + MockSetupTLS func(cert tls.Certificate) *tls.Config +} + +func (m *MockTLSConfigurator) SetupTLS(cert tls.Certificate) *tls.Config { + if m.MockSetupTLS != nil { + return m.MockSetupTLS(cert) + } + return &tls.Config{} +} + +// MockHTTPClient implements HTTPClientInterface +type MockHTTPClient struct { + MockDo func(req *http.Request) (*http.Response, error) + MockSetTLSConfig func(tlsConfig *tls.Config) error +} + +func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { + if m.MockDo != nil { + return m.MockDo(req) + } + return nil, fmt.Errorf("MockDo not implemented") +} + +func (m *MockHTTPClient) SetTLSConfig(tlsConfig *tls.Config) error { + if m.MockSetTLSConfig != nil { + return m.MockSetTLSConfig(tlsConfig) + } + return fmt.Errorf("MockSetTLSConfig not implemented") +} + +// MockImage implements v1.Image interface (from github.com/google/go-containerregistry/pkg/v1) +type MockImage struct { + manifest *v1.Manifest + configFile *v1.ConfigFile +} + +func (m *MockImage) RawManifest() ([]byte, error) { + return json.Marshal(m.manifest) +} + +func (m *MockImage) Manifest() (*v1.Manifest, error) { + return m.manifest, nil +} + +func (m *MockImage) Layers() ([]v1.Layer, error) { + return nil, nil +} + +func (m *MockImage) MediaType() (types.MediaType, error) { + return m.manifest.MediaType, nil +} + +func (m *MockImage) Size() (int64, error) { + return 0, nil +} + +func (m *MockImage) ConfigName() (v1.Hash, error) { + return v1.Hash{}, nil +} + +func (m *MockImage) ConfigFile() (*v1.ConfigFile, error) { + return m.configFile, nil +} + +func (m *MockImage) RawConfigFile() ([]byte, error) { + return json.Marshal(m.configFile) +} + +func (m *MockImage) Digest() (v1.Hash, error) { + return v1.Hash{}, nil +} + +func (m *MockImage) LayerByDigest(hash v1.Hash) (v1.Layer, error) { + return nil, nil +} + +func (m *MockImage) LayerByDiffID(hash v1.Hash) (v1.Layer, error) { + return nil, nil } diff --git a/pkg/sign/notary_structs.go b/pkg/sign/notary_structs.go new file mode 100644 index 000000000000..d227771ed338 --- /dev/null +++ b/pkg/sign/notary_structs.go @@ -0,0 +1,37 @@ +package sign + +import "time" + +type Target struct { + Name string `json:"name"` + ByteSize int64 `json:"byteSize"` + Digest string `json:"digest"` +} + +type TrustedCollection struct { + GUN string `json:"gun"` + Targets []Target `json:"targets"` +} + +type SigningPayload struct { + TrustedCollections []TrustedCollection `json:"trustedCollections"` +} + +type SignifySecret struct { + CertificateData string `json:"certData"` + PrivateKeyData string `json:"privateKeyData"` +} + +// NotaryConfig structs +type NotaryConfig struct { + Endpoint string `yaml:"endpoint" json:"endpoint"` + Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` + Timeout time.Duration `yaml:"timeout" json:"timeout"` + RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` + ReadFileFunc func(string) ([]byte, error) +} + +type AuthSecretConfig struct { + Path string `yaml:"path" json:"path"` + Type string `yaml:"type" json:"type"` +} diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 6d6ebb151140..53293a197c23 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -1,626 +1,373 @@ -package sign +package sign_test import ( + "bytes" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/base64" - "encoding/json" "encoding/pem" - "errors" - "fmt" - "io" "math/big" "net/http" "net/http/httptest" + "reflect" "testing" "time" "github.com/google/go-containerregistry/pkg/name" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/kyma-project/test-infra/pkg/sign" ) -func TestSuite(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Sign Package Test Suite") +// generateTestCert generuje samopodpisany certyfikat i klucz prywatny. +// Zwraca certyfikat i klucz w formacie PEM. +func generateTestCert() (string, string, error) { + // Generowanie klucza RSA + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return "", "", err + } + + // Tworzenie szablonu certyfikatu + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Test Organization"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), // Certyfikat ważny przez 24 godziny + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + // Samopodpisanie certyfikatu + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return "", "", err + } + + // Kodowanie certyfikatu do PEM + certPEM := new(bytes.Buffer) + if err := pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return "", "", err + } + + // Kodowanie klucza prywatnego do PEM + keyPEM := new(bytes.Buffer) + if err := pem.Encode(keyPEM, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil { + return "", "", err + } + + return certPEM.String(), keyPEM.String(), nil } -var _ = Describe("Sign Package Tests", func() { - setupDecodeCertAndKeyTests() - setupBuildSigningRequestTests() - setupBuildPayloadTests() - setupNewSignerTests() - setupGetImageTests() - setupParseReferenceTests() - setupSetupTLSTests() - setupRetryHTTPRequestTests() - setupSignTests() -}) - -func setupDecodeCertAndKeyTests() { - Describe("DecodeCertAndKey", func() { - var signifySecret SignifySecret - - BeforeEach(func() { - // Generating base64 encoded certificate and key - certBase64, keyBase64, err := GenerateBase64EncodedCert() - Expect(err).To(BeNil()) - - signifySecret = SignifySecret{ - CertificateData: certBase64, - PrivateKeyData: keyBase64, - } - }) - - Context("When decoding is successful", func() { - It("should correctly decode certificate and private key", func() { - cert, err := signifySecret.DecodeCertAndKey() - Expect(err).To(BeNil()) - Expect(cert).To(BeAssignableToTypeOf(tls.Certificate{})) - }) - }) - - Context("When decoding certificate fails", func() { - BeforeEach(func() { - signifySecret.CertificateData = "invalid-base64" - }) - - It("should return an error for invalid certificate data", func() { - _, err := signifySecret.DecodeCertAndKey() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to decode certificate")) - }) - }) - - Context("When decoding private key fails", func() { - BeforeEach(func() { - signifySecret.PrivateKeyData = "invalid-base64" - }) - - It("should return an error for invalid private key data", func() { - _, err := signifySecret.DecodeCertAndKey() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to decode private key")) - }) - }) - - Context("When loading TLS certificate fails", func() { - BeforeEach(func() { - signifySecret.CertificateData = base64.StdEncoding.EncodeToString([]byte("invalid-cert")) - signifySecret.PrivateKeyData = base64.StdEncoding.EncodeToString([]byte("invalid-key")) - }) - - It("should return an error for invalid certificate or key", func() { - _, err := signifySecret.DecodeCertAndKey() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unable to load certificate or key")) - }) - }) - }) +// TestImageService_ParseReference_Valid sprawdza poprawne parsowanie referencji obrazu. +func TestImageService_ParseReference_Valid(t *testing.T) { + imageService := sign.ImageService{} + ref, err := imageService.ParseReference("docker.io/library/alpine:latest") + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if ref == nil { + t.Errorf("Expected ref to be not nil") + } } -func setupBuildSigningRequestTests() { - Describe("NotarySigner", func() { - var ns NotarySigner - - BeforeEach(func() { - // Setting up NotarySigner with mock functions - ns = NotarySigner{ - ParseReferenceFunc: MockParseReference, - GetImageFunc: MockGetImage, - } - }) - - Describe("buildSigningRequest", func() { - Context("When valid images are provided", func() { - It("should correctly create signing requests", func() { - images := []string{ - "gcr.io/project/image:tag1", - "docker.io/library/ubuntu:latest", - "quay.io/repository/image:v2.1.0", - } - - signingRequests, err := ns.buildSigningRequest(images) - Expect(err).To(BeNil()) - Expect(signingRequests).To(HaveLen(len(images))) - - for i, req := range signingRequests { - Expect(req.NotaryGun).NotTo(BeEmpty(), "NotaryGun should not be empty for request %d", i) - Expect(req.SHA256).To(Equal("abc123def456"), "SHA256 should match for request %d", i) - Expect(req.ByteSize).To(Equal(int64(12345678)), "ByteSize should match for request %d", i) - } - }) - }) - - Context("When an invalid image reference is provided", func() { - BeforeEach(func() { - ns.ParseReferenceFunc = func(image string) (Reference, error) { - return nil, fmt.Errorf("invalid reference") - } - }) - - It("should return an error for invalid reference", func() { - images := []string{"invalid/image:tag"} - - _, err := ns.buildSigningRequest(images) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("ref parse")) - }) - }) - - Context("When image fetching fails", func() { - BeforeEach(func() { - ns.GetImageFunc = func(ref Reference) (Image, error) { - return nil, fmt.Errorf("image fetch failed") - } - }) - - It("should return an error for failed image fetch", func() { - images := []string{"gcr.io/project/image:tag1"} - - _, err := ns.buildSigningRequest(images) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("get image")) - }) - }) - }) - }) +// TestImageService_ParseReference_Invalid sprawdza błędne parsowanie referencji obrazu. +func TestImageService_ParseReference_Invalid(t *testing.T) { + imageService := sign.ImageService{} + // Użyj bardziej nieprawidłowego formatu referencji + invalidReferences := []string{ + ":::", + "invalid_image@sha256:invaliddigest", + "invalid image", + "@invalid", + "invalid_image@sha256:", + } + + for _, image := range invalidReferences { + _, err := imageService.ParseReference(image) + if err == nil { + t.Errorf("Expected an error for invalid image reference '%s', but got nil", image) + } + } } -func setupBuildPayloadTests() { - Describe("buildPayload", func() { - var ns NotarySigner - - BeforeEach(func() { - ns = NotarySigner{} - }) - - Context("When valid signing requests are provided", func() { - It("should create a payload with correct trusted collections", func() { - signingRequests := []SigningRequest{ - { - NotaryGun: "gcr.io/project", - SHA256: "abc123", - ByteSize: 123456, - Version: "v1.0", - }, - { - NotaryGun: "docker.io/library/ubuntu", - SHA256: "def456", - ByteSize: 654321, - Version: "latest", - }, - } - - payload, err := ns.buildPayload(signingRequests) - Expect(err).To(BeNil()) - - // Verify TrustedCollections in the payload - Expect(payload.TrustedCollections).To(HaveLen(2)) - - // Check the first trusted collection - Expect(payload.TrustedCollections[0].GUN).To(Equal("gcr.io/project")) - Expect(payload.TrustedCollections[0].Targets).To(HaveLen(1)) - Expect(payload.TrustedCollections[0].Targets[0].Name).To(Equal("v1.0")) - Expect(payload.TrustedCollections[0].Targets[0].ByteSize).To(Equal(int64(123456))) - Expect(payload.TrustedCollections[0].Targets[0].Digest).To(Equal("abc123")) - - // Check the second trusted collection - Expect(payload.TrustedCollections[1].GUN).To(Equal("docker.io/library/ubuntu")) - Expect(payload.TrustedCollections[1].Targets).To(HaveLen(1)) - Expect(payload.TrustedCollections[1].Targets[0].Name).To(Equal("latest")) - Expect(payload.TrustedCollections[1].Targets[0].ByteSize).To(Equal(int64(654321))) - Expect(payload.TrustedCollections[1].Targets[0].Digest).To(Equal("def456")) - }) - }) - - Context("When an empty list of signing requests is provided", func() { - It("should return an empty payload", func() { - signingRequests := []SigningRequest{} - - payload, err := ns.buildPayload(signingRequests) - Expect(err).To(BeNil()) - - // Verify that the payload contains no trusted collections - Expect(payload.TrustedCollections).To(BeEmpty()) - }) - }) - }) +// TestImageService_GetImage_Valid sprawdza pobieranie poprawnego obrazu. +func TestImageService_GetImage_Valid(t *testing.T) { + imageService := sign.ImageService{} + ref, err := name.ParseReference("docker.io/library/alpine:latest") + if err != nil { + t.Fatalf("Failed to parse reference: %v", err) + } + img, err := imageService.GetImage(ref) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if img == nil || reflect.ValueOf(img).IsNil() { + t.Errorf("Expected img to be not nil") + } } -func setupNewSignerTests() { - Describe("NewSigner", func() { - var nc NotaryConfig - - BeforeEach(func() { - // Initialize NotaryConfig with a mocked Secret - nc = NotaryConfig{ - Endpoint: "https://example.com/sign", - Timeout: 5 * time.Second, - RetryTimeout: 15 * time.Second, - Secret: &AuthSecretConfig{ - Path: "/mock/path/to/secret", - Type: "signify", - }, - } - }) - - Context("When a valid signify secret is provided", func() { - It("should return a valid NotarySigner", func() { - // Mock signify secret content - signifySecret := SignifySecret{ - CertificateData: "mockCertData", - PrivateKeyData: "mockPrivateKeyData", - } - secretContent, _ := json.Marshal(signifySecret) - - // Inject mocked ReadFileFunc - nc.ReadFileFunc = func(path string) ([]byte, error) { - Expect(path).To(Equal("/mock/path/to/secret")) - return secretContent, nil - } - - // Call NewSigner method - signer, err := nc.NewSigner() - Expect(err).To(BeNil()) - Expect(signer).NotTo(BeNil()) - - // Verify properties of NotarySigner - notarySigner, ok := signer.(*NotarySigner) - Expect(ok).To(BeTrue()) - Expect(notarySigner.signifySecret.CertificateData).To(Equal("mockCertData")) - Expect(notarySigner.signifySecret.PrivateKeyData).To(Equal("mockPrivateKeyData")) - Expect(notarySigner.retryTimeout).To(Equal(15 * time.Second)) - Expect(notarySigner.c.Timeout).To(Equal(5 * time.Second)) - Expect(notarySigner.url).To(Equal("https://example.com/sign")) - }) - }) - - Context("When reading the secret file fails", func() { - It("should return an error", func() { - // Mock error during file read - nc.Secret.Path = "/mock/invalid/path" - nc.ReadFileFunc = func(path string) ([]byte, error) { - Expect(path).To(Equal("/mock/invalid/path")) - return nil, errors.New("failed to read file") - } - - // Call NewSigner method - signer, err := nc.NewSigner() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to read secret file")) - Expect(signer).To(BeNil()) - }) - }) - - Context("When an unsupported secret type is provided", func() { - It("should return an error", func() { - // Set unsupported secret type - nc.Secret = &AuthSecretConfig{ - Type: "unsupported", - } - - // Call NewSigner method - signer, err := nc.NewSigner() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("'unsupported' auth service not supported")) - Expect(signer).To(BeNil()) - }) - }) - - Context("When unmarshalling the signify secret fails", func() { - It("should return an error", func() { - // Mock invalid JSON in the secret file - nc.Secret.Path = "/mock/path/to/secret" - nc.Secret.Type = "signify" - nc.ReadFileFunc = func(path string) ([]byte, error) { - Expect(path).To(Equal("/mock/path/to/secret")) - return []byte("invalid-json"), nil - } - - // Call NewSigner method - signer, err := nc.NewSigner() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to unmarshal signify secret")) - Expect(signer).To(BeNil()) - }) - }) - }) +// TestImageService_GetImage_Invalid sprawdza pobieranie niepoprawnego obrazu. +func TestImageService_GetImage_Invalid(t *testing.T) { + imageService := sign.ImageService{} + // Użyj bardziej nieprawidłowego formatu referencji + ref, err := name.ParseReference("invalid_image") + if err != nil { + t.Fatalf("Failed to parse reference: %v", err) + } + _, err = imageService.GetImage(ref) + if err == nil { + t.Errorf("Expected an error for invalid image") + } } -func setupGetImageTests() { - Describe("GetImage", func() { - Context("When a valid reference is provided", func() { - It("should return a SimpleImage with correct manifest data", func() { - // Create a valid reference - ref, err := name.ParseReference("gcr.io/project/image:tag") - Expect(err).To(BeNil()) - - // Mock remote.Image and manifest - // In this case, we assume the function works correctly, - // because testing actual image fetching would require access to external resources. - - // So we can test the case where the reference is invalid. - img, err := GetImage(ref) - // Since we don't have an actual image, an error might occur. - if err != nil { - // Check if the error is related to fetching the image - Expect(err.Error()).To(ContainSubstring("failed to fetch image")) - } else { - // If no error occurred, check that the image is not nil - Expect(img).NotTo(BeNil()) - } - }) - }) - - Context("When an invalid reference type is provided", func() { - It("should return an error indicating invalid reference type", func() { - // Provide a reference that is not of type name.Reference - ref := "invalid reference" - - img, err := GetImage(ref) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("invalid reference type")) - Expect(img).To(BeNil()) - }) - }) - }) +// TestPayloadBuilder_BuildPayload_Valid sprawdza budowanie payloadu dla poprawnych obrazów. +func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { + imageService := sign.ImageService{} + payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} + payload, err := payloadBuilder.BuildPayload([]string{"docker.io/library/alpine:latest"}) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if reflect.DeepEqual(payload, sign.SigningPayload{}) { + t.Errorf("Expected payload to be not zero value") + } } -func setupParseReferenceTests() { - Describe("ParseReference", func() { - Context("When a valid image string is provided", func() { - It("should correctly parse the reference", func() { - image := "gcr.io/project/image:tag" - ref, err := ParseReference(image) - Expect(err).To(BeNil()) - Expect(ref).NotTo(BeNil()) - }) - }) - - Context("When an invalid image string is provided", func() { - It("should return a parsing error", func() { - image := "invalid_image_string@@" - ref, err := ParseReference(image) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to parse image reference")) - Expect(ref).To(BeNil()) - }) - }) - }) +// TestPayloadBuilder_BuildPayload_Invalid sprawdza budowanie payloadu dla niepoprawnych obrazów. +func TestPayloadBuilder_BuildPayload_Invalid(t *testing.T) { + imageService := sign.ImageService{} + payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} + _, err := payloadBuilder.BuildPayload([]string{"invalid_image"}) + if err == nil { + t.Errorf("Expected an error for invalid images") + } +} + +// TestCertificateProvider_CreateKeyPair_Valid sprawdza tworzenie pary kluczy z poprawnymi danymi base64. +func TestCertificateProvider_CreateKeyPair_Valid(t *testing.T) { + certPEM, keyPEM, err := generateTestCert() + if err != nil { + t.Fatalf("Failed to generate test certificate: %v", err) + } + + signifySecret := sign.SignifySecret{ + CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), + } + certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + cert, err := certificateProvider.CreateKeyPair() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + // Sprawdzamy, czy cert nie jest pusty + if len(cert.Certificate) == 0 { + t.Errorf("Expected cert.Certificate to have data") + } + // Opcjonalnie: Sprawdzenie poprawności certyfikatu + // Możesz użyć bibliotek takich jak x509 do dalszej weryfikacji +} + +// TestCertificateProvider_CreateKeyPair_Invalid sprawdza tworzenie pary kluczy z niepoprawnymi danymi base64. +func TestCertificateProvider_CreateKeyPair_Invalid(t *testing.T) { + signifySecret := sign.SignifySecret{ + CertificateData: "invalid-base64", + PrivateKeyData: base64.StdEncoding.EncodeToString([]byte("private-key-data")), + } + certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + _, err := certificateProvider.CreateKeyPair() + if err == nil { + t.Errorf("Expected an error for invalid base64 data") + } } -func setupSetupTLSTests() { - Describe("setupTLS", func() { - Context("When a valid TLS certificate is provided", func() { - It("should return a correct TLS configuration", func() { - certBase64, keyBase64, err := GenerateBase64EncodedCert() - Expect(err).To(BeNil()) - - certData, err := base64.StdEncoding.DecodeString(certBase64) - Expect(err).To(BeNil()) - keyData, err := base64.StdEncoding.DecodeString(keyBase64) - Expect(err).To(BeNil()) - - cert, err := tls.X509KeyPair(certData, keyData) - Expect(err).To(BeNil()) - - tlsConfig := setupTLS(cert) - Expect(tlsConfig).NotTo(BeNil()) - Expect(tlsConfig.Certificates).To(HaveLen(1)) - }) - }) - }) +// TestTLSConfigurator_SetupTLS sprawdza konfigurację TLS. +func TestTLSConfigurator_SetupTLS(t *testing.T) { + certPEM, keyPEM, err := generateTestCert() + if err != nil { + t.Fatalf("Failed to generate test certificate: %v", err) + } + + certificate, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + if err != nil { + t.Fatalf("Failed to load X509 key pair: %v", err) + } + + tlsConfigurator := sign.TLSConfigurator{} + tlsConfig := tlsConfigurator.SetupTLS(certificate) + if tlsConfig == nil { + t.Errorf("Expected tlsConfig to be not nil") + } + if len(tlsConfig.Certificates) != 1 { + t.Errorf("Expected Certificates length to be 1, got %d", len(tlsConfig.Certificates)) + } } -func setupRetryHTTPRequestTests() { - Describe("retryHTTPRequest", func() { - var ( - server *httptest.Server - client *http.Client - request *http.Request - retryCount int - retryTimeout time.Duration - ) - - BeforeEach(func() { - retryCount = 3 - retryTimeout = 100 * time.Millisecond - }) - - Context("When the request is successful", func() { - BeforeEach(func() { - server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusAccepted) - })) - client = server.Client() - request, _ = http.NewRequest("GET", server.URL, nil) - }) - - AfterEach(func() { - server.Close() - }) - - It("should return the response without errors", func() { - resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) - Expect(err).To(BeNil()) - Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) - }) - }) - - Context("When the request fails a few times but eventually succeeds", func() { - var attempt int - - BeforeEach(func() { - attempt = 0 - server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - attempt++ - if attempt < 2 { - http.Error(w, "temporary error", http.StatusInternalServerError) - } else { - w.WriteHeader(http.StatusAccepted) - } - })) - client = server.Client() - request, _ = http.NewRequest("GET", server.URL, nil) - }) - - AfterEach(func() { - server.Close() - }) - - It("should retry the request and eventually return a successful response", func() { - resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) - Expect(err).To(BeNil()) - Expect(resp.StatusCode).To(Equal(http.StatusAccepted)) - Expect(attempt).To(Equal(2)) - }) - }) - - Context("When all request attempts fail", func() { - BeforeEach(func() { - server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "error", http.StatusInternalServerError) - })) - client = server.Client() - request, _ = http.NewRequest("GET", server.URL, nil) - }) - - AfterEach(func() { - server.Close() - }) - - It("should return an error after exhausting retries", func() { - resp, err := retryHTTPRequest(client, request, retryCount, retryTimeout) - Expect(err).To(HaveOccurred()) - Expect(resp).NotTo(BeNil()) - Expect(resp.StatusCode).To(Equal(http.StatusInternalServerError)) - Expect(err.Error()).To(ContainSubstring("unexpected status code: 500")) - }) - }) - }) +// TestHTTPClient_Do sprawdza wysyłanie żądania HTTP. +func TestHTTPClient_Do(t *testing.T) { + httpClient := sign.HTTPClient{Client: &http.Client{}} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + req, err := http.NewRequest("GET", server.URL, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + resp, err := httpClient.Do(req) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status %d, got %d", http.StatusOK, resp.StatusCode) + } } -func setupSignTests() { - Describe("Sign", func() { - var ns NotarySigner - var server *httptest.Server - - BeforeEach(func() { - ns = NotarySigner{ - ParseReferenceFunc: MockParseReference, - GetImageFunc: MockGetImage, - BuildPayloadFunc: func(sr []SigningRequest) (SigningPayload, error) { - return SigningPayload{ - TrustedCollections: []TrustedCollection{ - { - GUN: "example.com/image", - Targets: []Target{ - { - Name: "latest", - ByteSize: 12345, - Digest: "abc123", - }, - }, - }, - }, - }, nil - }, - DecodeCertFunc: func() (tls.Certificate, error) { - return tls.Certificate{}, nil - }, - SetupTLSFunc: setupTLS, - retryTimeout: 100 * time.Millisecond, - } - }) - - AfterEach(func() { - if server != nil { - server.Close() - } - }) - - Context("When signing is successful", func() { - It("should complete without errors", func() { - server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(io.Discard, r.Body) // Ensure the body is read - w.WriteHeader(http.StatusAccepted) - })) - - ns.url = server.URL - ns.HTTPClient = server.Client() // Use the server's client - - err := ns.Sign([]string{"example.com/image:latest"}) - Expect(err).To(BeNil()) - }) - }) - - Context("When an error occurs during signing", func() { - It("should return the appropriate error", func() { - server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(io.Discard, r.Body) // Ensure the body is read - r.Body.Close() - http.Error(w, "error", http.StatusInternalServerError) - })) - - ns.url = server.URL - ns.HTTPClient = server.Client() // Use the server's client - - err := ns.Sign([]string{"example.com/image:latest"}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to sign images")) - }) - }) - - }) +// TestHTTPClient_SetTLSConfig sprawdza ustawienie konfiguracji TLS w HTTPClient. +func TestHTTPClient_SetTLSConfig(t *testing.T) { + certPEM, keyPEM, err := generateTestCert() + if err != nil { + t.Fatalf("Failed to generate test certificate: %v", err) + } + + certificate, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + if err != nil { + t.Fatalf("Failed to load X509 key pair: %v", err) + } + + httpClient := sign.HTTPClient{Client: &http.Client{}} + tlsConfig := &tls.Config{Certificates: []tls.Certificate{certificate}} + err = httpClient.SetTLSConfig(tlsConfig) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + // Sprawdzamy, czy Transport jest ustawiony i jest typu *http.Transport + transport, ok := httpClient.Client.Transport.(*http.Transport) + if !ok { + t.Errorf("Expected Transport to be of type *http.Transport") + } + if transport == nil { + t.Errorf("Expected Transport to be set") + } } -// GenerateBase64EncodedCert generates a self-signed certificate and private key, -// and returns them as base64-encoded strings. -func GenerateBase64EncodedCert() (string, string, error) { - // Generate RSA private key - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) +// TestNotarySigner_Sign_Valid sprawdza podpisywanie poprawnych obrazów. +func TestNotarySigner_Sign_Valid(t *testing.T) { + certPEM, keyPEM, err := generateTestCert() if err != nil { - return "", "", err + t.Fatalf("Failed to generate test certificate: %v", err) } - // Create certificate template - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: "localhost", - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, + signifySecret := sign.SignifySecret{ + CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), + } + imageService := sign.ImageService{} + payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} + certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + tlsConfigurator := sign.TLSConfigurator{} + httpClient := sign.HTTPClient{Client: &http.Client{}} + notarySigner := sign.NotarySigner{ + URL: "http://example.com", + RetryTimeout: 1 * time.Second, + PayloadBuilder: &payloadBuilder, + CertificateProvider: &certificateProvider, + TLSConfigurator: &tlsConfigurator, + HTTPClient: &httpClient, } - // Create certificate - certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusAccepted) + })) + defer server.Close() + + notarySigner.URL = server.URL + err = notarySigner.Sign([]string{"docker.io/library/alpine:latest"}) if err != nil { - return "", "", err + t.Errorf("Expected no error, got %v", err) } +} - // Encode certificate to PEM format - certPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certDER, - }) +// TestNotarySigner_Sign_Invalid sprawdza podpisywanie niepoprawnych obrazów. +func TestNotarySigner_Sign_Invalid(t *testing.T) { + certPEM, keyPEM, err := generateTestCert() + if err != nil { + t.Fatalf("Failed to generate test certificate: %v", err) + } - // Encode private key to PEM format - keyPEM := pem.EncodeToMemory(&pem.Block{ - Type: "PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) + signifySecret := sign.SignifySecret{ + CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), + } + imageService := sign.ImageService{} + payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} + certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + tlsConfigurator := sign.TLSConfigurator{} + httpClient := sign.HTTPClient{Client: &http.Client{}} + notarySigner := sign.NotarySigner{ + URL: "http://example.com", + RetryTimeout: 1 * time.Second, + PayloadBuilder: &payloadBuilder, + CertificateProvider: &certificateProvider, + TLSConfigurator: &tlsConfigurator, + HTTPClient: &httpClient, + } - // Encode PEM data to base64 - certBase64 := base64.StdEncoding.EncodeToString(certPEM) - keyBase64 := base64.StdEncoding.EncodeToString(keyPEM) + err = notarySigner.Sign([]string{"invalid_image"}) + if err == nil { + t.Errorf("Expected an error for invalid images") + } +} + +// TestRetryHTTPRequest_Failure sprawdza, czy RetryHTTPRequest zwraca błąd po nieudanych próbach. +func TestRetryHTTPRequest_Failure(t *testing.T) { + httpClient := sign.HTTPClient{Client: &http.Client{}} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() - return certBase64, keyBase64, nil + req, err := http.NewRequest("GET", server.URL, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + resp, err := sign.RetryHTTPRequest(&httpClient, req, 3, 1*time.Second) + if err == nil { + t.Errorf("Expected an error on failure") + } + if resp.StatusCode != http.StatusInternalServerError { + t.Errorf("Expected status %d, got %d", http.StatusInternalServerError, resp.StatusCode) + } +} + +// TestRetryHTTPRequest_SuccessAfterRetries sprawdza, czy RetryHTTPRequest kończy sukcesem po określonej liczbie prób. +func TestRetryHTTPRequest_SuccessAfterRetries(t *testing.T) { + httpClient := sign.HTTPClient{Client: &http.Client{}} + attempts := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attempts++ + if attempts < 3 { + w.WriteHeader(http.StatusInternalServerError) + } else { + w.WriteHeader(http.StatusAccepted) + } + })) + defer server.Close() + + req, err := http.NewRequest("GET", server.URL, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + resp, err := sign.RetryHTTPRequest(&httpClient, req, 3, 1*time.Second) + if err != nil { + t.Errorf("Expected no error after retries, got %v", err) + } + if resp.StatusCode != http.StatusAccepted { + t.Errorf("Expected status %d, got %d", http.StatusAccepted, resp.StatusCode) + } } diff --git a/pkg/sign/sign.go b/pkg/sign/sign.go index e95aca5885cd..009e7a9b68dc 100644 --- a/pkg/sign/sign.go +++ b/pkg/sign/sign.go @@ -1,18 +1,12 @@ package sign import ( - "fmt" - "gopkg.in/yaml.v3" ) -type ErrBackendNotSupported struct { - Type string -} - -func (e ErrBackendNotSupported) Error() string { - return fmt.Sprintf("'%s' backend not supported", e.Type) -} +const ( + TypeNotaryBackend = "notary" +) type SignerConfig struct { // Name contains the custom name of defined signer @@ -38,27 +32,24 @@ type Signer interface { func (sc *SignerConfig) UnmarshalYAML(value *yaml.Node) error { var t struct { - Name string `yaml:"name"` - Type string `yaml:"type"` - JobType []string `yaml:"job-type"` + Name string `yaml:"name"` + Type string `yaml:"type"` + JobType []string `yaml:"job-type"` + Config yaml.Node `yaml:"config"` } if err := value.Decode(&t); err != nil { return err } - switch t.Type { - case TypeNotaryBackend: - var c struct { - Config NotaryConfig `yaml:"config"` - } - if err := value.Decode(&c); err != nil { - return err - } - sc.Config = c.Config - default: - return ErrBackendNotSupported{Type: t.Type} - } + sc.Type = t.Type sc.Name = t.Name sc.JobType = t.JobType + + var notaryConfig NotaryConfig + if err := t.Config.Decode(¬aryConfig); err != nil { + return err + } + sc.Config = ¬aryConfig + return nil } diff --git a/pkg/sign/sign_test.go b/pkg/sign/sign_test.go deleted file mode 100644 index 92465ae09b9c..000000000000 --- a/pkg/sign/sign_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package sign - -import ( - "gopkg.in/yaml.v3" - "testing" -) - -func TestSignerConfig_UnmarshalYAML(t *testing.T) { - tc := []struct { - name string - expectType string - expectName string - expectErr bool - config string - }{ - { - name: "signer config type notary", - expectName: "notary-config", - expectType: "notary", - config: ` -name: notary-config -type: notary -config: - endpoint: http://sign - timeout: 10m`, - }, - { - name: "backend not supported", - expectErr: true, - config: ` -name: unknown-backend -type: unsupported -config: - unsupported: true`, - }, - } - for _, c := range tc { - t.Run(c.name, func(t *testing.T) { - var sc SignerConfig - err := yaml.Unmarshal([]byte(c.config), &sc) - if err != nil && !c.expectErr { - t.Errorf("got error, but didn't want to: %v", err) - } - if c.expectName != sc.Name { - t.Errorf("unmarshal wrong name: %v != %v", sc.Name, c.expectName) - } - if c.expectType != sc.Type { - t.Errorf("unmarshal wrong type: %v != %v", sc.Type, c.expectType) - } - switch v := sc.Config.(type) { - case NotaryConfig: - if c.expectType != TypeNotaryBackend { - t.Errorf("got wrong config type: %v", v) - } - } - }) - } -} From 8f3cb5543e5cd39c73be8fa33dc7b8577eb73bd1 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 19 Sep 2024 00:53:13 +0200 Subject: [PATCH 41/54] test updates --- cmd/image-builder/main_test.go | 25 ++++++++---- pkg/sign/notary_test.go | 58 +++++++++++++-------------- pkg/sign/sign_test.go | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 pkg/sign/sign_test.go diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index b215e5ee4599..bc70de4553b2 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -439,9 +439,14 @@ func Test_getSignersForOrgRepo(t *testing.T) { orgRepo: "ci-org/ci-repo", }, } + for _, c := range tc { t.Run(c.name, func(t *testing.T) { t.Setenv("JOB_TYPE", c.jobType) + + // Utworzenie mocków SignerFactory + mockFactory := &mockSignerFactory{} + o := &options{isCI: c.ci, Config: Config{SignConfig: SignConfig{ EnabledSigners: map[string][]string{ "*": {"test-notary"}, @@ -453,21 +458,23 @@ func Test_getSignersForOrgRepo(t *testing.T) { { Name: "test-notary", Type: sign.TypeNotaryBackend, - Config: sign.NotaryConfig{}, + Config: mockFactory, // Użycie mocka zamiast NotaryConfig }, { Name: "test-notary2", Type: sign.TypeNotaryBackend, - Config: sign.NotaryConfig{}, + Config: mockFactory, // Użycie mocka zamiast NotaryConfig }, { Name: "ci-notary", Type: sign.TypeNotaryBackend, - Config: sign.NotaryConfig{}, + Config: mockFactory, // Użycie mocka zamiast NotaryConfig JobType: []string{"postsubmit"}, }, }, }}} + + // Wywołanie funkcji i testowanie wyników got, err := getSignersForOrgRepo(o, c.orgRepo) if err != nil && !c.expectErr { t.Errorf("got error but didn't want to %v", err) @@ -789,12 +796,16 @@ Build config file content: } } -type mockSigner struct { - signFunc func([]string) error +type mockSignerFactory struct{} + +func (m *mockSignerFactory) NewSigner() (sign.Signer, error) { + return &mockSigner{}, nil } -func (m *mockSigner) Sign(images []string) error { - return m.signFunc(images) +type mockSigner struct{} + +func (m *mockSigner) Sign([]string) error { + return nil } func Test_getDockerfileDirPath(t *testing.T) { diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 53293a197c23..5c4f361a91b7 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -20,41 +20,41 @@ import ( "github.com/kyma-project/test-infra/pkg/sign" ) -// generateTestCert generuje samopodpisany certyfikat i klucz prywatny. -// Zwraca certyfikat i klucz w formacie PEM. +// generateTestCert generates a self-signed certificate and private key. +// Returns the certificate and key in PEM format. func generateTestCert() (string, string, error) { - // Generowanie klucza RSA + // Generate RSA key privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return "", "", err } - // Tworzenie szablonu certyfikatu + // Create certificate template template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ Organization: []string{"Test Organization"}, }, NotBefore: time.Now(), - NotAfter: time.Now().Add(24 * time.Hour), // Certyfikat ważny przez 24 godziny + NotAfter: time.Now().Add(24 * time.Hour), // Certificate valid for 24 hours KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } - // Samopodpisanie certyfikatu + // Self-sign the certificate derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { return "", "", err } - // Kodowanie certyfikatu do PEM + // Encode certificate to PEM certPEM := new(bytes.Buffer) if err := pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { return "", "", err } - // Kodowanie klucza prywatnego do PEM + // Encode private key to PEM keyPEM := new(bytes.Buffer) if err := pem.Encode(keyPEM, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil { return "", "", err @@ -63,7 +63,7 @@ func generateTestCert() (string, string, error) { return certPEM.String(), keyPEM.String(), nil } -// TestImageService_ParseReference_Valid sprawdza poprawne parsowanie referencji obrazu. +// TestImageService_ParseReference_Valid checks the correct parsing of an image reference. func TestImageService_ParseReference_Valid(t *testing.T) { imageService := sign.ImageService{} ref, err := imageService.ParseReference("docker.io/library/alpine:latest") @@ -75,10 +75,10 @@ func TestImageService_ParseReference_Valid(t *testing.T) { } } -// TestImageService_ParseReference_Invalid sprawdza błędne parsowanie referencji obrazu. +// TestImageService_ParseReference_Invalid checks the incorrect parsing of an image reference. func TestImageService_ParseReference_Invalid(t *testing.T) { imageService := sign.ImageService{} - // Użyj bardziej nieprawidłowego formatu referencji + // Use more invalid reference formats invalidReferences := []string{ ":::", "invalid_image@sha256:invaliddigest", @@ -95,7 +95,7 @@ func TestImageService_ParseReference_Invalid(t *testing.T) { } } -// TestImageService_GetImage_Valid sprawdza pobieranie poprawnego obrazu. +// TestImageService_GetImage_Valid checks fetching a valid image. func TestImageService_GetImage_Valid(t *testing.T) { imageService := sign.ImageService{} ref, err := name.ParseReference("docker.io/library/alpine:latest") @@ -111,10 +111,10 @@ func TestImageService_GetImage_Valid(t *testing.T) { } } -// TestImageService_GetImage_Invalid sprawdza pobieranie niepoprawnego obrazu. +// TestImageService_GetImage_Invalid checks fetching an invalid image. func TestImageService_GetImage_Invalid(t *testing.T) { imageService := sign.ImageService{} - // Użyj bardziej nieprawidłowego formatu referencji + // Use a more invalid reference format ref, err := name.ParseReference("invalid_image") if err != nil { t.Fatalf("Failed to parse reference: %v", err) @@ -125,7 +125,7 @@ func TestImageService_GetImage_Invalid(t *testing.T) { } } -// TestPayloadBuilder_BuildPayload_Valid sprawdza budowanie payloadu dla poprawnych obrazów. +// TestPayloadBuilder_BuildPayload_Valid checks building a payload for valid images. func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { imageService := sign.ImageService{} payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} @@ -138,7 +138,7 @@ func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { } } -// TestPayloadBuilder_BuildPayload_Invalid sprawdza budowanie payloadu dla niepoprawnych obrazów. +// TestPayloadBuilder_BuildPayload_Invalid checks building a payload for invalid images. func TestPayloadBuilder_BuildPayload_Invalid(t *testing.T) { imageService := sign.ImageService{} payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} @@ -148,7 +148,7 @@ func TestPayloadBuilder_BuildPayload_Invalid(t *testing.T) { } } -// TestCertificateProvider_CreateKeyPair_Valid sprawdza tworzenie pary kluczy z poprawnymi danymi base64. +// TestCertificateProvider_CreateKeyPair_Valid checks creating a key pair with valid base64 data. func TestCertificateProvider_CreateKeyPair_Valid(t *testing.T) { certPEM, keyPEM, err := generateTestCert() if err != nil { @@ -164,15 +164,15 @@ func TestCertificateProvider_CreateKeyPair_Valid(t *testing.T) { if err != nil { t.Errorf("Expected no error, got %v", err) } - // Sprawdzamy, czy cert nie jest pusty + // Check if cert is not empty if len(cert.Certificate) == 0 { t.Errorf("Expected cert.Certificate to have data") } - // Opcjonalnie: Sprawdzenie poprawności certyfikatu - // Możesz użyć bibliotek takich jak x509 do dalszej weryfikacji + // Optionally: Check the correctness of the certificate + // You can use libraries like x509 for further verification } -// TestCertificateProvider_CreateKeyPair_Invalid sprawdza tworzenie pary kluczy z niepoprawnymi danymi base64. +// TestCertificateProvider_CreateKeyPair_Invalid checks creating a key pair with invalid base64 data. func TestCertificateProvider_CreateKeyPair_Invalid(t *testing.T) { signifySecret := sign.SignifySecret{ CertificateData: "invalid-base64", @@ -185,7 +185,7 @@ func TestCertificateProvider_CreateKeyPair_Invalid(t *testing.T) { } } -// TestTLSConfigurator_SetupTLS sprawdza konfigurację TLS. +// TestTLSConfigurator_SetupTLS checks TLS configuration. func TestTLSConfigurator_SetupTLS(t *testing.T) { certPEM, keyPEM, err := generateTestCert() if err != nil { @@ -207,7 +207,7 @@ func TestTLSConfigurator_SetupTLS(t *testing.T) { } } -// TestHTTPClient_Do sprawdza wysyłanie żądania HTTP. +// TestHTTPClient_Do checks sending an HTTP request. func TestHTTPClient_Do(t *testing.T) { httpClient := sign.HTTPClient{Client: &http.Client{}} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -228,7 +228,7 @@ func TestHTTPClient_Do(t *testing.T) { } } -// TestHTTPClient_SetTLSConfig sprawdza ustawienie konfiguracji TLS w HTTPClient. +// TestHTTPClient_SetTLSConfig checks setting TLS configuration in HTTPClient. func TestHTTPClient_SetTLSConfig(t *testing.T) { certPEM, keyPEM, err := generateTestCert() if err != nil { @@ -246,7 +246,7 @@ func TestHTTPClient_SetTLSConfig(t *testing.T) { if err != nil { t.Errorf("Expected no error, got %v", err) } - // Sprawdzamy, czy Transport jest ustawiony i jest typu *http.Transport + // Check if Transport is set and is of type *http.Transport transport, ok := httpClient.Client.Transport.(*http.Transport) if !ok { t.Errorf("Expected Transport to be of type *http.Transport") @@ -256,7 +256,7 @@ func TestHTTPClient_SetTLSConfig(t *testing.T) { } } -// TestNotarySigner_Sign_Valid sprawdza podpisywanie poprawnych obrazów. +// TestNotarySigner_Sign_Valid checks signing valid images. func TestNotarySigner_Sign_Valid(t *testing.T) { certPEM, keyPEM, err := generateTestCert() if err != nil { @@ -293,7 +293,7 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { } } -// TestNotarySigner_Sign_Invalid sprawdza podpisywanie niepoprawnych obrazów. +// TestNotarySigner_Sign_Invalid checks signing invalid images. func TestNotarySigner_Sign_Invalid(t *testing.T) { certPEM, keyPEM, err := generateTestCert() if err != nil { @@ -324,7 +324,7 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { } } -// TestRetryHTTPRequest_Failure sprawdza, czy RetryHTTPRequest zwraca błąd po nieudanych próbach. +// TestRetryHTTPRequest_Failure checks if RetryHTTPRequest returns an error after failed attempts. func TestRetryHTTPRequest_Failure(t *testing.T) { httpClient := sign.HTTPClient{Client: &http.Client{}} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -345,7 +345,7 @@ func TestRetryHTTPRequest_Failure(t *testing.T) { } } -// TestRetryHTTPRequest_SuccessAfterRetries sprawdza, czy RetryHTTPRequest kończy sukcesem po określonej liczbie prób. +// TestRetryHTTPRequest_SuccessAfterRetries checks if RetryHTTPRequest succeeds after a certain number of retries. func TestRetryHTTPRequest_SuccessAfterRetries(t *testing.T) { httpClient := sign.HTTPClient{Client: &http.Client{}} attempts := 0 diff --git a/pkg/sign/sign_test.go b/pkg/sign/sign_test.go new file mode 100644 index 000000000000..b08bfaea6b32 --- /dev/null +++ b/pkg/sign/sign_test.go @@ -0,0 +1,72 @@ +package sign + +import ( + "fmt" + "testing" + "time" + + "gopkg.in/yaml.v3" +) + +func TestNotaryConfigUnmarshalYAML(t *testing.T) { + yamlContent := ` +endpoint: "https://signing.example.com" +secret: + path: "/path/to/secret" + type: "signify" +timeout: 30s +retry-timeout: 10s +` + var notaryConfig NotaryConfig + err := yaml.Unmarshal([]byte(yamlContent), ¬aryConfig) + if err != nil { + t.Fatalf("expected no error, but got: %v", err) + } + + if notaryConfig.Endpoint != "https://signing.example.com" { + t.Errorf("expected endpoint to be %v, but got %v", "https://signing.example.com", notaryConfig.Endpoint) + } + + if notaryConfig.Secret.Path != "/path/to/secret" { + t.Errorf("expected secret path to be %v, but got %v", "/path/to/secret", notaryConfig.Secret.Path) + } + + if notaryConfig.Secret.Type != "signify" { + t.Errorf("expected secret type to be %v, but got %v", "signify", notaryConfig.Secret.Type) + } + + if notaryConfig.Timeout != 30*time.Second { + t.Errorf("expected timeout to be %v, but got %v", 30*time.Second, notaryConfig.Timeout) + } + + if notaryConfig.RetryTimeout != 10*time.Second { + t.Errorf("expected retry timeout to be %v, but got %v", 10*time.Second, notaryConfig.RetryTimeout) + } +} + +func TestNotaryConfig_NewSigner(t *testing.T) { + // Mocking the file reading function to simulate secret loading + mockReadFileFunc := func(path string) ([]byte, error) { + if path == "/path/to/secret" { + return []byte(`{"CertificateData": "mockCertData", "PrivateKeyData": "mockKeyData"}`), nil + } + return nil, fmt.Errorf("file not found") + } + + config := NotaryConfig{ + Endpoint: "https://signing.example.com", + Secret: &AuthSecretConfig{Path: "/path/to/secret", Type: "signify"}, + Timeout: 30 * time.Second, + RetryTimeout: 10 * time.Second, + ReadFileFunc: mockReadFileFunc, + } + + signer, err := config.NewSigner() + if err != nil { + t.Fatalf("expected no error, but got: %v", err) + } + + if signer == nil { + t.Errorf("expected a valid signer, but got nil") + } +} From 75600c7b79973ec673a1856ce7c2c91d09864202 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 19 Sep 2024 01:02:54 +0200 Subject: [PATCH 42/54] remove last field function --- pkg/sign/notary.go | 8 +--- pkg/sign/notary_mocks.go | 4 +- pkg/sign/notary_structs.go | 1 - pkg/sign/sign_test.go | 97 +++++++++++++++++++++++++------------- 4 files changed, 67 insertions(+), 43 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 596b0d55c5fa..e5395b0d0ae8 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -284,12 +284,8 @@ func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int // NewSigner constructs a new NotarySigner with dependencies injected. func (nc *NotaryConfig) NewSigner() (Signer, error) { - // Read Signify secret - readFileFunc := nc.ReadFileFunc - if readFileFunc == nil { - readFileFunc = os.ReadFile - } - secretFileContent, err := readFileFunc(nc.Secret.Path) + // Read Signify secret from the path directly + secretFileContent, err := os.ReadFile(nc.Secret.Path) if err != nil { return nil, fmt.Errorf("failed to read secret file: %v", err) } diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 87b2ffa69961..10a71411cae9 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -129,10 +129,10 @@ func (m *MockImage) Digest() (v1.Hash, error) { return v1.Hash{}, nil } -func (m *MockImage) LayerByDigest(hash v1.Hash) (v1.Layer, error) { +func (m *MockImage) LayerByDigest(v1.Hash) (v1.Layer, error) { return nil, nil } -func (m *MockImage) LayerByDiffID(hash v1.Hash) (v1.Layer, error) { +func (m *MockImage) LayerByDiffID(v1.Hash) (v1.Layer, error) { return nil, nil } diff --git a/pkg/sign/notary_structs.go b/pkg/sign/notary_structs.go index d227771ed338..dcc0d0beaa5f 100644 --- a/pkg/sign/notary_structs.go +++ b/pkg/sign/notary_structs.go @@ -28,7 +28,6 @@ type NotaryConfig struct { Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` Timeout time.Duration `yaml:"timeout" json:"timeout"` RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` - ReadFileFunc func(string) ([]byte, error) } type AuthSecretConfig struct { diff --git a/pkg/sign/sign_test.go b/pkg/sign/sign_test.go index b08bfaea6b32..95b919cd8d5a 100644 --- a/pkg/sign/sign_test.go +++ b/pkg/sign/sign_test.go @@ -1,7 +1,8 @@ package sign import ( - "fmt" + "encoding/json" + "os" "testing" "time" @@ -9,56 +10,84 @@ import ( ) func TestNotaryConfigUnmarshalYAML(t *testing.T) { - yamlContent := ` -endpoint: "https://signing.example.com" -secret: - path: "/path/to/secret" - type: "signify" -timeout: 30s -retry-timeout: 10s + // Mock YAML data with SignerConfig and NotaryConfig + yamlData := ` +name: notary-signer +type: notary +job-type: + - postsubmit +config: + endpoint: https://notary.example.com + secret: + path: /path/to/secret + type: signify + timeout: 10s + retry-timeout: 5s ` - var notaryConfig NotaryConfig - err := yaml.Unmarshal([]byte(yamlContent), ¬aryConfig) + + // Unmarshal the YAML into SignerConfig struct + var sc SignerConfig + err := yaml.Unmarshal([]byte(yamlData), &sc) if err != nil { - t.Fatalf("expected no error, but got: %v", err) + t.Fatalf("failed to unmarshal YAML: %v", err) } - if notaryConfig.Endpoint != "https://signing.example.com" { - t.Errorf("expected endpoint to be %v, but got %v", "https://signing.example.com", notaryConfig.Endpoint) + // Assertions for SignerConfig fields + if sc.Name != "notary-signer" { + t.Errorf("expected name to be 'notary-signer', got %s", sc.Name) + } + if sc.Type != "notary" { + t.Errorf("expected type to be 'notary', got %s", sc.Type) + } + if len(sc.JobType) != 1 || sc.JobType[0] != "postsubmit" { + t.Errorf("expected job-type to contain 'postsubmit', got %v", sc.JobType) } - if notaryConfig.Secret.Path != "/path/to/secret" { - t.Errorf("expected secret path to be %v, but got %v", "/path/to/secret", notaryConfig.Secret.Path) + // Assertions for NotaryConfig fields (from sc.Config) + notaryConfig, ok := sc.Config.(*NotaryConfig) + if !ok { + t.Fatalf("expected sc.Config to be of type *NotaryConfig, but got %T", sc.Config) } + if notaryConfig.Endpoint != "https://notary.example.com" { + t.Errorf("expected endpoint to be 'https://notary.example.com', got %s", notaryConfig.Endpoint) + } + if notaryConfig.Secret.Path != "/path/to/secret" { + t.Errorf("expected secret path to be '/path/to/secret', got %s", notaryConfig.Secret.Path) + } if notaryConfig.Secret.Type != "signify" { - t.Errorf("expected secret type to be %v, but got %v", "signify", notaryConfig.Secret.Type) + t.Errorf("expected secret type to be 'signify', got %s", notaryConfig.Secret.Type) } - - if notaryConfig.Timeout != 30*time.Second { - t.Errorf("expected timeout to be %v, but got %v", 30*time.Second, notaryConfig.Timeout) + if notaryConfig.Timeout != 10*time.Second { + t.Errorf("expected timeout to be 10s, got %v", notaryConfig.Timeout) } - - if notaryConfig.RetryTimeout != 10*time.Second { - t.Errorf("expected retry timeout to be %v, but got %v", 10*time.Second, notaryConfig.RetryTimeout) + if notaryConfig.RetryTimeout != 5*time.Second { + t.Errorf("expected retry timeout to be 5s, got %v", notaryConfig.RetryTimeout) } } func TestNotaryConfig_NewSigner(t *testing.T) { - // Mocking the file reading function to simulate secret loading - mockReadFileFunc := func(path string) ([]byte, error) { - if path == "/path/to/secret" { - return []byte(`{"CertificateData": "mockCertData", "PrivateKeyData": "mockKeyData"}`), nil - } - return nil, fmt.Errorf("file not found") + // Set up a mock secret file in a valid temporary path + secretPath := "/tmp/mock_secret.json" + mockSecret := SignifySecret{ + CertificateData: "mockCertData", + PrivateKeyData: "mockPrivateKeyData", + } + secretContent, _ := json.Marshal(mockSecret) + + // Write the mock secret to the file + err := os.WriteFile(secretPath, secretContent, 0644) + if err != nil { + t.Fatalf("Failed to write mock secret file: %v", err) } + defer os.Remove(secretPath) // Clean up the file after test - config := NotaryConfig{ - Endpoint: "https://signing.example.com", - Secret: &AuthSecretConfig{Path: "/path/to/secret", Type: "signify"}, - Timeout: 30 * time.Second, - RetryTimeout: 10 * time.Second, - ReadFileFunc: mockReadFileFunc, + // Prepare the NotaryConfig with the temporary file path + config := &NotaryConfig{ + Endpoint: "https://notary.example.com", + Secret: &AuthSecretConfig{Path: secretPath}, + Timeout: 10 * time.Second, + RetryTimeout: 5 * time.Second, } signer, err := config.NewSigner() From a8260b64ae6cd765d3fefdcc8e2a886983094a94 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Mon, 23 Sep 2024 09:51:36 +0200 Subject: [PATCH 43/54] delete comments --- cmd/image-builder/main_test.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index bc70de4553b2..91e907709c38 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -439,12 +439,9 @@ func Test_getSignersForOrgRepo(t *testing.T) { orgRepo: "ci-org/ci-repo", }, } - for _, c := range tc { t.Run(c.name, func(t *testing.T) { t.Setenv("JOB_TYPE", c.jobType) - - // Utworzenie mocków SignerFactory mockFactory := &mockSignerFactory{} o := &options{isCI: c.ci, Config: Config{SignConfig: SignConfig{ @@ -458,23 +455,22 @@ func Test_getSignersForOrgRepo(t *testing.T) { { Name: "test-notary", Type: sign.TypeNotaryBackend, - Config: mockFactory, // Użycie mocka zamiast NotaryConfig + Config: mockFactory, }, { Name: "test-notary2", Type: sign.TypeNotaryBackend, - Config: mockFactory, // Użycie mocka zamiast NotaryConfig + Config: mockFactory, }, { Name: "ci-notary", Type: sign.TypeNotaryBackend, - Config: mockFactory, // Użycie mocka zamiast NotaryConfig + Config: mockFactory, JobType: []string{"postsubmit"}, }, }, }}} - // Wywołanie funkcji i testowanie wyników got, err := getSignersForOrgRepo(o, c.orgRepo) if err != nil && !c.expectErr { t.Errorf("got error but didn't want to %v", err) From 405d22f5ab8ccdd54f2a1980e7bc881f0db3f3c5 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 24 Sep 2024 14:53:00 +0200 Subject: [PATCH 44/54] renaming stuff --- pkg/sign/notary.go | 67 ++++++++++++++++++++++++++++---------- pkg/sign/notary_mocks.go | 2 +- pkg/sign/notary_structs.go | 36 -------------------- pkg/sign/notary_test.go | 16 ++++----- pkg/sign/sign_test.go | 2 +- 5 files changed, 60 insertions(+), 63 deletions(-) delete mode 100644 pkg/sign/notary_structs.go diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index e5395b0d0ae8..4ea74fe45c49 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -22,8 +22,8 @@ const ( regRepoDelimiter = "/" ) -// ImageServiceInterface handles image parsing and fetching. -type ImageServiceInterface interface { +// ImageRepositoryInterface handles image parsing and fetching. +type ImageRepositoryInterface interface { ParseReference(image string) (name.Reference, error) GetImage(ref name.Reference) (v1.Image, error) } @@ -49,7 +49,40 @@ type HTTPClientInterface interface { SetTLSConfig(tlsConfig *tls.Config) error } -// ImageService implements ImageServiceInterface. +type Target struct { + Name string `json:"name"` + ByteSize int64 `json:"byteSize"` + Digest string `json:"digest"` +} + +type GUNTargets struct { + GUN string `json:"gun"` + Targets []Target `json:"targets"` +} + +type SigningPayload struct { + GunTargets []GUNTargets `json:"gunTargets"` +} + +type TLSCredentials struct { + CertificateData string `json:"certData"` + PrivateKeyData string `json:"privateKeyData"` +} + +// NotaryConfig structs +type NotaryConfig struct { + Endpoint string `yaml:"endpoint" json:"endpoint"` + Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` + Timeout time.Duration `yaml:"timeout" json:"timeout"` + RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` +} + +type AuthSecretConfig struct { + Path string `yaml:"path" json:"path"` + Type string `yaml:"type" json:"type"` +} + +// ImageService implements ImageRepositoryInterface. type ImageService struct{} func (is *ImageService) ParseReference(image string) (name.Reference, error) { @@ -70,11 +103,11 @@ func (is *ImageService) GetImage(ref name.Reference) (v1.Image, error) { // PayloadBuilder implements PayloadBuilderInterface. type PayloadBuilder struct { - ImageService ImageServiceInterface + ImageService ImageRepositoryInterface } func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) { - var trustedCollections []TrustedCollection + var gunTargets []GUNTargets for _, image := range images { base, tag, err := parseImageNameAndTag(image) if err != nil { @@ -106,17 +139,17 @@ func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) Digest: manifest.Config.Digest.String(), } - // Build trusted collection - trustedCollection := TrustedCollection{ + // Build GUN target + gunTarget := GUNTargets{ GUN: base, Targets: []Target{target}, } - trustedCollections = append(trustedCollections, trustedCollection) + gunTargets = append(gunTargets, gunTarget) } payload := SigningPayload{ - TrustedCollections: trustedCollections, + GunTargets: gunTargets, } return payload, nil } @@ -133,15 +166,15 @@ func parseImageNameAndTag(image string) (string, string, error) { // CertificateProvider implements CertificateProviderInterface. type CertificateProvider struct { - SignifySecret SignifySecret + Credentials TLSCredentials } func (cp *CertificateProvider) CreateKeyPair() (tls.Certificate, error) { - certData, err := base64.StdEncoding.DecodeString(cp.SignifySecret.CertificateData) + certData, err := base64.StdEncoding.DecodeString(cp.Credentials.CertificateData) if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) } - keyData, err := base64.StdEncoding.DecodeString(cp.SignifySecret.PrivateKeyData) + keyData, err := base64.StdEncoding.DecodeString(cp.Credentials.PrivateKeyData) if err != nil { return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) } @@ -284,15 +317,15 @@ func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int // NewSigner constructs a new NotarySigner with dependencies injected. func (nc *NotaryConfig) NewSigner() (Signer, error) { - // Read Signify secret from the path directly + // Read secret from the path directly secretFileContent, err := os.ReadFile(nc.Secret.Path) if err != nil { return nil, fmt.Errorf("failed to read secret file: %v", err) } - var signifySecret SignifySecret - err = json.Unmarshal(secretFileContent, &signifySecret) + var tlsCredentials TLSCredentials + err = json.Unmarshal(secretFileContent, &tlsCredentials) if err != nil { - return nil, fmt.Errorf("failed to unmarshal signify secret: %v", err) + return nil, fmt.Errorf("failed to unmarshal TLS credentials: %v", err) } // Initialize components @@ -301,7 +334,7 @@ func (nc *NotaryConfig) NewSigner() (Signer, error) { ImageService: imageService, } certificateProvider := &CertificateProvider{ - SignifySecret: signifySecret, + Credentials: tlsCredentials, } tlsConfigurator := &TLSConfigurator{} httpClient := &HTTPClient{ diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 10a71411cae9..7073c0e56027 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -11,7 +11,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/types" ) -// MockImageService implements ImageServiceInterface +// MockImageService implements ImageRepositoryInterface type MockImageService struct { MockParseReference func(image string) (name.Reference, error) MockGetImage func(ref name.Reference) (v1.Image, error) diff --git a/pkg/sign/notary_structs.go b/pkg/sign/notary_structs.go deleted file mode 100644 index dcc0d0beaa5f..000000000000 --- a/pkg/sign/notary_structs.go +++ /dev/null @@ -1,36 +0,0 @@ -package sign - -import "time" - -type Target struct { - Name string `json:"name"` - ByteSize int64 `json:"byteSize"` - Digest string `json:"digest"` -} - -type TrustedCollection struct { - GUN string `json:"gun"` - Targets []Target `json:"targets"` -} - -type SigningPayload struct { - TrustedCollections []TrustedCollection `json:"trustedCollections"` -} - -type SignifySecret struct { - CertificateData string `json:"certData"` - PrivateKeyData string `json:"privateKeyData"` -} - -// NotaryConfig structs -type NotaryConfig struct { - Endpoint string `yaml:"endpoint" json:"endpoint"` - Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` - Timeout time.Duration `yaml:"timeout" json:"timeout"` - RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` -} - -type AuthSecretConfig struct { - Path string `yaml:"path" json:"path"` - Type string `yaml:"type" json:"type"` -} diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 5c4f361a91b7..b75c8daf53d4 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -155,11 +155,11 @@ func TestCertificateProvider_CreateKeyPair_Valid(t *testing.T) { t.Fatalf("Failed to generate test certificate: %v", err) } - signifySecret := sign.SignifySecret{ + signifySecret := sign.TLSCredentials{ CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), } - certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + certificateProvider := sign.CertificateProvider{Credentials: signifySecret} cert, err := certificateProvider.CreateKeyPair() if err != nil { t.Errorf("Expected no error, got %v", err) @@ -174,11 +174,11 @@ func TestCertificateProvider_CreateKeyPair_Valid(t *testing.T) { // TestCertificateProvider_CreateKeyPair_Invalid checks creating a key pair with invalid base64 data. func TestCertificateProvider_CreateKeyPair_Invalid(t *testing.T) { - signifySecret := sign.SignifySecret{ + signifySecret := sign.TLSCredentials{ CertificateData: "invalid-base64", PrivateKeyData: base64.StdEncoding.EncodeToString([]byte("private-key-data")), } - certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + certificateProvider := sign.CertificateProvider{Credentials: signifySecret} _, err := certificateProvider.CreateKeyPair() if err == nil { t.Errorf("Expected an error for invalid base64 data") @@ -263,13 +263,13 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { t.Fatalf("Failed to generate test certificate: %v", err) } - signifySecret := sign.SignifySecret{ + signifySecret := sign.TLSCredentials{ CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), } imageService := sign.ImageService{} payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} - certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + certificateProvider := sign.CertificateProvider{Credentials: signifySecret} tlsConfigurator := sign.TLSConfigurator{} httpClient := sign.HTTPClient{Client: &http.Client{}} notarySigner := sign.NotarySigner{ @@ -300,13 +300,13 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { t.Fatalf("Failed to generate test certificate: %v", err) } - signifySecret := sign.SignifySecret{ + signifySecret := sign.TLSCredentials{ CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), } imageService := sign.ImageService{} payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} - certificateProvider := sign.CertificateProvider{SignifySecret: signifySecret} + certificateProvider := sign.CertificateProvider{Credentials: signifySecret} tlsConfigurator := sign.TLSConfigurator{} httpClient := sign.HTTPClient{Client: &http.Client{}} notarySigner := sign.NotarySigner{ diff --git a/pkg/sign/sign_test.go b/pkg/sign/sign_test.go index 95b919cd8d5a..6509072e763a 100644 --- a/pkg/sign/sign_test.go +++ b/pkg/sign/sign_test.go @@ -69,7 +69,7 @@ config: func TestNotaryConfig_NewSigner(t *testing.T) { // Set up a mock secret file in a valid temporary path secretPath := "/tmp/mock_secret.json" - mockSecret := SignifySecret{ + mockSecret := TLSCredentials{ CertificateData: "mockCertData", PrivateKeyData: "mockPrivateKeyData", } From 378334ff0e4429d63437aff4514716b35997a759 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 24 Sep 2024 16:12:47 +0200 Subject: [PATCH 45/54] remove tlsconfigratorinterface --- pkg/sign/notary.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 4ea74fe45c49..e6b9fe4c89a4 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -219,8 +219,8 @@ type NotarySigner struct { RetryTimeout time.Duration PayloadBuilder PayloadBuilderInterface CertificateProvider CertificateProviderInterface - TLSConfigurator TLSConfiguratorInterface HTTPClient HTTPClientInterface + TLSConfig *tls.Config } // Sign implements the Signer interface. @@ -239,15 +239,8 @@ func (ns *NotarySigner) Sign(images []string) error { return fmt.Errorf("marshal signing request: %v", err) } - // Decode certificate and key - cert, err := ns.CertificateProvider.CreateKeyPair() - if err != nil { - return fmt.Errorf("failed to load certificate and key: %v", err) - } - - // Setup TLS configuration - tlsConfig := ns.TLSConfigurator.SetupTLS(cert) - err = ns.HTTPClient.SetTLSConfig(tlsConfig) + // Use the stored TLS configuration + err = ns.HTTPClient.SetTLSConfig(ns.TLSConfig) if err != nil { return fmt.Errorf("failed to set TLS config: %v", err) } @@ -336,7 +329,16 @@ func (nc *NotaryConfig) NewSigner() (Signer, error) { certificateProvider := &CertificateProvider{ Credentials: tlsCredentials, } - tlsConfigurator := &TLSConfigurator{} + + // Create certificate and TLS configuration + cert, err := certificateProvider.CreateKeyPair() + if err != nil { + return nil, fmt.Errorf("failed to load certificate and key: %v", err) + } + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + httpClient := &HTTPClient{ Client: &http.Client{ Timeout: nc.Timeout, @@ -349,8 +351,8 @@ func (nc *NotaryConfig) NewSigner() (Signer, error) { RetryTimeout: nc.RetryTimeout, PayloadBuilder: payloadBuilder, CertificateProvider: certificateProvider, - TLSConfigurator: tlsConfigurator, HTTPClient: httpClient, + TLSConfig: tlsConfig, } return signer, nil From e703192651a212af60410314fbbfcdbae586ea99 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Tue, 24 Sep 2024 21:27:24 +0200 Subject: [PATCH 46/54] adjust tests --- pkg/sign/notary_test.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index b75c8daf53d4..38b6c4d2c75e 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -270,15 +270,22 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { imageService := sign.ImageService{} payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} certificateProvider := sign.CertificateProvider{Credentials: signifySecret} - tlsConfigurator := sign.TLSConfigurator{} httpClient := sign.HTTPClient{Client: &http.Client{}} + + // Create TLSConfig + cert, err := certificateProvider.CreateKeyPair() + if err != nil { + t.Fatalf("Failed to create key pair: %v", err) + } + tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} + notarySigner := sign.NotarySigner{ URL: "http://example.com", RetryTimeout: 1 * time.Second, PayloadBuilder: &payloadBuilder, CertificateProvider: &certificateProvider, - TLSConfigurator: &tlsConfigurator, HTTPClient: &httpClient, + TLSConfig: tlsConfig, } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -307,15 +314,22 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { imageService := sign.ImageService{} payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} certificateProvider := sign.CertificateProvider{Credentials: signifySecret} - tlsConfigurator := sign.TLSConfigurator{} httpClient := sign.HTTPClient{Client: &http.Client{}} + + // Create TLSConfig + cert, err := certificateProvider.CreateKeyPair() + if err != nil { + t.Fatalf("Failed to create key pair: %v", err) + } + tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} + notarySigner := sign.NotarySigner{ URL: "http://example.com", RetryTimeout: 1 * time.Second, PayloadBuilder: &payloadBuilder, CertificateProvider: &certificateProvider, - TLSConfigurator: &tlsConfigurator, HTTPClient: &httpClient, + TLSConfig: tlsConfig, } err = notarySigner.Sign([]string{"invalid_image"}) @@ -340,7 +354,7 @@ func TestRetryHTTPRequest_Failure(t *testing.T) { if err == nil { t.Errorf("Expected an error on failure") } - if resp.StatusCode != http.StatusInternalServerError { + if resp != nil && resp.StatusCode != http.StatusInternalServerError { t.Errorf("Expected status %d, got %d", http.StatusInternalServerError, resp.StatusCode) } } From f496d6898ce2576983d439d257b1a57abcb16aa8 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 25 Sep 2024 00:22:52 +0200 Subject: [PATCH 47/54] update to decouple external library --- pkg/sign/notary.go | 172 +++++++++++++++++++++------ pkg/sign/notary_mocks.go | 115 +++++++++++++++++- pkg/sign/notary_test.go | 250 ++++++++++++++++++++++++++++----------- 3 files changed, 424 insertions(+), 113 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index e6b9fe4c89a4..8f9132afe562 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -17,15 +17,123 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" ) -const ( - tagDelim = ":" - regRepoDelimiter = "/" -) +// ImageFetcherInterface abstracts fetching images. +type ImageFetcherInterface interface { + Fetch(ref ReferenceInterface) (ImageInterface, error) +} + +// ReferenceParserInterface abstracts parsing image references. +type ReferenceParserInterface interface { + Parse(image string) (ReferenceInterface, error) +} + +// ImageFetcher implements ImageFetcherInterface. +type ImageFetcher struct{} + +// ReferenceParser implements ReferenceParserInterface. +type ReferenceParser struct{} + +// Fetch fetches the image using the external library. +func (zf *ImageFetcher) Fetch(ref ReferenceInterface) (ImageInterface, error) { + rw, ok := ref.(*ReferenceWrapper) + if !ok { + return nil, fmt.Errorf("unexpected reference type") + } + img, err := remote.Image(rw.Ref) + if err != nil { + return nil, fmt.Errorf("failed to fetch image: %w", err) + } + return &ImageWrapper{img: img}, nil +} + +func (rp *ReferenceParser) Parse(image string) (ReferenceInterface, error) { + ref, err := name.ParseReference(image) + if err != nil { + return nil, fmt.Errorf("failed to parse image reference: %w", err) + } + return &ReferenceWrapper{Ref: ref}, nil +} + +// ReferenceInterface abstracts name.Reference +type ReferenceInterface interface { + Name() string + String() string + GetRepositoryName() string + GetTag() (string, error) +} + +type ManifestInterface interface { + GetConfigSize() int64 + GetConfigDigest() string +} + +// ImageInterface abstracts v1.Image +type ImageInterface interface { + Manifest() (ManifestInterface, error) +} + +// ReferenceWrapper wraps name.Reference +type ReferenceWrapper struct { + Ref name.Reference +} + +type ManifestWrapper struct { + manifest *v1.Manifest +} + +func (mw *ManifestWrapper) GetConfigSize() int64 { + return mw.manifest.Config.Size +} + +func (mw *ManifestWrapper) GetConfigDigest() string { + return mw.manifest.Config.Digest.String() +} + +func (rw *ReferenceWrapper) Name() string { + return rw.Ref.Name() +} + +func (rw *ReferenceWrapper) String() string { + return rw.Ref.String() +} + +func (rw *ReferenceWrapper) GetRepositoryName() string { + switch ref := rw.Ref.(type) { + case name.Tag: + return ref.Context().Name() + case name.Digest: + return ref.Context().Name() + default: + return "" + } +} + +func (rw *ReferenceWrapper) GetTag() (string, error) { + switch ref := rw.Ref.(type) { + case name.Tag: + return ref.TagStr(), nil + default: + return "", fmt.Errorf("reference is not a tag") + } +} + +// ImageWrapper wraps v1.Image +type ImageWrapper struct { + img v1.Image +} + +func (iw *ImageWrapper) Manifest() (ManifestInterface, error) { + manifest, err := iw.img.Manifest() + if err != nil { + return nil, err + } + return &ManifestWrapper{manifest: manifest}, nil +} // ImageRepositoryInterface handles image parsing and fetching. type ImageRepositoryInterface interface { - ParseReference(image string) (name.Reference, error) - GetImage(ref name.Reference) (v1.Image, error) + ParseReference(image string) (ReferenceInterface, error) + GetImage(ref ReferenceInterface) (ImageInterface, error) } // PayloadBuilderInterface constructs the signing payload. @@ -83,18 +191,17 @@ type AuthSecretConfig struct { } // ImageService implements ImageRepositoryInterface. -type ImageService struct{} +type ImageService struct { + ReferenceParser ReferenceParserInterface + ImageFetcher ImageFetcherInterface +} -func (is *ImageService) ParseReference(image string) (name.Reference, error) { - ref, err := name.ParseReference(image) - if err != nil { - return nil, fmt.Errorf("failed to parse image reference: %w", err) - } - return ref, nil +func (is *ImageService) ParseReference(image string) (ReferenceInterface, error) { + return is.ReferenceParser.Parse(image) } -func (is *ImageService) GetImage(ref name.Reference) (v1.Image, error) { - img, err := remote.Image(ref) +func (is *ImageService) GetImage(ref ReferenceInterface) (ImageInterface, error) { + img, err := is.ImageFetcher.Fetch(ref) if err != nil { return nil, fmt.Errorf("failed to fetch image: %w", err) } @@ -109,17 +216,19 @@ type PayloadBuilder struct { func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) { var gunTargets []GUNTargets for _, image := range images { - base, tag, err := parseImageNameAndTag(image) - if err != nil { - return SigningPayload{}, fmt.Errorf("parse image name and tag: %w", err) - } - // Parse reference ref, err := pb.ImageService.ParseReference(image) if err != nil { return SigningPayload{}, fmt.Errorf("ref parse: %w", err) } + // Get base (repository name) and tag from the reference + base := ref.GetRepositoryName() + tag, err := ref.GetTag() + if err != nil { + return SigningPayload{}, fmt.Errorf("failed to get tag: %w", err) + } + // Get image img, err := pb.ImageService.GetImage(ref) if err != nil { @@ -135,8 +244,8 @@ func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) // Build target target := Target{ Name: tag, - ByteSize: manifest.Config.Size, - Digest: manifest.Config.Digest.String(), + ByteSize: manifest.GetConfigSize(), + Digest: manifest.GetConfigDigest(), } // Build GUN target @@ -144,26 +253,14 @@ func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) GUN: base, Targets: []Target{target}, } - gunTargets = append(gunTargets, gunTarget) } - payload := SigningPayload{ GunTargets: gunTargets, } return payload, nil } -func parseImageNameAndTag(image string) (string, string, error) { - parts := strings.Split(image, tagDelim) - if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) { - base := strings.Join(parts[:len(parts)-1], tagDelim) - tag := parts[len(parts)-1] - return base, tag, nil - } - return "", "", fmt.Errorf("no tag provided") -} - // CertificateProvider implements CertificateProviderInterface. type CertificateProvider struct { Credentials TLSCredentials @@ -322,7 +419,12 @@ func (nc *NotaryConfig) NewSigner() (Signer, error) { } // Initialize components - imageService := &ImageService{} + referenceParser := &ReferenceParser{} + imageFetcher := &ImageFetcher{} + imageService := &ImageService{ + ReferenceParser: referenceParser, + ImageFetcher: imageFetcher, + } payloadBuilder := &PayloadBuilder{ ImageService: imageService, } diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index 7073c0e56027..ae114a46bafa 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -89,18 +89,15 @@ func (m *MockHTTPClient) SetTLSConfig(tlsConfig *tls.Config) error { // MockImage implements v1.Image interface (from github.com/google/go-containerregistry/pkg/v1) type MockImage struct { - manifest *v1.Manifest - configFile *v1.ConfigFile + manifest *v1.Manifest + configFile *v1.ConfigFile + MockManifest func() (ManifestInterface, error) } func (m *MockImage) RawManifest() ([]byte, error) { return json.Marshal(m.manifest) } -func (m *MockImage) Manifest() (*v1.Manifest, error) { - return m.manifest, nil -} - func (m *MockImage) Layers() ([]v1.Layer, error) { return nil, nil } @@ -136,3 +133,109 @@ func (m *MockImage) LayerByDigest(v1.Hash) (v1.Layer, error) { func (m *MockImage) LayerByDiffID(v1.Hash) (v1.Layer, error) { return nil, nil } + +// MockReferenceParser implements ReferenceParserInterface +type MockReferenceParser struct { + MockParse func(image string) (ReferenceInterface, error) +} + +// MockReference implements ReferenceInterface +type MockReference struct { + MockName func() string + MockString func() string + MockGetRepositoryName func() string + MockGetTag func() (string, error) +} + +func (mr *MockReference) Name() string { + if mr.MockName != nil { + return mr.MockName() + } + return "" +} + +func (mr *MockReference) String() string { + if mr.MockString != nil { + return mr.MockString() + } + return "" +} + +func (mr *MockReference) GetRepositoryName() string { + if mr.MockGetRepositoryName != nil { + return mr.MockGetRepositoryName() + } + return "" +} + +func (mr *MockReference) GetTag() (string, error) { + if mr.MockGetTag != nil { + return mr.MockGetTag() + } + return "", fmt.Errorf("MockGetTag not implemented") +} +func (mrp *MockReferenceParser) Parse(image string) (ReferenceInterface, error) { + if mrp.MockParse != nil { + return mrp.MockParse(image) + } + return nil, fmt.Errorf("MockParse not implemented") +} + +// MockImageFetcher implements ImageFetcherInterface +type MockImageFetcher struct { + MockFetch func(ref ReferenceInterface) (ImageInterface, error) +} + +func (mif *MockImageFetcher) Fetch(ref ReferenceInterface) (ImageInterface, error) { + if mif.MockFetch != nil { + return mif.MockFetch(ref) + } + return nil, fmt.Errorf("MockFetch not implemented") +} + +func (mi *MockImage) Manifest() (ManifestInterface, error) { + if mi.MockManifest != nil { + return mi.MockManifest() + } + return nil, fmt.Errorf("MockManifest not implemented") +} + +// MockManifest implements ManifestInterface +type MockManifest struct { + MockGetConfigSize func() int64 + MockGetConfigDigest func() string +} + +func (mm *MockManifest) GetConfigSize() int64 { + if mm.MockGetConfigSize != nil { + return mm.MockGetConfigSize() + } + return 0 +} + +func (mm *MockManifest) GetConfigDigest() string { + if mm.MockGetConfigDigest != nil { + return mm.MockGetConfigDigest() + } + return "" +} + +// MockImageRepository implements ImageRepositoryInterface +type MockImageRepository struct { + MockParseReference func(image string) (ReferenceInterface, error) + MockGetImage func(ref ReferenceInterface) (ImageInterface, error) +} + +func (mir *MockImageRepository) ParseReference(image string) (ReferenceInterface, error) { + if mir.MockParseReference != nil { + return mir.MockParseReference(image) + } + return nil, fmt.Errorf("MockParseReference not implemented") +} + +func (mir *MockImageRepository) GetImage(ref ReferenceInterface) (ImageInterface, error) { + if mir.MockGetImage != nil { + return mir.MockGetImage(ref) + } + return nil, fmt.Errorf("MockGetImage not implemented") +} diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 38b6c4d2c75e..404cf708cb35 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -9,10 +9,11 @@ import ( "crypto/x509/pkix" "encoding/base64" "encoding/pem" + "fmt" + "io/ioutil" "math/big" "net/http" "net/http/httptest" - "reflect" "testing" "time" @@ -65,20 +66,42 @@ func generateTestCert() (string, string, error) { // TestImageService_ParseReference_Valid checks the correct parsing of an image reference. func TestImageService_ParseReference_Valid(t *testing.T) { - imageService := sign.ImageService{} + mockRef := &sign.ReferenceWrapper{} // Możesz dostosować według potrzeb + + mockReferenceParser := &sign.MockReferenceParser{ + MockParse: func(image string) (sign.ReferenceInterface, error) { + if image == "docker.io/library/alpine:latest" { + return mockRef, nil + } + return nil, fmt.Errorf("invalid image") + }, + } + + imageService := sign.ImageService{ + ReferenceParser: mockReferenceParser, + } + ref, err := imageService.ParseReference("docker.io/library/alpine:latest") if err != nil { t.Errorf("Expected no error, got %v", err) } - if ref == nil { - t.Errorf("Expected ref to be not nil") + if ref != mockRef { + t.Errorf("Expected ref to be mockRef") } } // TestImageService_ParseReference_Invalid checks the incorrect parsing of an image reference. func TestImageService_ParseReference_Invalid(t *testing.T) { - imageService := sign.ImageService{} - // Use more invalid reference formats + mockReferenceParser := &sign.MockReferenceParser{ + MockParse: func(image string) (sign.ReferenceInterface, error) { + return nil, fmt.Errorf("invalid image reference") + }, + } + + imageService := sign.ImageService{ + ReferenceParser: mockReferenceParser, + } + invalidReferences := []string{ ":::", "invalid_image@sha256:invaliddigest", @@ -86,7 +109,6 @@ func TestImageService_ParseReference_Invalid(t *testing.T) { "@invalid", "invalid_image@sha256:", } - for _, image := range invalidReferences { _, err := imageService.ParseReference(image) if err == nil { @@ -97,29 +119,64 @@ func TestImageService_ParseReference_Invalid(t *testing.T) { // TestImageService_GetImage_Valid checks fetching a valid image. func TestImageService_GetImage_Valid(t *testing.T) { - imageService := sign.ImageService{} - ref, err := name.ParseReference("docker.io/library/alpine:latest") - if err != nil { - t.Fatalf("Failed to parse reference: %v", err) + mockRef := &sign.ReferenceWrapper{} + mockManifest := &sign.MockManifest{ + MockGetConfigSize: func() int64 { + return 1024 + }, + MockGetConfigDigest: func() string { + return "sha256:dummy-digest" + }, + } + mockImage := &sign.MockImage{ + MockManifest: func() (sign.ManifestInterface, error) { + return mockManifest, nil + }, + } + mockImageFetcher := &sign.MockImageFetcher{ + MockFetch: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { + if ref == mockRef { + return mockImage, nil + } + return nil, fmt.Errorf("image not found") + }, + } + + imageService := sign.ImageService{ + ImageFetcher: mockImageFetcher, } - img, err := imageService.GetImage(ref) + + img, err := imageService.GetImage(mockRef) if err != nil { t.Errorf("Expected no error, got %v", err) } - if img == nil || reflect.ValueOf(img).IsNil() { - t.Errorf("Expected img to be not nil") + if img != mockImage { + t.Errorf("Expected img to be mockImage") + } + manifest, err := img.Manifest() + if err != nil { + t.Errorf("Expected no error getting manifest, got %v", err) + } + if manifest != mockManifest { + t.Errorf("Expected manifest to be mockManifest") } } // TestImageService_GetImage_Invalid checks fetching an invalid image. func TestImageService_GetImage_Invalid(t *testing.T) { - imageService := sign.ImageService{} - // Use a more invalid reference format - ref, err := name.ParseReference("invalid_image") - if err != nil { - t.Fatalf("Failed to parse reference: %v", err) + mockRef := &sign.ReferenceWrapper{} + + mockImageFetcher := &sign.MockImageFetcher{ + MockFetch: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { + return nil, fmt.Errorf("failed to fetch image") + }, } - _, err = imageService.GetImage(ref) + + imageService := sign.ImageService{ + ImageFetcher: mockImageFetcher, + } + + _, err := imageService.GetImage(mockRef) if err == nil { t.Errorf("Expected an error for invalid image") } @@ -127,21 +184,58 @@ func TestImageService_GetImage_Invalid(t *testing.T) { // TestPayloadBuilder_BuildPayload_Valid checks building a payload for valid images. func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { - imageService := sign.ImageService{} - payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} + tag, err := name.NewTag("docker.io/library/alpine:latest") + if err != nil { + t.Fatalf("Failed to create name.Tag: %v", err) + } + mockRef := &sign.ReferenceWrapper{Ref: tag} + mockManifest := &sign.MockManifest{ + MockGetConfigSize: func() int64 { + return 1024 + }, + MockGetConfigDigest: func() string { + return "sha256:dummy-digest" + }, + } + mockImage := &sign.MockImage{ + MockManifest: func() (sign.ManifestInterface, error) { + return mockManifest, nil + }, + } + mockImageService := &sign.MockImageRepository{ + MockParseReference: func(image string) (sign.ReferenceInterface, error) { + return mockRef, nil + }, + MockGetImage: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { + return mockImage, nil + }, + } + + payloadBuilder := sign.PayloadBuilder{ + ImageService: mockImageService, + } + payload, err := payloadBuilder.BuildPayload([]string{"docker.io/library/alpine:latest"}) if err != nil { t.Errorf("Expected no error, got %v", err) } - if reflect.DeepEqual(payload, sign.SigningPayload{}) { - t.Errorf("Expected payload to be not zero value") + if len(payload.GunTargets) == 0 { + t.Errorf("Expected GunTargets to be populated") } } // TestPayloadBuilder_BuildPayload_Invalid checks building a payload for invalid images. func TestPayloadBuilder_BuildPayload_Invalid(t *testing.T) { - imageService := sign.ImageService{} - payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} + mockImageService := &sign.MockImageRepository{ + MockParseReference: func(image string) (sign.ReferenceInterface, error) { + return nil, fmt.Errorf("failed to parse reference") + }, + } + + payloadBuilder := sign.PayloadBuilder{ + ImageService: mockImageService, + } + _, err := payloadBuilder.BuildPayload([]string{"invalid_image"}) if err == nil { t.Errorf("Expected an error for invalid images") @@ -258,43 +352,54 @@ func TestHTTPClient_SetTLSConfig(t *testing.T) { // TestNotarySigner_Sign_Valid checks signing valid images. func TestNotarySigner_Sign_Valid(t *testing.T) { - certPEM, keyPEM, err := generateTestCert() - if err != nil { - t.Fatalf("Failed to generate test certificate: %v", err) + mockPayloadBuilder := &sign.MockPayloadBuilder{ + MockBuildPayload: func(images []string) (sign.SigningPayload, error) { + return sign.SigningPayload{ + GunTargets: []sign.GUNTargets{ + { + GUN: "docker.io/library/alpine", + Targets: []sign.Target{ + { + Name: "latest", + ByteSize: 1024, + Digest: "sha256:dummy-digest", + }, + }, + }, + }, + }, nil + }, } - signifySecret := sign.TLSCredentials{ - CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), - PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), + mockCertificateProvider := &sign.MockCertificateProvider{ + MockCreateKeyPair: func() (tls.Certificate, error) { + certPEM, keyPEM, _ := generateTestCert() + return tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + }, } - imageService := sign.ImageService{} - payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} - certificateProvider := sign.CertificateProvider{Credentials: signifySecret} - httpClient := sign.HTTPClient{Client: &http.Client{}} - // Create TLSConfig - cert, err := certificateProvider.CreateKeyPair() - if err != nil { - t.Fatalf("Failed to create key pair: %v", err) + mockHTTPClient := &sign.MockHTTPClient{ + MockDo: func(req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusAccepted, + Body: ioutil.NopCloser(bytes.NewReader([]byte{})), + }, nil + }, + MockSetTLSConfig: func(tlsConfig *tls.Config) error { + return nil + }, } - tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} notarySigner := sign.NotarySigner{ URL: "http://example.com", RetryTimeout: 1 * time.Second, - PayloadBuilder: &payloadBuilder, - CertificateProvider: &certificateProvider, - HTTPClient: &httpClient, - TLSConfig: tlsConfig, + PayloadBuilder: mockPayloadBuilder, + CertificateProvider: mockCertificateProvider, + HTTPClient: mockHTTPClient, + TLSConfig: &tls.Config{}, } - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusAccepted) - })) - defer server.Close() - - notarySigner.URL = server.URL - err = notarySigner.Sign([]string{"docker.io/library/alpine:latest"}) + err := notarySigner.Sign([]string{"docker.io/library/alpine:latest"}) if err != nil { t.Errorf("Expected no error, got %v", err) } @@ -302,37 +407,38 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { // TestNotarySigner_Sign_Invalid checks signing invalid images. func TestNotarySigner_Sign_Invalid(t *testing.T) { - certPEM, keyPEM, err := generateTestCert() - if err != nil { - t.Fatalf("Failed to generate test certificate: %v", err) + mockPayloadBuilder := &sign.MockPayloadBuilder{ + MockBuildPayload: func(images []string) (sign.SigningPayload, error) { + return sign.SigningPayload{}, fmt.Errorf("failed to build payload") + }, } - signifySecret := sign.TLSCredentials{ - CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), - PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), + mockCertificateProvider := &sign.MockCertificateProvider{ + MockCreateKeyPair: func() (tls.Certificate, error) { + certPEM, keyPEM, _ := generateTestCert() + return tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + }, } - imageService := sign.ImageService{} - payloadBuilder := sign.PayloadBuilder{ImageService: &imageService} - certificateProvider := sign.CertificateProvider{Credentials: signifySecret} - httpClient := sign.HTTPClient{Client: &http.Client{}} - // Create TLSConfig - cert, err := certificateProvider.CreateKeyPair() - if err != nil { - t.Fatalf("Failed to create key pair: %v", err) + mockHTTPClient := &sign.MockHTTPClient{ + MockDo: func(req *http.Request) (*http.Response, error) { + return nil, fmt.Errorf("request failed") + }, + MockSetTLSConfig: func(tlsConfig *tls.Config) error { + return nil + }, } - tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} notarySigner := sign.NotarySigner{ URL: "http://example.com", RetryTimeout: 1 * time.Second, - PayloadBuilder: &payloadBuilder, - CertificateProvider: &certificateProvider, - HTTPClient: &httpClient, - TLSConfig: tlsConfig, + PayloadBuilder: mockPayloadBuilder, + CertificateProvider: mockCertificateProvider, + HTTPClient: mockHTTPClient, + TLSConfig: &tls.Config{}, } - err = notarySigner.Sign([]string{"invalid_image"}) + err := notarySigner.Sign([]string{"invalid_image"}) if err == nil { t.Errorf("Expected an error for invalid images") } From c881544e519a42638e219c63bfaa112c5297d66f Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 25 Sep 2024 02:14:17 +0200 Subject: [PATCH 48/54] cleanup --- pkg/sign/notary.go | 409 +++++++++++++++++++-------------------- pkg/sign/notary_mocks.go | 142 ++------------ pkg/sign/notary_test.go | 365 +++++++++++++++++++++------------- 3 files changed, 459 insertions(+), 457 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 8f9132afe562..1e899cd25203 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -1,3 +1,4 @@ +// Package sign provides functionality for signing container images using Notary v2. package sign import ( @@ -17,86 +18,81 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" ) -// ImageFetcherInterface abstracts fetching images. -type ImageFetcherInterface interface { - Fetch(ref ReferenceInterface) (ImageInterface, error) -} - -// ReferenceParserInterface abstracts parsing image references. -type ReferenceParserInterface interface { - Parse(image string) (ReferenceInterface, error) -} - -// ImageFetcher implements ImageFetcherInterface. -type ImageFetcher struct{} - -// ReferenceParser implements ReferenceParserInterface. -type ReferenceParser struct{} - -// Fetch fetches the image using the external library. -func (zf *ImageFetcher) Fetch(ref ReferenceInterface) (ImageInterface, error) { - rw, ok := ref.(*ReferenceWrapper) - if !ok { - return nil, fmt.Errorf("unexpected reference type") - } - img, err := remote.Image(rw.Ref) - if err != nil { - return nil, fmt.Errorf("failed to fetch image: %w", err) - } - return &ImageWrapper{img: img}, nil -} - -func (rp *ReferenceParser) Parse(image string) (ReferenceInterface, error) { - ref, err := name.ParseReference(image) - if err != nil { - return nil, fmt.Errorf("failed to parse image reference: %w", err) - } - return &ReferenceWrapper{Ref: ref}, nil +// ImageRepositoryInterface defines methods for parsing image references and fetching images. +type ImageRepositoryInterface interface { + // ParseReference parses an image string into a ReferenceInterface. + ParseReference(image string) (ReferenceInterface, error) + // GetImage retrieves an image given its reference. + GetImage(ref ReferenceInterface) (ImageInterface, error) } -// ReferenceInterface abstracts name.Reference +// ReferenceInterface abstracts the functionality of name.Reference. type ReferenceInterface interface { + // Name returns the full name of the reference. Name() string + // String returns the string representation of the reference. String() string + // GetRepositoryName returns the repository name from the reference. GetRepositoryName() string + // GetTag returns the tag associated with the reference. GetTag() (string, error) } -type ManifestInterface interface { - GetConfigSize() int64 - GetConfigDigest() string -} - -// ImageInterface abstracts v1.Image +// ImageInterface abstracts the functionality of v1.Image. type ImageInterface interface { + // Manifest retrieves the manifest of the image. Manifest() (ManifestInterface, error) } -// ReferenceWrapper wraps name.Reference -type ReferenceWrapper struct { - Ref name.Reference +// ManifestInterface abstracts the functionality of v1.Manifest. +type ManifestInterface interface { + // GetConfigSize returns the size of the image config. + GetConfigSize() int64 + // GetConfigDigest returns the digest of the image config. + GetConfigDigest() string } -type ManifestWrapper struct { - manifest *v1.Manifest +// ImageService provides methods to parse image references and fetch images. +type ImageService struct{} + +// ParseReference parses the image string into a ReferenceInterface. +func (is *ImageService) ParseReference(image string) (ReferenceInterface, error) { + ref, err := name.ParseReference(image) + if err != nil { + return nil, fmt.Errorf("failed to parse image reference: %w", err) + } + return &ReferenceWrapper{Ref: ref}, nil } -func (mw *ManifestWrapper) GetConfigSize() int64 { - return mw.manifest.Config.Size +// GetImage fetches the image from the remote registry using the provided reference. +func (is *ImageService) GetImage(ref ReferenceInterface) (ImageInterface, error) { + rw, ok := ref.(*ReferenceWrapper) + if !ok { + return nil, fmt.Errorf("unexpected reference type") + } + img, err := remote.Image(rw.Ref) + if err != nil { + return nil, fmt.Errorf("failed to fetch image: %w", err) + } + return &ImageWrapper{img: img}, nil } -func (mw *ManifestWrapper) GetConfigDigest() string { - return mw.manifest.Config.Digest.String() +// ReferenceWrapper wraps a name.Reference to implement the ReferenceInterface. +type ReferenceWrapper struct { + Ref name.Reference } +// Name returns the full name of the reference. func (rw *ReferenceWrapper) Name() string { return rw.Ref.Name() } +// String returns the string representation of the reference. func (rw *ReferenceWrapper) String() string { return rw.Ref.String() } +// GetRepositoryName extracts and returns the repository name from the reference. func (rw *ReferenceWrapper) GetRepositoryName() string { switch ref := rw.Ref.(type) { case name.Tag: @@ -108,6 +104,7 @@ func (rw *ReferenceWrapper) GetRepositoryName() string { } } +// GetTag returns the tag associated with the reference. func (rw *ReferenceWrapper) GetTag() (string, error) { switch ref := rw.Ref.(type) { case name.Tag: @@ -117,11 +114,12 @@ func (rw *ReferenceWrapper) GetTag() (string, error) { } } -// ImageWrapper wraps v1.Image +// ImageWrapper wraps a v1.Image to implement the ImageInterface. type ImageWrapper struct { img v1.Image } +// Manifest retrieves the manifest of the image. func (iw *ImageWrapper) Manifest() (ManifestInterface, error) { manifest, err := iw.img.Manifest() if err != nil { @@ -130,125 +128,69 @@ func (iw *ImageWrapper) Manifest() (ManifestInterface, error) { return &ManifestWrapper{manifest: manifest}, nil } -// ImageRepositoryInterface handles image parsing and fetching. -type ImageRepositoryInterface interface { - ParseReference(image string) (ReferenceInterface, error) - GetImage(ref ReferenceInterface) (ImageInterface, error) -} - -// PayloadBuilderInterface constructs the signing payload. -type PayloadBuilderInterface interface { - BuildPayload(images []string) (SigningPayload, error) -} - -// CertificateProviderInterface manages certificate and key decoding. -type CertificateProviderInterface interface { - CreateKeyPair() (tls.Certificate, error) -} - -// TLSConfiguratorInterface sets up TLS configurations. -type TLSConfiguratorInterface interface { - SetupTLS(cert tls.Certificate) *tls.Config -} - -// HTTPClientInterface handles HTTP requests. -type HTTPClientInterface interface { - Do(req *http.Request) (*http.Response, error) - SetTLSConfig(tlsConfig *tls.Config) error -} - -type Target struct { - Name string `json:"name"` - ByteSize int64 `json:"byteSize"` - Digest string `json:"digest"` -} - -type GUNTargets struct { - GUN string `json:"gun"` - Targets []Target `json:"targets"` -} - -type SigningPayload struct { - GunTargets []GUNTargets `json:"gunTargets"` -} - -type TLSCredentials struct { - CertificateData string `json:"certData"` - PrivateKeyData string `json:"privateKeyData"` -} - -// NotaryConfig structs -type NotaryConfig struct { - Endpoint string `yaml:"endpoint" json:"endpoint"` - Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` - Timeout time.Duration `yaml:"timeout" json:"timeout"` - RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` -} - -type AuthSecretConfig struct { - Path string `yaml:"path" json:"path"` - Type string `yaml:"type" json:"type"` +// ManifestWrapper wraps a v1.Manifest to implement the ManifestInterface. +type ManifestWrapper struct { + manifest *v1.Manifest } -// ImageService implements ImageRepositoryInterface. -type ImageService struct { - ReferenceParser ReferenceParserInterface - ImageFetcher ImageFetcherInterface +// GetConfigSize returns the size of the image config. +func (mw *ManifestWrapper) GetConfigSize() int64 { + return mw.manifest.Config.Size } -func (is *ImageService) ParseReference(image string) (ReferenceInterface, error) { - return is.ReferenceParser.Parse(image) +// GetConfigDigest returns the digest of the image config. +func (mw *ManifestWrapper) GetConfigDigest() string { + return mw.manifest.Config.Digest.String() } -func (is *ImageService) GetImage(ref ReferenceInterface) (ImageInterface, error) { - img, err := is.ImageFetcher.Fetch(ref) - if err != nil { - return nil, fmt.Errorf("failed to fetch image: %w", err) - } - return img, nil +// PayloadBuilderInterface defines the method for constructing the signing payload. +type PayloadBuilderInterface interface { + // BuildPayload constructs the signing payload for the provided images. + BuildPayload(images []string) (SigningPayload, error) } -// PayloadBuilder implements PayloadBuilderInterface. +// PayloadBuilder constructs the signing payload using an ImageRepositoryInterface. type PayloadBuilder struct { ImageService ImageRepositoryInterface } +// BuildPayload builds the signing payload for the given images. func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) { var gunTargets []GUNTargets for _, image := range images { - // Parse reference + // Parse the image reference. ref, err := pb.ImageService.ParseReference(image) if err != nil { - return SigningPayload{}, fmt.Errorf("ref parse: %w", err) + return SigningPayload{}, fmt.Errorf("failed to parse image reference: %w", err) } - // Get base (repository name) and tag from the reference + // Extract repository name and tag. base := ref.GetRepositoryName() tag, err := ref.GetTag() if err != nil { - return SigningPayload{}, fmt.Errorf("failed to get tag: %w", err) + return SigningPayload{}, fmt.Errorf("failed to get tag from reference: %w", err) } - // Get image + // Fetch the image. img, err := pb.ImageService.GetImage(ref) if err != nil { - return SigningPayload{}, fmt.Errorf("get image: %w", err) + return SigningPayload{}, fmt.Errorf("failed to fetch image: %w", err) } - // Get manifest + // Retrieve the image manifest. manifest, err := img.Manifest() if err != nil { - return SigningPayload{}, fmt.Errorf("failed getting image manifest: %w", err) + return SigningPayload{}, fmt.Errorf("failed to get image manifest: %w", err) } - // Build target + // Build the target information. target := Target{ Name: tag, ByteSize: manifest.GetConfigSize(), Digest: manifest.GetConfigDigest(), } - // Build GUN target + // Build the GUN (Global Unique Name) targets. gunTarget := GUNTargets{ GUN: base, Targets: []Target{target}, @@ -261,132 +203,153 @@ func (pb *PayloadBuilder) BuildPayload(images []string) (SigningPayload, error) return payload, nil } -// CertificateProvider implements CertificateProviderInterface. -type CertificateProvider struct { +// TLSProviderInterface defines the method for obtaining TLS configuration. +type TLSProviderInterface interface { + // GetTLSConfig returns a TLS configuration based on provided credentials. + GetTLSConfig() (*tls.Config, error) +} + +// TLSProvider provides TLS configurations using the provided TLS credentials. +type TLSProvider struct { Credentials TLSCredentials } -func (cp *CertificateProvider) CreateKeyPair() (tls.Certificate, error) { - certData, err := base64.StdEncoding.DecodeString(cp.Credentials.CertificateData) +// GetTLSConfig constructs a tls.Config using the stored TLS credentials. +func (tp *TLSProvider) GetTLSConfig() (*tls.Config, error) { + certData, err := base64.StdEncoding.DecodeString(tp.Credentials.CertificateData) if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to decode certificate: %w", err) + return nil, fmt.Errorf("failed to decode certificate data: %w", err) } - keyData, err := base64.StdEncoding.DecodeString(cp.Credentials.PrivateKeyData) + keyData, err := base64.StdEncoding.DecodeString(tp.Credentials.PrivateKeyData) if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to decode private key: %w", err) + return nil, fmt.Errorf("failed to decode private key data: %w", err) } cert, err := tls.X509KeyPair(certData, keyData) if err != nil { - return tls.Certificate{}, fmt.Errorf("unable to load certificate or key: %w", err) + return nil, fmt.Errorf("unable to load certificate and key: %w", err) } - return cert, nil -} - -// TLSConfigurator implements TLSConfiguratorInterface. -type TLSConfigurator struct{} - -func (tc *TLSConfigurator) SetupTLS(cert tls.Certificate) *tls.Config { - return &tls.Config{ + tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, } + return tlsConfig, nil } -// HTTPClient implements HTTPClientInterface. +// HTTPClientInterface defines methods for making HTTP requests and setting TLS configurations. +type HTTPClientInterface interface { + // Do sends an HTTP request and returns an HTTP response. + Do(req *http.Request) (*http.Response, error) + // SetTLSConfig sets the TLS configuration for the HTTP client. + SetTLSConfig(tlsConfig *tls.Config) error +} + +// HTTPClient is a wrapper around http.Client that implements HTTPClientInterface. type HTTPClient struct { Client *http.Client } +// Do sends an HTTP request and returns an HTTP response. func (hc *HTTPClient) Do(req *http.Request) (*http.Response, error) { return hc.Client.Do(req) } +// SetTLSConfig sets the TLS configuration for the HTTP client. func (hc *HTTPClient) SetTLSConfig(tlsConfig *tls.Config) error { if hc.Client == nil { return fmt.Errorf("http.Client is nil") } - hc.Client.Transport = &http.Transport{ - TLSClientConfig: tlsConfig, + tr, ok := hc.Client.Transport.(*http.Transport) + if !ok || tr == nil { + tr = &http.Transport{} } + tr.TLSClientConfig = tlsConfig + hc.Client.Transport = tr return nil } -// NotarySigner struct +// NotarySigner is responsible for signing images using Notary v2. type NotarySigner struct { - URL string - RetryTimeout time.Duration - PayloadBuilder PayloadBuilderInterface - CertificateProvider CertificateProviderInterface - HTTPClient HTTPClientInterface - TLSConfig *tls.Config + URL string + RetryTimeout time.Duration + PayloadBuilder PayloadBuilderInterface + TLSProvider TLSProviderInterface + HTTPClient HTTPClientInterface } -// Sign implements the Signer interface. +// Sign signs the provided images by sending a signing request to the Notary server. func (ns *NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") - // Build payload + // Build the signing payload. payload, err := ns.PayloadBuilder.BuildPayload(images) if err != nil { - return fmt.Errorf("failed to build payload: %v", err) + return fmt.Errorf("failed to build payload: %w", err) } - // Marshal payload + // Marshal the payload into JSON. b, err := json.Marshal(payload) if err != nil { - return fmt.Errorf("marshal signing request: %v", err) + return fmt.Errorf("failed to marshal signing request: %w", err) + } + + // Obtain TLS configuration. + tlsConfig, err := ns.TLSProvider.GetTLSConfig() + if err != nil { + return fmt.Errorf("failed to get TLS configuration: %w", err) } - // Use the stored TLS configuration - err = ns.HTTPClient.SetTLSConfig(ns.TLSConfig) + // Set TLS configuration for the HTTP client. + err = ns.HTTPClient.SetTLSConfig(tlsConfig) if err != nil { - return fmt.Errorf("failed to set TLS config: %v", err) + return fmt.Errorf("failed to set TLS configuration: %w", err) } - // Create HTTP request + // Create an HTTP POST request with the signing payload. req, err := http.NewRequest("POST", ns.URL, bytes.NewReader(b)) if err != nil { - return fmt.Errorf("failed to create HTTP request: %v", err) + return fmt.Errorf("failed to create HTTP request: %w", err) } req.Header.Add("Content-Type", "application/json") - // Send request with retries + // Send the request with retries. resp, err := RetryHTTPRequest(ns.HTTPClient, req, 5, ns.RetryTimeout) if err != nil { - return fmt.Errorf("request failed: %v", err) + return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() + // Check if the response status code indicates success. if resp.StatusCode != http.StatusAccepted { respMsg, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to sign images: %s", string(respMsg)) } - fmt.Printf("Successfully signed images %s!\n", sImg) + fmt.Printf("Successfully signed images: %s\n", sImg) return nil } -// RetryHTTPRequest handles retry logic for HTTP requests. +// RetryHTTPRequest sends an HTTP request with retry logic in case of failures. func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int, retryInterval time.Duration) (*http.Response, error) { var lastResp *http.Response var lastErr error - // Read and store the request body + // Read and store the request body for potential retries. var bodyBytes []byte if req.Body != nil { var err error bodyBytes, err = io.ReadAll(req.Body) if err != nil { - return nil, fmt.Errorf("failed to read request body: %v", err) + return nil, fmt.Errorf("failed to read request body: %w", err) } req.Body.Close() } for retries > 0 { - // Reset the request body for each retry + // Reset the request body for each retry. if bodyBytes != nil { req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } + // Send the HTTP request. resp, err := client.Do(req) if err != nil { lastErr = err @@ -396,66 +359,102 @@ func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int lastErr = fmt.Errorf("unexpected status code: %d", resp.StatusCode) } lastResp = resp + + // Decrement the retry counter. retries-- if retries == 0 { break } + // Wait before the next retry. time.Sleep(retryInterval) } - return lastResp, fmt.Errorf("request failed after retries: %v", lastErr) + return lastResp, fmt.Errorf("request failed after retries: %w", lastErr) } -// NewSigner constructs a new NotarySigner with dependencies injected. +// NewSigner constructs a new NotarySigner with the necessary dependencies. func (nc *NotaryConfig) NewSigner() (Signer, error) { - // Read secret from the path directly + // Read the TLS credentials from the secret file. secretFileContent, err := os.ReadFile(nc.Secret.Path) if err != nil { - return nil, fmt.Errorf("failed to read secret file: %v", err) + return nil, fmt.Errorf("failed to read secret file: %w", err) } var tlsCredentials TLSCredentials err = json.Unmarshal(secretFileContent, &tlsCredentials) if err != nil { - return nil, fmt.Errorf("failed to unmarshal TLS credentials: %v", err) + return nil, fmt.Errorf("failed to unmarshal TLS credentials: %w", err) } - // Initialize components - referenceParser := &ReferenceParser{} - imageFetcher := &ImageFetcher{} - imageService := &ImageService{ - ReferenceParser: referenceParser, - ImageFetcher: imageFetcher, - } - payloadBuilder := &PayloadBuilder{ - ImageService: imageService, - } - certificateProvider := &CertificateProvider{ + // Initialize the TLS provider with the credentials. + tlsProvider := &TLSProvider{ Credentials: tlsCredentials, } - // Create certificate and TLS configuration - cert, err := certificateProvider.CreateKeyPair() + // **Validate the TLS credentials** + _, err = tlsProvider.GetTLSConfig() if err != nil { - return nil, fmt.Errorf("failed to load certificate and key: %v", err) + return nil, fmt.Errorf("invalid TLS credentials: %w", err) } - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, + + // Initialize the image service. + imageService := &ImageService{} + + // Initialize the payload builder. + payloadBuilder := &PayloadBuilder{ + ImageService: imageService, } + // Initialize the HTTP client with a timeout. httpClient := &HTTPClient{ Client: &http.Client{ Timeout: nc.Timeout, }, } - // Create NotarySigner + // Create the NotarySigner with all dependencies injected. signer := &NotarySigner{ - URL: nc.Endpoint, - RetryTimeout: nc.RetryTimeout, - PayloadBuilder: payloadBuilder, - CertificateProvider: certificateProvider, - HTTPClient: httpClient, - TLSConfig: tlsConfig, + URL: nc.Endpoint, + RetryTimeout: nc.RetryTimeout, + PayloadBuilder: payloadBuilder, + TLSProvider: tlsProvider, + HTTPClient: httpClient, } - return signer, nil } + +// Target represents an individual image target to be signed. +type Target struct { + Name string `json:"name"` + ByteSize int64 `json:"byteSize"` + Digest string `json:"digest"` +} + +// GUNTargets associates a GUN with its targets. +type GUNTargets struct { + GUN string `json:"gun"` + Targets []Target `json:"targets"` +} + +// SigningPayload represents the payload to be sent to the Notary server for signing. +type SigningPayload struct { + GunTargets []GUNTargets `json:"gunTargets"` +} + +// TLSCredentials holds the base64-encoded TLS certificate and private key data. +type TLSCredentials struct { + CertificateData string `json:"certData"` + PrivateKeyData string `json:"privateKeyData"` +} + +// NotaryConfig holds the configuration for the NotarySigner. +type NotaryConfig struct { + Endpoint string `yaml:"endpoint" json:"endpoint"` + Secret *AuthSecretConfig `yaml:"secret,omitempty" json:"secret,omitempty"` + Timeout time.Duration `yaml:"timeout" json:"timeout"` + RetryTimeout time.Duration `yaml:"retry-timeout" json:"retry-timeout"` +} + +// AuthSecretConfig specifies the path and type of the secret containing TLS credentials. +type AuthSecretConfig struct { + Path string `yaml:"path" json:"path"` + Type string `yaml:"type" json:"type"` +} diff --git a/pkg/sign/notary_mocks.go b/pkg/sign/notary_mocks.go index ae114a46bafa..fa34bab0128b 100644 --- a/pkg/sign/notary_mocks.go +++ b/pkg/sign/notary_mocks.go @@ -2,31 +2,26 @@ package sign import ( "crypto/tls" - "encoding/json" "fmt" "net/http" - - "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/types" ) -// MockImageService implements ImageRepositoryInterface -type MockImageService struct { - MockParseReference func(image string) (name.Reference, error) - MockGetImage func(ref name.Reference) (v1.Image, error) +// MockImageRepository implements ImageRepositoryInterface +type MockImageRepository struct { + MockParseReference func(image string) (ReferenceInterface, error) + MockGetImage func(ref ReferenceInterface) (ImageInterface, error) } -func (m *MockImageService) ParseReference(image string) (name.Reference, error) { - if m.MockParseReference != nil { - return m.MockParseReference(image) +func (mir *MockImageRepository) ParseReference(image string) (ReferenceInterface, error) { + if mir.MockParseReference != nil { + return mir.MockParseReference(image) } return nil, fmt.Errorf("MockParseReference not implemented") } -func (m *MockImageService) GetImage(ref name.Reference) (v1.Image, error) { - if m.MockGetImage != nil { - return m.MockGetImage(ref) +func (mir *MockImageRepository) GetImage(ref ReferenceInterface) (ImageInterface, error) { + if mir.MockGetImage != nil { + return mir.MockGetImage(ref) } return nil, fmt.Errorf("MockGetImage not implemented") } @@ -43,28 +38,16 @@ func (m *MockPayloadBuilder) BuildPayload(images []string) (SigningPayload, erro return SigningPayload{}, fmt.Errorf("MockBuildPayload not implemented") } -// MockCertificateProvider implements CertificateProviderInterface -type MockCertificateProvider struct { - MockCreateKeyPair func() (tls.Certificate, error) +// MockTLSProvider implements TLSProviderInterface +type MockTLSProvider struct { + MockGetTLSConfig func() (*tls.Config, error) } -func (m *MockCertificateProvider) CreateKeyPair() (tls.Certificate, error) { - if m.MockCreateKeyPair != nil { - return m.MockCreateKeyPair() +func (mtp *MockTLSProvider) GetTLSConfig() (*tls.Config, error) { + if mtp.MockGetTLSConfig != nil { + return mtp.MockGetTLSConfig() } - return tls.Certificate{}, fmt.Errorf("MockCreateKeyPair not implemented") -} - -// MockTLSConfigurator implements TLSConfiguratorInterface -type MockTLSConfigurator struct { - MockSetupTLS func(cert tls.Certificate) *tls.Config -} - -func (m *MockTLSConfigurator) SetupTLS(cert tls.Certificate) *tls.Config { - if m.MockSetupTLS != nil { - return m.MockSetupTLS(cert) - } - return &tls.Config{} + return nil, fmt.Errorf("MockGetTLSConfig not implemented") } // MockHTTPClient implements HTTPClientInterface @@ -87,58 +70,6 @@ func (m *MockHTTPClient) SetTLSConfig(tlsConfig *tls.Config) error { return fmt.Errorf("MockSetTLSConfig not implemented") } -// MockImage implements v1.Image interface (from github.com/google/go-containerregistry/pkg/v1) -type MockImage struct { - manifest *v1.Manifest - configFile *v1.ConfigFile - MockManifest func() (ManifestInterface, error) -} - -func (m *MockImage) RawManifest() ([]byte, error) { - return json.Marshal(m.manifest) -} - -func (m *MockImage) Layers() ([]v1.Layer, error) { - return nil, nil -} - -func (m *MockImage) MediaType() (types.MediaType, error) { - return m.manifest.MediaType, nil -} - -func (m *MockImage) Size() (int64, error) { - return 0, nil -} - -func (m *MockImage) ConfigName() (v1.Hash, error) { - return v1.Hash{}, nil -} - -func (m *MockImage) ConfigFile() (*v1.ConfigFile, error) { - return m.configFile, nil -} - -func (m *MockImage) RawConfigFile() ([]byte, error) { - return json.Marshal(m.configFile) -} - -func (m *MockImage) Digest() (v1.Hash, error) { - return v1.Hash{}, nil -} - -func (m *MockImage) LayerByDigest(v1.Hash) (v1.Layer, error) { - return nil, nil -} - -func (m *MockImage) LayerByDiffID(v1.Hash) (v1.Layer, error) { - return nil, nil -} - -// MockReferenceParser implements ReferenceParserInterface -type MockReferenceParser struct { - MockParse func(image string) (ReferenceInterface, error) -} - // MockReference implements ReferenceInterface type MockReference struct { MockName func() string @@ -174,23 +105,10 @@ func (mr *MockReference) GetTag() (string, error) { } return "", fmt.Errorf("MockGetTag not implemented") } -func (mrp *MockReferenceParser) Parse(image string) (ReferenceInterface, error) { - if mrp.MockParse != nil { - return mrp.MockParse(image) - } - return nil, fmt.Errorf("MockParse not implemented") -} -// MockImageFetcher implements ImageFetcherInterface -type MockImageFetcher struct { - MockFetch func(ref ReferenceInterface) (ImageInterface, error) -} - -func (mif *MockImageFetcher) Fetch(ref ReferenceInterface) (ImageInterface, error) { - if mif.MockFetch != nil { - return mif.MockFetch(ref) - } - return nil, fmt.Errorf("MockFetch not implemented") +// MockImage implements ImageInterface +type MockImage struct { + MockManifest func() (ManifestInterface, error) } func (mi *MockImage) Manifest() (ManifestInterface, error) { @@ -219,23 +137,3 @@ func (mm *MockManifest) GetConfigDigest() string { } return "" } - -// MockImageRepository implements ImageRepositoryInterface -type MockImageRepository struct { - MockParseReference func(image string) (ReferenceInterface, error) - MockGetImage func(ref ReferenceInterface) (ImageInterface, error) -} - -func (mir *MockImageRepository) ParseReference(image string) (ReferenceInterface, error) { - if mir.MockParseReference != nil { - return mir.MockParseReference(image) - } - return nil, fmt.Errorf("MockParseReference not implemented") -} - -func (mir *MockImageRepository) GetImage(ref ReferenceInterface) (ImageInterface, error) { - if mir.MockGetImage != nil { - return mir.MockGetImage(ref) - } - return nil, fmt.Errorf("MockGetImage not implemented") -} diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 404cf708cb35..37098948c77b 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -8,16 +8,17 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/base64" + "encoding/json" "encoding/pem" "fmt" "io/ioutil" "math/big" "net/http" "net/http/httptest" + "os" "testing" "time" - "github.com/google/go-containerregistry/pkg/name" "github.com/kyma-project/test-infra/pkg/sign" ) @@ -36,10 +37,12 @@ func generateTestCert() (string, string, error) { Subject: pkix.Name{ Organization: []string{"Test Organization"}, }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(24 * time.Hour), // Certificate valid for 24 hours - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), // Certificate valid for 24 hours + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + }, BasicConstraintsValid: true, } @@ -66,41 +69,21 @@ func generateTestCert() (string, string, error) { // TestImageService_ParseReference_Valid checks the correct parsing of an image reference. func TestImageService_ParseReference_Valid(t *testing.T) { - mockRef := &sign.ReferenceWrapper{} // Możesz dostosować według potrzeb - - mockReferenceParser := &sign.MockReferenceParser{ - MockParse: func(image string) (sign.ReferenceInterface, error) { - if image == "docker.io/library/alpine:latest" { - return mockRef, nil - } - return nil, fmt.Errorf("invalid image") - }, - } - - imageService := sign.ImageService{ - ReferenceParser: mockReferenceParser, - } + imageService := sign.ImageService{} + // Use a valid image reference ref, err := imageService.ParseReference("docker.io/library/alpine:latest") if err != nil { t.Errorf("Expected no error, got %v", err) } - if ref != mockRef { - t.Errorf("Expected ref to be mockRef") + if ref == nil { + t.Errorf("Expected ref to be non-nil") } } // TestImageService_ParseReference_Invalid checks the incorrect parsing of an image reference. func TestImageService_ParseReference_Invalid(t *testing.T) { - mockReferenceParser := &sign.MockReferenceParser{ - MockParse: func(image string) (sign.ReferenceInterface, error) { - return nil, fmt.Errorf("invalid image reference") - }, - } - - imageService := sign.ImageService{ - ReferenceParser: mockReferenceParser, - } + imageService := sign.ImageService{} invalidReferences := []string{ ":::", @@ -119,64 +102,80 @@ func TestImageService_ParseReference_Invalid(t *testing.T) { // TestImageService_GetImage_Valid checks fetching a valid image. func TestImageService_GetImage_Valid(t *testing.T) { - mockRef := &sign.ReferenceWrapper{} - mockManifest := &sign.MockManifest{ - MockGetConfigSize: func() int64 { - return 1024 - }, - MockGetConfigDigest: func() string { - return "sha256:dummy-digest" - }, - } - mockImage := &sign.MockImage{ - MockManifest: func() (sign.ManifestInterface, error) { - return mockManifest, nil + // Mock dependencies + mockImageRepository := &sign.MockImageRepository{ + MockParseReference: func(image string) (sign.ReferenceInterface, error) { + return &sign.MockReference{ + MockName: func() string { + return image + }, + MockGetRepositoryName: func() string { + return "docker.io/library/alpine" + }, + MockGetTag: func() (string, error) { + return "latest", nil + }, + }, nil }, - } - mockImageFetcher := &sign.MockImageFetcher{ - MockFetch: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { - if ref == mockRef { - return mockImage, nil - } - return nil, fmt.Errorf("image not found") + MockGetImage: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { + return &sign.MockImage{ + MockManifest: func() (sign.ManifestInterface, error) { + return &sign.MockManifest{ + MockGetConfigSize: func() int64 { + return 1024 + }, + MockGetConfigDigest: func() string { + return "sha256:dummy-digest" + }, + }, nil + }, + }, nil }, } - imageService := sign.ImageService{ - ImageFetcher: mockImageFetcher, - } + // Use mockImageRepository as imageService + imageService := mockImageRepository - img, err := imageService.GetImage(mockRef) + ref, err := imageService.ParseReference("docker.io/library/alpine:latest") if err != nil { t.Errorf("Expected no error, got %v", err) } - if img != mockImage { - t.Errorf("Expected img to be mockImage") + + img, err := imageService.GetImage(ref) + if err != nil { + t.Errorf("Expected no error, got %v", err) } manifest, err := img.Manifest() if err != nil { t.Errorf("Expected no error getting manifest, got %v", err) } - if manifest != mockManifest { - t.Errorf("Expected manifest to be mockManifest") + if manifest.GetConfigSize() != 1024 { + t.Errorf("Expected config size to be 1024, got %d", manifest.GetConfigSize()) + } + if manifest.GetConfigDigest() != "sha256:dummy-digest" { + t.Errorf("Expected config digest to be 'sha256:dummy-digest', got '%s'", manifest.GetConfigDigest()) } } // TestImageService_GetImage_Invalid checks fetching an invalid image. func TestImageService_GetImage_Invalid(t *testing.T) { - mockRef := &sign.ReferenceWrapper{} - - mockImageFetcher := &sign.MockImageFetcher{ - MockFetch: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { + mockImageRepository := &sign.MockImageRepository{ + MockParseReference: func(image string) (sign.ReferenceInterface, error) { + return &sign.MockReference{}, nil + }, + MockGetImage: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { return nil, fmt.Errorf("failed to fetch image") }, } - imageService := sign.ImageService{ - ImageFetcher: mockImageFetcher, + imageService := mockImageRepository + + ref, err := imageService.ParseReference("invalid_image") + if err != nil { + t.Errorf("Expected no error parsing reference, got %v", err) } - _, err := imageService.GetImage(mockRef) + _, err = imageService.GetImage(ref) if err == nil { t.Errorf("Expected an error for invalid image") } @@ -184,11 +183,15 @@ func TestImageService_GetImage_Invalid(t *testing.T) { // TestPayloadBuilder_BuildPayload_Valid checks building a payload for valid images. func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { - tag, err := name.NewTag("docker.io/library/alpine:latest") - if err != nil { - t.Fatalf("Failed to create name.Tag: %v", err) + mockRef := &sign.MockReference{ + MockGetRepositoryName: func() string { + return "docker.io/library/alpine" + }, + MockGetTag: func() (string, error) { + return "latest", nil + }, } - mockRef := &sign.ReferenceWrapper{Ref: tag} + mockManifest := &sign.MockManifest{ MockGetConfigSize: func() int64 { return 1024 @@ -197,12 +200,14 @@ func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { return "sha256:dummy-digest" }, } + mockImage := &sign.MockImage{ MockManifest: func() (sign.ManifestInterface, error) { return mockManifest, nil }, } - mockImageService := &sign.MockImageRepository{ + + mockImageRepository := &sign.MockImageRepository{ MockParseReference: func(image string) (sign.ReferenceInterface, error) { return mockRef, nil }, @@ -212,7 +217,7 @@ func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { } payloadBuilder := sign.PayloadBuilder{ - ImageService: mockImageService, + ImageService: mockImageRepository, } payload, err := payloadBuilder.BuildPayload([]string{"docker.io/library/alpine:latest"}) @@ -226,14 +231,14 @@ func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { // TestPayloadBuilder_BuildPayload_Invalid checks building a payload for invalid images. func TestPayloadBuilder_BuildPayload_Invalid(t *testing.T) { - mockImageService := &sign.MockImageRepository{ + mockImageRepository := &sign.MockImageRepository{ MockParseReference: func(image string) (sign.ReferenceInterface, error) { return nil, fmt.Errorf("failed to parse reference") }, } payloadBuilder := sign.PayloadBuilder{ - ImageService: mockImageService, + ImageService: mockImageRepository, } _, err := payloadBuilder.BuildPayload([]string{"invalid_image"}) @@ -242,62 +247,42 @@ func TestPayloadBuilder_BuildPayload_Invalid(t *testing.T) { } } -// TestCertificateProvider_CreateKeyPair_Valid checks creating a key pair with valid base64 data. -func TestCertificateProvider_CreateKeyPair_Valid(t *testing.T) { +// TestTLSProvider_GetTLSConfig_Valid checks getting TLS configuration with valid credentials. +func TestTLSProvider_GetTLSConfig_Valid(t *testing.T) { certPEM, keyPEM, err := generateTestCert() if err != nil { t.Fatalf("Failed to generate test certificate: %v", err) } - signifySecret := sign.TLSCredentials{ + tlsCredentials := sign.TLSCredentials{ CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), } - certificateProvider := sign.CertificateProvider{Credentials: signifySecret} - cert, err := certificateProvider.CreateKeyPair() + + tlsProvider := sign.TLSProvider{Credentials: tlsCredentials} + tlsConfig, err := tlsProvider.GetTLSConfig() if err != nil { t.Errorf("Expected no error, got %v", err) } - // Check if cert is not empty - if len(cert.Certificate) == 0 { - t.Errorf("Expected cert.Certificate to have data") + if tlsConfig == nil { + t.Errorf("Expected tlsConfig to be not nil") + } + if len(tlsConfig.Certificates) != 1 { + t.Errorf("Expected Certificates length to be 1, got %d", len(tlsConfig.Certificates)) } - // Optionally: Check the correctness of the certificate - // You can use libraries like x509 for further verification } -// TestCertificateProvider_CreateKeyPair_Invalid checks creating a key pair with invalid base64 data. -func TestCertificateProvider_CreateKeyPair_Invalid(t *testing.T) { - signifySecret := sign.TLSCredentials{ +// TestTLSProvider_GetTLSConfig_Invalid checks getting TLS configuration with invalid credentials. +func TestTLSProvider_GetTLSConfig_Invalid(t *testing.T) { + tlsCredentials := sign.TLSCredentials{ CertificateData: "invalid-base64", PrivateKeyData: base64.StdEncoding.EncodeToString([]byte("private-key-data")), } - certificateProvider := sign.CertificateProvider{Credentials: signifySecret} - _, err := certificateProvider.CreateKeyPair() - if err == nil { - t.Errorf("Expected an error for invalid base64 data") - } -} - -// TestTLSConfigurator_SetupTLS checks TLS configuration. -func TestTLSConfigurator_SetupTLS(t *testing.T) { - certPEM, keyPEM, err := generateTestCert() - if err != nil { - t.Fatalf("Failed to generate test certificate: %v", err) - } - certificate, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) - if err != nil { - t.Fatalf("Failed to load X509 key pair: %v", err) - } - - tlsConfigurator := sign.TLSConfigurator{} - tlsConfig := tlsConfigurator.SetupTLS(certificate) - if tlsConfig == nil { - t.Errorf("Expected tlsConfig to be not nil") - } - if len(tlsConfig.Certificates) != 1 { - t.Errorf("Expected Certificates length to be 1, got %d", len(tlsConfig.Certificates)) + tlsProvider := sign.TLSProvider{Credentials: tlsCredentials} + _, err := tlsProvider.GetTLSConfig() + if err == nil { + t.Errorf("Expected an error for invalid credentials") } } @@ -328,18 +313,18 @@ func TestHTTPClient_SetTLSConfig(t *testing.T) { if err != nil { t.Fatalf("Failed to generate test certificate: %v", err) } - certificate, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) if err != nil { t.Fatalf("Failed to load X509 key pair: %v", err) } - httpClient := sign.HTTPClient{Client: &http.Client{}} tlsConfig := &tls.Config{Certificates: []tls.Certificate{certificate}} + err = httpClient.SetTLSConfig(tlsConfig) if err != nil { t.Errorf("Expected no error, got %v", err) } + // Check if Transport is set and is of type *http.Transport transport, ok := httpClient.Client.Transport.(*http.Transport) if !ok { @@ -371,10 +356,14 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { }, } - mockCertificateProvider := &sign.MockCertificateProvider{ - MockCreateKeyPair: func() (tls.Certificate, error) { + mockTLSProvider := &sign.MockTLSProvider{ + MockGetTLSConfig: func() (*tls.Config, error) { certPEM, keyPEM, _ := generateTestCert() - return tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + if err != nil { + return nil, err + } + return &tls.Config{Certificates: []tls.Certificate{cert}}, nil }, } @@ -391,12 +380,11 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { } notarySigner := sign.NotarySigner{ - URL: "http://example.com", - RetryTimeout: 1 * time.Second, - PayloadBuilder: mockPayloadBuilder, - CertificateProvider: mockCertificateProvider, - HTTPClient: mockHTTPClient, - TLSConfig: &tls.Config{}, + URL: "http://example.com", + RetryTimeout: 1 * time.Second, + PayloadBuilder: mockPayloadBuilder, + TLSProvider: mockTLSProvider, + HTTPClient: mockHTTPClient, } err := notarySigner.Sign([]string{"docker.io/library/alpine:latest"}) @@ -413,10 +401,14 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { }, } - mockCertificateProvider := &sign.MockCertificateProvider{ - MockCreateKeyPair: func() (tls.Certificate, error) { + mockTLSProvider := &sign.MockTLSProvider{ + MockGetTLSConfig: func() (*tls.Config, error) { certPEM, keyPEM, _ := generateTestCert() - return tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) + if err != nil { + return nil, err + } + return &tls.Config{Certificates: []tls.Certificate{cert}}, nil }, } @@ -430,12 +422,11 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { } notarySigner := sign.NotarySigner{ - URL: "http://example.com", - RetryTimeout: 1 * time.Second, - PayloadBuilder: mockPayloadBuilder, - CertificateProvider: mockCertificateProvider, - HTTPClient: mockHTTPClient, - TLSConfig: &tls.Config{}, + URL: "http://example.com", + RetryTimeout: 1 * time.Second, + PayloadBuilder: mockPayloadBuilder, + TLSProvider: mockTLSProvider, + HTTPClient: mockHTTPClient, } err := notarySigner.Sign([]string{"invalid_image"}) @@ -491,3 +482,117 @@ func TestRetryHTTPRequest_SuccessAfterRetries(t *testing.T) { t.Errorf("Expected status %d, got %d", http.StatusAccepted, resp.StatusCode) } } + +func TestNotaryConfig_NewSigner_Success(t *testing.T) { + // Prepare a temporary file with valid TLS data + certPEM, keyPEM, err := generateTestCert() + if err != nil { + t.Fatalf("Failed to generate test certificate: %v", err) + } + + tlsCredentials := sign.TLSCredentials{ + CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), + } + + // Serialize TLS data to JSON format + secretContent, err := json.Marshal(tlsCredentials) + if err != nil { + t.Fatalf("Failed to marshal TLS credentials: %v", err) + } + + // Create a temporary file for the secret + tempFile, err := ioutil.TempFile("", "secret-*.json") + if err != nil { + t.Fatalf("Failed to create temporary file: %v", err) + } + defer os.Remove(tempFile.Name()) + + // Write TLS data to the file + if _, err := tempFile.Write(secretContent); err != nil { + t.Fatalf("Failed to write to temporary file: %v", err) + } + tempFile.Close() // Close the file to ensure the data is written + + // Create NotaryConfig with the path to the secret file + notaryConfig := &sign.NotaryConfig{ + Endpoint: "http://example.com", + Secret: &sign.AuthSecretConfig{Path: tempFile.Name()}, + Timeout: 10 * time.Second, + RetryTimeout: 1 * time.Second, + } + + // Call the NewSigner method + signer, err := notaryConfig.NewSigner() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if signer == nil { + t.Errorf("Expected signer to be not nil") + } + + // Optionally, check if signer is of type *NotarySigner + notarySigner, ok := signer.(*sign.NotarySigner) + if !ok { + t.Errorf("Expected signer to be of type *NotarySigner") + } + + // Additional checks for NotarySigner fields + if notarySigner.URL != notaryConfig.Endpoint { + t.Errorf("Expected NotarySigner URL to be '%s', got '%s'", notaryConfig.Endpoint, notarySigner.URL) + } +} + +func TestNotaryConfig_NewSigner_InvalidSecretFile(t *testing.T) { + // Create NotaryConfig with a non-existent secret file + notaryConfig := &sign.NotaryConfig{ + Endpoint: "http://example.com", + Secret: &sign.AuthSecretConfig{Path: "non-existent-file.json"}, + Timeout: 10 * time.Second, + RetryTimeout: 1 * time.Second, + } + + // Call the NewSigner method + signer, err := notaryConfig.NewSigner() + if err == nil { + t.Errorf("Expected error due to invalid secret file, got nil") + } + if signer != nil { + t.Errorf("Expected signer to be nil due to error") + } +} + +func TestNotaryConfig_NewSigner_InvalidTLSCredentials(t *testing.T) { + // Prepare invalid TLS data + secretContent := []byte(`{"certData": "invalid", "privateKeyData": "also-invalid"}`) + + // Create a temporary file for the secret + tempFile, err := ioutil.TempFile("", "secret-invalid-*.json") + if err != nil { + t.Fatalf("Failed to create temporary file: %v", err) + } + defer os.Remove(tempFile.Name()) + + // Write invalid TLS data to the file + if _, err := tempFile.Write(secretContent); err != nil { + t.Fatalf("Failed to write to temporary file: %v", err) + } + tempFile.Close() + + // Create NotaryConfig with the path to the secret file + notaryConfig := &sign.NotaryConfig{ + Endpoint: "http://example.com", + Secret: &sign.AuthSecretConfig{Path: tempFile.Name()}, + Timeout: 10 * time.Second, + RetryTimeout: 1 * time.Second, + } + + // Call the NewSigner method + signer, err := notaryConfig.NewSigner() + if err == nil { + t.Errorf("Expected error due to invalid TLS credentials, got nil") + } + if signer != nil { + t.Errorf("Expected signer to be nil due to error") + } +} From 19f1c78386b3405ea30796b9cc703bb8800e6e1a Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 25 Sep 2024 02:26:49 +0200 Subject: [PATCH 49/54] remove redundant status check --- pkg/sign/notary.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 1e899cd25203..2a0d4bc48c12 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -317,12 +317,6 @@ func (ns *NotarySigner) Sign(images []string) error { } defer resp.Body.Close() - // Check if the response status code indicates success. - if resp.StatusCode != http.StatusAccepted { - respMsg, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to sign images: %s", string(respMsg)) - } - fmt.Printf("Successfully signed images: %s\n", sImg) return nil } From 441012b60773405983b29b31f7c1eefd40c72c8b Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 25 Sep 2024 02:38:26 +0200 Subject: [PATCH 50/54] remove last variable --- pkg/sign/notary.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 2a0d4bc48c12..e1faf55649ac 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -323,13 +323,12 @@ func (ns *NotarySigner) Sign(images []string) error { // RetryHTTPRequest sends an HTTP request with retry logic in case of failures. func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int, retryInterval time.Duration) (*http.Response, error) { - var lastResp *http.Response - var lastErr error + var resp *http.Response + var err error // Read and store the request body for potential retries. var bodyBytes []byte if req.Body != nil { - var err error bodyBytes, err = io.ReadAll(req.Body) if err != nil { return nil, fmt.Errorf("failed to read request body: %w", err) @@ -344,25 +343,30 @@ func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int } // Send the HTTP request. - resp, err := client.Do(req) + resp, err = client.Do(req) if err != nil { - lastErr = err + // err is already set } else if resp.StatusCode == http.StatusAccepted { return resp, nil } else { - lastErr = fmt.Errorf("unexpected status code: %d", resp.StatusCode) + // Read and discard the response body to free resources + io.Copy(io.Discard, resp.Body) + resp.Body.Close() + err = fmt.Errorf("unexpected status code: %d", resp.StatusCode) + resp = nil // Discard the unsuccessful response } - lastResp = resp // Decrement the retry counter. retries-- if retries == 0 { break } + // Wait before the next retry. time.Sleep(retryInterval) } - return lastResp, fmt.Errorf("request failed after retries: %w", lastErr) + + return nil, fmt.Errorf("request failed after retries: %w", err) } // NewSigner constructs a new NotarySigner with the necessary dependencies. From 74f29b4f33e78961f89d18ff911cd83eee713295 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 25 Sep 2024 03:04:48 +0200 Subject: [PATCH 51/54] use buffered tls config --- pkg/sign/notary.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index e1faf55649ac..9915ddef817a 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -212,10 +212,14 @@ type TLSProviderInterface interface { // TLSProvider provides TLS configurations using the provided TLS credentials. type TLSProvider struct { Credentials TLSCredentials + tlsConfig *tls.Config } // GetTLSConfig constructs a tls.Config using the stored TLS credentials. func (tp *TLSProvider) GetTLSConfig() (*tls.Config, error) { + if tp.tlsConfig != nil { + return tp.tlsConfig, nil + } certData, err := base64.StdEncoding.DecodeString(tp.Credentials.CertificateData) if err != nil { return nil, fmt.Errorf("failed to decode certificate data: %w", err) @@ -228,10 +232,11 @@ func (tp *TLSProvider) GetTLSConfig() (*tls.Config, error) { if err != nil { return nil, fmt.Errorf("unable to load certificate and key: %w", err) } - tlsConfig := &tls.Config{ + tp.tlsConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, } - return tlsConfig, nil + + return tp.tlsConfig, nil } // HTTPClientInterface defines methods for making HTTP requests and setting TLS configurations. @@ -387,7 +392,7 @@ func (nc *NotaryConfig) NewSigner() (Signer, error) { Credentials: tlsCredentials, } - // **Validate the TLS credentials** + // Load the TLS credentials _, err = tlsProvider.GetTLSConfig() if err != nil { return nil, fmt.Errorf("invalid TLS credentials: %w", err) From f7ca8462eb95f96e7b045ae3ec2f76144daf0ed7 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 25 Sep 2024 08:57:31 +0200 Subject: [PATCH 52/54] fix retryhttp --- pkg/sign/notary.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index 9915ddef817a..c42496327533 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -350,25 +350,27 @@ func RetryHTTPRequest(client HTTPClientInterface, req *http.Request, retries int // Send the HTTP request. resp, err = client.Do(req) if err != nil { - // err is already set - } else if resp.StatusCode == http.StatusAccepted { + // Continue to the next retry attempt + time.Sleep(retryInterval) + retries-- + continue + } + + if resp.StatusCode == http.StatusAccepted { return resp, nil - } else { - // Read and discard the response body to free resources - io.Copy(io.Discard, resp.Body) - resp.Body.Close() - err = fmt.Errorf("unexpected status code: %d", resp.StatusCode) - resp = nil // Discard the unsuccessful response } - // Decrement the retry counter. + // Read and discard the response body to free resources + io.Copy(io.Discard, resp.Body) + resp.Body.Close() + err = fmt.Errorf("unexpected status code: %d", resp.StatusCode) + resp = nil // Discard the unsuccessful response + + // Decrement the retry counter and wait before the next retry. retries-- - if retries == 0 { - break + if retries > 0 { + time.Sleep(retryInterval) } - - // Wait before the next retry. - time.Sleep(retryInterval) } return nil, fmt.Errorf("request failed after retries: %w", err) From 3a40a15990f44773fecbadfe9ab895ece89e9572 Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Wed, 25 Sep 2024 09:06:15 +0200 Subject: [PATCH 53/54] fix test certificate --- pkg/sign/sign_test.go | 69 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/pkg/sign/sign_test.go b/pkg/sign/sign_test.go index 6509072e763a..468c2a2abad0 100644 --- a/pkg/sign/sign_test.go +++ b/pkg/sign/sign_test.go @@ -1,7 +1,15 @@ package sign import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" "encoding/json" + "encoding/pem" + "math/big" "os" "testing" "time" @@ -67,16 +75,22 @@ config: } func TestNotaryConfig_NewSigner(t *testing.T) { - // Set up a mock secret file in a valid temporary path - secretPath := "/tmp/mock_secret.json" + certPEM, keyPEM, err := generateTestCert() + if err != nil { + t.Fatalf("Failed to generate test certificate: %v", err) + } + mockSecret := TLSCredentials{ - CertificateData: "mockCertData", - PrivateKeyData: "mockPrivateKeyData", + CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), } + + // Set up a mock secret file in a valid temporary path + secretPath := "/tmp/mock_secret.json" secretContent, _ := json.Marshal(mockSecret) // Write the mock secret to the file - err := os.WriteFile(secretPath, secretContent, 0644) + err = os.WriteFile(secretPath, secretContent, 0644) if err != nil { t.Fatalf("Failed to write mock secret file: %v", err) } @@ -99,3 +113,48 @@ func TestNotaryConfig_NewSigner(t *testing.T) { t.Errorf("expected a valid signer, but got nil") } } + +// generateTestCert generates a self-signed certificate and private key. +// Returns the certificate and key in PEM format. +func generateTestCert() (string, string, error) { + // Generate RSA key + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return "", "", err + } + + // Create certificate template + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Test Organization"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), // Certificate valid for 24 hours + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + }, + BasicConstraintsValid: true, + } + + // Self-sign the certificate + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return "", "", err + } + + // Encode certificate to PEM + certPEM := new(bytes.Buffer) + if err := pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return "", "", err + } + + // Encode private key to PEM + keyPEM := new(bytes.Buffer) + if err := pem.Encode(keyPEM, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil { + return "", "", err + } + + return certPEM.String(), keyPEM.String(), nil +} From 1047003a0ac4d4cccea2313a8111cc9e8811892e Mon Sep 17 00:00:00 2001 From: Patryk Dobrowolski Date: Thu, 26 Sep 2024 22:21:59 +0200 Subject: [PATCH 54/54] Make NotarySigner fields private --- pkg/sign/notary.go | 32 ++++----- pkg/sign/notary_test.go | 140 ++++++++++++++++++++-------------------- pkg/sign/sign_test.go | 52 --------------- 3 files changed, 85 insertions(+), 139 deletions(-) diff --git a/pkg/sign/notary.go b/pkg/sign/notary.go index c42496327533..a1095486bc77 100644 --- a/pkg/sign/notary.go +++ b/pkg/sign/notary.go @@ -271,13 +271,13 @@ func (hc *HTTPClient) SetTLSConfig(tlsConfig *tls.Config) error { return nil } -// NotarySigner is responsible for signing images using Notary v2. +// NotarySigner is responsible for signing images type NotarySigner struct { - URL string - RetryTimeout time.Duration - PayloadBuilder PayloadBuilderInterface - TLSProvider TLSProviderInterface - HTTPClient HTTPClientInterface + url string + retryTimeout time.Duration + payloadBuilder PayloadBuilderInterface + tlsProvider TLSProviderInterface + httpClient HTTPClientInterface } // Sign signs the provided images by sending a signing request to the Notary server. @@ -285,7 +285,7 @@ func (ns *NotarySigner) Sign(images []string) error { sImg := strings.Join(images, ", ") // Build the signing payload. - payload, err := ns.PayloadBuilder.BuildPayload(images) + payload, err := ns.payloadBuilder.BuildPayload(images) if err != nil { return fmt.Errorf("failed to build payload: %w", err) } @@ -297,26 +297,26 @@ func (ns *NotarySigner) Sign(images []string) error { } // Obtain TLS configuration. - tlsConfig, err := ns.TLSProvider.GetTLSConfig() + tlsConfig, err := ns.tlsProvider.GetTLSConfig() if err != nil { return fmt.Errorf("failed to get TLS configuration: %w", err) } // Set TLS configuration for the HTTP client. - err = ns.HTTPClient.SetTLSConfig(tlsConfig) + err = ns.httpClient.SetTLSConfig(tlsConfig) if err != nil { return fmt.Errorf("failed to set TLS configuration: %w", err) } // Create an HTTP POST request with the signing payload. - req, err := http.NewRequest("POST", ns.URL, bytes.NewReader(b)) + req, err := http.NewRequest("POST", ns.url, bytes.NewReader(b)) if err != nil { return fmt.Errorf("failed to create HTTP request: %w", err) } req.Header.Add("Content-Type", "application/json") // Send the request with retries. - resp, err := RetryHTTPRequest(ns.HTTPClient, req, 5, ns.RetryTimeout) + resp, err := RetryHTTPRequest(ns.httpClient, req, 5, ns.retryTimeout) if err != nil { return fmt.Errorf("request failed: %w", err) } @@ -417,11 +417,11 @@ func (nc *NotaryConfig) NewSigner() (Signer, error) { // Create the NotarySigner with all dependencies injected. signer := &NotarySigner{ - URL: nc.Endpoint, - RetryTimeout: nc.RetryTimeout, - PayloadBuilder: payloadBuilder, - TLSProvider: tlsProvider, - HTTPClient: httpClient, + url: nc.Endpoint, + retryTimeout: nc.RetryTimeout, + payloadBuilder: payloadBuilder, + tlsProvider: tlsProvider, + httpClient: httpClient, } return signer, nil } diff --git a/pkg/sign/notary_test.go b/pkg/sign/notary_test.go index 37098948c77b..87101e921266 100644 --- a/pkg/sign/notary_test.go +++ b/pkg/sign/notary_test.go @@ -1,4 +1,4 @@ -package sign_test +package sign import ( "bytes" @@ -18,8 +18,6 @@ import ( "os" "testing" "time" - - "github.com/kyma-project/test-infra/pkg/sign" ) // generateTestCert generates a self-signed certificate and private key. @@ -69,7 +67,7 @@ func generateTestCert() (string, string, error) { // TestImageService_ParseReference_Valid checks the correct parsing of an image reference. func TestImageService_ParseReference_Valid(t *testing.T) { - imageService := sign.ImageService{} + imageService := ImageService{} // Use a valid image reference ref, err := imageService.ParseReference("docker.io/library/alpine:latest") @@ -83,7 +81,7 @@ func TestImageService_ParseReference_Valid(t *testing.T) { // TestImageService_ParseReference_Invalid checks the incorrect parsing of an image reference. func TestImageService_ParseReference_Invalid(t *testing.T) { - imageService := sign.ImageService{} + imageService := ImageService{} invalidReferences := []string{ ":::", @@ -103,9 +101,9 @@ func TestImageService_ParseReference_Invalid(t *testing.T) { // TestImageService_GetImage_Valid checks fetching a valid image. func TestImageService_GetImage_Valid(t *testing.T) { // Mock dependencies - mockImageRepository := &sign.MockImageRepository{ - MockParseReference: func(image string) (sign.ReferenceInterface, error) { - return &sign.MockReference{ + mockImageRepository := &MockImageRepository{ + MockParseReference: func(image string) (ReferenceInterface, error) { + return &MockReference{ MockName: func() string { return image }, @@ -117,10 +115,10 @@ func TestImageService_GetImage_Valid(t *testing.T) { }, }, nil }, - MockGetImage: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { - return &sign.MockImage{ - MockManifest: func() (sign.ManifestInterface, error) { - return &sign.MockManifest{ + MockGetImage: func(ref ReferenceInterface) (ImageInterface, error) { + return &MockImage{ + MockManifest: func() (ManifestInterface, error) { + return &MockManifest{ MockGetConfigSize: func() int64 { return 1024 }, @@ -159,11 +157,11 @@ func TestImageService_GetImage_Valid(t *testing.T) { // TestImageService_GetImage_Invalid checks fetching an invalid image. func TestImageService_GetImage_Invalid(t *testing.T) { - mockImageRepository := &sign.MockImageRepository{ - MockParseReference: func(image string) (sign.ReferenceInterface, error) { - return &sign.MockReference{}, nil + mockImageRepository := &MockImageRepository{ + MockParseReference: func(image string) (ReferenceInterface, error) { + return &MockReference{}, nil }, - MockGetImage: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { + MockGetImage: func(ref ReferenceInterface) (ImageInterface, error) { return nil, fmt.Errorf("failed to fetch image") }, } @@ -183,7 +181,7 @@ func TestImageService_GetImage_Invalid(t *testing.T) { // TestPayloadBuilder_BuildPayload_Valid checks building a payload for valid images. func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { - mockRef := &sign.MockReference{ + mockRef := &MockReference{ MockGetRepositoryName: func() string { return "docker.io/library/alpine" }, @@ -192,7 +190,7 @@ func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { }, } - mockManifest := &sign.MockManifest{ + mockManifest := &MockManifest{ MockGetConfigSize: func() int64 { return 1024 }, @@ -201,22 +199,22 @@ func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { }, } - mockImage := &sign.MockImage{ - MockManifest: func() (sign.ManifestInterface, error) { + mockImage := &MockImage{ + MockManifest: func() (ManifestInterface, error) { return mockManifest, nil }, } - mockImageRepository := &sign.MockImageRepository{ - MockParseReference: func(image string) (sign.ReferenceInterface, error) { + mockImageRepository := &MockImageRepository{ + MockParseReference: func(image string) (ReferenceInterface, error) { return mockRef, nil }, - MockGetImage: func(ref sign.ReferenceInterface) (sign.ImageInterface, error) { + MockGetImage: func(ref ReferenceInterface) (ImageInterface, error) { return mockImage, nil }, } - payloadBuilder := sign.PayloadBuilder{ + payloadBuilder := PayloadBuilder{ ImageService: mockImageRepository, } @@ -231,13 +229,13 @@ func TestPayloadBuilder_BuildPayload_Valid(t *testing.T) { // TestPayloadBuilder_BuildPayload_Invalid checks building a payload for invalid images. func TestPayloadBuilder_BuildPayload_Invalid(t *testing.T) { - mockImageRepository := &sign.MockImageRepository{ - MockParseReference: func(image string) (sign.ReferenceInterface, error) { + mockImageRepository := &MockImageRepository{ + MockParseReference: func(image string) (ReferenceInterface, error) { return nil, fmt.Errorf("failed to parse reference") }, } - payloadBuilder := sign.PayloadBuilder{ + payloadBuilder := PayloadBuilder{ ImageService: mockImageRepository, } @@ -254,12 +252,12 @@ func TestTLSProvider_GetTLSConfig_Valid(t *testing.T) { t.Fatalf("Failed to generate test certificate: %v", err) } - tlsCredentials := sign.TLSCredentials{ + tlsCredentials := TLSCredentials{ CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), } - tlsProvider := sign.TLSProvider{Credentials: tlsCredentials} + tlsProvider := TLSProvider{Credentials: tlsCredentials} tlsConfig, err := tlsProvider.GetTLSConfig() if err != nil { t.Errorf("Expected no error, got %v", err) @@ -274,12 +272,12 @@ func TestTLSProvider_GetTLSConfig_Valid(t *testing.T) { // TestTLSProvider_GetTLSConfig_Invalid checks getting TLS configuration with invalid credentials. func TestTLSProvider_GetTLSConfig_Invalid(t *testing.T) { - tlsCredentials := sign.TLSCredentials{ + tlsCredentials := TLSCredentials{ CertificateData: "invalid-base64", PrivateKeyData: base64.StdEncoding.EncodeToString([]byte("private-key-data")), } - tlsProvider := sign.TLSProvider{Credentials: tlsCredentials} + tlsProvider := TLSProvider{Credentials: tlsCredentials} _, err := tlsProvider.GetTLSConfig() if err == nil { t.Errorf("Expected an error for invalid credentials") @@ -288,7 +286,7 @@ func TestTLSProvider_GetTLSConfig_Invalid(t *testing.T) { // TestHTTPClient_Do checks sending an HTTP request. func TestHTTPClient_Do(t *testing.T) { - httpClient := sign.HTTPClient{Client: &http.Client{}} + httpClient := HTTPClient{Client: &http.Client{}} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) @@ -317,7 +315,7 @@ func TestHTTPClient_SetTLSConfig(t *testing.T) { if err != nil { t.Fatalf("Failed to load X509 key pair: %v", err) } - httpClient := sign.HTTPClient{Client: &http.Client{}} + httpClient := HTTPClient{Client: &http.Client{}} tlsConfig := &tls.Config{Certificates: []tls.Certificate{certificate}} err = httpClient.SetTLSConfig(tlsConfig) @@ -337,13 +335,13 @@ func TestHTTPClient_SetTLSConfig(t *testing.T) { // TestNotarySigner_Sign_Valid checks signing valid images. func TestNotarySigner_Sign_Valid(t *testing.T) { - mockPayloadBuilder := &sign.MockPayloadBuilder{ - MockBuildPayload: func(images []string) (sign.SigningPayload, error) { - return sign.SigningPayload{ - GunTargets: []sign.GUNTargets{ + mockPayloadBuilder := &MockPayloadBuilder{ + MockBuildPayload: func(images []string) (SigningPayload, error) { + return SigningPayload{ + GunTargets: []GUNTargets{ { GUN: "docker.io/library/alpine", - Targets: []sign.Target{ + Targets: []Target{ { Name: "latest", ByteSize: 1024, @@ -356,7 +354,7 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { }, } - mockTLSProvider := &sign.MockTLSProvider{ + mockTLSProvider := &MockTLSProvider{ MockGetTLSConfig: func() (*tls.Config, error) { certPEM, keyPEM, _ := generateTestCert() cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) @@ -367,7 +365,7 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { }, } - mockHTTPClient := &sign.MockHTTPClient{ + mockHTTPClient := &MockHTTPClient{ MockDo: func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusAccepted, @@ -379,12 +377,12 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { }, } - notarySigner := sign.NotarySigner{ - URL: "http://example.com", - RetryTimeout: 1 * time.Second, - PayloadBuilder: mockPayloadBuilder, - TLSProvider: mockTLSProvider, - HTTPClient: mockHTTPClient, + notarySigner := NotarySigner{ + url: "http://example.com", + retryTimeout: 1 * time.Second, + payloadBuilder: mockPayloadBuilder, + tlsProvider: mockTLSProvider, + httpClient: mockHTTPClient, } err := notarySigner.Sign([]string{"docker.io/library/alpine:latest"}) @@ -395,13 +393,13 @@ func TestNotarySigner_Sign_Valid(t *testing.T) { // TestNotarySigner_Sign_Invalid checks signing invalid images. func TestNotarySigner_Sign_Invalid(t *testing.T) { - mockPayloadBuilder := &sign.MockPayloadBuilder{ - MockBuildPayload: func(images []string) (sign.SigningPayload, error) { - return sign.SigningPayload{}, fmt.Errorf("failed to build payload") + mockPayloadBuilder := &MockPayloadBuilder{ + MockBuildPayload: func(images []string) (SigningPayload, error) { + return SigningPayload{}, fmt.Errorf("failed to build payload") }, } - mockTLSProvider := &sign.MockTLSProvider{ + mockTLSProvider := &MockTLSProvider{ MockGetTLSConfig: func() (*tls.Config, error) { certPEM, keyPEM, _ := generateTestCert() cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) @@ -412,7 +410,7 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { }, } - mockHTTPClient := &sign.MockHTTPClient{ + mockHTTPClient := &MockHTTPClient{ MockDo: func(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("request failed") }, @@ -421,12 +419,12 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { }, } - notarySigner := sign.NotarySigner{ - URL: "http://example.com", - RetryTimeout: 1 * time.Second, - PayloadBuilder: mockPayloadBuilder, - TLSProvider: mockTLSProvider, - HTTPClient: mockHTTPClient, + notarySigner := NotarySigner{ + url: "http://example.com", + retryTimeout: 1 * time.Second, + payloadBuilder: mockPayloadBuilder, + tlsProvider: mockTLSProvider, + httpClient: mockHTTPClient, } err := notarySigner.Sign([]string{"invalid_image"}) @@ -437,7 +435,7 @@ func TestNotarySigner_Sign_Invalid(t *testing.T) { // TestRetryHTTPRequest_Failure checks if RetryHTTPRequest returns an error after failed attempts. func TestRetryHTTPRequest_Failure(t *testing.T) { - httpClient := sign.HTTPClient{Client: &http.Client{}} + httpClient := HTTPClient{Client: &http.Client{}} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) @@ -447,7 +445,7 @@ func TestRetryHTTPRequest_Failure(t *testing.T) { if err != nil { t.Fatalf("Failed to create request: %v", err) } - resp, err := sign.RetryHTTPRequest(&httpClient, req, 3, 1*time.Second) + resp, err := RetryHTTPRequest(&httpClient, req, 3, 1*time.Second) if err == nil { t.Errorf("Expected an error on failure") } @@ -458,7 +456,7 @@ func TestRetryHTTPRequest_Failure(t *testing.T) { // TestRetryHTTPRequest_SuccessAfterRetries checks if RetryHTTPRequest succeeds after a certain number of retries. func TestRetryHTTPRequest_SuccessAfterRetries(t *testing.T) { - httpClient := sign.HTTPClient{Client: &http.Client{}} + httpClient := HTTPClient{Client: &http.Client{}} attempts := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ @@ -474,7 +472,7 @@ func TestRetryHTTPRequest_SuccessAfterRetries(t *testing.T) { if err != nil { t.Fatalf("Failed to create request: %v", err) } - resp, err := sign.RetryHTTPRequest(&httpClient, req, 3, 1*time.Second) + resp, err := RetryHTTPRequest(&httpClient, req, 3, 1*time.Second) if err != nil { t.Errorf("Expected no error after retries, got %v", err) } @@ -490,7 +488,7 @@ func TestNotaryConfig_NewSigner_Success(t *testing.T) { t.Fatalf("Failed to generate test certificate: %v", err) } - tlsCredentials := sign.TLSCredentials{ + tlsCredentials := TLSCredentials{ CertificateData: base64.StdEncoding.EncodeToString([]byte(certPEM)), PrivateKeyData: base64.StdEncoding.EncodeToString([]byte(keyPEM)), } @@ -515,9 +513,9 @@ func TestNotaryConfig_NewSigner_Success(t *testing.T) { tempFile.Close() // Close the file to ensure the data is written // Create NotaryConfig with the path to the secret file - notaryConfig := &sign.NotaryConfig{ + notaryConfig := &NotaryConfig{ Endpoint: "http://example.com", - Secret: &sign.AuthSecretConfig{Path: tempFile.Name()}, + Secret: &AuthSecretConfig{Path: tempFile.Name()}, Timeout: 10 * time.Second, RetryTimeout: 1 * time.Second, } @@ -532,22 +530,22 @@ func TestNotaryConfig_NewSigner_Success(t *testing.T) { } // Optionally, check if signer is of type *NotarySigner - notarySigner, ok := signer.(*sign.NotarySigner) + notarySigner, ok := signer.(*NotarySigner) if !ok { t.Errorf("Expected signer to be of type *NotarySigner") } // Additional checks for NotarySigner fields - if notarySigner.URL != notaryConfig.Endpoint { - t.Errorf("Expected NotarySigner URL to be '%s', got '%s'", notaryConfig.Endpoint, notarySigner.URL) + if notarySigner.url != notaryConfig.Endpoint { + t.Errorf("Expected NotarySigner URL to be '%s', got '%s'", notaryConfig.Endpoint, notarySigner.url) } } func TestNotaryConfig_NewSigner_InvalidSecretFile(t *testing.T) { // Create NotaryConfig with a non-existent secret file - notaryConfig := &sign.NotaryConfig{ + notaryConfig := &NotaryConfig{ Endpoint: "http://example.com", - Secret: &sign.AuthSecretConfig{Path: "non-existent-file.json"}, + Secret: &AuthSecretConfig{Path: "non-existent-file.json"}, Timeout: 10 * time.Second, RetryTimeout: 1 * time.Second, } @@ -580,9 +578,9 @@ func TestNotaryConfig_NewSigner_InvalidTLSCredentials(t *testing.T) { tempFile.Close() // Create NotaryConfig with the path to the secret file - notaryConfig := &sign.NotaryConfig{ + notaryConfig := &NotaryConfig{ Endpoint: "http://example.com", - Secret: &sign.AuthSecretConfig{Path: tempFile.Name()}, + Secret: &AuthSecretConfig{Path: tempFile.Name()}, Timeout: 10 * time.Second, RetryTimeout: 1 * time.Second, } diff --git a/pkg/sign/sign_test.go b/pkg/sign/sign_test.go index 468c2a2abad0..cc4ce6f43cb9 100644 --- a/pkg/sign/sign_test.go +++ b/pkg/sign/sign_test.go @@ -1,15 +1,8 @@ package sign import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" "encoding/base64" "encoding/json" - "encoding/pem" - "math/big" "os" "testing" "time" @@ -113,48 +106,3 @@ func TestNotaryConfig_NewSigner(t *testing.T) { t.Errorf("expected a valid signer, but got nil") } } - -// generateTestCert generates a self-signed certificate and private key. -// Returns the certificate and key in PEM format. -func generateTestCert() (string, string, error) { - // Generate RSA key - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return "", "", err - } - - // Create certificate template - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Test Organization"}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(24 * time.Hour), // Certificate valid for 24 hours - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageServerAuth, - }, - BasicConstraintsValid: true, - } - - // Self-sign the certificate - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) - if err != nil { - return "", "", err - } - - // Encode certificate to PEM - certPEM := new(bytes.Buffer) - if err := pem.Encode(certPEM, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return "", "", err - } - - // Encode private key to PEM - keyPEM := new(bytes.Buffer) - if err := pem.Encode(keyPEM, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil { - return "", "", err - } - - return certPEM.String(), keyPEM.String(), nil -}