// +build integration
package main
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"testing"
"github.com/stretchr/testify/require"
)
const trustedCADir = "/etc/osbuild-composer-test/ca"
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
}
func TestWorkerAPIAuth(t *testing.T) {
t.Run("certificate signed by a trusted CA", func(t *testing.T) {
cases := []struct {
caseDesc string
subj string
addext string
success bool
}{
{"valid CN 1", "/CN=worker.osbuild.org/emailAddress=osbuild@example.com", "subjectAltName=DNS:example.com,DNS:worker.osbuild.org", true},
{"valid CN 2", "/CN=localhost/emailAddress=osbuild@example.com", "subjectAltName=DNS:example.com,DNS:localhost", true},
{"invalid CN", "/CN=example.com/emailAddress=osbuild@example.com", "subjectAltName=DNS:example.com", false},
}
authority := &ca{BaseDir: trustedCADir}
for _, c := range cases {
t.Run(c.caseDesc, func(t *testing.T) {
ckp, err := authority.newCertificateKeyPair(c.subj, osbuildClientExt, c.addext)
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 := newCA("/CN=untrusted.osbuild.org")
require.NoError(t, err)
defer ca.remove()
// create a new certificate and signed it with the new CA
ckp, err := ca.newCertificateKeyPair("/CN=localhost/emailAddress=osbuild@example.com", osbuildClientExt, "")
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
addext string
success bool
}{
{"valid CN and SAN 1", "/CN=client.osbuild.org/emailAddress=osbuild@example.com", "subjectAltName=DNS:example.com,DNS:client.osbuild.org", true},
{"valid CN and SAN 2", "/CN=localhost/emailAddress=osbuild@example.com", "subjectAltName=DNS:example.com,DNS:localhost", true},
{"invalid CN and SAN", "/CN=example.com/emailAddress=osbuild@example.com", "subjectAltName=DNS:example.com", false},
}
authority := &ca{BaseDir: trustedCADir}
for _, c := range cases {
t.Run(c.caseDesc, func(t *testing.T) {
ckp, err := authority.newCertificateKeyPair(c.subj, osbuildClientExt, c.addext)
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 := newCA("/CN=osbuild.org")
require.NoError(t, err)
defer ca.remove()
// create a new certificate and signed it with the new CA
ckp, err := ca.newCertificateKeyPair("/CN=localhost/emailAddress=osbuild@example.com", osbuildClientExt, "subjectAltName=DNS: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)
}
}