-
Notifications
You must be signed in to change notification settings - Fork 41
[WIP] Check GnuPG signature of CoreOS image #251
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,8 +19,10 @@ package bootstrap | |
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"path" | ||
|
@@ -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 ( | ||
|
@@ -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 { | ||
|
@@ -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) | ||
} | ||
} | ||
|
||
// 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some systems have both |
||
} | ||
|
||
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is machinectl's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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.
Maybe @nhlfr could give us more background. |
||
return err | ||
} | ||
|
||
func RemoveImage(image string) error { | ||
_, err := machinectl(nil, nil, "", "remove", image) | ||
return err | ||
|
This file was deleted.
This file was deleted.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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?