Skip to content

Commit 19f9dbe

Browse files
Pin Alpine image digest and add SHA256 verification for TF provider (#4849)
## Summary - Pin `alpine:3.22` in Dockerfile to its `@sha256` digest for reproducible builds - Add SHA256 checksum verification for the Terraform provider download in `docker/setup.sh`, matching the existing pattern for the TF binary - Auto-fetch provider checksums during codegen (`go run .`) from the GitHub release SHA256SUMS file, so bumping the provider version in `version.go` is the only manual step needed - The codegen also downloads the linux_amd64 zip as a sanity check to verify the parsed checksum is correct - Expose provider checksums via `databricks bundle debug terraform --output json` under `providerChecksum` - Gate the existing `TestTerraformArchiveChecksums` behind `testing.Short()` to avoid large downloads on every test run ## Test plan - [x] `go build ./...` compiles cleanly - [x] `go test -short ./bundle/deploy/terraform/` passes (checksum download tests are skipped) This pull request was AI-assisted by Isaac.
1 parent 7498df4 commit 19f9dbe

File tree

9 files changed

+159
-14
lines changed

9 files changed

+159
-14
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM alpine:3.22 as builder
1+
FROM alpine:3.22@sha256:55ae5d250caebc548793f321534bc6a8ef1d116f334f18f4ada1b2daad3251b2 as builder
22

33
RUN ["apk", "add", "jq"]
44
RUN ["apk", "add", "bash"]
@@ -13,7 +13,7 @@ ARG ARCH
1313
RUN /build/docker/setup.sh
1414

1515
# Start from a fresh base image, to remove any build artifacts and scripts.
16-
FROM alpine:3.22
16+
FROM alpine:3.22@sha256:55ae5d250caebc548793f321534bc6a8ef1d116f334f18f4ada1b2daad3251b2
1717

1818
ENV DATABRICKS_TF_EXEC_PATH "/app/bin/terraform"
1919
ENV DATABRICKS_TF_CLI_CONFIG_FILE "/app/config/config.tfrc"

bundle/deploy/terraform/pkg.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,12 @@ type Checksum struct {
7777
}
7878

7979
type TerraformMetadata struct {
80-
Version string `json:"version"`
81-
Checksum Checksum `json:"checksum"`
82-
ProviderHost string `json:"providerHost"`
83-
ProviderSource string `json:"providerSource"`
84-
ProviderVersion string `json:"providerVersion"`
80+
Version string `json:"version"`
81+
Checksum Checksum `json:"checksum"`
82+
ProviderHost string `json:"providerHost"`
83+
ProviderSource string `json:"providerSource"`
84+
ProviderVersion string `json:"providerVersion"`
85+
ProviderChecksum Checksum `json:"providerChecksum"`
8586
}
8687

8788
func NewTerraformMetadata(ctx context.Context) (*TerraformMetadata, error) {
@@ -98,6 +99,10 @@ func NewTerraformMetadata(ctx context.Context) (*TerraformMetadata, error) {
9899
ProviderHost: schema.ProviderHost,
99100
ProviderSource: schema.ProviderSource,
100101
ProviderVersion: schema.ProviderVersion,
102+
ProviderChecksum: Checksum{
103+
LinuxAmd64: schema.ProviderChecksumLinuxAmd64,
104+
LinuxArm64: schema.ProviderChecksumLinuxArm64,
105+
},
101106
}, nil
102107
}
103108

bundle/internal/tf/codegen/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The entry point for this tool is `.`.
77
It uses `./tmp` a temporary data directory and `../schema` as output directory.
88

99
It automatically installs the Terraform binary as well as the Databricks Terraform provider.
10+
It also fetches SHA256 checksums for the provider archive from GitHub releases.
1011

1112
Run with:
1213

bundle/internal/tf/codegen/generator/generator.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ func (c *collection) Generate(path string) error {
3535
}
3636

3737
type root struct {
38-
OutputFile string
39-
ProviderVersion string
38+
OutputFile string
39+
ProviderVersion string
40+
ProviderChecksumLinuxAmd64 string
41+
ProviderChecksumLinuxArm64 string
4042
}
4143

4244
func (r *root) Generate(path string) error {
@@ -51,7 +53,7 @@ func (r *root) Generate(path string) error {
5153
return tmpl.Execute(f, r)
5254
}
5355

54-
func Run(ctx context.Context, schema *tfjson.ProviderSchema, path string) error {
56+
func Run(ctx context.Context, schema *tfjson.ProviderSchema, checksums *schemapkg.ProviderChecksums, path string) error {
5557
// Generate types for resources
5658
var resources []*namedBlock
5759
for _, k := range sortKeys(schema.ResourceSchemas) {
@@ -147,8 +149,10 @@ func Run(ctx context.Context, schema *tfjson.ProviderSchema, path string) error
147149
// Generate root.go
148150
{
149151
r := &root{
150-
OutputFile: "root.go",
151-
ProviderVersion: schemapkg.ProviderVersion,
152+
OutputFile: "root.go",
153+
ProviderVersion: schemapkg.ProviderVersion,
154+
ProviderChecksumLinuxAmd64: checksums.LinuxAmd64,
155+
ProviderChecksumLinuxArm64: checksums.LinuxArm64,
152156
}
153157
err := r.Generate(path)
154158
if err != nil {

bundle/internal/tf/codegen/main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,20 @@ import (
1111
func main() {
1212
ctx := context.Background()
1313

14-
schema, err := schema.Load(ctx)
14+
s, err := schema.Load(ctx)
1515
if err != nil {
1616
log.Fatal(err)
1717
}
1818

19-
err = generator.Run(ctx, schema, "../schema")
19+
log.Printf("fetching provider checksums for v%s", schema.ProviderVersion)
20+
checksums, err := schema.FetchProviderChecksums(schema.ProviderVersion)
21+
if err != nil {
22+
log.Fatal(err)
23+
}
24+
log.Printf(" linux_amd64: %s", checksums.LinuxAmd64)
25+
log.Printf(" linux_arm64: %s", checksums.LinuxArm64)
26+
27+
err = generator.Run(ctx, s, checksums, "../schema")
2028
if err != nil {
2129
log.Fatal(err)
2230
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package schema
2+
3+
import (
4+
"bufio"
5+
"crypto/sha256"
6+
"encoding/hex"
7+
"fmt"
8+
"io"
9+
"log"
10+
"net/http"
11+
"strings"
12+
)
13+
14+
// ProviderChecksums holds the SHA256 checksums for the Databricks Terraform
15+
// provider archive for supported Linux architectures.
16+
type ProviderChecksums struct {
17+
LinuxAmd64 string
18+
LinuxArm64 string
19+
}
20+
21+
// FetchProviderChecksums downloads the SHA256SUMS file from the GitHub release
22+
// for the given provider version and extracts checksums for the linux_amd64 and
23+
// linux_arm64 archives. It also downloads both zips to verify that the parsed
24+
// checksums are correct.
25+
// https://github.com/databricks/terraform-provider-databricks/releases
26+
func FetchProviderChecksums(version string) (*ProviderChecksums, error) {
27+
url := fmt.Sprintf(
28+
"https://github.com/databricks/terraform-provider-databricks/releases/download/v%s/terraform-provider-databricks_%s_SHA256SUMS",
29+
version, version,
30+
)
31+
32+
resp, err := http.Get(url)
33+
if err != nil {
34+
return nil, fmt.Errorf("downloading SHA256SUMS for provider v%s: %w", version, err)
35+
}
36+
defer resp.Body.Close()
37+
38+
if resp.StatusCode != http.StatusOK {
39+
return nil, fmt.Errorf("downloading SHA256SUMS for provider v%s: HTTP %s", version, resp.Status)
40+
}
41+
42+
checksums := &ProviderChecksums{}
43+
amd64Suffix := fmt.Sprintf("terraform-provider-databricks_%s_linux_amd64.zip", version)
44+
arm64Suffix := fmt.Sprintf("terraform-provider-databricks_%s_linux_arm64.zip", version)
45+
46+
scanner := bufio.NewScanner(resp.Body)
47+
for scanner.Scan() {
48+
line := scanner.Text()
49+
parts := strings.Fields(line)
50+
if len(parts) != 2 {
51+
continue
52+
}
53+
switch parts[1] {
54+
case amd64Suffix:
55+
checksums.LinuxAmd64 = parts[0]
56+
case arm64Suffix:
57+
checksums.LinuxArm64 = parts[0]
58+
}
59+
}
60+
if err := scanner.Err(); err != nil {
61+
return nil, fmt.Errorf("reading SHA256SUMS for provider v%s: %w", version, err)
62+
}
63+
64+
if checksums.LinuxAmd64 == "" {
65+
return nil, fmt.Errorf("checksum not found for %s in SHA256SUMS", amd64Suffix)
66+
}
67+
if checksums.LinuxArm64 == "" {
68+
return nil, fmt.Errorf("checksum not found for %s in SHA256SUMS", arm64Suffix)
69+
}
70+
71+
// Sanity check: download both zips and verify the checksums match.
72+
err = verifyProviderChecksum(version, "linux_amd64", checksums.LinuxAmd64)
73+
if err != nil {
74+
return nil, err
75+
}
76+
err = verifyProviderChecksum(version, "linux_arm64", checksums.LinuxArm64)
77+
if err != nil {
78+
return nil, err
79+
}
80+
81+
return checksums, nil
82+
}
83+
84+
// verifyProviderChecksum downloads the provider zip for the given platform and
85+
// verifies it matches the expected SHA256 checksum.
86+
func verifyProviderChecksum(version, platform, expectedChecksum string) error {
87+
url := fmt.Sprintf(
88+
"https://github.com/databricks/terraform-provider-databricks/releases/download/v%s/terraform-provider-databricks_%s_%s.zip",
89+
version, version, platform,
90+
)
91+
92+
log.Printf("verifying checksum for %s provider archive", platform)
93+
resp, err := http.Get(url)
94+
if err != nil {
95+
return fmt.Errorf("downloading provider archive for checksum verification: %w", err)
96+
}
97+
defer resp.Body.Close()
98+
99+
if resp.StatusCode != http.StatusOK {
100+
return fmt.Errorf("downloading provider archive for checksum verification: HTTP %s", resp.Status)
101+
}
102+
103+
hash := sha256.New()
104+
if _, err := io.Copy(hash, resp.Body); err != nil {
105+
return fmt.Errorf("computing checksum for provider archive: %w", err)
106+
}
107+
108+
actualChecksum := hex.EncodeToString(hash.Sum(nil))
109+
if actualChecksum != expectedChecksum {
110+
return fmt.Errorf("checksum mismatch for %s provider archive: expected %s, got %s", platform, expectedChecksum, actualChecksum)
111+
}
112+
113+
log.Printf("checksum verified for %s provider archive", platform)
114+
return nil
115+
}

bundle/internal/tf/codegen/templates/root.go.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type Root struct {
2222
const ProviderHost = "registry.terraform.io"
2323
const ProviderSource = "databricks/databricks"
2424
const ProviderVersion = "{{ .ProviderVersion }}"
25+
const ProviderChecksumLinuxAmd64 = "{{ .ProviderChecksumLinuxAmd64 }}"
26+
const ProviderChecksumLinuxArm64 = "{{ .ProviderChecksumLinuxArm64 }}"
2527

2628
func NewRoot() *Root {
2729
return &Root{

bundle/internal/tf/schema/root.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker/setup.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,11 @@ mv zip/terraform/terraform /app/bin/terraform
3030
TF_PROVIDER_NAME=terraform-provider-databricks_${DATABRICKS_TF_PROVIDER_VERSION}_linux_${ARCH}.zip
3131
mkdir -p /app/providers/registry.terraform.io/databricks/databricks
3232
wget https://github.com/databricks/terraform-provider-databricks/releases/download/v${DATABRICKS_TF_PROVIDER_VERSION}/${TF_PROVIDER_NAME} -O /app/providers/registry.terraform.io/databricks/databricks/${TF_PROVIDER_NAME}
33+
34+
# Verify the provider checksum.
35+
EXPECTED_PROVIDER_CHECKSUM="$(/app/databricks bundle debug terraform --output json | jq -r .terraform.providerChecksum.linux_$ARCH)"
36+
COMPUTED_PROVIDER_CHECKSUM=$(sha256sum /app/providers/registry.terraform.io/databricks/databricks/${TF_PROVIDER_NAME} | awk '{ print $1 }')
37+
if [ "$COMPUTED_PROVIDER_CHECKSUM" != "$EXPECTED_PROVIDER_CHECKSUM" ]; then
38+
echo "Checksum mismatch for Terraform provider. Version: $DATABRICKS_TF_PROVIDER_VERSION, Arch: $ARCH, Expected checksum: $EXPECTED_PROVIDER_CHECKSUM, Computed checksum: $COMPUTED_PROVIDER_CHECKSUM."
39+
exit 1
40+
fi

0 commit comments

Comments
 (0)