blob: 264871928dabca8075e017e90533d7b859acd475 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package providercache
import (
"archive/zip"
"bytes"
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
)
type providerPkg struct {
zipContent []byte
shaSumContent []byte
sigContent []byte
key *bytes.Buffer
keyID string
filePrefix string // <provider_name>_<version>
}
// createTestProvider creates a test provider package with required verification files
func createTestProvider(t *testing.T, providerName, version string) *providerPkg {
entity, pub := generateGPGKey(t)
dir := t.TempDir()
fileName := fmt.Sprintf("%s_%s", providerName, version)
// Create a zip file for the provider
zipFile := filepath.Join(dir, fileName)
zipContent := createProviderZip(t, zipFile, fileName)
// Generate SHA256SUMS file
sumFile, sumContent, err := createSHASums(dir, fileName)
if err != nil {
t.Fatalf("failed to create SHA sums: %s", err)
}
// Sign the SHA256SUMS file
sigContent, err := createDetachedSignature(sumFile, entity)
if err != nil {
t.Fatalf("failed to create signature: %s", err)
}
return &providerPkg{
zipContent: zipContent,
shaSumContent: sumContent,
sigContent: sigContent,
key: pub,
keyID: entity.PrimaryKey.KeyIdString(),
filePrefix: fileName,
}
}
func generateGPGKey(t *testing.T) (*openpgp.Entity, *bytes.Buffer) {
entity, err := openpgp.NewEntity("Terraform Test", "test", "terraform@example.com", nil)
if err != nil {
t.Fatalf("failed to create entity: %s", err)
}
// Export the public key in armored format
pubBuf := bytes.NewBuffer(nil)
w, err := armor.Encode(pubBuf, openpgp.PublicKeyType, nil)
if err != nil {
t.Fatalf("failed to create armor writer: %s", err)
}
if err := entity.Serialize(w); err != nil {
t.Fatalf("failed to serialize key: %s", err)
}
if err := w.Close(); err != nil {
t.Fatalf("failed to finalize armor: %s", err)
}
return entity, pubBuf
}
func createProviderZip(t *testing.T, zipPath, filename string) []byte {
zipBuffer := new(bytes.Buffer)
zipWriter := zip.NewWriter(zipBuffer)
// Add file to the archive
_, err := zipWriter.Create(fmt.Sprintf("terraform-provider-%s", filename))
if err != nil {
t.Fatalf("failed to create zip entry: %s", err)
}
if err := zipWriter.Close(); err != nil {
t.Fatalf("failed to close zip writer: %s", err)
}
// Write the zip to a file
zipContent := zipBuffer.Bytes()
if err := os.WriteFile(zipPath+".zip", zipContent, 0666); err != nil {
t.Fatalf("failed to write zip file: %s", err)
}
return zipContent
}
// createDetachedSignature creates a PGP detached signature for the given file
func createDetachedSignature(filePath string, entity *openpgp.Entity) ([]byte, error) {
r, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer safeClose(r)
sigPath := filePath + ".sig"
w, err := os.Create(sigPath)
if err != nil {
return nil, err
}
defer safeClose(w)
if err := openpgp.DetachSign(w, entity, r, nil); err != nil {
return nil, err
}
// Read the signature back
return os.ReadFile(sigPath)
}
// createSHASums creates SHA256 sums for all files in the directory
func createSHASums(dir, name string) (string, []byte, error) {
var sums []string
files, err := os.ReadDir(dir)
if err != nil {
return "", nil, err
}
for _, file := range files {
if file.IsDir() {
continue
}
filePath := filepath.Join(dir, file.Name())
sum, err := calculateFileHash(filePath)
if err != nil {
return "", nil, err
}
sums = append(sums, fmt.Sprintf("%x %s", sum, file.Name()))
}
sumContent := []byte(strings.Join(sums, "\n") + "\n")
sumPath := filepath.Join(dir, fmt.Sprintf("%s_SHA256SUMS", name))
if err := os.WriteFile(sumPath, sumContent, 0666); err != nil {
return "", nil, err
}
return sumPath, sumContent, nil
}
// calculateFileHash returns the SHA256 hash of a file
func calculateFileHash(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer safeClose(f)
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// safeClose safely closes a file, logging any errors
func safeClose(f *os.File) {
if err := f.Close(); err != nil {
fmt.Printf("failed to close file: %s\n", err)
}
}