Add an additional integration test, which builds a fedora-32 image via the newly moved composer API. This adds a new certificate for clients to authenticate, with CN=client.osbuild.org. Also stop allowing certificates with `CN=worker.osbuild.org` in the `osbuild-composer.toml` used in tests and amend `osbuild-auth-tests` to use `CN=client.osbuild.org` for accessing the koji API. Heavily based on an earlier version of this test in `test/cmd/aws.sh` by Sanne Raymaekers.
250 lines
6.2 KiB
Go
250 lines
6.2 KiB
Go
// +build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type connectionConfig struct {
|
|
CACertFile string
|
|
ClientKeyFile string
|
|
ClientCertFile string
|
|
}
|
|
|
|
func createTLSConfig(config *connectionConfig) (*tls.Config, error) {
|
|
caCertPEM, err := ioutil.ReadFile(config.CACertFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
roots := x509.NewCertPool()
|
|
ok := roots.AppendCertsFromPEM(caCertPEM)
|
|
if !ok {
|
|
return nil, errors.New("failed to append root certificate")
|
|
}
|
|
|
|
cert, err := tls.LoadX509KeyPair(config.ClientCertFile, config.ClientKeyFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &tls.Config{
|
|
RootCAs: roots,
|
|
Certificates: []tls.Certificate{cert},
|
|
}, nil
|
|
}
|
|
|
|
type certificateKeyPair struct {
|
|
baseDir string
|
|
}
|
|
|
|
func (ckp certificateKeyPair) remove() {
|
|
err := os.RemoveAll(ckp.baseDir)
|
|
if err != nil {
|
|
log.Printf("cannot delete the certificate key pair: %v", err)
|
|
}
|
|
}
|
|
|
|
func (ckp certificateKeyPair) certificate() string {
|
|
return path.Join(ckp.baseDir, "crt")
|
|
}
|
|
|
|
func (ckp certificateKeyPair) key() string {
|
|
return path.Join(ckp.baseDir, "key")
|
|
}
|
|
|
|
func newCertificateKeyPair(CA, CAkey, subj string) (*certificateKeyPair, error) {
|
|
dir, err := ioutil.TempDir("", "osbuild-auth-tests-")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create a temporary directory for the certificate: %v", err)
|
|
}
|
|
|
|
ckp := certificateKeyPair{baseDir: dir}
|
|
certificateRequest := path.Join(dir, "csr")
|
|
|
|
cmd := exec.Command(
|
|
"openssl", "req", "-new", "-nodes",
|
|
"-subj", subj,
|
|
"-keyout", ckp.key(),
|
|
"-out", certificateRequest,
|
|
)
|
|
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot generate a private key and a certificate request: %v", err)
|
|
}
|
|
|
|
defer os.Remove(certificateRequest)
|
|
|
|
cmd = exec.Command(
|
|
"openssl", "x509", "-req", "-CAcreateserial",
|
|
"-in", certificateRequest,
|
|
"-CA", CA,
|
|
"-CAkey", CAkey,
|
|
"-out", ckp.certificate(),
|
|
)
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot sign the certificate: %v", err)
|
|
}
|
|
|
|
return &ckp, nil
|
|
}
|
|
|
|
func newSelfSignedCertificateKeyPair(subj string) (*certificateKeyPair, error) {
|
|
dir, err := ioutil.TempDir("", "osbuild-auth-tests-")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create a temporary directory for the certificate: %v", err)
|
|
}
|
|
|
|
ckp := certificateKeyPair{baseDir: dir}
|
|
|
|
cmd := exec.Command(
|
|
"openssl", "req", "-nodes", "-x509",
|
|
"-subj", subj,
|
|
"-out", ckp.certificate(),
|
|
"-keyout", ckp.key(),
|
|
)
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot generate a self-signed certificate: %v", err)
|
|
}
|
|
|
|
return &ckp, nil
|
|
}
|
|
|
|
func TestWorkerAPIAuth(t *testing.T) {
|
|
t.Run("certificate signed by a trusted CA", func(t *testing.T) {
|
|
cases := []struct {
|
|
caseDesc string
|
|
subj string
|
|
success bool
|
|
}{
|
|
{"valid CN 1", "/CN=worker.osbuild.org", true},
|
|
{"valid CN 2", "/CN=localhost", true},
|
|
{"invalid CN", "/CN=example.com", false},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.caseDesc, func(t *testing.T) {
|
|
ckp, err := newCertificateKeyPair("/etc/osbuild-composer/ca-crt.pem", "/etc/osbuild-composer/ca-key.pem", c.subj)
|
|
require.NoError(t, err)
|
|
defer ckp.remove()
|
|
|
|
testRoute(t, "https://localhost:8700/api/worker/v1/status", ckp, c.success)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("certificate signed by an untrusted CA", func(t *testing.T) {
|
|
// generate a new CA
|
|
ca, err := newSelfSignedCertificateKeyPair("/CN=osbuild.org")
|
|
require.NoError(t, err)
|
|
defer ca.remove()
|
|
|
|
// create a new certificate and signed it with the new CA
|
|
ckp, err := newCertificateKeyPair(ca.certificate(), ca.key(), "/CN=localhost")
|
|
require.NoError(t, err)
|
|
defer ckp.remove()
|
|
|
|
testRoute(t, "https://localhost:8700/api/worker/v1/status", ckp, false)
|
|
})
|
|
|
|
t.Run("self-signed certificate", func(t *testing.T) {
|
|
// generate a new self-signed certificate
|
|
ckp, err := newSelfSignedCertificateKeyPair("/CN=osbuild.org")
|
|
require.NoError(t, err)
|
|
defer ckp.remove()
|
|
|
|
testRoute(t, "https://localhost:8700/api/worker/v1/status", ckp, false)
|
|
})
|
|
}
|
|
|
|
func TestKojiAPIAuth(t *testing.T) {
|
|
t.Run("certificate signed by a trusted CA", func(t *testing.T) {
|
|
cases := []struct {
|
|
caseDesc string
|
|
subj string
|
|
success bool
|
|
}{
|
|
{"valid CN 1", "/CN=client.osbuild.org", true},
|
|
{"valid CN 2", "/CN=localhost", true},
|
|
{"invalid CN", "/CN=example.com", false},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.caseDesc, func(t *testing.T) {
|
|
ckp, err := newCertificateKeyPair("/etc/osbuild-composer/ca-crt.pem", "/etc/osbuild-composer/ca-key.pem", c.subj)
|
|
require.NoError(t, err)
|
|
defer ckp.remove()
|
|
|
|
testRoute(t, "https://localhost/api/composer-koji/v1/status", ckp, c.success)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("certificate signed by an untrusted CA", func(t *testing.T) {
|
|
// generate a new CA
|
|
ca, err := newSelfSignedCertificateKeyPair("/CN=osbuild.org")
|
|
require.NoError(t, err)
|
|
defer ca.remove()
|
|
|
|
// create a new certificate and signed it with the new CA
|
|
ckp, err := newCertificateKeyPair(ca.certificate(), ca.key(), "/CN=localhost")
|
|
require.NoError(t, err)
|
|
defer ckp.remove()
|
|
|
|
testRoute(t, "https://localhost/api/composer-koji/v1/status", ckp, false)
|
|
})
|
|
|
|
t.Run("self-signed certificate", func(t *testing.T) {
|
|
// generate a new self-signed certificate
|
|
ckp, err := newSelfSignedCertificateKeyPair("/CN=osbuild.org")
|
|
require.NoError(t, err)
|
|
defer ckp.remove()
|
|
|
|
testRoute(t, "https://localhost/api/composer-koji/v1/status", ckp, false)
|
|
})
|
|
}
|
|
|
|
func testRoute(t *testing.T, route string, ckp *certificateKeyPair, expectSuccess bool) {
|
|
tlsConfig, err := createTLSConfig(&connectionConfig{
|
|
CACertFile: "/etc/osbuild-composer/ca-crt.pem",
|
|
ClientKeyFile: ckp.key(),
|
|
ClientCertFile: ckp.certificate(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
transport.TLSClientConfig = tlsConfig
|
|
client := http.Client{Transport: transport}
|
|
|
|
response, err := client.Get(route)
|
|
if expectSuccess {
|
|
require.NoError(t, err)
|
|
|
|
var status struct {
|
|
Status string `json:"status"`
|
|
}
|
|
err := json.NewDecoder(response.Body).Decode(&status)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "OK", status.Status)
|
|
} else {
|
|
require.Error(t, err)
|
|
}
|
|
}
|