d9d0beda3b
94 new tests across 4 packages. Coverage: engine 8.3%→65.1%, transport
26.3%→84.6%, proxmox 5.1%→82.7%, audit 0%→100%. Bug fix: dispatch.go
bytesReadCloser.Read returned fmt.Errorf("EOF") instead of io.EOF —
broke HTTP request body transmission (latent since v0.2 P02).
---ci---
project: orca
phase: 3
milestone: v0.7
status: verify
requirements:
covered: [REQ-055]
partial: []
---/ci---
224 lines
6.1 KiB
Go
224 lines
6.1 KiB
Go
package transport
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/security"
|
|
)
|
|
|
|
func generateTestCerts(t *testing.T, dir, serverName string) (certPath, keyPath, caPath string) {
|
|
t.Helper()
|
|
ca, err := security.CAInit(dir, "orca-test-ca")
|
|
if err != nil {
|
|
t.Fatalf("CAInit: %v", err)
|
|
}
|
|
keyPEM, csrPEM, err := security.GenerateCSR(serverName, []string{serverName, "127.0.0.1"})
|
|
if err != nil {
|
|
t.Fatalf("GenerateCSR: %v", err)
|
|
}
|
|
signedPEM, err := ca.SignCSR(csrPEM)
|
|
if err != nil {
|
|
t.Fatalf("SignCSR: %v", err)
|
|
}
|
|
certPath = filepath.Join(dir, "server.crt")
|
|
keyPath = filepath.Join(dir, "server.key")
|
|
caPath = filepath.Join(dir, security.CACertFile)
|
|
if err := os.WriteFile(certPath, signedPEM, 0o644); err != nil {
|
|
t.Fatalf("write cert: %v", err)
|
|
}
|
|
if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil {
|
|
t.Fatalf("write key: %v", err)
|
|
}
|
|
return certPath, keyPath, caPath
|
|
}
|
|
|
|
func TestServerTLSConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
certPath, keyPath, caPath := generateTestCerts(t, dir, "localhost")
|
|
cfg, err := security.ServerTLSConfig(certPath, keyPath, caPath)
|
|
if err != nil {
|
|
t.Fatalf("ServerTLSConfig: %v", err)
|
|
}
|
|
if cfg.MinVersion != tls.VersionTLS13 {
|
|
t.Errorf("MinVersion = %d, want %d", cfg.MinVersion, tls.VersionTLS13)
|
|
}
|
|
if cfg.MaxVersion != tls.VersionTLS13 {
|
|
t.Errorf("MaxVersion = %d, want %d", cfg.MaxVersion, tls.VersionTLS13)
|
|
}
|
|
if cfg.ClientAuth != tls.RequireAndVerifyClientCert {
|
|
t.Errorf("ClientAuth = %v, want RequireAndVerifyClientCert", cfg.ClientAuth)
|
|
}
|
|
if cfg.ClientCAs == nil {
|
|
t.Error("ClientCAs is nil")
|
|
}
|
|
if len(cfg.CipherSuites) == 0 {
|
|
t.Error("CipherSuites is empty")
|
|
}
|
|
}
|
|
|
|
func TestServerTLSConfig_MissingFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, err := security.ServerTLSConfig(
|
|
filepath.Join(dir, "nope.crt"),
|
|
filepath.Join(dir, "nope.key"),
|
|
filepath.Join(dir, "nope.ca"),
|
|
)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing files")
|
|
}
|
|
}
|
|
|
|
func TestClientTLSConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
certPath, keyPath, caPath := generateTestCerts(t, dir, "localhost")
|
|
cfg, err := security.ClientTLSConfig(caPath, "localhost", certPath, keyPath)
|
|
if err != nil {
|
|
t.Fatalf("ClientTLSConfig: %v", err)
|
|
}
|
|
if cfg.MinVersion != tls.VersionTLS13 {
|
|
t.Errorf("MinVersion = %d, want %d", cfg.MinVersion, tls.VersionTLS13)
|
|
}
|
|
if cfg.RootCAs == nil {
|
|
t.Error("RootCAs is nil")
|
|
}
|
|
if cfg.ServerName != "localhost" {
|
|
t.Errorf("ServerName = %q, want localhost", cfg.ServerName)
|
|
}
|
|
if len(cfg.Certificates) != 1 {
|
|
t.Errorf("Certificates len = %d, want 1", len(cfg.Certificates))
|
|
}
|
|
}
|
|
|
|
func TestClientTLSConfig_NoClientCert(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, _, caPath := generateTestCerts(t, dir, "localhost")
|
|
cfg, err := security.ClientTLSConfig(caPath, "localhost", "", "")
|
|
if err != nil {
|
|
t.Fatalf("ClientTLSConfig: %v", err)
|
|
}
|
|
if len(cfg.Certificates) != 0 {
|
|
t.Errorf("Certificates len = %d, want 0", len(cfg.Certificates))
|
|
}
|
|
}
|
|
|
|
func TestClientTLSConfig_MismatchedCertKey(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, _, caPath := generateTestCerts(t, dir, "localhost")
|
|
if _, err := security.ClientTLSConfig(caPath, "localhost", "only-cert", ""); err == nil {
|
|
t.Error("expected error for cert without key")
|
|
}
|
|
if _, err := security.ClientTLSConfig(caPath, "localhost", "", "only-key"); err == nil {
|
|
t.Error("expected error for key without cert")
|
|
}
|
|
}
|
|
|
|
func TestNewMTLSClient(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, _, caPath := generateTestCerts(t, dir, "localhost")
|
|
c, err := NewMTLSClient(caPath, "localhost", "", "")
|
|
if err != nil {
|
|
t.Fatalf("NewMTLSClient: %v", err)
|
|
}
|
|
if c == nil {
|
|
t.Fatal("client is nil")
|
|
}
|
|
}
|
|
|
|
func TestNewMTLSClient_EmptyCAPath(t *testing.T) {
|
|
_, err := NewMTLSClient("", "localhost", "", "")
|
|
if err == nil {
|
|
t.Fatal("expected error for empty caPath")
|
|
}
|
|
}
|
|
|
|
func TestNewMTLSClient_EmptyServerName(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, _, caPath := generateTestCerts(t, dir, "localhost")
|
|
_, err := NewMTLSClient(caPath, "", "", "")
|
|
if err == nil {
|
|
t.Fatal("expected error for empty serverName")
|
|
}
|
|
}
|
|
|
|
func TestNewMTLSClient_MissingCAFile(t *testing.T) {
|
|
_, err := NewMTLSClient("/nonexistent/ca.crt", "localhost", "", "")
|
|
if err == nil {
|
|
t.Fatal("expected error for missing CA file")
|
|
}
|
|
}
|
|
|
|
func TestMTLSClient_Do_NilReceiver(t *testing.T) {
|
|
var c *MTLSClient
|
|
_, err := c.Do(nil)
|
|
if err == nil {
|
|
t.Fatal("expected error for nil receiver")
|
|
}
|
|
}
|
|
|
|
func TestVerifyPeerCertificate_NoCerts(t *testing.T) {
|
|
cb := VerifyPeerCertificate("expected")
|
|
if err := cb(nil, nil); err == nil {
|
|
t.Error("expected error for no peer certs")
|
|
}
|
|
}
|
|
|
|
func TestVerifyPeerCertificate_Mismatch(t *testing.T) {
|
|
dir := t.TempDir()
|
|
certPath, _, _ := generateTestCerts(t, dir, "localhost")
|
|
certPEM, err := os.ReadFile(certPath)
|
|
if err != nil {
|
|
t.Fatalf("read cert: %v", err)
|
|
}
|
|
block, _ := pem.Decode(certPEM)
|
|
if block == nil {
|
|
t.Fatal("pem.Decode: no cert block")
|
|
}
|
|
cb := VerifyPeerCertificate("wrong-fingerprint")
|
|
if err := cb([][]byte{block.Bytes}, nil); err == nil {
|
|
t.Error("expected error for fingerprint mismatch")
|
|
}
|
|
}
|
|
|
|
func TestVerifyPeerCertificate_Match(t *testing.T) {
|
|
dir := t.TempDir()
|
|
certPath, _, _ := generateTestCerts(t, dir, "localhost")
|
|
certPEM, err := os.ReadFile(certPath)
|
|
if err != nil {
|
|
t.Fatalf("read cert: %v", err)
|
|
}
|
|
block, _ := pem.Decode(certPEM)
|
|
if block == nil {
|
|
t.Fatal("pem.Decode: no cert block")
|
|
}
|
|
leaf, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
t.Fatalf("ParseCertificate: %v", err)
|
|
}
|
|
expected := security.FingerprintOf(leaf.Raw)
|
|
cb := VerifyPeerCertificate(expected)
|
|
if err := cb([][]byte{block.Bytes}, nil); err != nil {
|
|
t.Errorf("expected match, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDialContext_EmptyCAPath(t *testing.T) {
|
|
_, err := DialContext(t.Context(), "tcp", "127.0.0.1:0", "", "localhost")
|
|
if err == nil {
|
|
t.Fatal("expected error for empty caPath")
|
|
}
|
|
}
|
|
|
|
func TestDialContext_ConnectionRefused(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, _, caPath := generateTestCerts(t, dir, "localhost")
|
|
_, err := DialContext(t.Context(), "tcp", "127.0.0.1:1", caPath, "localhost")
|
|
if err == nil {
|
|
t.Fatal("expected error for connection refused")
|
|
}
|
|
}
|