Previous commits introduces a new way to generate all X.509 certificates needed for testing. This commit reuses the same method for auth tests. This has two benefits: 1) The new code generates certificates with Subject Alternative Name which means we can use it on systems with Go 1.15 (Fedora 33, RHEL 8.4). 2) The new code generates much saner certificates.
174 lines
5 KiB
Go
174 lines
5 KiB
Go
// +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)
|
|
}
|
|
}
|