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
154 lines
4.8 KiB
|
2 months ago
|
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)
|
||
|
|
}
|
||
|
|
}
|