koji: add config files to configure kerberos settings

Kerberos keytabs and principals are configured per koji server both in
composer and in the worker.

Signed-off-by: Tom Gundersen <teg@jklm.no>
This commit is contained in:
Tom Gundersen 2020-09-14 01:58:55 +01:00
parent 9666be2891
commit c6cf9de85d
6 changed files with 129 additions and 17 deletions

View file

@ -9,11 +9,13 @@ import (
"os" "os"
"path" "path"
"github.com/BurntSushi/toml"
"github.com/osbuild/osbuild-composer/internal/distro/fedora31" "github.com/osbuild/osbuild-composer/internal/distro/fedora31"
"github.com/osbuild/osbuild-composer/internal/distro/fedora32" "github.com/osbuild/osbuild-composer/internal/distro/fedora32"
"github.com/osbuild/osbuild-composer/internal/distro/rhel8" "github.com/osbuild/osbuild-composer/internal/distro/rhel8"
"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue" "github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
"github.com/osbuild/osbuild-composer/internal/kojiapi" "github.com/osbuild/osbuild-composer/internal/kojiapi"
"github.com/osbuild/osbuild-composer/internal/upload/koji"
"github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro" "github.com/osbuild/osbuild-composer/internal/distro"
@ -25,6 +27,8 @@ import (
"github.com/coreos/go-systemd/activation" "github.com/coreos/go-systemd/activation"
) )
const configFile = "/etc/osbuild-composer/osbuild-composer.toml"
type connectionConfig struct { type connectionConfig struct {
CACertFile string CACertFile string
ServerKeyFile string ServerKeyFile string
@ -55,10 +59,30 @@ func createTLSConfig(c *connectionConfig) (*tls.Config, error) {
} }
func main() { func main() {
var config struct {
KojiServers map[string]struct {
Kerberos *struct {
Principal string `toml:"principal"`
KeyTab string `toml:"keytab"`
} `toml:"kerberos,omitempty"`
} `toml:"koji"`
}
var verbose bool var verbose bool
flag.BoolVar(&verbose, "v", false, "Print access log") flag.BoolVar(&verbose, "v", false, "Print access log")
flag.Parse() flag.Parse()
_, err := toml.DecodeFile(configFile, &config)
if err == nil {
log.Println("Composer configuration:")
encoder := toml.NewEncoder(log.Writer())
err := encoder.Encode(&config)
if err != nil {
log.Fatalf("Could not print config: %v", err)
}
} else if !os.IsNotExist(err) {
log.Fatalf("Could not load config file '%s': %v", configFile, err)
}
stateDir, ok := os.LookupEnv("STATE_DIRECTORY") stateDir, ok := os.LookupEnv("STATE_DIRECTORY")
if !ok { if !ok {
log.Fatal("STATE_DIRECTORY is not set. Is the service file missing StateDirectory=?") log.Fatal("STATE_DIRECTORY is not set. Is the service file missing StateDirectory=?")
@ -151,7 +175,19 @@ func main() {
// Optionally run Koji API // Optionally run Koji API
if kojiListeners, exists := listeners["osbuild-composer-koji.socket"]; exists { if kojiListeners, exists := listeners["osbuild-composer-koji.socket"]; exists {
kojiServer := kojiapi.NewServer(workers, rpm, distros) kojiServers := make(map[string]koji.GSSAPICredentials)
for server, creds := range config.KojiServers {
if creds.Kerberos == nil {
// For now we only support Kerberos authentication.
continue
}
kojiServers[server] = koji.GSSAPICredentials{
Principal: creds.Kerberos.Principal,
KeyTab: creds.Kerberos.KeyTab,
}
}
kojiServer := kojiapi.NewServer(workers, rpm, distros, kojiServers)
tlsConfig, err := createTLSConfig(&connectionConfig{ tlsConfig, err := createTLSConfig(&connectionConfig{
CACertFile: "/etc/osbuild-composer/ca-crt.pem", CACertFile: "/etc/osbuild-composer/ca-crt.pem",

View file

@ -10,10 +10,12 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"net/url"
"os" "os"
"path" "path"
"time" "time"
"github.com/BurntSushi/toml"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/common"
@ -26,6 +28,8 @@ import (
"github.com/osbuild/osbuild-composer/internal/worker" "github.com/osbuild/osbuild-composer/internal/worker"
) )
const configFile = "/etc/osbuild-worker/osbuild-worker.toml"
type connectionConfig struct { type connectionConfig struct {
CACertFile string CACertFile string
ClientKeyFile string ClientKeyFile string
@ -92,7 +96,7 @@ func osbuildStagesToRPMs(stages []osbuild.StageResult) []koji.RPM {
return rpms return rpms
} }
func RunJob(job worker.Job, store string) (*osbuild.Result, error) { func RunJob(job worker.Job, store string, kojiServers map[string]koji.GSSAPICredentials) (*osbuild.Result, error) {
outputDirectory, err := ioutil.TempDir("/var/tmp", "osbuild-worker-*") outputDirectory, err := ioutil.TempDir("/var/tmp", "osbuild-worker-*")
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating temporary output directory: %v", err) return nil, fmt.Errorf("error creating temporary output directory: %v", err)
@ -201,7 +205,13 @@ func RunJob(job worker.Job, store string) (*osbuild.Result, error) {
Renegotiation: tls.RenegotiateOnceAsClient, Renegotiation: tls.RenegotiateOnceAsClient,
} }
k, err := koji.NewFromGSSAPI(options.Server, &koji.GSSAPICredentials{}, transport) kojiServer, _ := url.Parse(options.Server)
creds, exists := kojiServers[kojiServer.Hostname()]
if !exists {
r = append(r, fmt.Errorf("Koji server has not been configured: %s", kojiServer.Hostname()))
}
k, err := koji.NewFromGSSAPI(options.Server, &creds, transport)
if err != nil { if err != nil {
r = append(r, err) r = append(r, err)
continue continue
@ -294,7 +304,7 @@ func RunJob(job worker.Job, store string) (*osbuild.Result, error) {
return result, nil return result, nil
} }
func FailJob(job worker.Job) { func FailJob(job worker.Job, kojiServers map[string]koji.GSSAPICredentials) {
_, targets, err := job.OSBuildArgs() _, targets, err := job.OSBuildArgs()
if err != nil { if err != nil {
panic(err) panic(err)
@ -310,7 +320,14 @@ func FailJob(job worker.Job) {
Renegotiation: tls.RenegotiateOnceAsClient, Renegotiation: tls.RenegotiateOnceAsClient,
} }
k, err := koji.NewFromGSSAPI(options.Server, &koji.GSSAPICredentials{}, transport) kojiServer, _ := url.Parse(options.Server)
creds, exists := kojiServers[kojiServer.Hostname()]
if !exists {
log.Printf("Koji server has not been configured: %s", kojiServer.Hostname())
return
}
k, err := koji.NewFromGSSAPI(options.Server, &creds, transport)
if err != nil { if err != nil {
log.Printf("koji login failed: %v", err) log.Printf("koji login failed: %v", err)
return return
@ -357,6 +374,14 @@ func WatchJob(ctx context.Context, job worker.Job) {
} }
func main() { func main() {
var config struct {
KojiServers map[string]struct {
Kerberos *struct {
Principal string `toml:"principal"`
KeyTab string `toml:"keytab"`
} `toml:"kerberos,omitempty"`
} `toml:"koji"`
}
var unix bool var unix bool
flag.BoolVar(&unix, "unix", false, "Interpret 'address' as a path to a unix domain socket instead of a network address") flag.BoolVar(&unix, "unix", false, "Interpret 'address' as a path to a unix domain socket instead of a network address")
@ -373,12 +398,36 @@ func main() {
flag.Usage() flag.Usage()
} }
_, err := toml.DecodeFile(configFile, &config)
if err == nil {
log.Println("Composer configuration:")
encoder := toml.NewEncoder(log.Writer())
err := encoder.Encode(&config)
if err != nil {
log.Fatalf("Could not print config: %v", err)
}
} else if !os.IsNotExist(err) {
log.Fatalf("Could not load config file '%s': %v", configFile, err)
}
cacheDirectory, ok := os.LookupEnv("CACHE_DIRECTORY") cacheDirectory, ok := os.LookupEnv("CACHE_DIRECTORY")
if !ok { if !ok {
log.Fatal("CACHE_DIRECTORY is not set. Is the service file missing CacheDirectory=?") log.Fatal("CACHE_DIRECTORY is not set. Is the service file missing CacheDirectory=?")
} }
store := path.Join(cacheDirectory, "osbuild-store") store := path.Join(cacheDirectory, "osbuild-store")
kojiServers := make(map[string]koji.GSSAPICredentials)
for server, creds := range config.KojiServers {
if creds.Kerberos == nil {
// For now we only support Kerberos authentication.
continue
}
kojiServers[server] = koji.GSSAPICredentials{
Principal: creds.Kerberos.Principal,
KeyTab: creds.Kerberos.KeyTab,
}
}
var client *worker.Client var client *worker.Client
if unix { if unix {
client = worker.NewClientUnix(address) client = worker.NewClientUnix(address)
@ -411,13 +460,13 @@ func main() {
go WatchJob(ctx, job) go WatchJob(ctx, job)
var status common.ImageBuildState var status common.ImageBuildState
result, err := RunJob(job, store) result, err := RunJob(job, store, kojiServers)
if err != nil { if err != nil {
log.Printf(" Job failed: %v", err) log.Printf(" Job failed: %v", err)
status = common.IBFailed status = common.IBFailed
// Fail the jobs in any targets that expects it // Fail the jobs in any targets that expects it
FailJob(job) FailJob(job, kojiServers)
// If the error comes from osbuild, retrieve the result // If the error comes from osbuild, retrieve the result
if osbuildError, ok := err.(*OSBuildError); ok { if osbuildError, ok := err.(*OSBuildError); ok {

View file

@ -10,6 +10,7 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"net/url"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/osbuild/osbuild-composer/internal/blueprint" "github.com/osbuild/osbuild-composer/internal/blueprint"
@ -26,14 +27,16 @@ type Server struct {
workers *worker.Server workers *worker.Server
rpmMetadata rpmmd.RPMMD rpmMetadata rpmmd.RPMMD
distros *distro.Registry distros *distro.Registry
kojiServers map[string]koji.GSSAPICredentials
} }
// NewServer creates a new koji server // NewServer creates a new koji server
func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distro.Registry) *Server { func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distro.Registry, kojiServers map[string]koji.GSSAPICredentials) *Server {
server := &Server{ server := &Server{
workers: workers, workers: workers,
rpmMetadata: rpmMetadata, rpmMetadata: rpmMetadata,
distros: distros, distros: distros,
kojiServers: kojiServers,
} }
return server return server
} }
@ -71,6 +74,18 @@ func (server *Server) PostCompose(w http.ResponseWriter, r *http.Request) {
return return
} }
kojiServer, err := url.Parse(request.Koji.Server)
if err != nil {
http.Error(w, fmt.Sprintf("Invalid Koji server: %s", request.Koji.Server), http.StatusBadRequest)
return
}
creds, exists := server.kojiServers[kojiServer.Hostname()]
if !exists {
http.Error(w, fmt.Sprintf("Koji server has not been configured: %s", kojiServer.Hostname()), http.StatusBadRequest)
return
}
type imageRequest struct { type imageRequest struct {
manifest distro.Manifest manifest distro.Manifest
filename string filename string
@ -137,7 +152,7 @@ func (server *Server) PostCompose(w http.ResponseWriter, r *http.Request) {
Renegotiation: tls.RenegotiateOnceAsClient, Renegotiation: tls.RenegotiateOnceAsClient,
} }
k, err := koji.NewFromGSSAPI(request.Koji.Server, &koji.GSSAPICredentials{}, transport) k, err := koji.NewFromGSSAPI(request.Koji.Server, &creds, transport)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("Could not log into Koji: %v", err), http.StatusBadRequest) http.Error(w, fmt.Sprintf("Could not log into Koji: %v", err), http.StatusBadRequest)
return return

View file

@ -36,31 +36,37 @@ fi
greenprint "Starting containers" greenprint "Starting containers"
sudo ./internal/upload/koji/run-koji-container.sh start sudo ./internal/upload/koji/run-koji-container.sh start
greenprint "Copying custom composer/worker config"
sudo mkdir -p /etc/osbuild-composer
sudo cp test/image-tests/osbuild-composer.toml \
/etc/osbuild-composer/
sudo mkdir -p /etc/osbuild-worker
sudo cp test/image-tests/osbuild-worker.toml \
/etc/osbuild-worker/
greenprint "Adding kerberos config" greenprint "Adding kerberos config"
sudo cp \ sudo cp \
/tmp/osbuild-composer-koji-test/client.keytab \ /tmp/osbuild-composer-koji-test/client.keytab \
/etc/krb5.keytab /etc/osbuild-composer/client.keytab
sudo cp \
/tmp/osbuild-composer-koji-test/client.keytab \
/etc/osbuild-worker/client.keytab
sudo cp \ sudo cp \
test/image-tests/krb5-local.conf \ test/image-tests/krb5-local.conf \
/etc/krb5.conf.d/local /etc/krb5.conf.d/local
greenprint "Initializing Kerberos"
kinit osbuild-krb@LOCAL -k
sudo -u _osbuild-composer kinit osbuild-krb@LOCAL -k
greenprint "Adding generated CA cert for Koji" greenprint "Adding generated CA cert for Koji"
sudo cp \ sudo cp \
/tmp/osbuild-composer-koji-test/ca-crt.pem \ /tmp/osbuild-composer-koji-test/ca-crt.pem \
/etc/pki/ca-trust/source/anchors/koji-ca-crt.pem /etc/pki/ca-trust/source/anchors/koji-ca-crt.pem
sudo update-ca-trust sudo update-ca-trust
greenprint "Restarting composer to pick up new certs" greenprint "Restarting composer to pick up new config"
sudo systemctl restart osbuild-composer sudo systemctl restart osbuild-composer
sudo systemctl restart osbuild-worker\@1
greenprint "Testing Koji" greenprint "Testing Koji"
koji --server=http://localhost/kojihub --user=osbuild --password=osbuildpass --authtype=password hello koji --server=http://localhost/kojihub --user=osbuild --password=osbuildpass --authtype=password hello
koji --server=http://localhost/kojihub hello
sudo -u _osbuild-composer koji --server=http://localhost/kojihub hello
greenprint "Creating Koji task" greenprint "Creating Koji task"
koji --server=http://localhost/kojihub --user kojiadmin --password kojipass --authtype=password make-task image koji --server=http://localhost/kojihub --user kojiadmin --password kojipass --authtype=password make-task image

View file

@ -0,0 +1,3 @@
[koji.localhost.kerberos]
principal = "osbuild-krb@LOCAL"
keytab = "/etc/osbuild-composer/client.keytab"

View file

@ -0,0 +1,3 @@
[koji.localhost.kerberos]
principal = "osbuild-krb@LOCAL"
keytab = "/etc/osbuild-worker/client.keytab"