diff --git a/internal/versions/hash-generator/BUILD.bazel b/internal/versions/hash-generator/BUILD.bazel index 4503eb85e0..8b92d86531 100644 --- a/internal/versions/hash-generator/BUILD.bazel +++ b/internal/versions/hash-generator/BUILD.bazel @@ -6,7 +6,10 @@ go_library( srcs = ["generate.go"], importpath = "github.com/edgelesssys/constellation/v2/internal/versions/hash-generator", visibility = ["//visibility:private"], - deps = ["@org_golang_x_tools//go/ast/astutil"], + deps = [ + "//internal/sigstore", + "@org_golang_x_tools//go/ast/astutil", + ], ) go_binary( diff --git a/internal/versions/hash-generator/generate.go b/internal/versions/hash-generator/generate.go index 2df826860b..58f3dcc743 100644 --- a/internal/versions/hash-generator/generate.go +++ b/internal/versions/hash-generator/generate.go @@ -19,10 +19,14 @@ import ( "log" "net/http" "os" + "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] @@ -78,9 +82,80 @@ 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) + } + + base64Cert, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + + // TODO: implement https://github.com/sigstore/cosign/blob/v2.2.0/cmd/cosign/cli/verify/verify_blob.go keyless verification + + return verifier.VerifySignature(content, sig) +} + func main() { fmt.Println("Generating hashes...")