diff --git a/.github/workflows/test-go-generate.yml b/.github/workflows/test-go-generate.yml index 8607c1d4b2..3de60f35e5 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 816ef80648..efdf9fa7a4 100644 --- a/internal/versions/generateHashes.go +++ b/internal/versions/generateHashes.go @@ -21,10 +21,15 @@ import ( "log" "net/http" "os" + "regexp" + "strconv" + "github.com/edgelesssys/constellation/v2/internal/sigstore" "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,78 @@ 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 { + content, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + if err := verifyCosignSignature(content, url); err != nil { + panic(err) + } + } + } + return fmt.Sprintf("\"sha256:%x\"", fileHash) } +func verifyCosignSignature(content []byte, url string) error { + // 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) + } + + sig, err := io.ReadAll(resp.Body) + if 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) + } + + cert, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + + return sigstore.VerifySignature(content, sig, cert) +} + func main() { fmt.Println("Generating hashes...")