From 5b2fa47c0e2ef9c492dee24b618d8c4db61c8ae3 Mon Sep 17 00:00:00 2001 From: Stavros Date: Fri, 13 Mar 2026 23:04:47 +0200 Subject: [PATCH] wip --- integration/.env | 16 +++ integration/config.yml | 15 +++ integration/docker-compose.envoy.yml | 16 +++ integration/docker-compose.nginx.yml | 16 +++ integration/docker-compose.traefik.yml | 28 +++++ integration/envoy.yml | 111 +++++++++++++++++ integration/integrarion_tests.go | 62 +++++++++ integration/integration.go | 166 +++++++++++++++++++++++++ integration/nginx.conf | 33 +++++ 9 files changed, 463 insertions(+) create mode 100644 integration/.env create mode 100644 integration/config.yml create mode 100644 integration/docker-compose.envoy.yml create mode 100644 integration/docker-compose.nginx.yml create mode 100644 integration/docker-compose.traefik.yml create mode 100644 integration/envoy.yml create mode 100644 integration/integrarion_tests.go create mode 100644 integration/integration.go create mode 100644 integration/nginx.conf diff --git a/integration/.env b/integration/.env new file mode 100644 index 0000000..2e9d6c8 --- /dev/null +++ b/integration/.env @@ -0,0 +1,16 @@ +# Nginx configuration +NGINX_VERSION=1.29 + +# Whoami configuration +WHOAMI_VERSION=latest + +# Traefik configuration +TRAEFIK_VERSION=v3.6 +TINYAUTH_HOST=tinyauth.127.0.0.1.sslip.io +WHOAMI_HOST=whoami.127.0.0.1.sslip.io + +# Envoy configuration +ENVOY_VERSION=v1.33-latest + +# Tinyauth configuration +TINYAUTH_VERSION=dev diff --git a/integration/config.yml b/integration/config.yml new file mode 100644 index 0000000..678dcf9 --- /dev/null +++ b/integration/config.yml @@ -0,0 +1,15 @@ +appUrl: http://tinyauth.127.0.0.1.sslip.io + +auth: + users: test:$2a$10$eG88kFow83l5YRSlTSL2o.sZimjxFHrpiKdaSUZqpLBGX7Y2.4PZG + +log: + json: true + level: trace + +apps: + whoami: + config: + domain: whoami.127.0.0.1.sslip.io + path: + allow: /allow diff --git a/integration/docker-compose.envoy.yml b/integration/docker-compose.envoy.yml new file mode 100644 index 0000000..20f0535 --- /dev/null +++ b/integration/docker-compose.envoy.yml @@ -0,0 +1,16 @@ +services: + envoy: + image: envoyproxy/envoy:${ENVOY_VERSION} + ports: + - 80:80 + volumes: + - ./envoy.yml:/etc/envoy/envoy.yaml:ro + + whoami: + image: traefik/whoami:${WHOAMI_VERSION} + + tinyauth: + image: ghcr.io/steveiliop56/tinyauth:${TINYAUTH_VERSION} + command: --experimental.configfile=/data/config.yml + volumes: + - ./config.yml:/data/config.yml:ro diff --git a/integration/docker-compose.nginx.yml b/integration/docker-compose.nginx.yml new file mode 100644 index 0000000..4645067 --- /dev/null +++ b/integration/docker-compose.nginx.yml @@ -0,0 +1,16 @@ +services: + nginx: + image: nginx:${NGINX_VERSION} + ports: + - 80:80 + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + + whoami: + image: traefik/whoami:${WHOAMI_VERSION} + + tinyauth: + image: ghcr.io/steveiliop56/tinyauth:${TINYAUTH_VERSION} + command: --experimental.configfile=/data/config.yml + volumes: + - ./config.yml:/data/config.yml:ro diff --git a/integration/docker-compose.traefik.yml b/integration/docker-compose.traefik.yml new file mode 100644 index 0000000..384dced --- /dev/null +++ b/integration/docker-compose.traefik.yml @@ -0,0 +1,28 @@ +services: + traefik: + image: traefik:${TRAEFIK_VERSION} + command: | + --api.insecure=true + --providers.docker + --entryPoints.web.address=:80 + ports: + - 80:80 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + whoami: + image: traefik/whoami:${WHOAMI_VERSION} + labels: + traefik.enable: true + traefik.http.routers.whoami.rule: Host(`${WHOAMI_HOST}`) + traefik.http.routers.whoami.middlewares: tinyauth + + tinyauth: + image: ghcr.io/steveiliop56/tinyauth:${TINYAUTH_VERSION} + command: --experimental.configfile=/data/config.yml + volumes: + - ./config.yml:/data/config.yml:ro + labels: + traefik.enable: true + traefik.http.routers.tinyauth.rule: Host(`${TINYAUTH_HOST}`) + traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik diff --git a/integration/envoy.yml b/integration/envoy.yml new file mode 100644 index 0000000..01725e8 --- /dev/null +++ b/integration/envoy.yml @@ -0,0 +1,111 @@ +static_resources: + listeners: + - name: "listener_http" + address: + socket_address: + address: "0.0.0.0" + port_value: 80 + filter_chains: + - filters: + - name: "envoy.filters.network.http_connection_manager" + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" + stat_prefix: "ingress_http" + use_remote_address: true + skip_xff_append: false + route_config: + name: "local_route" + virtual_hosts: + - name: "whoami_service" + domains: ["whoami.127.0.0.1.sslip.io"] + routes: + - match: + prefix: "/" + route: + cluster: "whoami" + - name: "tinyauth_service" + domains: ["tinyauth.127.0.0.1.sslip.io"] + typed_per_filter_config: + envoy.filters.http.ext_authz: + "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute" + disabled: true + routes: + - match: + prefix: "/" + route: + cluster: "tinyauth" + http_filters: + - name: "envoy.filters.http.ext_authz" + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz" + transport_api_version: "v3" + http_service: + path_prefix: "/api/auth/envoy" + server_uri: + uri: "tinyauth:3000" + cluster: "tinyauth" + timeout: "0.25s" + authorization_request: + allowed_headers: + patterns: + - exact: "authorization" + - exact: "accept" + - exact: "cookie" + - exact: "location" + headers_to_add: + - key: "X-Forwarded-Proto" + value: "%REQ(:SCHEME)%" + authorization_response: + allowed_upstream_headers: + patterns: + - prefix: "remote-" + allowed_client_headers: + patterns: + - exact: "set-cookie" + allowed_client_headers_on_success: + patterns: + - exact: "set-cookie" + failure_mode_allow: false + - name: "envoy.filters.http.router" + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + clusters: + - name: "whoami" + connect_timeout: "0.25s" + type: "logical_dns" + dns_lookup_family: "v4_only" + lb_policy: "round_robin" + load_assignment: + cluster_name: "whoami" + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: "whoami" + port_value: 80 + - name: "tinyauth" + connect_timeout: "0.25s" + type: "logical_dns" + dns_lookup_family: "v4_only" + lb_policy: "round_robin" + load_assignment: + cluster_name: "tinyauth" + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: "tinyauth" + port_value: 3000 +layered_runtime: + layers: + - name: "static_layer_0" + static_layer: + envoy: + resource_limits: + listener: + example_listener_name: + connection_limit: 10000 + overload: + global_downstream_max_connections: 50000 diff --git a/integration/integrarion_tests.go b/integration/integrarion_tests.go new file mode 100644 index 0000000..47a3cd2 --- /dev/null +++ b/integration/integrarion_tests.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "strings" +) + +func testUnauthorized(client *http.Client) error { + req, err := http.NewRequest("GET", WhoamiURL, nil) + if err != nil { + return err + } + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + // nginx and envoy will throw us at the frontend + if resp.StatusCode != http.StatusUnauthorized && !strings.Contains(string(body), "
") { + return fmt.Errorf("expected status code %d or to to contain '
', got %d", http.StatusUnauthorized, resp.StatusCode) + } + return nil +} + +func testLoggedIn(client *http.Client) error { + req, err := http.NewRequest("GET", WhoamiURL, nil) + if err != nil { + return err + } + req.SetBasicAuth(DefaultUsername, DefaultPassword) + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode) + } + return nil +} + +func testACLAllowed(client *http.Client) error { + req, err := http.NewRequest("GET", WhoamiURL+"/allow", nil) + if err != nil { + return err + } + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode) + } + return nil +} diff --git a/integration/integration.go b/integration/integration.go new file mode 100644 index 0000000..9d3724e --- /dev/null +++ b/integration/integration.go @@ -0,0 +1,166 @@ +package main + +import ( + "bufio" + "context" + "errors" + "flag" + "fmt" + "log/slog" + "net/http" + "os" + "os/exec" + "time" +) + +var ProxiesToTest = []string{"traefik", "nginx"} + +const ( + EnvFile = ".env" + ConfigFile = "config.yml" +) + +const ( + TinyauthURL = "http://tinyauth.127.0.0.1.sslip.io" + WhoamiURL = "http://whoami.127.0.0.1.sslip.io" +) + +const ( + DefaultUsername = "test" + DefaultPassword = "password" +) + +func main() { + logFlag := flag.Bool("log", false, "enable stack logging") + flag.Parse() + + for _, proxy := range ProxiesToTest { + slog.Info("begin", "proxy", proxy) + compose := fmt.Sprintf("docker-compose.%s.yml", proxy) + if _, err := os.Stat(compose); err != nil { + slog.Error("fail", "proxy", proxy, "error", err) + os.Exit(1) + } + if err := createInstanceAndRunTests(compose, *logFlag, proxy); err != nil { + slog.Error("fail", "proxy", proxy, "error", err) + os.Exit(1) + } + slog.Info("end", "proxy", proxy) + } +} + +func runTests(client *http.Client, name string) error { + if err := testUnauthorized(client); err != nil { + slog.Error("fail unauthorized test", "name", name) + return err + } + + slog.Info("unauthorized test passed", "name", name) + + if err := testLoggedIn(client); err != nil { + slog.Error("fail logged in test", "name", name) + return err + } + + slog.Info("logged in test passed", "name", name) + + if err := testACLAllowed(client); err != nil { + slog.Error("fail acl test", "name", name) + return err + } + + slog.Info("acl test passed", "name", name) + + return nil +} + +func createInstanceAndRunTests(compose string, log bool, name string) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + cmdArgs := []string{"compose", "-f", compose, "--env-file", EnvFile, "up", "--build", "--force-recreate", "--remove-orphans"} + cmd := exec.CommandContext(ctx, "docker", cmdArgs...) + + if log { + setupCmdLogging(cmd) + } + + if err := cmd.Start(); err != nil { + return err + } + + defer func() { + cmd.Process.Signal(os.Interrupt) + cmd.Wait() + }() + + slog.Info("instance created", "name", name) + + if err := waitForHealthy(); err != nil { + return err + } + + slog.Info("instance healthy", "name", name) + + client := &http.Client{} + + if err := runTests(client, name); err != nil { + return err + } + + slog.Info("tests passed", "name", name) + + cmd.Process.Signal(os.Interrupt) + + if err := cmd.Wait(); cmd.ProcessState.ExitCode() != 130 && err != nil { + return err + } + + return nil +} + +func setupCmdLogging(cmd *exec.Cmd) { + stdout, _ := cmd.StdoutPipe() + stderr, _ := cmd.StderrPipe() + + go func() { + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + slog.Info("docker out", "stdout", scanner.Text()) + } + }() + + go func() { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + slog.Error("docker out", "stderr", scanner.Text()) + } + }() +} + +func waitForHealthy() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + ticker := time.NewTicker(10 * time.Second) + client := http.Client{} + + for { + select { + case <-ctx.Done(): + return errors.New("tinyauth not healthy after 5 minutes") + case <-ticker.C: + req, err := http.NewRequestWithContext(ctx, "GET", TinyauthURL+"/api/healthz", nil) + if err != nil { + return err + } + res, err := client.Do(req) + if err != nil { + continue + } + if res.StatusCode == http.StatusOK { + return nil + } + } + } +} diff --git a/integration/nginx.conf b/integration/nginx.conf new file mode 100644 index 0000000..a9fd268 --- /dev/null +++ b/integration/nginx.conf @@ -0,0 +1,33 @@ +server { + listen 80; + listen [::]:80; + + server_name tinyauth.127.0.0.1.sslip.io; + + location / { + proxy_pass http://tinyauth:3000; + } +} + +server { + listen 80; + listen [::]:80; + + server_name whoami.127.0.0.1.sslip.io; + + location / { + proxy_pass http://whoami; + auth_request /tinyauth; + error_page 401 = @tinyauth_login; + } + + location /tinyauth { + internal; + proxy_pass http://tinyauth:3000/api/auth/nginx; + proxy_set_header x-original-url $scheme://$http_host$request_uri; + } + + location @tinyauth_login { + return 302 http://tinyauth.127.0.0.1.sslip.io/login?redirect_uri=$scheme://$http_host$request_uri; + } +}