package sources import ( "context" "net/http" "net/http/httptest" "testing" "time" "github.com/salvacybersec/keyhunter/pkg/providers" "github.com/salvacybersec/keyhunter/pkg/recon" ) func cloudTestRegistry() *providers.Registry { return providers.NewRegistryFromProviders([]providers.Provider{ {Name: "testprov", Keywords: []string{"testprov-key"}}, }) } func s3TestServer() *httptest.Server { mux := http.NewServeMux() // Respond to HEAD for the testprov-keys bucket with 200 (public). mux.HandleFunc("/testprov-keys/", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodHead { w.WriteHeader(http.StatusOK) return } // GET — return S3 ListBucketResult XML. w.Header().Set("Content-Type", "application/xml") _, _ = w.Write([]byte(` .env config.yaml readme.md data/settings.json `)) }) // All other buckets return 404 (not found). mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) }) return httptest.NewServer(mux) } func TestS3Scanner_Sweep(t *testing.T) { srv := s3TestServer() defer srv.Close() src := &S3Scanner{ Registry: cloudTestRegistry(), BaseURL: srv.URL + "/%s/", client: NewClient(), } out := make(chan recon.Finding, 32) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := src.Sweep(ctx, "", out); err != nil { t.Fatalf("Sweep error: %v", err) } close(out) var findings []recon.Finding for f := range out { findings = append(findings, f) } // .env, config.yaml, data/settings.json match; readme.md does not. if len(findings) != 3 { t.Fatalf("expected 3 findings, got %d: %+v", len(findings), findings) } for _, f := range findings { if f.SourceType != "recon:s3" { t.Errorf("unexpected SourceType: %s", f.SourceType) } if f.Confidence != "medium" { t.Errorf("unexpected Confidence: %s", f.Confidence) } } } func TestS3Scanner_EmptyRegistry(t *testing.T) { src := &S3Scanner{ Registry: providers.NewRegistryFromProviders(nil), Limiters: recon.NewLimiterRegistry(), client: NewClient(), } out := make(chan recon.Finding, 4) ctx := context.Background() if err := src.Sweep(ctx, "", out); err != nil { t.Fatalf("Sweep error: %v", err) } close(out) if len(out) != 0 { t.Fatal("expected 0 findings with empty registry") } } func TestS3Scanner_CtxCancelled(t *testing.T) { srv := s3TestServer() defer srv.Close() src := &S3Scanner{ Registry: cloudTestRegistry(), BaseURL: srv.URL + "/%s/", client: NewClient(), } ctx, cancel := context.WithCancel(context.Background()) cancel() out := make(chan recon.Finding, 4) if err := src.Sweep(ctx, "", out); err == nil { t.Fatal("expected ctx error") } } func TestS3Scanner_EnabledAndMeta(t *testing.T) { s := &S3Scanner{} if s.Name() != "s3" { t.Fatalf("unexpected name: %s", s.Name()) } if !s.Enabled(recon.Config{}) { t.Fatal("expected Enabled=true") } if s.RespectsRobots() { t.Fatal("expected RespectsRobots=false") } if s.Burst() != 3 { t.Fatal("expected Burst=3") } }