release/cli/pkg/operations/bundle_release.go (238 lines of code) (raw):

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package operations import ( "context" "fmt" "os/exec" "strings" "github.com/pkg/errors" "golang.org/x/sync/errgroup" anywhereconstants "github.com/aws/eks-anywhere/pkg/constants" anywherev1alpha1 "github.com/aws/eks-anywhere/release/api/v1alpha1" "github.com/aws/eks-anywhere/release/cli/pkg/assets" "github.com/aws/eks-anywhere/release/cli/pkg/aws/ecrpublic" "github.com/aws/eks-anywhere/release/cli/pkg/bundles" "github.com/aws/eks-anywhere/release/cli/pkg/constants" "github.com/aws/eks-anywhere/release/cli/pkg/filereader" "github.com/aws/eks-anywhere/release/cli/pkg/images" sig "github.com/aws/eks-anywhere/release/cli/pkg/signature" releasetypes "github.com/aws/eks-anywhere/release/cli/pkg/types" artifactutils "github.com/aws/eks-anywhere/release/cli/pkg/util/artifacts" commandutils "github.com/aws/eks-anywhere/release/cli/pkg/util/command" ) func GenerateBundleArtifactsTable(r *releasetypes.ReleaseConfig) (releasetypes.ArtifactsTable, error) { fmt.Println("\n==========================================================") fmt.Println(" Bundle Artifacts Table Generation") fmt.Println("==========================================================") eksDReleaseMap, err := filereader.ReadEksDReleases(r) if err != nil { return releasetypes.ArtifactsTable{}, err } supportedK8sVersions, err := filereader.GetSupportedK8sVersions(r) if err != nil { return releasetypes.ArtifactsTable{}, errors.Wrapf(err, "Error getting supported Kubernetes versions for bottlerocket") } artifactsTable, err := assets.GetBundleReleaseAssets(supportedK8sVersions, eksDReleaseMap, r) if err != nil { return releasetypes.ArtifactsTable{}, errors.Wrapf(err, "Error getting bundle release assets") } fmt.Printf("%s Successfully generated bundle artifacts table\n", constants.SuccessIcon) return artifactsTable, nil } func BundleArtifactsRelease(r *releasetypes.ReleaseConfig) error { fmt.Println("\n==========================================================") fmt.Println(" Bundle Artifacts Release") fmt.Println("==========================================================") err := DownloadArtifacts(context.Background(), r, r.BundleArtifactsTable) if err != nil { return errors.Cause(err) } err = RenameArtifacts(context.Background(), r, r.BundleArtifactsTable) if err != nil { return errors.Cause(err) } err = UploadArtifacts(context.Background(), r, r.BundleArtifactsTable, true) if err != nil { return errors.Cause(err) } return nil } func GenerateImageDigestsTable(ctx context.Context, r *releasetypes.ReleaseConfig) (releasetypes.ImageDigestsTable, error) { fmt.Println("\n==========================================================") fmt.Println(" Image Digests Table Generation") fmt.Println("==========================================================") var imageDigests releasetypes.ImageDigestsTable errGroup, ctx := errgroup.WithContext(ctx) r.BundleArtifactsTable.Range(func(k, v interface{}) bool { artifacts := v.([]releasetypes.Artifact) for _, artifact := range artifacts { r, artifact := r, artifact errGroup.Go(func() error { if artifact.Image != nil { imageDigest, err := getImageDigest(ctx, r, artifact) if err != nil { return errors.Wrapf(err, "getting image digest for image %s", artifact.Image.ReleaseImageURI) } imageDigests.Store(artifact.Image.ReleaseImageURI, imageDigest) fmt.Printf("Image digest for %s - %s\n", artifact.Image.ReleaseImageURI, imageDigest) } return nil }) } return true }) if err := errGroup.Wait(); err != nil { return releasetypes.ImageDigestsTable{}, fmt.Errorf("generating image digests table: %v", err) } fmt.Printf("%s Successfully generated image digests table\n", constants.SuccessIcon) return imageDigests, nil } func SignImagesNotation(r *releasetypes.ReleaseConfig, imageDigests releasetypes.ImageDigestsTable) error { if r.DryRun { fmt.Println("Skipping image signing in dry-run mode") return nil } releaseRegistryUsername := r.ReleaseClients.ECRPublic.AuthConfig.Username releaseRegistryPassword := r.ReleaseClients.ECRPublic.AuthConfig.Password var rangeErr error imageDigests.Range(func(k, v interface{}) bool { image := k.(string) digest := v.(string) imageURI := fmt.Sprintf("%s@%s", image, digest) cmd := exec.Command("notation", "list", imageURI, "-u", releaseRegistryUsername, "-p", releaseRegistryPassword) out, err := commandutils.ExecCommand(cmd) if err != nil { rangeErr = fmt.Errorf("listing signatures associated with image %s: %v", imageURI, err) return false } // Skip signing image if it is already signed. if strings.Contains(out, "no associated signature") { // Sign public ECR image using AWS signer and notation CLI // notation sign <registry>/<repository>@<sha256:shasum> --plugin com.amazonaws.signer.notation.plugin --id <signer_profile_arn> cmd := exec.Command("notation", "sign", imageURI, "--plugin", "com.amazonaws.signer.notation.plugin", "--id", r.AwsSignerProfileArn, "-u", releaseRegistryUsername, "-p", releaseRegistryPassword) out, err := commandutils.ExecCommand(cmd) fmt.Println(out) if err != nil { rangeErr = fmt.Errorf("signing container image with Notation CLI: %v", err) return false } } else { rangeErr = nil fmt.Printf("Skipping image signing for image %s since it has already been signed\n", imageURI) } return true }) return rangeErr } // Copy image signatures to production account from staging account func CopyImageSignatureUsingOras(r *releasetypes.ReleaseConfig, imageDigests releasetypes.ImageDigestsTable) error { if r.DryRun { fmt.Println("Skipping image signature copy in dry-run mode") return nil } sourceRegistryClient := r.SourceClients.ECR.EcrPublicClient sourceRegistryUsername := r.SourceClients.ECR.AuthConfig.Username sourceRegistryPassword := r.SourceClients.ECR.AuthConfig.Password releaseRegistryClient := r.ReleaseClients.ECRPublic.Client releaseRegistryUsername := r.ReleaseClients.ECRPublic.AuthConfig.Username releaseRegistryPassword := r.ReleaseClients.ECRPublic.AuthConfig.Password var rangeErr error imageDigests.Range(func(k, v interface{}) bool { image := k.(string) digest := v.(string) // Get imageRespository name since we have a different source and release registry. imageRepository, imageTag := artifactutils.SplitImageUri(image, r.ReleaseContainerRegistry) // Compute image digest for source and destination images from manifest contents. The digest will be in the // form sha256:digest, so we are changing it to Notation's image index and signatures format, sha256-digest. sourceImageDigest, err := images.ComputeImageDigestFromManifest(sourceRegistryClient, r.SourceContainerRegistry, imageRepository, imageTag) if err != nil { rangeErr = fmt.Errorf("computing digest for source image %s from manifest: %v\n", image, err) return false } sourceImageSignatureDigest := fmt.Sprintf("sha256-%s", sourceImageDigest) releaseImageDigest, err := images.ComputeImageDigestFromManifest(releaseRegistryClient, r.ReleaseContainerRegistry, imageRepository, imageTag) if err != nil { rangeErr = fmt.Errorf("computing digest for destination image %s from manifest: %v\n", image, err) return false } releaseImageSignatureDigest := fmt.Sprintf("sha256-%s", releaseImageDigest) // Form releaseImageURI in the form <source-registry>/<repository>:<sha256-digest> sourceImageURI := fmt.Sprintf("%s/%s:%s", r.SourceContainerRegistry, imageRepository, sourceImageSignatureDigest) // Form releaseImageURI in the form <release-registry>/<repository>:<sha256-digest> releaseImageURI := fmt.Sprintf("%s/%s:%s", r.ReleaseContainerRegistry, imageRepository, releaseImageSignatureDigest) cmd := exec.Command("oras", "copy", "--from-username", sourceRegistryUsername, "--from-password", sourceRegistryPassword, sourceImageURI, "--to-username", releaseRegistryUsername, "--to-password", releaseRegistryPassword, releaseImageURI) out, err := commandutils.ExecCommand(cmd) fmt.Println(out) if err != nil { rangeErr = fmt.Errorf("copying signatures associated with image %s: %v\n", fmt.Sprintf("%s@%s", image, digest), err) return false } return true }) return rangeErr } func GenerateBundleSpec(r *releasetypes.ReleaseConfig, bundle *anywherev1alpha1.Bundles, imageDigests releasetypes.ImageDigestsTable) error { fmt.Println("\n==========================================================") fmt.Println(" Bundles Manifest Spec Generation") fmt.Println("==========================================================") versionsBundles, err := bundles.GetVersionsBundles(r, imageDigests) if err != nil { return err } bundle.Spec.VersionsBundles = versionsBundles fmt.Printf("%s Successfully generated bundle manifest spec\n", constants.SuccessIcon) return nil } // SignBundleManifest is the top-level function that computes the Bundles // manifest signature using AWS KMS and attaches that signature as an // annotation on the Bundles object. func SignBundleManifest(ctx context.Context, bundle *anywherev1alpha1.Bundles) error { fmt.Println("\n==========================================================") fmt.Println(" Bundles Manifest Signing") fmt.Println("==========================================================") if bundle.Annotations == nil { bundle.Annotations = make(map[string]string, 1) } bundle.Annotations[anywhereconstants.ExcludesAnnotation] = anywhereconstants.Excludes fmt.Printf("Generating bundle manifest signature with KMS key: %s\n", constants.BundlesKmsKey) signature, err := sig.GetBundleSignature(ctx, bundle, constants.BundlesKmsKey) if err != nil { return err } bundle.Annotations[anywhereconstants.SignatureAnnotation] = signature fmt.Printf("%s Successfully signed bundle manifest\n", constants.SuccessIcon) return nil } // SignEKSDistroManifest is the top-level function that computes the EKS Distro // manifest signature using AWS KMS and attaches that signature as an // annotation on the Bundles object for each supported kubernetes version. func SignEKSDistroManifest(ctx context.Context, bundle *anywherev1alpha1.Bundles) error { fmt.Println("\n==========================================================") fmt.Println(" EKS Distro Manifest Signing") fmt.Println("==========================================================") if bundle.Annotations == nil { bundle.Annotations = make(map[string]string, 1) } bundle.Annotations[anywhereconstants.EKSDistroExcludesAnnotation] = anywhereconstants.EKSDistroExcludes for _, versionsBundle := range bundle.Spec.VersionsBundles { releaseChannel := versionsBundle.EksD.ReleaseChannel releaseUrl := versionsBundle.EksD.EksDReleaseUrl fmt.Printf("Generating eks distro manifest signature for %s release channel with KMS key: %s\n", releaseChannel, constants.EKSDistroManifestKmsKey) signature, err := sig.GetEKSDistroManifestSignature(ctx, bundle, constants.EKSDistroManifestKmsKey, releaseUrl) if err != nil { return err } signatureAnnotation := anywhereconstants.EKSDistroSignatureAnnotation + "-" + releaseChannel bundle.Annotations[signatureAnnotation] = signature } fmt.Printf("%s Successfully signed eks distro manifest\n", constants.SuccessIcon) return nil } func getImageDigest(_ context.Context, r *releasetypes.ReleaseConfig, artifact releasetypes.Artifact) (string, error) { var imageDigest string var err error if r.DryRun { sha256sum, err := artifactutils.GetFakeSHA(256) if err != nil { return "", errors.Cause(err) } imageDigest = fmt.Sprintf("sha256:%s", sha256sum) } else { releaseImageUri := artifact.Image.ReleaseImageURI releaseContainerRegistry := r.ReleaseContainerRegistry ecrPublicClient := r.ReleaseClients.ECRPublic.Client if r.DevRelease && (strings.Contains(releaseImageUri, "eks-anywhere-packages") || strings.Contains(releaseImageUri, "ecr-token-refresher") || strings.Contains(releaseImageUri, "credential-provider-package")) { ecrPublicClient = r.ReleaseClients.Packages.Client releaseContainerRegistry = r.PackagesReleaseContainerRegistry } imageDigest, err = ecrpublic.GetImageDigest(releaseImageUri, releaseContainerRegistry, ecrPublicClient) if err != nil { return "", errors.Cause(err) } } return imageDigest, nil }