diff --git a/.github/workflows/test-go-generate.yml b/.github/workflows/test-go-generate.yml index 8607c1d4b20..3de60f35e5c 100644 --- a/.github/workflows/test-go-generate.yml +++ b/.github/workflows/test-go-generate.yml @@ -28,6 +28,9 @@ jobs: - name: Install docgen uses: ./constellation/.github/actions/install_docgen + - name: Install Cosign + uses: sigstore/cosign-installer@9becc617647dfa20ae7b1151972e9b3a2c338a2b # tag=v2.8.1 + - name: Install stringer shell: bash run: go install golang.org/x/tools/cmd/stringer@latest diff --git a/internal/versions/generateHashes.go b/internal/versions/generateHashes.go index 816ef80648f..918a196b36d 100644 --- a/internal/versions/generateHashes.go +++ b/internal/versions/generateHashes.go @@ -21,10 +21,15 @@ import ( "log" "net/http" "os" + "os/exec" + "regexp" + "strconv" "golang.org/x/tools/go/ast/astutil" ) +var kuberntesMinorRegex = regexp.MustCompile(`^.*\.(?P\d+)\..*(kubelet|kubeadm|kubectl)+$`) + func mustGetHash(url string) string { // remove quotes around url url = url[1 : len(url)-1] @@ -80,9 +85,83 @@ func mustGetHash(url string) string { panic("hash mismatch") } + // Verify cosign signature if available + // Currently, we verify the signature of kubeadm, kubelet and kubectl with minor version >=1.26. + minorVersion := kuberntesMinorRegex.FindStringSubmatch(url) + if minorVersion == nil { + return fmt.Sprintf("\"sha256:%x\"", fileHash) + } + minorVersionIndex := kuberntesMinorRegex.SubexpIndex("Minor") + if minorVersionIndex != -1 { + minorVersionNumber, err := strconv.Atoi(minorVersion[minorVersionIndex]) + if err != nil { + panic(err) + } + if minorVersionNumber >= 26 { + verifyCosignSignature(url) + } + } + return fmt.Sprintf("\"sha256:%x\"", fileHash) } +func verifyCosignSignature(url string) { + tmpSig, err := os.CreateTemp("", url+".sig") + if err != nil { + panic(err) + } + defer os.Remove(tmpSig.Name()) + tmpCert, err := os.CreateTemp("", url+".cert") + if err != nil { + panic(err) + } + defer os.Remove(tmpCert.Name()) + + // Get the signature + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url+".sig", nil) + if err != nil { + panic(err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + // Check server response + if resp.StatusCode != http.StatusOK { + panic("bad status: " + resp.Status) + } + + if _, err = io.Copy(tmpSig, resp.Body); err != nil { + panic(err) + } + + // Get the certificate + req, err = http.NewRequestWithContext(context.Background(), http.MethodGet, url+".cert", nil) + if err != nil { + panic(err) + } + resp, err = http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + // Check server response + if resp.StatusCode != http.StatusOK { + panic("bad status: " + resp.Status) + } + + if _, err = io.Copy(tmpCert, resp.Body); err != nil { + panic(err) + } + + if err := exec.CommandContext(context.Background(), "cosign", "verify-blob", "-signature", tmpSig.Name(), "-certificate", tmpCert.Name()).Run(); err != nil { + panic(err) + } +} + func main() { fmt.Println("Generating hashes...")