// +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) } }