diff --git a/Dockerfile b/Dockerfile index d4e7614c..b2a61a76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM alpine:3.19 as builder RUN ["apk", "add", "jq"] +RUN ["apk", "add", "bash"] WORKDIR /build diff --git a/bundle/deploy/terraform/pkg.go b/bundle/deploy/terraform/pkg.go index 911583f2..cad75402 100644 --- a/bundle/deploy/terraform/pkg.go +++ b/bundle/deploy/terraform/pkg.go @@ -15,18 +15,40 @@ const TerraformVersionEnv = "DATABRICKS_TF_VERSION" const TerraformCliConfigPathEnv = "DATABRICKS_TF_CLI_CONFIG_FILE" const TerraformProviderVersionEnv = "DATABRICKS_TF_PROVIDER_VERSION" +// Terraform CLI version to use and the corresponding checksums for it. The +// checksums are used to verify the integrity of the downloaded binary. Please +// update the checksums when the Terraform version is updated. The checksums +// were obtained from https://releases.hashicorp.com/terraform/1.5.5. +// +// These hashes are not used inside the CLI. They are only co-located here to be +// output in the "databricks bundle debug terraform" output. Downstream applications +// like the CLI docker image use these checksums to verify the integrity of the +// downloaded Terraform archive. var TerraformVersion = version.Must(version.NewVersion("1.5.5")) +const checksumLinuxArm64 = "b055aefe343d0b710d8a7afd31aeb702b37bbf4493bb9385a709991e48dfbcd2" +const checksumLinuxAmd64 = "ad0c696c870c8525357b5127680cd79c0bdf58179af9acd091d43b1d6482da4a" + +type Checksum struct { + LinuxArm64 string `json:"linux_arm64"` + LinuxAmd64 string `json:"linux_amd64"` +} + type TerraformMetadata struct { - Version string `json:"version"` - ProviderHost string `json:"providerHost"` - ProviderSource string `json:"providerSource"` - ProviderVersion string `json:"providerVersion"` + Version string `json:"version"` + Checksum Checksum `json:"checksum"` + ProviderHost string `json:"providerHost"` + ProviderSource string `json:"providerSource"` + ProviderVersion string `json:"providerVersion"` } func NewTerraformMetadata() *TerraformMetadata { return &TerraformMetadata{ - Version: TerraformVersion.String(), + Version: TerraformVersion.String(), + Checksum: Checksum{ + LinuxArm64: checksumLinuxArm64, + LinuxAmd64: checksumLinuxAmd64, + }, ProviderHost: schema.ProviderHost, ProviderSource: schema.ProviderSource, ProviderVersion: schema.ProviderVersion, diff --git a/bundle/deploy/terraform/pkg_test.go b/bundle/deploy/terraform/pkg_test.go new file mode 100644 index 00000000..b8dcb9e0 --- /dev/null +++ b/bundle/deploy/terraform/pkg_test.go @@ -0,0 +1,51 @@ +package terraform + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func downloadAndChecksum(t *testing.T, url string, expectedChecksum string) { + resp, err := http.Get(url) + require.NoError(t, err) + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("failed to download %s: %s", url, resp.Status) + } + + tmpDir := t.TempDir() + tmpFile, err := os.Create(filepath.Join(tmpDir, "archive.zip")) + require.NoError(t, err) + defer tmpFile.Close() + + _, err = io.Copy(tmpFile, resp.Body) + require.NoError(t, err) + + _, err = tmpFile.Seek(0, 0) // go back to the start of the file + require.NoError(t, err) + + hash := sha256.New() + _, err = io.Copy(hash, tmpFile) + require.NoError(t, err) + + checksum := hex.EncodeToString(hash.Sum(nil)) + assert.Equal(t, expectedChecksum, checksum) +} + +func TestTerraformArchiveChecksums(t *testing.T) { + armUrl := fmt.Sprintf("https://releases.hashicorp.com/terraform/%s/terraform_%s_linux_arm64.zip", TerraformVersion, TerraformVersion) + amdUrl := fmt.Sprintf("https://releases.hashicorp.com/terraform/%s/terraform_%s_linux_amd64.zip", TerraformVersion, TerraformVersion) + + downloadAndChecksum(t, amdUrl, checksumLinuxAmd64) + downloadAndChecksum(t, armUrl, checksumLinuxArm64) +} diff --git a/docker/setup.sh b/docker/setup.sh index 3f6c09dc..0dc06ce1 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -1,12 +1,27 @@ -#!/bin/sh +#!/bin/bash set -euo pipefail DATABRICKS_TF_VERSION=$(/app/databricks bundle debug terraform --output json | jq -r .terraform.version) DATABRICKS_TF_PROVIDER_VERSION=$(/app/databricks bundle debug terraform --output json | jq -r .terraform.providerVersion) +if [ $ARCH != "amd64" ] && [ $ARCH != "arm64" ]; then + echo "Unsupported architecture: $ARCH" + exit 1 +fi + # Download the terraform binary mkdir -p zip wget https://releases.hashicorp.com/terraform/${DATABRICKS_TF_VERSION}/terraform_${DATABRICKS_TF_VERSION}_linux_${ARCH}.zip -O zip/terraform.zip + +# Verify the checksum. This is to ensure that the downloaded archive is not tampered with. +EXPECTED_CHECKSUM="$(/app/databricks bundle debug terraform --output json | jq -r .terraform.checksum.linux_$ARCH)" +COMPUTED_CHECKSUM=$(sha256sum zip/terraform.zip | awk '{ print $1 }') +if [ "$COMPUTED_CHECKSUM" != "$EXPECTED_CHECKSUM" ]; then + echo "Checksum mismatch for Terraform binary. Version: $DATABRICKS_TF_VERSION, Arch: $ARCH, Expected checksum: $EXPECTED_CHECKSUM, Computed checksum: $COMPUTED_CHECKSUM." + exit 1 +fi + +# Unzip the terraform binary. It's safe to do so because we have already verified the checksum. unzip zip/terraform.zip -d zip/terraform mkdir -p /app/bin mv zip/terraform/terraform /app/bin/terraform