Skip to content
This repository has been archived by the owner on Jan 4, 2022. It is now read-only.

[WIP] Check GnuPG signature of CoreOS image #251

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion cmd/kube-spawn/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func init() {
createCmd.Flags().Bool("dev", false, "create a cluster from a local build of Kubernetes")
createCmd.Flags().IntP("nodes", "n", 0, "number of nodes to spawn")
createCmd.Flags().StringP("image", "i", "", "base image for nodes")
createCmd.Flags().Bool("image-gpg-verify", true, "GnuPG image verification. Defaults to true")
viper.BindPFlags(createCmd.Flags())

viper.BindEnv("runtime-config.rkt.rkt-bin", "KUBE_SPAWN_RKT_BIN")
Expand Down Expand Up @@ -144,7 +145,7 @@ func doCreate() {
})

if cfg.Image == config.DefaultBaseImage {
if err := bootstrap.PrepareCoreosImage(); err != nil {
if err := bootstrap.PrepareCoreosImage(cfg.ImageGpgVerify); err != nil {
log.Fatal(errors.Wrap(err, "error setting up default base image"))
}
}
Expand Down
194 changes: 162 additions & 32 deletions pkg/bootstrap/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ package bootstrap
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path"
Expand All @@ -29,10 +31,12 @@ import (
"syscall"

"github.com/Masterminds/semver"
"github.com/coreos/ioprogress"
"github.com/kinvolk/kube-spawn/pkg/config"
"github.com/kinvolk/kube-spawn/pkg/machinetool"
"github.com/kinvolk/kube-spawn/pkg/utils/fs"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
)

const (
Expand All @@ -43,6 +47,10 @@ const (
machinesDir string = "/var/lib/machines"
machinesImage string = "/var/lib/machines.raw"
coreosStableVersion string = "1478.0.0"
imageUrl string = "https://alpha.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2"
signatureUrl string = "https://alpha.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2.sig"
imageTmpFile string = "/tmp/coreos_developer_container.bin.bz2"
signatureTmpFile string = "/tmp/coreos_developer_container.bin.bz2.sig"
)

type Node struct {
Expand Down Expand Up @@ -745,57 +753,179 @@ func checkCoreosVersion() error {
return nil
}

func pullRawCoreosImage() error {
var cmdPath string
var err error
func ensureCoreosVersion() {
if err := checkCoreosVersion(); err != nil {
log.Println(err)
log.Fatalf("You will need to remove the image by 'sudo machinectl remove coreos' then the next run of kube-spawn will download version %s of coreos image automatically.", coreosStableVersion)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to stop here? Can we do the work for the user?

}
}

// TODO: use machinetool pkg
if cmdPath, err = exec.LookPath("machinectl"); err != nil {
// fall back to an ordinary abspath to machinectl
cmdPath = "/usr/bin/machinectl"
func PrepareCoreosImage(ImageGpgVerify bool) error {
// If no coreos image exists, just download it
if !machinetool.ImageExists("coreos") {
log.Printf("pulling coreos image...")
if err := pullRawCoreosImage(ImageGpgVerify); err != nil {
return err
}
} else {
// If coreos image is not new enough, remove the existing image,
// then next time `kube-spawn up` will download a new image again.
ensureCoreosVersion()
}
return nil
}

args := []string{
cmdPath,
"pull-raw",
"--verify=no",
"https://alpha.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2",
"coreos",
func downloadFile(dest, url string) error {
f, err := os.Create(dest)
if err != nil {
return err
}
defer f.Close()

cmd := exec.Cmd{
Path: cmdPath,
Args: args,
response, err := http.Get(url)
if err != nil {
return err
}
defer response.Body.Close()

progress := &ioprogress.Reader{
Reader: response.Body,
Size: response.ContentLength,
}

if _, err := io.Copy(f, progress); err != nil {
return err
}

if err := f.Sync(); err != nil {
return err
}

return nil
}

func downloadImage() error {
return downloadFile(imageTmpFile, imageUrl)
}

func downloadSignature() error {
return downloadFile(signatureTmpFile, signatureUrl)
}

func verifyImage() error {
// TODO(nhlfr): Try to use or implement some library for managing PGP keys instead
// of executing gpg binary.
gpgCmdPath, err := exec.LookPath("gpg")
if err != nil {
gpgCmdPath = "/usr/bin/gpg"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some systems have both gpg and gpg2. Not sure picking /usr/bin/gpg is always right.

}

importPubKeyArgs := []string{
gpgCmdPath,
"--keyserver",
"keyserver.ubuntu.com",
"--recv-key",
"50E0885593D2DCB4",
}

importPubKeyCmd := exec.Cmd{
Path: gpgCmdPath,
Args: importPubKeyArgs,
Env: os.Environ(),
Stdout: os.Stdout,
Stderr: os.Stderr,
}

if err := cmd.Run(); err != nil {
return fmt.Errorf("error running machinectl pull-raw: %s", err)
if err := importPubKeyCmd.Run(); err != nil {
return err
}

exportPubKeyArgs := []string{
gpgCmdPath,
"--export",
"50E0885593D2DCB4",
"--export-options",
"export-minimal,no-export-attributes",
}

exportPubKeyCmd := exec.Cmd{
Path: gpgCmdPath,
Args: exportPubKeyArgs,
Env: os.Environ(),
Stderr: os.Stderr,
}

exportStdout, err := exportPubKeyCmd.StdoutPipe()
if err != nil {
return fmt.Errorf("error creating stdout pipe: %s", err)
}
defer exportStdout.Close()

if err := exportPubKeyCmd.Start(); err != nil {
return fmt.Errorf("error running gpg: %s", err)
}

pubKeyArmor, err := ioutil.ReadAll(exportStdout)
if err != nil {
return fmt.Errorf("error reading public key from stdout: %s", err)
}

if err := exportPubKeyCmd.Wait(); err != nil {
return fmt.Errorf("error running gpg: %s", err)
}

imageFile, err := os.Open(imageTmpFile)
if err != nil {
return err
}
defer imageFile.Close()

keyRingReader := strings.NewReader(string(pubKeyArmor))
keyring, err := openpgp.ReadKeyRing(keyRingReader)
if err != nil {
return fmt.Errorf("error reading keyring: %v\n", err)
}

signatureFile, err := os.Open(signatureTmpFile)
if err != nil {
return err
}
defer signatureFile.Close()

_, err = openpgp.CheckDetachedSignature(keyring, imageFile, signatureFile)
if err != nil {
return fmt.Errorf("error checking detached signature: %v\n", err)
}

return nil
}

func ensureCoreosVersion() {
if err := checkCoreosVersion(); err != nil {
log.Println(err)
log.Fatalf("You will need to remove the image by 'sudo machinectl remove coreos' then the next run of kube-spawn will download version %s of coreos image automatically.", coreosStableVersion)
func pullRawCoreosImage(imageGpgVerify bool) error {
if err := downloadImage(); err != nil {
return err
}
}
defer os.Remove(imageTmpFile)

func PrepareCoreosImage() error {
// If no coreos image exists, just download it
if !machinetool.ImageExists("coreos") {
log.Printf("pulling coreos image...")
if err := pullRawCoreosImage(); err != nil {
log.Println("Image downloaded successfully")

if imageGpgVerify {
if err := downloadSignature(); err != nil {
return err
}
} else {
// If coreos image is not new enough, remove the existing image,
// then next time `kube-spawn up` will download a new image again.
ensureCoreosVersion()
defer os.Remove(signatureTmpFile)

if err := verifyImage(); err != nil {
return err
}

log.Println("Image verified successfully")
}

log.Printf("Importing raw image %s ...", imageTmpFile)
if err := machinetool.ImportRaw(imageTmpFile, "coreos"); err != nil {
return fmt.Errorf("error importing image %s\n", imageTmpFile)
}
log.Printf("Done.")

return nil
}
1 change: 1 addition & 0 deletions pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type ClusterConfiguration struct {
KubernetesVersion string `toml:"kubernetes-version" mapstructure:"kubernetes-version"`
Image string `toml:"image" mapstructure:"image"`
Nodes int `toml:"nodes" mapstructure:"nodes"`
ImageGpgVerify bool `toml:"image-gpg-verify" mapstructure:"image-gpg-verify"`

// DevCluster indicates if we should run
// from a local kubernetes build
Expand Down
5 changes: 5 additions & 0 deletions pkg/machinetool/machinetool.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func Terminate(machine string) error {
return err
}

func ImportRaw(imagePath, imageName string) error {
_, err := machinectl(nil, nil, "--verify=no", "import-raw", imagePath, imageName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is machinectl's --verify not an option for verification?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is machinectl's --verify not an option for verification?

Because systemd only supports special name of GPG key files, which are not supported from CoreOS. AFAIK that was the whole point of writing this code in this place.

Quote from machinectl(1), in the section of pull-tar/pull-raw.

Verification is done via SHA256SUMS and SHA256SUMS.gpg files that need to be made available on the same web server, under the same URL as the .tar file, but with the same web server, under the same URL as the .tar file, but with the last component (the filename) of the URL replaced.

Maybe @nhlfr could give us more background.

return err
}

func RemoveImage(image string) error {
_, err := machinectl(nil, nil, "", "remove", image)
return err
Expand Down

This file was deleted.

This file was deleted.

21 changes: 21 additions & 0 deletions vendor/github.com/coreos/ioprogress/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions vendor/github.com/coreos/ioprogress/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading