You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

154 lines
4.8 KiB

package managementaccess
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"tera-clone/services/tera-api/internal/domain"
)
func TestRuntimeAppliesHTTPAndHTTPSListeners(t *testing.T) {
httpPort := unusedTCPPort(t)
httpsPort := unusedTCPPort(t)
tlsRoot := t.TempDir()
runtime, err := NewRuntime(RuntimeOptions{
BindHost: "127.0.0.1", Handler: http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(response, "tera-access-ok")
}),
CertFile: filepath.Join(tlsRoot, "server.crt"),
KeyFile: filepath.Join(tlsRoot, "server.key"), TLSHosts: []string{"127.0.0.1"},
})
if err != nil {
t.Fatal(err)
}
defer runtime.Close()
settings := domain.ManagementAccessSettings{
HTTPEnabled: true, HTTPPort: httpPort, HTTPSEnabled: true, HTTPSPort: httpsPort,
SSHPort: 22, TelnetPort: unusedTCPPort(t),
SerialConsoleEnabled: true, IdleTimeoutMinutes: 15,
}
if err := runtime.Apply(context.Background(), settings); err != nil {
t.Fatalf("Apply() error = %v", err)
}
assertHTTPBody(t, "http://127.0.0.1:"+portString(httpPort), http.DefaultClient)
tlsClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, Timeout: 3 * time.Second} // #nosec G402 -- generated test certificate.
assertHTTPBody(t, "https://127.0.0.1:"+portString(httpsPort), tlsClient)
}
func TestValidateRejectsOutOfRangeLoginProtection(t *testing.T) {
base := domain.ManagementAccessSettings{HTTPSEnabled: true, HTTPSPort: 443, HTTPPort: 80, IdleTimeoutMinutes: 15}
base.LoginDetectionMinutes = 1441
if err := Validate(base); err == nil {
t.Fatal("expected out-of-range login detection time to fail")
}
base.LoginDetectionMinutes = 0
base.LoginSecurityHitCount = 101
if err := Validate(base); err == nil {
t.Fatal("expected out-of-range login security hit count to fail")
}
}
func TestHTTPSListenerReloadsReplacedCertificateForNewConnections(t *testing.T) {
httpsPort := unusedTCPPort(t)
tlsRoot := t.TempDir()
certPath := filepath.Join(tlsRoot, "active.crt")
keyPath := filepath.Join(tlsRoot, "active.key")
runtime, err := NewRuntime(RuntimeOptions{
BindHost: "127.0.0.1", Handler: http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) { response.WriteHeader(http.StatusNoContent) }),
CertFile: certPath, KeyFile: keyPath, TLSHosts: []string{"127.0.0.1"},
})
if err != nil {
t.Fatal(err)
}
defer runtime.Close()
settings := domain.ManagementAccessSettings{HTTPSEnabled: true, HTTPSPort: httpsPort, HTTPPort: unusedTCPPort(t), IdleTimeoutMinutes: 15}
if err := runtime.Apply(context.Background(), settings); err != nil {
t.Fatal(err)
}
first := peerCertificate(t, httpsPort)
replacementRoot := t.TempDir()
replacementCert := filepath.Join(replacementRoot, "replacement.crt")
replacementKey := filepath.Join(replacementRoot, "replacement.key")
if _, err := ensureTLSCertificate(replacementCert, replacementKey, []string{"replacement.local"}); err != nil {
t.Fatal(err)
}
certPEM, err := os.ReadFile(replacementCert)
if err != nil {
t.Fatal(err)
}
keyPEM, err := os.ReadFile(replacementKey)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(certPath, certPEM, 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(keyPath, keyPEM, 0o600); err != nil {
t.Fatal(err)
}
future := time.Now().Add(2 * time.Second)
if err := os.Chtimes(certPath, future, future); err != nil {
t.Fatal(err)
}
if err := os.Chtimes(keyPath, future, future); err != nil {
t.Fatal(err)
}
second := peerCertificate(t, httpsPort)
if bytes.Equal(first, second) {
t.Fatal("new HTTPS connection still received the replaced certificate's predecessor")
}
}
func peerCertificate(t *testing.T, port int) []byte {
t.Helper()
connection, err := tls.Dial("tcp", "127.0.0.1:"+portString(port), &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12}) // #nosec G402 -- local generated test certificates.
if err != nil {
t.Fatal(err)
}
defer connection.Close()
certificates := connection.ConnectionState().PeerCertificates
if len(certificates) == 0 {
t.Fatal("HTTPS peer did not provide a certificate")
}
return append([]byte(nil), certificates[0].Raw...)
}
func unusedTCPPort(t *testing.T) int {
t.Helper()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
port := listener.Addr().(*net.TCPAddr).Port
_ = listener.Close()
return port
}
func portString(port int) string { return fmt.Sprintf("%d", port) }
func assertHTTPBody(t *testing.T, address string, client *http.Client) {
t.Helper()
response, err := client.Get(address)
if err != nil {
t.Fatal(err)
}
defer response.Body.Close()
payload, err := io.ReadAll(response.Body)
if err != nil {
t.Fatal(err)
}
if string(payload) != "tera-access-ok" {
t.Fatalf("body = %q", payload)
}
}