Skip to content

Commit

Permalink
Merge pull request #7 from etsy/checksum
Browse files Browse the repository at this point in the history
feat: add sha256sum check when downloading from hashicorp
  • Loading branch information
c4po authored Apr 18, 2022
2 parents d33778d + 0483a59 commit 9a1b0f2
Showing 1 changed file with 78 additions and 9 deletions.
87 changes: 78 additions & 9 deletions internal/releaseapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package releaseapi

import (
"archive/zip"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
Expand All @@ -10,6 +12,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/gregjones/httpcache"
Expand All @@ -19,7 +22,8 @@ import (
)

const (
releasesURL = "https://releases.hashicorp.com/terraform/index.json"
releasesURL = "https://releases.hashicorp.com/terraform/index.json"
releaseRootURL = "https://releases.hashicorp.com/terraform"
)

type ReleaseIndex struct {
Expand All @@ -28,6 +32,7 @@ type ReleaseIndex struct {

type Release struct {
Version *semver.Version `json:"version"`
Shasums string `json:"shasums"`
Builds []Build `json:"builds"`
}

Expand Down Expand Up @@ -87,7 +92,7 @@ func (c *Client) ListReleases() (ReleaseIndex, error) {

func (c *Client) DownloadRelease(r Release, os, arch string) (string, error) {
var matchingBuild Build

var checkSha256Sum string
for _, build := range r.Builds {
if build.OS == os && build.Arch == arch {
matchingBuild = build
Expand All @@ -101,15 +106,54 @@ func (c *Client) DownloadRelease(r Release, os, arch string) (string, error) {
)
}

return c.downloadBuild(matchingBuild)
checkSums, err := c.getReleaseCheckSums(r)
if err != nil {
return "", errors.Wrap(err, "could not download checksum file")
}

for _, line := range strings.Split(checkSums, "\n") {
checksum := strings.Split(line, " ")
if checksum[1] == matchingBuild.zipFileName() {
checkSha256Sum = checksum[0]
break
}
}

build, err := c.downloadBuild(matchingBuild, checkSha256Sum)
return build, err
}

func (c *Client) downloadBuild(build Build) (string, error) {
func (c *Client) getReleaseCheckSums(release Release) (string, error) {
request, err := http.NewRequest("GET", release.ShaSumsURL(), nil)
if err != nil {
return "", errors.Wrap(err, "could not create request for Terraform release checksum")
}
response, err := c.httpClient.Do(request)
if err != nil {
return "", errors.Wrap(err, "could not send request for Terraform release checksum")
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return "", errors.Errorf("error: unexpected status code '%s' in response", response.StatusCode)
}

bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
return "", errors.Wrap(err, "error: could not read response")
}
checkSums := string(bodyBytes)
return checkSums, nil
}

func (r *Release) ShaSumsURL() string {
return fmt.Sprintf("%s/%s/%s", releaseRootURL, r.Version, r.Shasums)
}

func (c *Client) downloadBuild(build Build, checkSha256Sum string) (string, error) {
path := cachedExecutablePath(c.cacheDir, build)

if _, err := os.Stat(path); err == nil {
log.Printf("found cached Terraform executable at %s", path)

return path, nil
} else if !os.IsNotExist(err) {
return "", errors.Wrap(err, "could not stat Terraform executable")
Expand All @@ -118,12 +162,34 @@ func (c *Client) downloadBuild(build Build) (string, error) {
log.Printf("dowloading release archive from %s", build.URL)

zipFile, zipLength, err := c.downloadReleaseArchive(build)
defer os.Remove(zipFile.Name())
defer zipFile.Close()

if err != nil {
return "", err
}

defer zipFile.Close()
f, err := os.Open(zipFile.Name())
if err != nil {
return "", errors.Wrap(err, "could not open zip archive")
}
defer f.Close()

h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", errors.Wrap(err, "could not check sha256sum for zip archive")
}
sha256Sum := h.Sum(nil)

if checkSha256Sum != "" {
if checkSha256Sum != hex.EncodeToString(sha256Sum) {
return "", errors.Errorf(
"checksum for %s should be %s, got %s", build.URL, checkSha256Sum, hex.EncodeToString(sha256Sum),
)
} else {
log.Printf("checksum match\n")
}
}

zipReader, err := zip.NewReader(zipFile, zipLength)

Expand Down Expand Up @@ -188,14 +254,17 @@ func (c *Client) downloadReleaseArchive(build Build) (*os.File, int64, error) {
if _, err := io.Copy(tmp, response.Body); err != nil {
return nil, 0, errors.Wrap(err, "could not copy release archive to temporary file")
}

return tmp, response.ContentLength, nil
}

func cachedExecutablePath(cacheDir string, b Build) string {
return filepath.Join(cacheDir, executableName(b))
return filepath.Join(cacheDir, b.executableName())
}

func executableName(b Build) string {
func (b *Build) executableName() string {
return fmt.Sprintf("terraform_%s_%s_%s", b.Version.String(), b.OS, b.Arch)
}

func (b *Build) zipFileName() string {
return filepath.Base(b.URL)
}

0 comments on commit 9a1b0f2

Please sign in to comment.