|
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 |
}
|