Skip to content

Commit d933347

Browse files
authored
Merge pull request #719 from srivathsan-srinivasan/dev
Fix GCP provider to iterate over only provided project_ids in config instead of looping through All projects
2 parents 94162a1 + 03b4920 commit d933347

File tree

8 files changed

+536
-51
lines changed

8 files changed

+536
-51
lines changed

PROVIDERS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ Google Cloud Platform supports **two discovery approaches** and **two authentica
152152
- `service_account_email` (string, required if short-lived): Target service account to impersonate
153153
- `source_credentials` (string, optional): Path to source credentials file (uses ADC if not provided)
154154
- `token_lifetime` (string, optional): Token lifetime in seconds (e.g., "3600s") or Go duration format (e.g., "1h"). Range: 1s to 3600s (1 hour). Default: "3600s"
155+
- `project_ids` (list, optional): Comma-separated/list of project IDs to enumerate. When provided, Cloudlist skips discovery in every other accessible project, both for individual APIs and the organization-level Asset API.
155156

156157
---
157158

@@ -191,8 +192,13 @@ Google Cloud Platform supports **two discovery approaches** and **two authentica
191192
use_short_lived_credentials: true
192193
service_account_email: "asset-viewer-sa@project.iam.gserviceaccount.com"
193194
token_lifetime: "7200s" # 2 hours
195+
project_ids:
196+
- security-core
197+
- shared-infra
194198
```
195199

200+
Add `project_ids` to either configuration style to limit enumeration strictly to the listed projects (Cloud Asset API requests are filtered too), which is helpful for large organizations or delegated-access service accounts.
201+
196202
**Required Organization-Level Roles:**
197203
1. `roles/cloudasset.viewer` - Core Asset API access
198204
2. `roles/resourcemanager.viewer` - List projects in organization

internal/runner/runner.go

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import (
66
"os"
77
"strconv"
88
"strings"
9+
"sync"
910

1011
jsoniter "github.com/json-iterator/go"
1112
"github.com/projectdiscovery/cloudlist/pkg/inventory"
1213
"github.com/projectdiscovery/cloudlist/pkg/schema"
14+
"github.com/projectdiscovery/cloudlist/pkg/schema/validate"
1315
"github.com/projectdiscovery/gologger"
1416
)
1517

@@ -115,6 +117,8 @@ func (r *Runner) Enumerate() {
115117
continue
116118
}
117119

120+
sanitizePrivateIPs(instance, r.options.ExcludePrivate)
121+
118122
builder.Reset()
119123

120124
if r.options.JSON {
@@ -124,7 +128,7 @@ func (r *Runner) Enumerate() {
124128
} else {
125129
builder.Write(data)
126130
builder.WriteString("\n")
127-
output.Write(builder.Bytes()) //nolint
131+
writeOutputBytes(output, builder.Bytes())
128132

129133
if instance.DNSName != "" {
130134
hostsCount++
@@ -152,7 +156,7 @@ func (r *Runner) Enumerate() {
152156
hostsCount++
153157
builder.WriteString(instance.DNSName)
154158
builder.WriteRune('\n')
155-
output.WriteString(builder.String()) //nolint
159+
writeOutputString(output, builder.String())
156160
builder.Reset()
157161
gologger.Silent().Msgf("%s", instance.DNSName)
158162
}
@@ -163,31 +167,31 @@ func (r *Runner) Enumerate() {
163167
ipCount++
164168
builder.WriteString(instance.PublicIPv4)
165169
builder.WriteRune('\n')
166-
output.WriteString(builder.String()) //nolint
170+
writeOutputString(output, builder.String())
167171
builder.Reset()
168172
gologger.Silent().Msgf("%s", instance.PublicIPv4)
169173
}
170174
if instance.PublicIPv6 != "" {
171175
ipCount++
172176
builder.WriteString(instance.PublicIPv6)
173177
builder.WriteRune('\n')
174-
output.WriteString(builder.String()) //nolint
178+
writeOutputString(output, builder.String())
175179
builder.Reset()
176180
gologger.Silent().Msgf("%s", instance.PublicIPv6)
177181
}
178182
if instance.PrivateIpv4 != "" && !r.options.ExcludePrivate {
179183
ipCount++
180184
builder.WriteString(instance.PrivateIpv4)
181185
builder.WriteRune('\n')
182-
output.WriteString(builder.String()) //nolint
186+
writeOutputString(output, builder.String())
183187
builder.Reset()
184188
gologger.Silent().Msgf("%s", instance.PrivateIpv4)
185189
}
186190
if instance.PrivateIpv6 != "" && !r.options.ExcludePrivate {
187191
ipCount++
188192
builder.WriteString(instance.PrivateIpv6)
189193
builder.WriteRune('\n')
190-
output.WriteString(builder.String()) //nolint
194+
writeOutputString(output, builder.String())
191195
builder.Reset()
192196
gologger.Silent().Msgf("%s", instance.PrivateIpv6)
193197
}
@@ -198,39 +202,39 @@ func (r *Runner) Enumerate() {
198202
hostsCount++
199203
builder.WriteString(instance.DNSName)
200204
builder.WriteRune('\n')
201-
output.WriteString(builder.String()) //nolint
205+
writeOutputString(output, builder.String())
202206
builder.Reset()
203207
gologger.Silent().Msgf("%s", instance.DNSName)
204208
}
205209
if instance.PublicIPv4 != "" {
206210
ipCount++
207211
builder.WriteString(instance.PublicIPv4)
208212
builder.WriteRune('\n')
209-
output.WriteString(builder.String()) //nolint
213+
writeOutputString(output, builder.String())
210214
builder.Reset()
211215
gologger.Silent().Msgf("%s", instance.PublicIPv4)
212216
}
213217
if instance.PublicIPv6 != "" {
214218
ipCount++
215219
builder.WriteString(instance.PublicIPv6)
216220
builder.WriteRune('\n')
217-
output.WriteString(builder.String()) //nolint
221+
writeOutputString(output, builder.String())
218222
builder.Reset()
219223
gologger.Silent().Msgf("%s", instance.PublicIPv6)
220224
}
221225
if instance.PrivateIpv4 != "" && !r.options.ExcludePrivate {
222226
ipCount++
223227
builder.WriteString(instance.PrivateIpv4)
224228
builder.WriteRune('\n')
225-
output.WriteString(builder.String()) //nolint
229+
writeOutputString(output, builder.String())
226230
builder.Reset()
227231
gologger.Silent().Msgf("%s", instance.PrivateIpv4)
228232
}
229233
if instance.PrivateIpv6 != "" && !r.options.ExcludePrivate {
230234
ipCount++
231235
builder.WriteString(instance.PrivateIpv6)
232236
builder.WriteRune('\n')
233-
output.WriteString(builder.String()) //nolint
237+
writeOutputString(output, builder.String())
234238
builder.Reset()
235239
gologger.Silent().Msgf("%s", instance.PrivateIpv6)
236240
}
@@ -263,3 +267,55 @@ func Contains(s []string, e string) bool {
263267
}
264268
return false
265269
}
270+
271+
func sanitizePrivateIPs(resource *schema.Resource, exclude bool) {
272+
if !exclude || resource == nil {
273+
return
274+
}
275+
resource.PrivateIpv4 = ""
276+
resource.PrivateIpv6 = ""
277+
278+
if isPrivateIP(resource.PublicIPv4) {
279+
resource.PublicIPv4 = ""
280+
}
281+
if isPrivateIP(resource.PublicIPv6) {
282+
resource.PublicIPv6 = ""
283+
}
284+
}
285+
286+
var (
287+
ipValidatorOnce sync.Once
288+
ipValidator *validate.Validator
289+
)
290+
291+
func isPrivateIP(ip string) bool {
292+
if ip == "" {
293+
return false
294+
}
295+
ipValidatorOnce.Do(func() {
296+
var err error
297+
ipValidator, err = validate.NewValidator()
298+
if err != nil {
299+
gologger.Warning().Msgf("could not initialize ip validator: %s", err)
300+
}
301+
})
302+
if ipValidator == nil {
303+
return false
304+
}
305+
resourceType := ipValidator.Identify(ip)
306+
return resourceType == validate.PrivateIPv4 || resourceType == validate.PrivateIPv6
307+
}
308+
309+
func writeOutputString(output *os.File, data string) {
310+
if output == nil || data == "" {
311+
return
312+
}
313+
_, _ = output.WriteString(data)
314+
}
315+
316+
func writeOutputBytes(output *os.File, data []byte) {
317+
if output == nil || len(data) == 0 {
318+
return
319+
}
320+
_, _ = output.Write(data)
321+
}

internal/runner/runner_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package runner
2+
3+
import (
4+
"testing"
5+
6+
"github.com/projectdiscovery/cloudlist/pkg/schema"
7+
)
8+
9+
func TestSanitizePrivateIPs(t *testing.T) {
10+
resource := &schema.Resource{
11+
PublicIPv4: "1.2.3.4",
12+
PrivateIpv4: "10.0.0.5",
13+
PrivateIpv6: "fd00::1",
14+
PublicIPv6: "2606:4700:4700::1111",
15+
DNSName: "example.internal",
16+
}
17+
18+
sanitizePrivateIPs(resource, true)
19+
20+
if resource.PrivateIpv4 != "" || resource.PrivateIpv6 != "" {
21+
t.Fatalf("expected private addresses to be cleared, got %q and %q", resource.PrivateIpv4, resource.PrivateIpv6)
22+
}
23+
24+
if resource.PublicIPv4 == "" || resource.PublicIPv6 == "" || resource.DNSName == "" {
25+
t.Fatalf("public fields should remain untouched: %+v", resource)
26+
}
27+
28+
resource.PrivateIpv4 = "10.0.0.5"
29+
resource.PrivateIpv6 = "fd00::1"
30+
sanitizePrivateIPs(resource, false)
31+
32+
if resource.PrivateIpv4 == "" || resource.PrivateIpv6 == "" {
33+
t.Fatalf("expected private addresses to remain when exclusion disabled")
34+
}
35+
36+
sanitizePrivateIPs(nil, true)
37+
}
38+
39+
func TestSanitizePrivateIPsOnMisclassifiedFields(t *testing.T) {
40+
resource := &schema.Resource{
41+
PublicIPv4: "10.10.0.5",
42+
PublicIPv6: "fd00::5",
43+
}
44+
45+
sanitizePrivateIPs(resource, true)
46+
47+
if resource.PublicIPv4 != "" || resource.PublicIPv6 != "" {
48+
t.Fatalf("expected private addresses in public fields to be cleared, got ipv4=%q ipv6=%q", resource.PublicIPv4, resource.PublicIPv6)
49+
}
50+
51+
resource.PublicIPv4 = "8.8.8.8"
52+
resource.PublicIPv6 = "2606:4700:4700::1111"
53+
sanitizePrivateIPs(resource, true)
54+
55+
if resource.PublicIPv4 == "" || resource.PublicIPv6 == "" {
56+
t.Fatalf("expected public addresses to remain when exclusion enabled")
57+
}
58+
}

0 commit comments

Comments
 (0)