From 987b45e5efe251382aaff5e780d03f231a191f7b Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Fri, 6 Oct 2023 16:31:45 -0700 Subject: [PATCH] chore: Replace with in unzipper on non-Windows platforms --- .github/workflows/container.yml | 157 ++++++++++++++++++ Dockerfile | 6 +- cmd/orchs_ext.go | 2 +- pkg/cmdutil/extensions/extension_installer.go | 34 +++- pkg/cmdutil/helpers.go | 5 + 5 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/container.yml diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 00000000..74d2e8bf --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,157 @@ +name: Build and Release +on: + push: + branches: + - '*' + pull_request: + branches: + - 'v*' + types: + # action should run when the pull request is closed + # (regardless of whether it was merged or just closed) + - closed + # Make sure the action runs every time new commits are + # pushed to the pull request's branch + - synchronize + +env: + REGISTRY: ghcr.io + +jobs: + build: + name: Build Containers + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - linux/arm64 + - linux/amd64 + - linux/s390x + - linux/ppc64le + - linux/386 + + permissions: + contents: read + packages: write + + steps: + + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + + # Checkout code + # https://github.com/actions/checkout + - name: Checkout code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Set up QEMU + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login to Docker registry + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Build and push Docker image with Buildx + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + push: ${{ github.event.pull_request.merged == true }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true + + # Export digest + - name: Export digest + if: github.event.pull_request.merged == true + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + # Upload digest + - name: Upload digest + if: github.event.pull_request.merged == true + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: digests + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + needs: + - build + steps: + - name: Set IMAGE_NAME + run: | + echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} + + # Download digests + # https://github.com/actions/download-artifact + - name: Download digests + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: digests + path: /tmp/digests + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Login to Docker registry + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Create manifest list and push + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} diff --git a/Dockerfile b/Dockerfile index a81fd489..f53df97a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY pkg/ pkg/ # Build # the GOARCH has a default value to allow the binary be built according to the host where the command -# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# was called. For example, if we build in a local env which has the Apple Silicon M1 chip, # the docker BUILDPLATFORM arg will be linux/arm64, but for Apple x86 it will be linux/amd64. Therefore, # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o kfutil main.go @@ -28,8 +28,8 @@ WORKDIR / COPY --from=builder /workspace/kfutil /usr/local/bin/kfutil # Install ca-certificates so that HTTPS works consistently -RUN apt update -RUN apt install ca-certificates -y +RUN apt-get update +RUN apt-get install ca-certificates -y # Spin forever so that the container doesn't exit and the user can exec into it ENTRYPOINT ["tail", "-f", "/dev/null"] \ No newline at end of file diff --git a/cmd/orchs_ext.go b/cmd/orchs_ext.go index c27fe8a5..f9d9f5e3 100644 --- a/cmd/orchs_ext.go +++ b/cmd/orchs_ext.go @@ -89,7 +89,7 @@ func (f *OrchsExtFlags) AddFlags(flags *pflag.FlagSet) { flags.StringVarP(f.GithubToken, "token", "t", *f.GithubToken, "Token used for related authentication - required for private repositories") flags.StringVarP(f.GithubOrg, "org", "", *f.GithubOrg, "Github organization to download extensions from. Default is keyfactor.") flags.StringVarP(f.OutDir, "out", "o", *f.OutDir, "Path to the extensions directory to download extensions into. Default is ./extensions") - flags.StringSliceVarP(f.Extensions, "extensions", "e", *f.Extensions, "List of extensions to download. Should be in the format :. If no version is specified, the latest official version will be downloaded.") + flags.StringSliceVarP(f.Extensions, "extension", "e", *f.Extensions, "List of extensions to download. Should be in the format @. If no version is specified, the latest official version will be downloaded.") flags.BoolVarP(f.AutoConfirm, "confirm", "y", *f.AutoConfirm, "Automatically confirm the download of extensions") flags.BoolVarP(f.Upgrade, "update", "u", *f.Upgrade, "Update existing extensions if they are out of date.") flags.BoolVarP(f.Prune, "prune", "P", *f.Prune, "Remove extensions from the extensions directory that are not in the extension configuration file or specified on the command line") diff --git a/pkg/cmdutil/extensions/extension_installer.go b/pkg/cmdutil/extensions/extension_installer.go index 26e3ba68..a3ea3a9b 100644 --- a/pkg/cmdutil/extensions/extension_installer.go +++ b/pkg/cmdutil/extensions/extension_installer.go @@ -27,7 +27,6 @@ import ( "log" "os" "path/filepath" - "runtime" "sort" "strings" ) @@ -510,13 +509,22 @@ func (b *ExtensionInstaller) unzip(zipFilePath, destinationDirectory string) err }(archive) for _, f := range archive.File { - filePath := filepath.Join(destinationDirectory, f.Name) - log.Println("unzipping file", filePath) + rawPath := adjustFilePath(f.Name) + log.Println("unzipping file", rawPath) + // Remove the root directory from the path + pathComponents := strings.Split(rawPath, string(os.PathSeparator)) + if len(pathComponents) > 1 { + pathComponents = pathComponents[1:] // remove the first directory + } + filePath := filepath.Join(destinationDirectory, filepath.Join(pathComponents...)) + + // Safety check for any malicious zip archives that might attempt to navigate to parent directories if !strings.HasPrefix(filePath, filepath.Clean(destinationDirectory)+string(os.PathSeparator)) { - return fmt.Errorf("%s: illegal file path", filePath) + return fmt.Errorf("%s: illegal file path", rawPath) } - if f.FileInfo().IsDir() { + + if isDirectory(f) { log.Println("creating directory...") err = os.MkdirAll(filePath, os.ModePerm) if err != nil { @@ -525,10 +533,6 @@ func (b *ExtensionInstaller) unzip(zipFilePath, destinationDirectory string) err continue } - if strings.Contains(filePath, "\\") && runtime.GOOS != "windows" { - return fmt.Errorf("illegal file path: %s", filePath) - } - if err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { return fmt.Errorf("failed to create directory %s: %s", filepath.Dir(filePath), err) } @@ -560,6 +564,18 @@ func (b *ExtensionInstaller) unzip(zipFilePath, destinationDirectory string) err return nil } +func isDirectory(file *zip.File) bool { + return file.FileInfo().IsDir() || (strings.HasSuffix(adjustFilePath(file.Name), "/") && cmdutil.GetOs() != "windows") +} + +func adjustFilePath(path string) string { + if strings.Contains(path, "\\") && cmdutil.GetOs() != "windows" { + path = strings.ReplaceAll(path, "\\", "/") + } + + return path +} + func (b *ExtensionInstaller) PromptForExtensions() error { extensionList, err := NewGithubReleaseFetcher(b.githubOrg, b.githubToken).GetExtensionList() if err != nil { diff --git a/pkg/cmdutil/helpers.go b/pkg/cmdutil/helpers.go index 83c64d0c..816b54fd 100644 --- a/pkg/cmdutil/helpers.go +++ b/pkg/cmdutil/helpers.go @@ -18,6 +18,7 @@ package cmdutil import ( "fmt" + "runtime" ) func PrintError(err error) { @@ -26,3 +27,7 @@ func PrintError(err error) { fmt.Printf("\033[31m%s\u001B[0m\n", err) } } + +func GetOs() string { + return runtime.GOOS +}