Skip to content

Commit 72c82ec

Browse files
committed
Initial commit
0 parents  commit 72c82ec

35 files changed

+2301
-0
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Dockerfile

.github/workflows/lint.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-go@v5
16+
with:
17+
go-version: '1.24'
18+
19+
- name: Lint
20+
uses: golangci/golangci-lint-action@v7
21+
with:
22+
version: v2.0.2

.github/workflows/release.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
permissions:
9+
contents: write
10+
id-token: write
11+
12+
jobs:
13+
goreleaser:
14+
runs-on: ubuntu-latest
15+
steps:
16+
-
17+
name: Checkout
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
-
22+
name: Set up Go
23+
uses: actions/setup-go@v5
24+
25+
-
26+
name: Set up cosign
27+
uses: sigstore/cosign-installer@v3.8.1
28+
29+
-
30+
name: Run GoReleaser
31+
uses: goreleaser/goreleaser-action@v6
32+
with:
33+
distribution: goreleaser
34+
version: '~> v2'
35+
args: release --clean
36+
env:
37+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/terraform.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Terraform Lint
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: opentofu/setup-opentofu@v1
16+
with:
17+
tofu_version: 1.9
18+
19+
- name: Format
20+
run: tofu fmt -check
21+
working-directory: ./terraform
22+
23+
- name: Init
24+
run: tofu init
25+
working-directory: ./terraform
26+
27+
- name: Validate
28+
run: tofu validate
29+
working-directory: ./terraform

