Merge pull request #22 from monstermunchkin/issues/15

Extend GPG support and add Suite option to Source
This commit is contained in:
Christian Brauner 2018-03-01 10:03:38 +01:00 committed by GitHub
commit 11c776100a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 133 additions and 63 deletions

View File

@ -161,8 +161,8 @@ func run(c *cli.Context) error {
}
// Download the root filesystem
err = downloader.Run(def.Source, def.Image.Release, def.Image.Variant,
arch, c.GlobalString("cache-dir"))
err = downloader.Run(def.Source, def.Image.Release, arch,
c.GlobalString("cache-dir"))
if err != nil {
return fmt.Errorf("Error while downloading source: %s", err)
}

View File

@ -32,9 +32,11 @@ type DefinitionImage struct {
// A DefinitionSource specifies the download type and location
type DefinitionSource struct {
Downloader string `yaml:"downloader"`
URL string `yaml:"url"`
Keys []string `yaml:"keys"`
URL string `yaml:"url,omitempty"`
Keys []string `yaml:"keys,omitempty"`
Keyserver string `yaml:"keyserver,omitempty"`
Variant string `yaml:"variant,omitempty"`
Suite string `yaml:"suite,omitempty"`
}
// A DefinitionTargetLXC represents LXC specific files as part of the metadata.
@ -97,6 +99,11 @@ func SetDefinitionDefaults(def *Definition) {
if def.Source.Keyserver == "" {
def.Source.Keyserver = "hkps.pool.sks-keyservers.net"
}
// If no Source.Variant is specified, use the one in Image.Variant.
if def.Source.Variant == "" {
def.Source.Variant = def.Image.Variant
}
}
// ValidateDefinition validates the given Definition.
@ -116,14 +123,6 @@ func ValidateDefinition(def Definition) error {
return fmt.Errorf("source.downloader must be one of %v", validDownloaders)
}
if strings.TrimSpace(def.Source.URL) == "" {
return errors.New("source.url may not be empty")
}
if len(def.Source.Keys) == 0 {
return errors.New("source.keys may not be empty")
}
validManagers := []string{
"apk",
"apt",

View File

@ -47,6 +47,41 @@ func TestValidateDefinition(t *testing.T) {
"",
false,
},
{
"valid Definition without source.keys",
Definition{
Image: DefinitionImage{
Distribution: "ubuntu",
Release: "artful",
},
Source: DefinitionSource{
Downloader: "debootstrap",
URL: "https://ubuntu.com",
},
Packages: DefinitionPackages{
Manager: "apt",
},
},
"",
false,
},
{
"valid Defintion without source.url",
Definition{
Image: DefinitionImage{
Distribution: "ubuntu",
Release: "artful",
},
Source: DefinitionSource{
Downloader: "debootstrap",
},
Packages: DefinitionPackages{
Manager: "apt",
},
},
"",
false,
},
{
"empty image.distribution",
Definition{},
@ -67,35 +102,6 @@ func TestValidateDefinition(t *testing.T) {
"source.downloader must be one of .+",
true,
},
{
"empty source.url",
Definition{
Image: DefinitionImage{
Distribution: "ubuntu",
Release: "artful",
},
Source: DefinitionSource{
Downloader: "debootstrap",
},
},
"source.url may not be empty",
true,
},
{
"empty source.keys",
Definition{
Image: DefinitionImage{
Distribution: "ubuntu",
Release: "artful",
},
Source: DefinitionSource{
Downloader: "debootstrap",
URL: "https://ubuntu.com",
},
},
"source.keys may not be empty",
true,
},
{
"invalid package.manager",
Definition{
@ -123,6 +129,9 @@ func TestValidateDefinition(t *testing.T) {
if !tt.shouldFail && err != nil {
t.Fatalf("Validation failed: %s", err)
} else if tt.shouldFail {
if err == nil {
t.Fatal("Expected failure")
}
match, _ := regexp.MatchString(tt.expected, err.Error())
if !match {
t.Fatalf("Validation failed: Expected '%s', got '%s'", tt.expected, err.Error())

View File

@ -57,29 +57,19 @@ func RunCommand(name string, arg ...string) error {
// VerifyFile verifies a file using gpg.
func VerifyFile(signedFile, signatureFile string, keys []string, keyserver string) (bool, error) {
var out string
gpgDir := filepath.Join(os.TempDir(), "distrobuilder.gpg")
err := os.MkdirAll(gpgDir, 0700)
gpgDir, err := CreateGPGKeyring(keyserver, keys)
if err != nil {
return false, err
}
defer os.RemoveAll(gpgDir)
out, err = lxd.RunCommand("gpg", append([]string{
"--homedir", gpgDir, "--keyserver", keyserver, "--recv-keys"}, keys...)...)
if err != nil {
return false, fmt.Errorf("Failed to receive keys: %s", out)
}
if signatureFile != "" {
out, err = lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signatureFile, signedFile)
out, err := lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signatureFile, signedFile)
if err != nil {
return false, fmt.Errorf("Failed to verify: %s", out)
}
} else {
out, err = lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signedFile)
out, err := lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signedFile)
if err != nil {
return false, fmt.Errorf("Failed to verify: %s", out)
}
@ -88,6 +78,32 @@ func VerifyFile(signedFile, signatureFile string, keys []string, keyserver strin
return true, nil
}
// CreateGPGKeyring creates a new GPG keyring.
func CreateGPGKeyring(keyserver string, keys []string) (string, error) {
gpgDir := filepath.Join(os.TempDir(), "distrobuilder.gpg")
err := os.MkdirAll(gpgDir, 0700)
if err != nil {
return "", err
}
args := []string{"--homedir", gpgDir}
if keyserver != "" {
args = append(args, "--keyserver", keyserver)
}
args = append(args, append([]string{"--recv-keys"}, keys...)...)
out, err := lxd.RunCommand("gpg", args...)
if err != nil {
os.RemoveAll(gpgDir)
return "", fmt.Errorf("Failed to create keyring: %s", out)
}
return gpgDir, nil
}
// Pack creates an xz-compressed tarball.
func Pack(filename, path string, args ...string) error {
return RunCommand("tar", append([]string{"-cJf", filename, "-C", path}, args...)...)

View File

@ -5,6 +5,8 @@ import (
"os"
"path/filepath"
"testing"
lxd "github.com/lxc/lxd/shared"
)
func TestVerifyFile(t *testing.T) {
@ -87,3 +89,26 @@ func TestVerifyFile(t *testing.T) {
}
}
}
func TestCreateGPGKeyring(t *testing.T) {
gpgDir, err := CreateGPGKeyring("pgp.mit.edu", []string{"0x5DE8949A899C8D99"})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if !lxd.PathExists(gpgDir) {
t.Fatalf("Failed to create gpg directory: %s", gpgDir)
}
os.RemoveAll(gpgDir)
// This shouldn't fail either.
gpgDir, err = CreateGPGKeyring("", []string{})
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if !lxd.PathExists(gpgDir) {
t.Fatalf("Failed to create gpg directory: %s", gpgDir)
}
os.RemoveAll(gpgDir)
}

View File

@ -20,7 +20,7 @@ func NewAlpineLinuxHTTP() *AlpineLinuxHTTP {
}
// Run downloads an Alpine Linux mini root filesystem.
func (s *AlpineLinuxHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
func (s *AlpineLinuxHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
fname := fmt.Sprintf("alpine-minirootfs-%s-%s.tar.gz", release, arch)
tarball := fmt.Sprintf("%s/v%s/releases/%s/%s", source.URL,
strings.Join(strings.Split(release, ".")[0:2], "."), arch, fname)

View File

@ -19,7 +19,7 @@ func NewArchLinuxHTTP() *ArchLinuxHTTP {
}
// Run downloads an Arch Linux tarball.
func (s *ArchLinuxHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
func (s *ArchLinuxHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
fname := fmt.Sprintf("archlinux-bootstrap-%s-x86_64.tar.gz", release)
tarball := fmt.Sprintf("%s/%s/%s", source.URL, release, fname)

View File

@ -27,11 +27,11 @@ func NewCentOSHTTP() *CentOSHTTP {
}
// Run downloads the tarball and unpacks it.
func (s *CentOSHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
func (s *CentOSHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
s.cacheDir = cacheDir
baseURL := fmt.Sprintf("%s/%s/isos/%s/", source.URL, strings.Split(release, ".")[0], arch)
s.fname = getRelease(source.URL, release, variant, arch)
s.fname = getRelease(source.URL, release, source.Variant, arch)
if s.fname == "" {
return fmt.Errorf("Couldn't get name of iso")
}

View File

@ -16,24 +16,45 @@ func NewDebootstrap() *Debootstrap {
}
// Run runs debootstrap.
func (s *Debootstrap) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
func (s *Debootstrap) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
var args []string
os.RemoveAll(filepath.Join(cacheDir, "rootfs"))
if variant != "" {
args = append(args, "--variant", variant)
if source.Variant != "" {
args = append(args, "--variant", source.Variant)
}
if arch != "" {
args = append(args, "--arch", arch)
}
if len(source.Keys) > 0 {
gpgDir, err := shared.CreateGPGKeyring(source.Keyserver, source.Keys)
if err != nil {
return err
}
defer os.RemoveAll(gpgDir)
args = append(args, "--keyring", filepath.Join(gpgDir, "pubring.kbx"))
}
args = append(args, release, filepath.Join(cacheDir, "rootfs"))
if source.URL != "" {
args = append(args, source.URL)
}
// If source.Suite is set, create a symlink in /usr/share/debootstrap/scripts
// pointing release to source.Suite.
if source.Suite != "" {
link := filepath.Join("/usr/share/debootstrap/scripts", release)
err := os.Symlink(source.Suite, link)
if err != nil {
return err
}
defer os.Remove(link)
}
return shared.RunCommand("debootstrap", args...)
}

View File

@ -4,7 +4,7 @@ import "github.com/lxc/distrobuilder/shared"
// A Downloader represents a source downloader.
type Downloader interface {
Run(shared.DefinitionSource, string, string, string, string) error
Run(shared.DefinitionSource, string, string, string) error
}
// Get returns a Downloader.

View File

@ -25,7 +25,7 @@ func NewUbuntuHTTP() *UbuntuHTTP {
}
// Run downloads the tarball and unpacks it.
func (s *UbuntuHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
func (s *UbuntuHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
baseURL := fmt.Sprintf("%s/releases/%s/release/", source.URL, release)
if strings.ContainsAny(release, "0123456789") {