forked from warrensbox/terraform-switcher
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- implemented check for signature and checksums for warrensbox#160 and …
…warrensbox#290 - added github action for testing - added test for checksum matching - add gitattributes for windows testing. if not present the lf line endings will be converted to crlf which messes with the checksum tests. - update changelog and readme
- Loading branch information
1 parent
6aaa2c4
commit ca5001b
Showing
15 changed files
with
298 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
* text=auto | ||
* text eol=lf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: Go | ||
|
||
on: | ||
push: | ||
branches: [ '*' ] | ||
|
||
jobs: | ||
|
||
integration_tests: | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, windows-latest] | ||
go_version: ['1.22'] | ||
runs-on: ${{ matrix.os }} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: ${{ matrix.go_version }} | ||
|
||
- name: Build | ||
run: go build -v ./... | ||
|
||
- name: Test | ||
run: go test -v ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package lib | ||
|
||
import ( | ||
"bufio" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"fmt" | ||
"golang.org/x/crypto/openpgp" | ||
"io" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// getChecksumFromFile Extract the checksum from the signature file | ||
func getChecksumFromHashFile(signatureFilePath string, terraformFileName string) (string, error) { | ||
readFile, err := os.Open(signatureFilePath) | ||
if err != nil { | ||
fmt.Println("[Error]: Could not open ", signatureFilePath) | ||
return "", err | ||
} | ||
defer readFile.Close() | ||
|
||
scanner := bufio.NewScanner(readFile) | ||
scanner.Split(bufio.ScanLines) | ||
for scanner.Scan() { | ||
split := strings.Split(scanner.Text(), " ") | ||
if split[1] == terraformFileName { | ||
return split[0], nil | ||
} | ||
} | ||
return "", nil | ||
} | ||
|
||
// checkChecksumMatches This will calculate and compare the check sum of the downloaded zip file. | ||
func checkChecksumMatches(hashFile string, targetFile *os.File) bool { | ||
_, fileName := filepath.Split(targetFile.Name()) | ||
expectedChecksum, err := getChecksumFromHashFile(hashFile, fileName) | ||
if err != nil { | ||
fmt.Println("[Error]: could not get expected checksum from file: " + err.Error()) | ||
return false | ||
} | ||
hash := sha256.New() | ||
if _, err := io.Copy(hash, targetFile); err != nil { | ||
fmt.Println("[Error]: Calculating Checksum failed: " + err.Error()) | ||
return false | ||
} | ||
checksum := hex.EncodeToString(hash.Sum(nil)) | ||
if expectedChecksum != checksum { | ||
fmt.Println("[Error]: Checksum mismatch. Expected: ", expectedChecksum, " got ", checksum) | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// checkSignatureOfChecksums THis will verify the signature of the file containing the hash sums | ||
func checkSignatureOfChecksums(keyRingReader *os.File, hashFile *os.File, signatureFile *os.File) bool { | ||
log.Println("Verifying signature of checksum file...") | ||
keyring, err := openpgp.ReadArmoredKeyRing(keyRingReader) | ||
if err != nil { | ||
log.Fatal("[Error]: Read armored key ring: " + err.Error()) | ||
return false | ||
} | ||
|
||
_, err = openpgp.CheckDetachedSignature(keyring, hashFile, signatureFile) | ||
if err != nil { | ||
log.Fatal("[Error]: Checking detached signature: " + err.Error()) | ||
return false | ||
} | ||
log.Println("Verification successful.") | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package lib | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
) | ||
|
||
func Test_getChecksumFromHashFile(t *testing.T) { | ||
expected := "3ff056b5e8259003f67fd0f0ed7229499cfb0b41f3ff55cc184088589994f7a5" | ||
got, err := getChecksumFromHashFile("../test-data/terraform_1.7.5_SHA256SUMS", "terraform_1.7.5_linux_amd64.zip") | ||
if err != nil { | ||
t.Errorf("getChecksumFromHashFile() error = %v", err) | ||
return | ||
} | ||
if got != expected { | ||
t.Errorf("getChecksumFromHashFile() got = %v, expected %v", got, expected) | ||
} | ||
} | ||
|
||
func Test_checkChecksumMatches(t *testing.T) { | ||
targetFile, err := os.Open("../test-data/checksum-check-file") | ||
if err != nil { | ||
t.Errorf("[Error]: Could not open testfile for signature verification.") | ||
} | ||
|
||
if got := checkChecksumMatches("../test-data/terraform_1.7.5_SHA256SUMS", targetFile); got != true { | ||
t.Errorf("checkChecksumMatches() = %v, want %v", got, true) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,142 @@ | ||
package lib | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// DownloadFromURL : Downloads the binary from the source url | ||
func DownloadFromURL(installLocation string, url string) (string, error) { | ||
const ( | ||
pubKeyId = "72D7468F" | ||
pubKeyPrefix = "hashicorp_" | ||
pubKeySuffix = ".asc" | ||
pubKeyUri = "https://www.hashicorp.com/.well-known/pgp-key.txt" | ||
) | ||
|
||
// DownloadFromURL : Downloads the terraform binary and its hash from the source url | ||
func DownloadFromURL(installLocation string, mirrorURL string, tfversion string, versionPrefix string, goos string, goarch string) (string, error) { | ||
pubKeyFilename := filepath.Join(installLocation, "/", pubKeyPrefix+pubKeyId+pubKeySuffix) | ||
zipUrl := mirrorURL + tfversion + "/" + versionPrefix + tfversion + "_" + goos + "_" + goarch + ".zip" | ||
hashUrl := mirrorURL + tfversion + "/" + versionPrefix + tfversion + "_SHA256SUMS" | ||
hashSignatureUrl := mirrorURL + tfversion + "/" + versionPrefix + tfversion + "_SHA256SUMS." + pubKeyId + ".sig" | ||
|
||
err := downloadPublicKey(installLocation, pubKeyFilename) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not download public key file") | ||
return "", err | ||
} | ||
|
||
log.Println("Downloading ", zipUrl) | ||
zipFilePath, err := downloadFromURL(installLocation, zipUrl) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not download zip file") | ||
return "", err | ||
} | ||
|
||
log.Println("Downloading ", hashUrl) | ||
hashFilePath, err := downloadFromURL(installLocation, hashUrl) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not download hash file") | ||
return "", err | ||
} | ||
|
||
log.Println("Downloading ", hashSignatureUrl) | ||
hashSigFilePath, err := downloadFromURL(installLocation, hashSignatureUrl) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not download hash signature file") | ||
return "", err | ||
} | ||
|
||
publicKeyFile, err := os.Open(pubKeyFilename) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not open the public key") | ||
return "", err | ||
} | ||
|
||
signatureFile, err := os.Open(hashSigFilePath) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not open the public key") | ||
return "", err | ||
} | ||
|
||
targetFile, err := os.Open(zipFilePath) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not open the terraform binary for signature verification.") | ||
return "", err | ||
} | ||
|
||
hashFile, err := os.Open(hashFilePath) | ||
if err != nil { | ||
log.Fatal("[Error]: Could not open the terraform binary for signature verification.") | ||
return "", err | ||
} | ||
verified := checkSignatureOfChecksums(publicKeyFile, hashFile, signatureFile) | ||
if !verified { | ||
return "", errors.New("signature of checksum files could not be verified") | ||
} | ||
match := checkChecksumMatches(hashFilePath, targetFile) | ||
if !match { | ||
return "", errors.New("checksums did not match") | ||
} | ||
return zipFilePath, err | ||
} | ||
|
||
func downloadFromURL(installLocation string, url string) (string, error) { | ||
tokens := strings.Split(url, "/") | ||
fileName := tokens[len(tokens)-1] | ||
fmt.Printf("Downloading to: %s\n", installLocation) | ||
log.Printf("Downloading to: %s\n", filepath.Join(installLocation, "/", fileName)) | ||
|
||
response, err := http.Get(url) | ||
if err != nil { | ||
fmt.Println("[Error] : Error while downloading", url, "-", err) | ||
log.Fatal("[Error] : Error while downloading", url, "-", err) | ||
return "", err | ||
} | ||
defer response.Body.Close() | ||
|
||
if response.StatusCode != 200 { | ||
//Sometimes hashicorp terraform file names are not consistent | ||
//For example 0.12.0-alpha4 naming convention in the release repo is not consistent | ||
return "", fmt.Errorf("[Error] : Unable to download from %s", url) | ||
log.Fatalf("[Error] : Unable to download from %s", url) | ||
} | ||
|
||
zipFile := filepath.Join(installLocation, fileName) | ||
output, err := os.Create(zipFile) | ||
if err != nil { | ||
fmt.Println("[Error] : Error while creating", zipFile, "-", err) | ||
log.Fatal("[Error] : Error while creating", zipFile, "-", err) | ||
return "", err | ||
} | ||
defer output.Close() | ||
|
||
n, err := io.Copy(output, response.Body) | ||
if err != nil { | ||
fmt.Println("[Error] : Error while downloading", url, "-", err) | ||
log.Fatal("[Error] : Error while writing file", url, "-", err) | ||
return "", err | ||
} | ||
|
||
fmt.Println(n, "bytes downloaded") | ||
log.Println(n, "bytes downloaded") | ||
return zipFile, nil | ||
} | ||
|
||
func downloadPublicKey(installLocation string, targetFileName string) error { | ||
fmt.Println("Looking up public key file at ", targetFileName) | ||
publicKeyFileExists := FileExists(targetFileName) | ||
if !publicKeyFileExists { | ||
// Public key does not exist. Let's grab it from hashicorp | ||
pubKeyFile, errDl := downloadFromURL(installLocation, pubKeyUri) | ||
if errDl != nil { | ||
log.Fatal("[Error]: Error while fetching the public key file from ", pubKeyUri) | ||
return errDl | ||
} | ||
errRename := os.Rename(pubKeyFile, targetFileName) | ||
if errRename != nil { | ||
log.Fatal("[Error]: Error while renaming the public key file from ", pubKeyFile, " to ", targetFileName) | ||
return errRename | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package lib | ||
|
||
import "os" | ||
|
||
// fileExists checks if a file exists and is not a directory before we try using it to prevent further errors. | ||
func FileExists(filename string) bool { | ||
info, err := os.Stat(filename) | ||
if os.IsNotExist(err) { | ||
return false | ||
} | ||
return !info.IsDir() | ||
} |
Oops, something went wrong.