.github/workflows/tests.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-go@v5
16+
with:
17+
go-version: '1.24'
18+
19+
- name: Build
20+
run: go build -v github.com/mark-adams/gcp-ip-list/cmd/gcp-ip-list
21+
22+
- name: Test
23+
run: go test -v ./...

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
terraform/.terraform*
2+
terraform/*.tfstate*
3+
.vscode# Added by goreleaser init:
4+
dist/

.goreleaser.yaml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
version: 2
2+
3+
before:
4+
hooks:
5+
# You may remove this if you don't use go modules.
6+
- go mod tidy
7+
# you may remove this if you don't need go generate
8+
- go generate ./...
9+
10+
builds:
11+
- main: ./cmd/gcp-ip-list
12+
env:
13+
- CGO_ENABLED=0
14+
goos:
15+
- linux
16+
# - windows
17+
# - darwin
18+
19+
archives:
20+
- formats: [tar.gz]
21+
# this name template makes the OS and Arch compatible with the results of `uname`.
22+
name_template: >-
23+
{{ .ProjectName }}_
24+
{{- title .Os }}_
25+
{{- if eq .Arch "amd64" }}x86_64
26+
{{- else if eq .Arch "386" }}i386
27+
{{- else }}{{ .Arch }}{{ end }}
28+
{{- if .Arm }}v{{ .Arm }}{{ end }}
29+
# use zip for windows archives
30+
format_overrides:
31+
- goos: windows
32+
formats: [zip]
33+
34+
changelog:
35+
sort: asc
36+
filters:
37+
exclude:
38+
- "^docs:"
39+
- "^test:"
40+
41+
release:
42+
draft: true
43+
footer: >-
44+
45+
---
46+
47+
Released by [GoReleaser](https://github.com/goreleaser/goreleaser).
48+
49+
binary_signs:
50+
- cmd: cosign
51+
args:
52+
- "sign-blob"
53+
- "--output-signature=${signature}"
54+
- "${artifact}"
55+
- "--yes" # needed on cosign 2.0.0+

Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM golang:1.24 as builder
2+
3+
WORKDIR $GOPATH/src/github.com/mark-adams/gcp-ip-list
4+
COPY . .
5+
6+
RUN go get -v ./...
7+
RUN GOOS=linux CGO_ENABLED=0 go build -o /go/bin/gcp-ip-list github.com/mark-adams/gcp-ip-list/cmd/gcp-ip-list
8+
9+
FROM cgr.dev/chainguard/static
10+
11+
COPY --from=builder /go/bin/gcp-ip-list /bin/gcp-ip-list
12+
13+
ENTRYPOINT ["/bin/gcp-ip-list"]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Mark Adams
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# GCP IP List
2+
3+
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/mark-adams/gcp-ip-list) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/mark-adams/gcp-ip-list/main/LICENSE) [![Build Status](https://github.com/mark-adams/gcp-ip-list/actions/workflows/tests.yml/badge.svg)](https://github.com/mark-adams/gcp-ip-list/actions/workflows/tests.yml)
4+
5+
6+
`gcp-ip-list` is a simple CLI tool (and library) written in Go to simplify the process of retrieving IP addresses from infrastructure hosted on Google Cloud Platform (GCP).
7+
8+
Most enumeration tooling today uses the normal CRUD REST APIs provided by Google to retrieve GCP assets and their IP address information. This is less than ideal because it typically involves interacting with several different Google APIs and puts additional load on the very same APIs that are used as the control plane for GCP customers.
9+
10+
This tool takes a different approach and queries information about assets from Google's [Cloud Asset Inventory API](https://cloud.google.com/asset-inventory/docs/overview) instead. This allows us to use a single API to pull down all the data about assets that could potentially have public IP addresses assigned to them which allows us to download data for organizations of any size much more efficiently.
11+
12+
# Getting Started
13+
14+
## Installing the tool
15+
The easiest way to install the latest version of the tool is to use the `go install` command:
16+
```
17+
go install github.com/mark-adams/gcp-ip-list@latest
18+
```
19+
20+
## Running the tool
21+
22+
Since this tool uses the [Google Cloud Client Libraries for Go](https://github.com/googleapis/google-cloud-go), the application will authenticate with Google using [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials).
23+
24+
### Quick and easy
25+
```
26+
$ gcp-ip-list --scope=projects/sample-project -public
27+
+----------------+--------------+---------------------------------------+-------------------------------------------------------------------------------------------------------------------------+
28+
| ADDRESS | ADDRESS TYPE | RESOURCE TYPE | RESOURCE NAME |
29+
+----------------+--------------+---------------------------------------+-------------------------------------------------------------------------------------------------------------------------+
30+
| 35.244.150.176 | public | compute.googleapis.com/ForwardingRule | //compute.googleapis.com/projects/sample-project/global/forwardingRules/ip-list-test-forwarding-rule-external |
31+
| 34.54.75.78 | public | compute.googleapis.com/ForwardingRule | //compute.googleapis.com/projects/sample-project/global/forwardingRules/ip-list-test-forwarding-rule-external-static |
32+
| 34.83.163.216 | public | compute.googleapis.com/Instance | //compute.googleapis.com/projects/sample-project/zones/us-west1-a/instances/ip-list-test-vm |
33+
| 34.105.8.244 | public | compute.googleapis.com/Router | //compute.googleapis.com/projects/sample-project/regions/us-west1/routers/ip-list-test-router |
34+
| 34.19.43.198 | public | container.googleapis.com/Cluster | //container.googleapis.com/projects/sample-project/locations/us-west1/clusters/ip-list-test-cluster |
35+
| 34.127.47.18 | public | sqladmin.googleapis.com/Instance | //cloudsql.googleapis.com/projects/sample-project/instances/ip-list-test-db |
36+
+----------------+--------------+---------------------------------------+-------------------------------------------------------------------------------------------------------------------------+
37+
```
38+
39+
```
40+
$ gcp-ip-list --scope=projects/sample-project -public -format=list
41+
35.244.150.176
42+
34.54.75.78
43+
34.83.163.216
44+
34.105.8.244
45+
34.19.43.198
46+
34.127.47.18
47+
```
48+
49+
### Usage
50+
```
51+
$ gcp-ip-list -h
52+
Usage of gcp-ip-list:
53+
-format string
54+
The output format (csv, json, table) (default "table")
55+
-private
56+
Include private IPs only
57+
-public
58+
Include public IPs only
59+
-scope string
60+
The scope (organization, folder, or project) to search
61+
```
62+
63+
# Contributing
64+
Contributions are welcome via pull requests! Often it can be helpful if you submit an issue and discuss your potential contribution before creating a pull request to help it get reviewed and merged more quickly.
65+
66+
## Terraform resources
67+
The `terraform` directory contains sample resources that are handy when doing local development on `gcp-ip-list`.
68+
If you add support for a new resource type, please add the appropriate Terraform resources in the same PR.

cmd/gcp-ip-list/main.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"cmp"
5+
"context"
6+
"flag"
7+
"log"
8+
"os"
9+
"regexp"
10+
"slices"
11+
12+
"github.com/mark-adams/gcp-ip-list/pkg/gcp"
13+
"github.com/mark-adams/gcp-ip-list/pkg/output"
14+
)
15+
16+
var (
17+
scope = flag.String("scope", "", "The scope (organization, folder, or project) to search")
18+
scopePattern = regexp.MustCompile(`^organizations/\d+$|^folders/\d+$|^projects/\S+$`)
19+
20+
format = flag.String("format", "table", "The output format (csv, json, table, list)")
21+
22+
public = flag.Bool("public", false, "Include public IPs only")
23+
private = flag.Bool("private", false, "Include private IPs only")
24+
)
25+
26+
func main() {
27+
flag.Parse()
28+
29+
if *scope == "" {
30+
log.Fatal("scope is required")
31+
}
32+
if !scopePattern.MatchString(*scope) {
33+
log.Fatalf("invalid scope: %s, scope must be organizations/1234, folders/1234, projects/1234", *scope)
34+
}
35+
36+
if *public && *private {
37+
log.Fatalf("cannot specify both public and private flags")
38+
}
39+
40+
formatters := output.GetFormatters()
41+
42+
formatter := formatters[*format]
43+
if formatter == nil {
44+
log.Fatalf("invalid formatter: %s", *format)
45+
}
46+
47+
ctx := context.Background()
48+
49+
addresses, err := gcp.GetAllAddressesFromAssetInventory(ctx, *scope)
50+
if err != nil {
51+
log.Fatalf("error getting public addresses: %s", err)
52+
}
53+
54+
if *public {
55+
addresses = gcp.FilterPublicAddresses(addresses)
56+
} else if *private {
57+
addresses = gcp.FilterPrivateAddresses(addresses)
58+
}
59+
60+
// Sort the output by the address type (descending), then by resource type, then by resource name
61+
// (chosen somewhat arbitrarily)
62+
slices.SortFunc(addresses, func(a, b *gcp.Address) int {
63+
return cmp.Or(
64+
cmp.Compare(a.AddressType, b.AddressType)*-1,
65+
cmp.Compare(a.ResourceType, b.ResourceType),
66+
cmp.Compare(a.ResourceName, b.ResourceName),
67+
)
68+
})
69+
70+
if err := formatter(os.Stdout, addresses); err != nil {
71+
log.Fatalf("error writing output: %s", err)
72+
}
73+
}

0 commit comments

Comments
 (0)