Blame cmd/osbuild-composer/composer.go

Packit Service 509fd4
package main
Packit Service 509fd4
Packit Service 509fd4
import (
Packit Service 509fd4
	"crypto/tls"
Packit Service 509fd4
	"crypto/x509"
Packit Service 509fd4
	"errors"
Packit Service 509fd4
	"fmt"
Packit Service 509fd4
	"io/ioutil"
Packit Service 509fd4
	"log"
Packit Service 509fd4
	"net"
Packit Service 509fd4
	"net/http"
Packit Service 509fd4
	"os"
Packit Service 509fd4
	"path"
Packit Service 509fd4
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/cloudapi"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/common"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/kojiapi"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/rpmmd"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/store"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/weldr"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/worker"
Packit Service 509fd4
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/distro"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/distro/fedora32"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/distro/fedora33"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/distro/rhel8"
Packit Service 509fd4
	"github.com/osbuild/osbuild-composer/internal/distro/rhel84"
Packit Service 509fd4
)
Packit Service 509fd4
Packit Service 509fd4
type Composer struct {
Packit Service 509fd4
	config   *ComposerConfigFile
Packit Service 509fd4
	stateDir string
Packit Service 509fd4
	cacheDir string
Packit Service 509fd4
	logger   *log.Logger
Packit Service 509fd4
	distros  *distro.Registry
Packit Service 509fd4
Packit Service 509fd4
	rpm rpmmd.RPMMD
Packit Service 509fd4
Packit Service 509fd4
	workers *worker.Server
Packit Service 509fd4
	weldr   *weldr.API
Packit Service 509fd4
	api     *cloudapi.Server
Packit Service 509fd4
	koji    *kojiapi.Server
Packit Service 509fd4
Packit Service 509fd4
	weldrListener, localWorkerListener, workerListener, apiListener net.Listener
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func NewComposer(config *ComposerConfigFile, stateDir, cacheDir string, logger *log.Logger) (*Composer, error) {
Packit Service 509fd4
	c := Composer{
Packit Service 509fd4
		config:   config,
Packit Service 509fd4
		stateDir: stateDir,
Packit Service 509fd4
		cacheDir: cacheDir,
Packit Service 509fd4
		logger:   logger,
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	queueDir, err := c.ensureStateDirectory("jobs", 0700)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return nil, err
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	artifactsDir, err := c.ensureStateDirectory("artifacts", 0755)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return nil, err
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service bcdfb1
	c.distros, err = distro.NewRegistry(fedora32.New(), fedora33.New(), rhel8.New(), rhel84.New(), rhel84.NewCentos())
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return nil, fmt.Errorf("Error loading distros: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	c.rpm = rpmmd.NewRPMMD(path.Join(c.cacheDir, "rpmmd"), "/usr/libexec/osbuild-composer/dnf-json")
Packit Service 509fd4
Packit Service 509fd4
	jobs, err := fsjobqueue.New(queueDir)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return nil, fmt.Errorf("cannot create jobqueue: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	c.workers = worker.NewServer(c.logger, jobs, artifactsDir)
Packit Service 509fd4
Packit Service 509fd4
	return &c, nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func (c *Composer) InitWeldr(repoPaths []string, weldrListener net.Listener) error {
Packit Service 509fd4
	archName := common.CurrentArch()
Packit Service 509fd4
Packit Service bcdfb1
	hostDistro, beta, isStream, err := c.distros.FromHost()
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return err
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	arch, err := hostDistro.GetArch(archName)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("Host distro does not support host architecture: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	// TODO: refactor to be more generic
Packit Service 509fd4
	name := hostDistro.Name()
Packit Service 509fd4
	if name == "rhel-84" {
Packit Service 509fd4
		name = "rhel-8"
Packit Service 509fd4
	}
Packit Service 509fd4
	if beta {
Packit Service 509fd4
		name += "-beta"
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service bcdfb1
	// override repository for centos stream, remove when CentOS 8 is EOL
Packit Service bcdfb1
	if isStream && name == "centos-8" {
Packit Service bcdfb1
		name = "centos-stream-8"
Packit Service bcdfb1
	}
Packit Service bcdfb1
Packit Service 509fd4
	repos, err := rpmmd.LoadRepositories(repoPaths, name)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("Error loading repositories for %s: %v", hostDistro.Name(), err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	store := store.New(&c.stateDir, arch, c.logger)
Packit Service 509fd4
	compatOutputDir := path.Join(c.stateDir, "outputs")
Packit Service 509fd4
Packit Service 509fd4
	c.weldr = weldr.New(c.rpm, arch, hostDistro, repos[archName], c.logger, store, c.workers, compatOutputDir)
Packit Service 509fd4
Packit Service 509fd4
	c.weldrListener = weldrListener
Packit Service 509fd4
Packit Service 509fd4
	return nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func (c *Composer) InitAPI(cert, key string, l net.Listener) error {
Packit Service 509fd4
	c.api = cloudapi.NewServer(c.workers, c.rpm, c.distros)
Packit Service 509fd4
	c.koji = kojiapi.NewServer(c.logger, c.workers, c.rpm, c.distros)
Packit Service 509fd4
Packit Service 509fd4
	tlsConfig, err := createTLSConfig(&connectionConfig{
Packit Service 509fd4
		CACertFile:     c.config.Koji.CA,
Packit Service 509fd4
		ServerKeyFile:  key,
Packit Service 509fd4
		ServerCertFile: cert,
Packit Service 509fd4
		AllowedDomains: c.config.Koji.AllowedDomains,
Packit Service 509fd4
	})
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("Error creating TLS configuration: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	c.apiListener = tls.NewListener(l, tlsConfig)
Packit Service 509fd4
Packit Service 509fd4
	return nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func (c *Composer) InitLocalWorker(l net.Listener) {
Packit Service 509fd4
	c.localWorkerListener = l
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func (c *Composer) InitRemoteWorkers(cert, key string, l net.Listener) error {
Packit Service 509fd4
	tlsConfig, err := createTLSConfig(&connectionConfig{
Packit Service 509fd4
		CACertFile:     c.config.Worker.CA,
Packit Service 509fd4
		ServerKeyFile:  key,
Packit Service 509fd4
		ServerCertFile: cert,
Packit Service 509fd4
		AllowedDomains: c.config.Worker.AllowedDomains,
Packit Service 509fd4
	})
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return fmt.Errorf("Error creating TLS configuration for remote worker API: %v", err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	c.workerListener = tls.NewListener(l, tlsConfig)
Packit Service 509fd4
Packit Service 509fd4
	return nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
// Start Composer with all the APIs that had their respective Init*() called.
Packit Service 509fd4
//
Packit Service 509fd4
// Running without the weldr API is currently not supported.
Packit Service 509fd4
func (c *Composer) Start() error {
Packit Service 509fd4
	// sanity checks
Packit Service 509fd4
	if c.localWorkerListener == nil && c.workerListener == nil {
Packit Service 509fd4
		log.Fatal("neither the local worker socket nor the remote worker socket is enabled, osbuild-composer is useless without workers")
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	if c.apiListener == nil && c.weldrListener == nil {
Packit Service 509fd4
		log.Fatal("neither the weldr API socket nor the composer API socket is enabled, osbuild-composer is useless without one of these APIs enabled")
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	if c.localWorkerListener != nil {
Packit Service 509fd4
		go func() {
Packit Service 509fd4
			s := &http.Server{
Packit Service 509fd4
				ErrorLog: c.logger,
Packit Service 509fd4
				Handler:  c.workers.Handler(),
Packit Service 509fd4
			}
Packit Service 509fd4
			err := s.Serve(c.localWorkerListener)
Packit Service 509fd4
			if err != nil {
Packit Service 509fd4
				panic(err)
Packit Service 509fd4
			}
Packit Service 509fd4
		}()
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	if c.workerListener != nil {
Packit Service 509fd4
		go func() {
Packit Service 509fd4
			s := &http.Server{
Packit Service 509fd4
				ErrorLog: c.logger,
Packit Service 509fd4
				Handler:  c.workers.Handler(),
Packit Service 509fd4
			}
Packit Service 509fd4
			err := s.Serve(c.workerListener)
Packit Service 509fd4
			if err != nil {
Packit Service 509fd4
				panic(err)
Packit Service 509fd4
			}
Packit Service 509fd4
		}()
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	if c.apiListener != nil {
Packit Service 509fd4
		go func() {
Packit Service 509fd4
			const apiRoute = "/api/composer/v1"
Packit Service 509fd4
			const kojiRoute = "/api/composer-koji/v1"
Packit Service 509fd4
Packit Service 509fd4
			mux := http.NewServeMux()
Packit Service 509fd4
Packit Service 509fd4
			// Add a "/" here, because http.ServeMux expects the
Packit Service 509fd4
			// trailing slash for rooted subtrees, whereas the
Packit Service 509fd4
			// handler functions don't.
Packit Service 509fd4
			mux.Handle(apiRoute+"/", c.api.Handler(apiRoute))
Packit Service 509fd4
			mux.Handle(kojiRoute+"/", c.koji.Handler(kojiRoute))
Packit Service 509fd4
Packit Service 509fd4
			s := &http.Server{
Packit Service 509fd4
				ErrorLog: c.logger,
Packit Service 509fd4
				Handler:  mux,
Packit Service 509fd4
			}
Packit Service 509fd4
Packit Service 509fd4
			err := s.Serve(c.apiListener)
Packit Service 509fd4
			if err != nil {
Packit Service 509fd4
				panic(err)
Packit Service 509fd4
			}
Packit Service 509fd4
		}()
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	if c.weldrListener != nil {
Packit Service 509fd4
		go func() {
Packit Service 509fd4
			err := c.weldr.Serve(c.weldrListener)
Packit Service 509fd4
			if err != nil {
Packit Service 509fd4
				panic(err)
Packit Service 509fd4
			}
Packit Service 509fd4
		}()
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	// wait indefinitely
Packit Service 509fd4
	select {}
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func (c *Composer) ensureStateDirectory(name string, perm os.FileMode) (string, error) {
Packit Service 509fd4
	d := path.Join(c.stateDir, name)
Packit Service 509fd4
Packit Service 509fd4
	err := os.Mkdir(d, perm)
Packit Service 509fd4
	if err != nil && !os.IsExist(err) {
Packit Service 509fd4
		return "", fmt.Errorf("cannot create state directory %s: %v", name, err)
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	return d, nil
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
type connectionConfig struct {
Packit Service 509fd4
	// CA used for client certificate validation. If empty, then the CAs
Packit Service 509fd4
	// trusted by the host system are used.
Packit Service 509fd4
	CACertFile string
Packit Service 509fd4
Packit Service 509fd4
	ServerKeyFile  string
Packit Service 509fd4
	ServerCertFile string
Packit Service 509fd4
	AllowedDomains []string
Packit Service 509fd4
}
Packit Service 509fd4
Packit Service 509fd4
func createTLSConfig(c *connectionConfig) (*tls.Config, error) {
Packit Service 509fd4
	var roots *x509.CertPool
Packit Service 509fd4
Packit Service 509fd4
	if c.CACertFile != "" {
Packit Service 509fd4
		caCertPEM, err := ioutil.ReadFile(c.CACertFile)
Packit Service 509fd4
		if err != nil {
Packit Service 509fd4
			return nil, err
Packit Service 509fd4
		}
Packit Service 509fd4
Packit Service 509fd4
		roots = x509.NewCertPool()
Packit Service 509fd4
		ok := roots.AppendCertsFromPEM(caCertPEM)
Packit Service 509fd4
		if !ok {
Packit Service 509fd4
			panic("failed to parse root certificate")
Packit Service 509fd4
		}
Packit Service 509fd4
	}
Packit Service 509fd4
Packit Service 509fd4
	cert, err := tls.LoadX509KeyPair(c.ServerCertFile, c.ServerKeyFile)
Packit Service 509fd4
	if err != nil {
Packit Service 509fd4
		return nil, err
Packit Service 509fd4
	}
Packit Service 509fd4
	return &tls.Config{
Packit Service 509fd4
		Certificates: []tls.Certificate{cert},
Packit Service 509fd4
		ClientAuth:   tls.RequireAndVerifyClientCert,
Packit Service 509fd4
		ClientCAs:    roots,
Packit Service 509fd4
		VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
Packit Service 509fd4
			for _, chain := range verifiedChains {
Packit Service 509fd4
				for _, domain := range c.AllowedDomains {
Packit Service 509fd4
					if chain[0].VerifyHostname(domain) == nil {
Packit Service 509fd4
						return nil
Packit Service 509fd4
					}
Packit Service 509fd4
				}
Packit Service 509fd4
			}
Packit Service 509fd4
Packit Service 509fd4
			return errors.New("domain not in allowlist")
Packit Service 509fd4
		},
Packit Service 509fd4
	}, nil
Packit Service 509fd4
}