Skip to content

Commit ef9018e

Browse files
committed
fixup CI integration test
1 parent dab9bc0 commit ef9018e

2 files changed

Lines changed: 109 additions & 7 deletions

File tree

ci/docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ services:
7171
--api.debug=true
7272
--ping=true
7373
--entryPoints.http.address=:80
74-
--entryPoints.http.forwardedHeaders.trustedIPs=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
74+
--entryPoints.http.forwardedHeaders.insecure=true
75+
--entryPoints.http.forwardedHeaders.trustedIPs=127.0.0.1/32,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
7576
--providers.docker=true
7677
--providers.docker.network=default
7778
--experimental.localPlugins.captcha-protect.moduleName=github.com/libops/captcha-protect

ci/test.go

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"io"
78
"log/slog"
@@ -10,6 +11,7 @@ import (
1011
"net/http"
1112
"os"
1213
"os/exec"
14+
"path/filepath"
1315
"strings"
1416
"sync"
1517
"time"
@@ -28,9 +30,9 @@ const parallelism = 10
2830

2931
func main() {
3032
log := slog.New(slog.NewTextHandler(os.Stdout, nil))
31-
googleCIDRs, err := helper.FetchGooglebotIPs(log, http.DefaultClient, "https://developers.google.com/static/search/apis/ipranges/googlebot.json")
33+
googleCIDRs, err := helper.FetchGoogleCrawlerIPs(log, http.DefaultClient, helper.GoogleCrawlerIPRangeURLs)
3234
if err != nil {
33-
slog.Error("unable to fetch google bot ips", "err", err)
35+
slog.Error("unable to fetch google crawler ips", "err", err)
3436
os.Exit(1)
3537
}
3638

@@ -52,16 +54,27 @@ func main() {
5254
fmt.Printf("Generating %d IPs\n", numIPs)
5355
ips := generateUniquePublicIPs(numIPs)
5456

57+
statePath, err := prepareStateFile(0o777, 0o666)
58+
if err != nil {
59+
slog.Error("Failed to prepare state file", "statePath", statePath, "err", err)
60+
os.Exit(1)
61+
}
62+
5563
fmt.Println("Bringing traefik/nginx online")
5664
runCommand("docker", "compose", "up", "-d")
5765
waitForService("http://localhost")
5866
waitForService("http://localhost/app2")
67+
waitForGoogleExemptionReady(googleCIDRs)
5968

6069
fmt.Printf("Making sure %d attempt(s) pass\n", rateLimit)
6170
runParallelChecks(ips, rateLimit, "http://localhost")
6271

63-
time.Sleep(cp.StateSaveInterval + cp.StateSaveJitter + (1 * time.Second))
64-
runCommand("jq", ".", "tmp/state.json")
72+
statePath, err = waitForStateFile(30 * time.Second)
73+
if err != nil {
74+
slog.Error("State file was not created in time", "err", err)
75+
os.Exit(1)
76+
}
77+
runCommand("jq", ".", statePath)
6578

6679
fmt.Printf("Making sure attempt #%d causes a redirect to the challenge page\n", rateLimit+1)
6780
ensureRedirect(ips, "http://localhost")
@@ -81,7 +94,7 @@ func main() {
8194
time.Sleep(10 * time.Second)
8295
checkStateReload()
8396

84-
runCommand("rm", "tmp/state.json")
97+
runCommand("rm", "-f", statePath)
8598

8699
}
87100

@@ -400,7 +413,7 @@ func testGoogleBotGetsThrough(googleCIDRs []string) {
400413

401414
// Prime the rate limiter for the GoogleBot IP with parameters
402415
fmt.Printf("Priming rate limiter for GoogleBot IP %s with params (%d requests)\n", googleIP, rateLimit)
403-
for i := 0; i < rateLimit; i++ {
416+
for i := range rateLimit {
404417
output = httpRequest(googleIP, "http://localhost/?foo=bar") // Assign value
405418
if output != "" {
406419
slog.Error(fmt.Sprintf("GoogleBot with params was challenged prematurely on request #%d", i+1), "ip", googleIP, "output", output)
@@ -421,3 +434,91 @@ func testGoogleBotGetsThrough(googleCIDRs []string) {
421434
// set things back to normal for other tests
422435
runCommand("docker", "compose", "down")
423436
}
437+
438+
func waitForGoogleExemptionReady(googleCIDRs []string) {
439+
googleIP, err := firstUsableIPv4FromCIDRs(googleCIDRs)
440+
if err != nil {
441+
slog.Warn("Unable to select Google IP for readiness check; skipping warmup", "err", err)
442+
return
443+
}
444+
445+
deadline := time.Now().Add(90 * time.Second)
446+
for time.Now().Before(deadline) {
447+
ready := true
448+
for i := 0; i < rateLimit+1; i++ {
449+
if output := httpRequest(googleIP, "http://localhost"); output != "" {
450+
ready = false
451+
break
452+
}
453+
}
454+
if ready {
455+
fmt.Printf("Google exemption is active for %s\n", googleIP)
456+
return
457+
}
458+
time.Sleep(500 * time.Millisecond)
459+
}
460+
461+
slog.Error("Timed out waiting for Google crawler IP exemption to become active", "googleIP", googleIP)
462+
os.Exit(1)
463+
}
464+
465+
func firstUsableIPv4FromCIDRs(cidrs []string) (string, error) {
466+
for _, cidr := range cidrs {
467+
ip, err := getIPFromCIDR(cidr)
468+
if err != nil {
469+
continue
470+
}
471+
parsed := net.ParseIP(ip)
472+
if parsed != nil && parsed.To4() != nil {
473+
return ip, nil
474+
}
475+
}
476+
477+
return "", fmt.Errorf("no usable IPv4 found in CIDR list")
478+
}
479+
480+
func waitForStateFile(timeout time.Duration) (string, error) {
481+
paths := []string{
482+
filepath.Join("tmp", "state.json"),
483+
filepath.Join("ci", "tmp", "state.json"),
484+
}
485+
486+
deadline := time.Now().Add(timeout)
487+
for time.Now().Before(deadline) {
488+
for _, p := range paths {
489+
info, err := os.Stat(p)
490+
if err == nil && !info.IsDir() {
491+
return p, nil
492+
}
493+
if err != nil && !errors.Is(err, os.ErrNotExist) {
494+
return "", fmt.Errorf("failed to stat %s: %w", p, err)
495+
}
496+
}
497+
time.Sleep(500 * time.Millisecond)
498+
}
499+
500+
return "", fmt.Errorf("state file not found; checked: %s", strings.Join(paths, ", "))
501+
}
502+
503+
func prepareStateFile(dirMode, fileMode os.FileMode) (string, error) {
504+
p := filepath.Join("tmp", "state.json")
505+
506+
dir := filepath.Dir(p)
507+
if err := os.MkdirAll(dir, dirMode); err != nil {
508+
return "", fmt.Errorf("failed to create state dir %s: %w", dir, err)
509+
}
510+
if err := os.Chmod(dir, dirMode); err != nil {
511+
return "", fmt.Errorf("failed to chmod state dir %s: %w", dir, err)
512+
}
513+
514+
f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fileMode)
515+
if err != nil {
516+
return "", fmt.Errorf("failed to open state file %s: %w", p, err)
517+
}
518+
_ = f.Close()
519+
if err := os.Chmod(p, fileMode); err != nil {
520+
return "", fmt.Errorf("failed to chmod state file %s: %w", p, err)
521+
}
522+
523+
return p, nil
524+
}

0 commit comments

Comments
 (0)