mirror of
https://github.com/lxc/distrobuilder.git
synced 2026-02-05 06:45:19 +01:00
This commit introduces a centralized GPG verification requirement logic in `sources/common.go` via the `validateGPGRequirements` method. It ensures consistent security constraints across multiple supported distributions. Specific security fixes included: - Rocky Linux: Fixed an issue where the `CHECKSUM` file was downloaded but not GPG verified. - CentOS: Fixed an issue where 'SHA256SUM' and 'CHECKSUM' files were downloaded but not GPG verified. - Gentoo: Added GPG requirement validation for the portage snapshot download URL. Fixes: https://github.com/lxc/distrobuilder/issues/963 Signed-off-by: Chaosoffire <81634128+chaosoffire@users.noreply.github.com>
150 lines
3.8 KiB
Go
150 lines
3.8 KiB
Go
package sources
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/antchfx/htmlquery"
|
|
|
|
"github.com/lxc/distrobuilder/shared"
|
|
)
|
|
|
|
type archlinux struct {
|
|
common
|
|
}
|
|
|
|
// Run downloads an Arch Linux tarball.
|
|
func (s *archlinux) Run() error {
|
|
release := s.definition.Image.Release
|
|
|
|
// Releases are only available for the x86_64 architecture. ARM only has
|
|
// a "latest" tarball.
|
|
if s.definition.Image.ArchitectureMapped == "x86_64" && release == "" {
|
|
var err error
|
|
|
|
// Get latest release
|
|
release, err = s.getLatestRelease(s.definition.Source.URL, s.definition.Image.ArchitectureMapped)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to get latest release: %w", err)
|
|
}
|
|
}
|
|
|
|
var fname string
|
|
var tarball string
|
|
|
|
switch s.definition.Image.ArchitectureMapped {
|
|
case "x86_64":
|
|
fname = fmt.Sprintf("archlinux-bootstrap-%s-%s.tar.zst",
|
|
release, s.definition.Image.ArchitectureMapped)
|
|
tarball = fmt.Sprintf("%s/%s/%s", s.definition.Source.URL,
|
|
release, fname)
|
|
case "riscv64":
|
|
fname = "archriscv-latest.tar.zst"
|
|
tarball = fmt.Sprintf("%s/images/%s", s.definition.Source.URL, fname)
|
|
default:
|
|
fname = fmt.Sprintf("ArchLinuxARM-%s-latest.tar.gz",
|
|
s.definition.Image.ArchitectureMapped)
|
|
tarball = fmt.Sprintf("%s/os/%s", s.definition.Source.URL, fname)
|
|
}
|
|
|
|
url, err := url.Parse(tarball)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse URL %q: %w", tarball, err)
|
|
}
|
|
|
|
skip, err := s.validateGPGRequirements(url)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to validate GPG requirements: %w", err)
|
|
}
|
|
|
|
s.definition.Source.SkipVerification = skip
|
|
|
|
fpath, err := s.DownloadHash(s.definition.Image, tarball, "", nil)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to download %q: %w", tarball, err)
|
|
}
|
|
|
|
if !s.definition.Source.SkipVerification {
|
|
_, err = s.DownloadHash(s.definition.Image, tarball+".sig", "", nil)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed downloading %q: %w", tarball+".sig", err)
|
|
}
|
|
|
|
valid, err := s.VerifyFile(
|
|
filepath.Join(fpath, fname),
|
|
filepath.Join(fpath, fname+".sig"))
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to verify %q: %w", fname, err)
|
|
}
|
|
|
|
if !valid {
|
|
return fmt.Errorf("Invalid signature for %q", fname)
|
|
}
|
|
}
|
|
|
|
s.logger.WithField("file", filepath.Join(fpath, fname)).Info("Unpacking image")
|
|
|
|
// Unpack
|
|
err = shared.Unpack(filepath.Join(fpath, fname), s.rootfsDir)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to unpack file %q: %w", filepath.Join(fpath, fname), err)
|
|
}
|
|
|
|
// Move everything inside 'root.<architecture>' (which was is the tarball) to its
|
|
// parent directory
|
|
files, err := filepath.Glob(fmt.Sprintf("%s/*", filepath.Join(s.rootfsDir,
|
|
"root."+s.definition.Image.ArchitectureMapped)))
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to get files: %w", err)
|
|
}
|
|
|
|
for _, file := range files {
|
|
err = os.Rename(file, filepath.Join(s.rootfsDir, path.Base(file)))
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to rename file %q: %w", file, err)
|
|
}
|
|
}
|
|
|
|
path := filepath.Join(s.rootfsDir, "root."+s.definition.Image.ArchitectureMapped)
|
|
|
|
err = os.RemoveAll(path)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to remove %q: %w", path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *archlinux) getLatestRelease(URL string, arch string) (string, error) {
|
|
doc, err := htmlquery.LoadURL(URL)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to load URL %q: %w", URL, err)
|
|
}
|
|
|
|
re := regexp.MustCompile(`^\d{4}\.\d{2}\.\d{2}/?$`)
|
|
|
|
var releases []string
|
|
|
|
for _, node := range htmlquery.Find(doc, `//a[@href]/text()`) {
|
|
if re.MatchString(node.Data) {
|
|
releases = append(releases, strings.TrimSuffix(node.Data, "/"))
|
|
}
|
|
}
|
|
|
|
if len(releases) == 0 {
|
|
return "", errors.New("Failed to determine latest release")
|
|
}
|
|
|
|
// Sort releases in case they're out-of-order
|
|
sort.Strings(releases)
|
|
|
|
return releases[len(releases)-1], nil
|
|
}
|